Jump to content
Matthew10_28

Locality issue: code not working on dedicated server

Recommended Posts

After learning a ton beating this code into submission in locally hosted MP via the editor, it broke when I put it on my separate dedicated server.  It basically turns on enemy units and objects within an area when the player walks up to a Keyboard object back at base.  The intent is to keep an area "clean" and in its default state until the user selects the mission site to turn on.

 

On-Activation box of Independent Present Trigger:
RaidArea079124 = []; //sets up an array to grab all the units within the area
{RaidArea079124  pushBack _x} forEach thisList;  //finds every unit with the area and adds it to the array
Keyboard Init:
RaidSite079124ObjectList = nearestObjects [HVT1, [], 200]; //finds all the objects like empty vehicles and other "set dressings" within 200m ofthe centrally spawned "HVT1" unit.
this addAction ["Spawn Raid Site @ 079124",{{if (isNil compile format ["'%1'", _x]) then {} else  
{_x hideObjectGlobal false, _x enableSimulation true}} forEach (RaidArea079124 + RaidSite079124ObjectList); }];  //adds and action to the keyboard object that when run, combines both arrays, and the un-hides and enables the units and objects that actually exist (some done't based on their own probability of presence)

I've been reading about BIS_fcn_MP and have been reading a ton on public/global variables but I could not modify it appropriately to get it to work on the server.  I've tried declaring publicVariables for RaidArea079124, RaidSite079124ObjectList,  & HVT1 (the variable name of a bad guy that always spawns) but it doesn't work.  I've read about problems with addAction and MP, so I tried using addAction wrapped inside BIS_fcn_MP.  That didn't work and oddly duplicated the addAction label attached to the keyboard object.  Now I read that hideObjectGlobal and enableSimulationGlobal are MP functions that are called on the server.  I must not be understanding how these functions work together.  The only thing I can think of is the magic variable _x is local to each bit of code, thereby making the various values that pass through it not actually make it to the server.

 
I've tried basically the following:
On-Activation box of Independent Present Trigger:
RaidArea079124 = []; 
{RaidArea079124  pushBack _x} forEach thisList; publicVariable "RaidArea079124";

Keyboard Init:
RaidSite079124ObjectList = nearestObjects [HVT1, [], 200]; publicVariable "RaidSite079124ObjectList"; 
this addAction ["Spawn Raid Site @ 079124",{{if (isNil compile format ["'%1'", _x]) then {} else  
{_x hideObjectGlobal false, _x enableSimulation true}} forEach (RaidArea079124 + RaidSite079124ObjectList); }];
or
RaidSite079124ObjectList = nearestObjects [HVT1, [], 200]; publicVariable "RaidSite079124ObjectList";
[[this, ["Spawn Raid Site @ 079124",{{if (isNil compile format ["'%1'", _x]) then {} else  
{_x hideObjectGlobal false, _x enableSimulation true}} forEach (RaidArea079124 + RaidSite079124ObjectList); }]],"addAction",true,true] call BIS_fnc_MP;

What am I not understanding?

Share this post


Link to post
Share on other sites

For anyone following, I've replaced BIS_fcn_MP with remoteExec, but once again it doesn't work on a dedicated.  When I was self hosting via the editor, it worked, but it spit out a black box error saying there was an undefined variable "_x" yet it still functioned as intended.  Is the magic variable _x used in context of forEach where it's not advisable?  Even if I did an i=i+1 with a do until, wouldn't that make "i" an undefined variable too?  In both cases they are used internally as a vehicle for a loop right?  I feel so stuck :unsure:

RaidSite079124ObjectList = nearestObjects [HVT1, [], 200]; publicVariable "RaidSite079124ObjectList";  
this addAction ["Spawn Raid Site @ 079124", 
     { 
     if (isNil compile format ["'%1'", _x]) then {} else   
      { 
       [{_x hideObjectGlobal false, _x enableSimulationGlobal true;} forEach (RaidArea079124 + RaidSite079124ObjectList)] remoteExec ["call", 0, true]; 
      } 
     }
    ];

Share this post


Link to post
Share on other sites

When you're a server, when you hit that action and this line executes

[{_x hideObjectGlobal false, _x enableSimulationGlobal true;} forEach (RaidArea079124 + RaidSite079124ObjectList)] remoteExec ["call", 0, true];

here's what happens:

1. The contents of remoteExec's left argument array are not of type 'Code'; therefore evaluated locally, that is forEach command runs, unhides objects and enables simulation for them since you're a server; forEach returns nil (result of last executed command).

2. command [nil] remoteExec ["call", 0, true] is executed, which is nonsense because it stands for call [nil] an all clients. But anyway, all job is done in 1, so everything looks fine (kinda works when you're a host, though some crazy luck it is).

 

When you're not server, you fail to change objects' states since you're not the server, and hideObjectGlobal and enableSimulationGlobal are to be run by server only. You also fail the step 2 because of incorrect syntax.

Correct syntax for call is arguments call body

Therefore correct syntax for remoteExec would be [arguments, body] remoteExec ["call", targets, JIP], body is of type 'Code', so extra curly braces added:

[[], {{_x hideObjectGlobal false, _x enableSimulationGlobal true;} forEach (RaidArea079124 + RaidSite079124ObjectList)}] remoteExec ["call", 2, true];

Since required commands are to be run by server only, targets parameter for remoteExec is set to 2, not 0.

BTW, is it really necessary to have JIP=true?

 

In general, having "call" to be allowed for remote execution with arbitrary code as argument is bad design, consider reworking this part. For example, have a script for this and pass only list of objects to process.

Share this post


Link to post
Share on other sites

Thank you.  What I was seeking first was understanding.  Then hopefully I'd be able to fix my code.

 

What you are saying is, everything "to the left of" remoteExec....the stuff "[iN HERE] remoteExec [yadda yadda]" is calculated until completion.  So even before it reaches the server, it finishes its thing and returns nil.  Then only the nil is passed to the server for it to "call" to everyone so it does nothing.  Even then, since I used "call", its looking for the stuff...  "[iN HERE] remoteExec [yadda yadda] to match the syntax of "call".  Also didn't realize the role of the curly brackets.

 

I can only say duh on the remoteExec needing to be 2 after reading the wiki on it 2 more times.  Also, my understanding of the optional JIP needing to be true seems unecessary.  I was worried about someone joining the server after everything got unhidden and enabled not seeing everything those who joined prior to the switch.

 

Now I understand, and it makes sense...but now using your code, it breaks when I self host through the editor.  I get an error call type array expected code.  undefined variable _x   :( Shouldn't that work because, although it is sending it to the server, the server and the local player is the same?

 

Going the script route - would that basically mean that the addAction this is centered around does nothing more than call a script of the code I basically came up with to execute instead of trying to do nested in the addAction?  The intent is to have multiple raid sites pre-defined in the editor and have the user be able to select one and "turn it on".  I'd have to come up with a way of passing the arguments to that script through the addAction right?

Share this post


Link to post
Share on other sites

Now I understand, and it makes sense...but now using your code, it breaks when I self host through the editor.  I get an error call type array expected code.  undefined variable _x   :( Shouldn't that work because, although it is sending it to the server, the server and the local player is the same?

Ah, that's probably because of some nils sneak into array. You had this isNil check from the beginning. Try

if (isServer) then {
    {
        if (!isNil "_x") then {
            _x hideObjectGlobal false;
            _x enableSimulationGlobal true;
        }
    } forEach (RaidArea079124 + RaidSite079124ObjectList)
} else {
    [
        [],
        {
            {
                if (!isNil "_x") then {
                    _x hideObjectGlobal false;
                    _x enableSimulationGlobal true;
                }
            } forEach (RaidArea079124 + RaidSite079124ObjectList)
        }
    ] remoteExec ["call", 2, true];
}

Going the script route - would that basically mean that the addAction this is centered around does nothing more than call a script of the code I basically came up with to execute instead of trying to do nested in the addAction?  The intent is to have multiple raid sites pre-defined in the editor and have the user be able to select one and "turn it on".  I'd have to come up with a way of passing the arguments to that script through the addAction right?

Right. Also try to minimize passing values through network. It looks like all you should really pass is identifier of raid area (or unit HVT1, which I guess is tied to that area). Then server uses all that heavy nearestObjects commands and enables and unhides what it finds there.

After all I don't completely understand what is trigger for. nearestObjects could find all objects anyway.

Share this post


Link to post
Share on other sites

Ah, that's probably because of some nils sneak into array. You had this isNil check from the beginning. Try

if (isServer) then {
    {
        if (!isNil "_x") then {
            _x hideObjectGlobal false;
            _x enableSimulationGlobal true;
        }
    } forEach (RaidArea079124 + RaidSite079124ObjectList)
}

After all I don't completely understand what is trigger for. nearestObjects could find all objects anyway.

Wouldn't the arguments in the first if{isserver} then {"stuff in here"} be unnecessary?  instead of doing nothing for the nil items it "turns on" something that doesn't exist?

 

On the suggestion of another forum user in a previous post, the trigger was a convenient way to collect all the bad guys in it's area.  The group of bad guys may or may not spawn because of probability of presence and the random placement radius I have set on them.  The trigger area is shaped appropriately to collect them all no matter what.  I learned that the trigger wouldn't collect objects like empty vehicles and things like desks and chairs so that's where I added the nearestObjects.  By the nature of a raid site, those objects would be mainly in and around buildings where the bad guys are pushed out much further from there.  The intent is there may be some sites that would overlap if they were defined by a simple radius, but by using an irregular trigger shape, I separate two sites a bit easier that may in close proximity.

 

Off to explore making this into a script that I just pass a few global variables from each raid site through.  I see now why using a separate scripts is better design.  That way I don't have to generate virtually the same bit of code for every raid site.

Share this post


Link to post
Share on other sites

In converting this functionality to a script upon previous suggestion, and I'm getting an error: 0 elements provided, 3 expected pointing to line 8 which is the _ObjectList definition.

 

On the keyboard object the player uses.  Both "RaidSite079124" and "HVT1" were declared publicVariables in the init lines of their respective objects.

ETA: this version does nothing.  I apparently don't know how to compile my script or something? and then call that script as a function?

this addAction ["Spawn Raid Site @ 079124", {["RaidSite079124","HVT1"] remoteExec ["scripts\RaidSiteActivator.sqf", 2];}];

ETA:  Even though its outdated, this makes more sense, and seems to get farther as it looks like the scripts attempts to run but I get the error.

this addAction ["Spawn Raid Site @ 079124", {[[[RaidSite079124, HVT1], "scripts\RaidSiteActivator.sqf"], "BIS_fnc_execVM", true] call BIS_fnc_MP;}];

RaidSiteActivator.sqf

private ["_RaidArea", "_HVT"];


_RaidArea = _this select 0;


_HVT = _this select 1;


_ObjectList = nearestObjects [_HVT,[],200];


{if (isNil compile format ["'%1'", _x]) then {} else   
      { 
       {_x hideObjectGlobal false, _x enableSimulationGlobal true} forEach (_RaidArea + _ObjectList)
      }};

Share this post


Link to post
Share on other sites

How to use the remoteExec in the same way as you're using BIS_fnc_MP:

this addAction ["Spawn Raid Site @ 079124", {[[RaidSite079124,HVT1], "scripts\RaidSiteActivator.sqf"] remoteExec ["execVM", 2];}];

And your RaidSiteActivator.sqf fixed of too many brackets {}, wrong parameter being given to the isNil and the if being outside of the forEach:

private ["_RaidArea", "_HVT"];


_RaidArea = _this select 0;


_HVT = _this select 1;


_ObjectList = nearestObjects [_HVT,[],200];


{
    if !(isNil "_x") then { 
        _x hideObjectGlobal false; 
        _x enableSimulationGlobal true;
    };    
} forEach (_RaidArea + _ObjectList);

Now passing the right values with remoteExec or BIS_fnc_MP should make the whole thing work.

 

You can also use https://community.bistudio.com/wiki/str instead of format when converting a value to a string.

With the exclamation mark ! you can also swap the result of the if statement, so no need for:

if (asd) then {} else {asd};

Instead:

if !(asd) then {asd};

Share this post


Link to post
Share on other sites

Wouldn't the arguments in the first if{isserver} then {"stuff in here"} be unnecessary?  instead of doing nothing for the nil items it "turns on" something that doesn't exist?

If you host the game (isServer) you can apply changes to objects directly. Otherwise, you use remoteExec with targets==2 to have this code executed on server. Of course, you can have remoteExec branch only, this will work on host too, costing you some time to execute extra call.

This code does not do nothing to nils. Notice exclamation mark that inverts isNil. This code only operates on all non-nil elements.

Share this post


Link to post
Share on other sites

Took me a while to figure out all my syntax mistakes.  I had a programming professor tell us once, "The good thing about computers is they do exactly what you tell them to.  The bad thing about computers is they do exactly what you tell them to."  So true.

this addAction ["Spawn Raid Site @ 079124", {[[RaidSite079124,HVT1], "scripts\RaidSiteActivator.sqf"] remoteExec ["execVM", 2];}];
private ["_RaidArea", "_HVT"];

_RaidArea = _this select 0;
_HVT = _this select 1;

_ObjectList = nearestObjects [_HVT,[],200];

{
    if !(isNil "_x") then { 
        _x hideObjectGlobal false; 
        _x enableSimulationGlobal true;
    };    
} forEach (_RaidArea + _ObjectList);

With the above in place I get error: undefined variable _raidarea at this line: } forEach (_RaidArea + _ObjectList); 

 

The _RaidArea that should have been passed to it was RaidArea079124 which was an array created in the On Activation box of an Independent Present trigger:

RaidArea079124 = []; 
{RaidArea079124  pushBack _x} forEach thisList;
publicVariable "RaidArea079124";

So it should be passing that array of the units it finds in that trigger area to the script.  The units defined within that trigger area have a probability of presence built in which is why the isNil is in place to kick out the ones that never spawned.

 

<smash head on keyboard>  :huh:

 

ETA: I'm an idiot and forgot my own variable name.  I passed it "RaidSite079124" and it should have been "RaidArea079124" 

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×