Jump to content
thy_

How to transfer a vehicle from the server-side to the client-side, and the way back again

Recommended Posts

Context:

Empty vehicles at the main base are protected (allowDamage false) and this protection is controlled by a custom pre-compiled server-side function.

For each player protection, just another function file is called by execVM in onPlayerRespawn.sqf or initPlayerLocal.sqf.

 

Issue when dedicated server:

When the vehicles are parked at the protected main base, the vehicles are unbreakable. Players are immortal. Both are as expected. However, when any player gets a vehicle, immediately the vehicle gets its natural state: breakable, even with the server-side function telling that vehicle to be unbreakable. When the player leaves the vehicle, the vehicle remains (badly) breakable, with the server (at least for a while) not protecting that anymore.

 

Am I right:

Reading some posts here, when a player ramps in a vehicle, this object is immediately transferred to the player's machine ownership (client-side). 

 

Question:

With a simplified example specifically aiming at this matter, how can we manage this vehicle protection when a player is inside the vehicle? And how do we give back to the server the ownership as soon the player leaves the vehicle withing the protected main base range?

 

Script setup:

Spoiler

// description.ext
class CfgFunctions
{
	#include "XXXX\THY_SD_functions.hpp"
};

// THY_SD_functions.hpp
class THY_SD_functions {
	tag = "THY";
	class XXXX {
		file = "XXXX";
		class SD_globalFunctions { preInit = 1 };
		class SD_serverSide { preInit = 1 };
		//class SD_clientSide { preInit = 1 }; <-- not needed pre loaded coz it's ran in client side.
	};
};

// onPlayerRespawn.sqf
params [ "_newUnit", "_oldUnit", "_respawn", "_respawnDelay" ];
[_newUnit] execVM "XXXX\fn_SD_clientSide.sqf";

 

 

fn_THY_clientSide.sqf (called on onPlayerRespawn.sqf or initPlayerLocal.sqf)

Spoiler

if !hasInterface exitWith {};

params ["_unit"];
private ["...", "..." ...];

// Initial values:
_zone       = objNull;
_zonePos    = [];
_rng        = 0;
_rngMkr     = "";
_sideZones  = [];

// Setting each Protected Zones (Markers):
{
	// Internal Declarations:
	_zone = _x # 0;
	_rng  = _x # 1;
	// If zone is from the same _unit's side:
	if ( (_x # 2) isEqualTo playerSide ) then {
		// Add as valid zone for this _unit:
		_sideZones pushBack [_zone, _rng];
		// If it's to show the protected zones on the map:
	};
} forEach SD_zonesCollection;

// Wait for the _unit be alive on the map to execute the codes right after this one below:
waitUntil { sleep 0.5; !isNull _unit };

// Looping to check the protected zones:
while { alive _unit } do {
	{
		// Internal Declarations:
		_zone    = _x # 0;
		_zonePos = getMarkerPos _zone;
		_rng     = _x # 1;
		// if _unit is into the base range:
		if ( _unit distance _zonePos <= _rng ) then {
			// if respecting the speed limit, and can breath regularly:
			if ( abs (speed _unit) <= SD_speedLimit && abs ((velocity _unit) # 2) <= SD_velocityLimit && isAbleToBreathe _unit ) then {
				// Makes _unit immortal:
				_unit allowDamage false;
				// Stay checking the unit until something break the checking:
				waitUntil {
					// Internal breath:
					sleep SD_checkDelay;
					// if unit's dead:
					!alive _unit ||
					// if unit's not in the zone:
					_unit distance _zonePos > _rng ||
					// if unit exceeded the horizontal speed:
					abs (speed _unit) > SD_speedLimit ||
					// if unit exceeded the vertical speed:
					abs ((velocity _unit) # 2) > SD_velocityLimit ||
					// if unit's deadly not breathing (underwater without diver gear):
					!isAbleToBreathe _unit
				};
				// Restores the _unit mortality:
				_unit allowDamage true;
			};
		};
		// Breather:
		sleep 10;
	} forEach _sideZones;
}; // while-looping ends.
// Return:
true;

 

 

fn_THY_serverSide.sqf

Spoiler

if !isServer exitWith {};

[] spawn {
		
	//params [""];
	private ["...", "..." ...];

	// Initial values:
	_mkr              = ""; 
	_rng              = 0;
	_side             = nil;
	_objTypesByZone   = [];  // debug purposes.
	_vehsByZone       = [];
	_aiUnitsByZone    = [];
	_zonePos          = [];
	_tag              = "";
	_result           = [];
	_allProtectedVehs = [];
	_dangerEqpnts     = [];
	_zonesAllSides    = [[/* 0=blu */],[/* 1=opf */],[/* 2=ind */],[/* 3=civ */]];
	_zonesBySide      = [];

	// Wait for the match get started:
	waitUntil { sleep 0.5; time > SD_wait };

	// STEP 1 - SCAN
	// Check each protected zone and select what vehicles will be protected:
	// doesnt matter here...

	// STEP 2 - GIVING PROTECTION:
	// Check each side:
	for "_i" from 0 to 3 do {
		// Internal declarations - part 1/3:
		_zonesBySide = _zonesAllSides # _i;
		// Escape if index content's empty:
		if ( count _zonesBySide isEqualTo 0 ) then { continue };
		{
		    // Internal declarations - part 2/3:
		    _vehsByZone = _x # 3;
		    // Starts a new thread for each equipment of a specific side (like vehicle and static weapon that must be protected):
		    { [_zonesBySide, _x] spawn THY_fnc_SD_protection_equipment; sleep 0.1 } forEach _vehsByZone;  // each = obj
		} forEach _zonesBySide;
		// CPU breather:
		sleep 1;
	};  // For-loop ends.
};	// Spawn ends.
// Return:
true;

THY_fnc_SD_protection_equipment = {
	// This function protects individualy each equipment (vehicle or static weapon) when inside of the range of all zones from the same side of the zone originally scanning the equipment at the mission starts.
	// Param: _obj: vehicle or static weapon.
	// Returns nothing.

	params ["_zonesBySide", "_obj"];
	private ["_rng", "_zonePos", "_var"];

	// Initial values:
	_rng     = 0;
	_zonePos = [];
	_var     = vehicleVarName _obj;
	// If _obj in-game:
	while { alive _obj } do {
		{  // forEach _zonesBySide:
			// Internal Declarations:
			_rng     = _x # 1;
			_zonePos = _x # 2;
			// if inside the protection range:
			if ( _obj distance _zonePos <= _rng ) then {
					// Makes _obj unbreakable:
					_obj allowDamage false;

					waitUntil {
						// Looping breather:
						sleep 3;
						// If obj's dead:
						!alive _obj ||
						// If obj's not in zone:
						_obj distance _zonePos > _rng
					};
					// Not inside the zone:
					if ( alive _obj && _obj distance _zonePos > _rng ) then {
						// Restores the _obj original fragility:
						_obj allowDamage true;
					};					
			};
			// Breather:
			sleep 3;
		} forEach _zonesBySide;
	};  // While-loop ends.
	// Return:
	true;
};

 

 

Share this post


Link to post
Share on other sites

the allowDamage command is LA GE. So, yes, you need to re-apply it when the vehicle's owner shifts (driver's PC).

You can use an EH such as:

group player addEventHandler ["VehicleAdded", { params ["_group", "_newVehicle"]; _newVehicle allowDamage FALSE}];

 

or even using "handleDamage" EH, everywhere!

yourVehicle addEventHandler ["HandleDamage", { params ["_veh","","_damage"]; if (condition for inside base && local _veh) then {_damage = 0}; _damage  }]; // always end by (return) _damage value.

This EH fires on every PC (place it in init.sqf for example, or init field on an edited vehicle...)  (and is persistent no extra code after respawn).

That's for the principle.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
19 hours ago, pierremgi said:

You can use an EH such as:

group player addEventHandler ["VehicleAdded", { params ["_group", "_newVehicle"]; _newVehicle allowDamage FALSE}];

 

Right... At night I'll check it on a dedicated server:

// vehicle inside the protected zone:
_obj allowDamage false;
// Getting the current crew:
private _crew = (crew _obj) select { isPlayer _x && alive _x };
// Checking if some player in veh:
if ( count _crew > 0 ) then {
    // Broadcast the settings for the players' machines:
    { (group _x) addEventHandler["VehicleAdded", { params ["_group", "_newVehicle"]; _newVehicle allowDamage false}] } forEach _crew;
};

Should I broadcast also the EH vehicleRemoved when the player leaves the vehicle or the vehicle is destroyed?

(I'm at work, so I'm not thinking deeply about whether makes sense).

Share this post


Link to post
Share on other sites

Could always use the locality EH, give each vehicle in your system some variable to flag whether they are currently protected, something like...

//server

THY_fnc_handleProtection = {
	params[ "_veh", "_isProtected" ];
	
	_veh setVariable[ "THY_isProtected", _isProtected, true ]; //Flag protected state to all machines
	
	//add protection base on current locality
	if ( local _veh ) then {
		_veh allowDamage !_isProtected;
	}else{
		[ _veh, !_isProtected ] remoteExec[ "allowDamage", owner _veh ];
	};
	
	//Only has to be added once for the life of the vehicle
	if ( _veh getVariable[ "THY_hasLocalityProtection", false ] ) then {
		_veh setVariable[ "THY_hasLocalityProtection", true ];
		
		//add EH to all machines for when the vehicle changes locality
		[ _veh, [ "local", {
			params[ "_vehicle", "_isLocal" ];
			
			//Is the vehicle still flagged as protected && is local
			if ( _isLocal && { _vehicle getVariable[ "THY_isProtected", false ] } ) then {
				_vehicle allowDamage false;
			};
		}] ] remoteExec[ "addEventHandler", 0, _veh ]; //and JIP whilst _veh still exists
	};
		
};

Then your check loop...

while { alive _obj } do {
	{  // forEach _zonesBySide:
		// Internal Declarations:
		_rng     = _x # 1;
		_zonePos = _x # 2;
		// if inside the protection range:
		if ( _obj distance _zonePos <= _rng ) then {
				// Makes _obj unbreakable:
				[ _obj, true ] call THY_fnc_handleProtection;

				waitUntil {
					// Looping breather:
					sleep 3;
					// If obj's dead:
					!alive _obj ||
					// If obj's not in zone:
					_obj distance _zonePos > _rng
				};
				// Not inside the zone:
				if ( alive _obj && _obj distance _zonePos > _rng ) then {
					// Restores the _obj original fragility:
					[ _obj, false ] call THY_fnc_handleProtection;
				};					
		};
		// Breather:
		sleep 3;
	} forEach _zonesBySide;
};  // While-loop ends.

Will need to reset THY_hasLocalityProtection to false / nil on all respawned vehicles as the module copies all object variables across to the new one.

 

Think that works out correctly as long as addEventhandler and allowDamage are remote executable.

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
12 hours ago, thy_ said:

 

Right... At night I'll check it on a dedicated server:


// vehicle inside the protected zone:
_obj allowDamage false;
// Getting the current crew:
private _crew = (crew _obj) select { isPlayer _x && alive _x };
// Checking if some player in veh:
if ( count _crew > 0 ) then {
    // Broadcast the settings for the players' machines:
    { (group _x) addEventHandler["VehicleAdded", { params ["_group", "_newVehicle"]; _newVehicle allowDamage false}] } forEach _crew;
};

 

 

It doesn't work 😞

Share this post


Link to post
Share on other sites
4 hours ago, thy_ said:

 

It doesn't work 😞

 

17 hours ago, thy_ said:

 


// vehicle inside the protected zone:
_obj allowDamage false;
// Getting the current crew:
private _crew = (crew _obj) select { isPlayer _x && alive _x };
// Checking if some player in veh:
if ( count _crew > 0 ) then {
    // Broadcast the settings for the players' machines:
    { (group _x) addEventHandler["VehicleAdded", { params ["_group", "_newVehicle"]; _newVehicle allowDamage false}] } forEach _crew;
};

Should I broadcast also the EH vehicleRemoved when the player leaves the vehicle or the vehicle is destroyed?

(I'm at work, so I'm not thinking deeply about whether makes sense).

 

You don't broadcast anything like this. Broadcasting doesn't mean "apply on all crew members"! A crew can be located on server, or on one PC (same group, and leader is client player), or even several PCs (more than one player in crew).

I don't know when/while you are running this code, but the check must be looped as far as players can join/leave the crew. Usually, an EH doesn't need a loop anyway, so your code is not right.

 

For a vehicle, the vehicle is always on DRIVER's PC. The vehicle can stay a little time on driver's PC when the played driver disembark, then return to server (its side changes, and probably its group changes, EH not tested).

 

NOTE: the EH vehicle added will fire as soon as the vehicle belongs to your group. That means as soon as you "give" a driver to it. If you order an AI to jump as driver, the EH fires even if the AI is still far from the vehicle.I can't say if this occurs after the locality of the vehicle changes. So, perhaps, you need a step more, just to be sure the locality has changed.

 

See Larrow's code for broadcasting.

If you don't want to broadcast a code, think about initPlayerLocal.sqf  where you can run code for all players locally. It's a good place for EHs like "vehicleAdded" which will fire locally and easily such command as allowdamage.

For debug, make sure the vehicle is added by a simple hint like:   hint format ["a %1 is added",typeOf _newVehicle];

 

So, in initPlayerLocal.sqf, the little code:
 

group player addEventHandler ["vehicleAdded", {
  params ["_grp","_newVeh"];
  _newVeh spawn {
    params ["_newVeh"];
    waitUntil {local _newVeh};
    _newVeh allowDamage FALSE
  }
}];

Once again, not sure the waitUntil local is mandatory... not deeply tested.

The protected area is a looped condition as Larrow wrote.

 

  • Thanks 1

Share this post


Link to post
Share on other sites

@pierremgi, I will check this out.

 

Just a comment: this looked a bit confusing for me. First part you're saying to keep (?) the loop check, and in the second one it's not the way to do it? 😅

On 11/17/2023 at 4:41 AM, pierremgi said:

I don't know when/while you are running this code, but the check must be looped as far as players can join/leave the crew. Usually, an EH doesn't need a loop anyway, so your code is not right.

 

 

 

On 11/17/2023 at 4:41 AM, pierremgi said:

So, in initPlayerLocal.sqf, the little code

fn_THY_clientSide.sqf is being called through onPlayerRespawn.sqf (and other cases via initPlayerLocal.sqf already. 

 

I will take some tests and keep you all posted. Thanks for investing time here, boys. 

Share this post


Link to post
Share on other sites

Event handlers are "waiting" for an event. So, most of the time you don't need loop. anyway if you have an extra condition which should be checked more often than the event occurs, you need to loop this check.  Here, adding a vehicle in a group can help for finding its locality (but not fine if 2 players in the same group if they drive at their turn). The check for position in your protected area's) are not correlated with that, so you need an extra check (loop, as far as the position changes).   No immediate solution.

 

Other test, in init field of vehicle (edited):
 

this addEventHandler ["Local", {
  params ["_veh", "_isLocal"];
  if _isLocal then {
    _veh spawn {
      while {alive _veh && local _veh} do {
        waitUntil {sleep 1; _veh inArea [center,a,b,0,false, c]};
        _veh allowDamage FALSE;
        waitUntil {sleep 1; !(_veh inArea [center,a,b,0,false,c])};
        _veh allowDamage TRUE;
      };
    };
  };
}];

[center,a,b,0,false,c] must be like : [getMarkerPos "mk1",200,200,0,false,200]  (no local variable).

  • Like 1

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

×