Pārlūkot izejas kodu

1.28更新内容

Dcr 3 nedēļas atpakaļ
vecāks
revīzija
1c57d76cc0
100 mainītis faili ar 2689 papildinājumiem un 1363 dzēšanām
  1. 34 0
      Scripts/1_core/proto/dbgui.c
  2. 19 11
      Scripts/1_core/proto/endebug.c
  3. 2 0
      Scripts/1_core/proto/enentity.c
  4. 13 1
      Scripts/1_core/proto/enmath.c
  5. 8 0
      Scripts/1_core/proto/enmath3d.c
  6. 49 13
      Scripts/1_core/proto/enphysics.c
  7. 24 0
      Scripts/1_core/proto/enscript.c
  8. 1 1
      Scripts/1_core/proto/enworld.c
  9. 30 4
      Scripts/3_game/cfggameplaydatajson.c
  10. 10 0
      Scripts/3_game/cfggameplayhandler.c
  11. 2 0
      Scripts/3_game/constants.c
  12. 1 1
      Scripts/3_game/dayzplayer.c
  13. 27 2
      Scripts/3_game/effect.c
  14. 18 5
      Scripts/3_game/effects/effectsound.c
  15. 3 3
      Scripts/3_game/entities/building.c
  16. 7 0
      Scripts/3_game/entities/camera.c
  17. 44 0
      Scripts/3_game/entities/dayzanimal.c
  18. 4 0
      Scripts/3_game/entities/entity.c
  19. 8 3
      Scripts/3_game/entities/entityai.c
  20. 36 8
      Scripts/3_game/entities/man.c
  21. 32 29
      Scripts/3_game/entities/object.c
  22. 4 1
      Scripts/3_game/entities/pawn.c
  23. 45 4
      Scripts/3_game/entities/scriptedentity.c
  24. 2 0
      Scripts/3_game/enums/eactions.c
  25. 9 9
      Scripts/3_game/enums/eagents.c
  26. 4 4
      Scripts/3_game/enums/econtaminationtypes.c
  27. 1 0
      Scripts/3_game/enums/ecrewmemberstate.c
  28. 2 0
      Scripts/3_game/enums/ediagmenuids.c
  29. 1 0
      Scripts/3_game/enums/edynamicmusicplayercategory.c
  30. 56 1
      Scripts/3_game/global/game.c
  31. 19 3
      Scripts/3_game/human.c
  32. 5 5
      Scripts/3_game/impacteffects.c
  33. 4 0
      Scripts/3_game/particles/particlelist.c
  34. 5 5
      Scripts/3_game/persistentflag.c
  35. 5 5
      Scripts/3_game/ppemanager/ppeconstants.c
  36. 60 2
      Scripts/3_game/ppemanager/requesters/pperunconeffects.c
  37. 3 3
      Scripts/3_game/surfaceinfo.c
  38. 130 48
      Scripts/3_game/systems/dynamicmusicplayer/dynamicmusicplayer.c
  39. 34 1
      Scripts/3_game/systems/dynamicmusicplayer/dynamicmusicplayerregistry.c
  40. 15 9
      Scripts/3_game/systems/inventory/debug.c
  41. 36 10
      Scripts/3_game/systems/inventory/hand_events.c
  42. 2 2
      Scripts/3_game/systems/universaltemperaturesource/universaltemperaturesourcelambdabase.c
  43. 66 0
      Scripts/3_game/tools/blend2d.c
  44. 1 1
      Scripts/3_game/tools/component/componentenergymanager.c
  45. 8 0
      Scripts/3_game/tools/input.c
  46. 1 0
      Scripts/3_game/tools/uiscriptedmenu.c
  47. 29 31
      Scripts/3_game/vehicles/boat.c
  48. 144 161
      Scripts/3_game/vehicles/car.c
  49. 116 19
      Scripts/3_game/vehicles/transport.c
  50. 15 1
      Scripts/3_game/weather.c
  51. 1 1
      Scripts/4_world/classes/basebuilding/construction.c
  52. 3 3
      Scripts/4_world/classes/basebuilding/constructionactiondata.c
  53. 10 6
      Scripts/4_world/classes/bleedingsources/bleedingsource.c
  54. 30 43
      Scripts/4_world/classes/bleedingsources/bleedingsourcesmanagerremote.c
  55. 58 51
      Scripts/4_world/classes/bleedingsources/bleedingsourcesmanagerserver.c
  56. 20 24
      Scripts/4_world/classes/contaminatedarea/contaminatedarea.c
  57. 70 221
      Scripts/4_world/classes/contaminatedarea/contaminatedarea_dynamic.c
  58. 160 0
      Scripts/4_world/classes/contaminatedarea/contaminatedarea_dynamicbase.c
  59. 25 16
      Scripts/4_world/classes/contaminatedarea/contaminatedarea_local.c
  60. 25 17
      Scripts/4_world/classes/contaminatedarea/contaminatedarealoader.c
  61. 216 101
      Scripts/4_world/classes/contaminatedarea/effectarea.c
  62. 82 30
      Scripts/4_world/classes/contaminatedarea/geyserarea.c
  63. 8 1
      Scripts/4_world/classes/contaminatedarea/hotspringarea.c
  64. 12 18
      Scripts/4_world/classes/contaminatedarea/spookyarea.c
  65. 8 1
      Scripts/4_world/classes/contaminatedarea/volcanicarea.c
  66. 5 8
      Scripts/4_world/classes/emoteclasses/emoteclasses.c
  67. 75 29
      Scripts/4_world/classes/emotemanager.c
  68. 326 114
      Scripts/4_world/classes/environment/environment.c
  69. 1 1
      Scripts/4_world/classes/explosions/flashbangeffect.c
  70. 16 37
      Scripts/4_world/classes/heatcomfortanimhandler.c
  71. 11 0
      Scripts/4_world/classes/playergearspawn/cfgplayerspawnhandler.c
  72. 2 2
      Scripts/4_world/classes/playermodifiers/modifiers/diseases/salmonella.c
  73. 57 22
      Scripts/4_world/classes/recipes/recipebase.c
  74. 8 20
      Scripts/4_world/classes/recipes/recipes/cleanweapon.c
  75. 2 2
      Scripts/4_world/classes/recipes/recipes/craftwoodenplank.c
  76. 3 3
      Scripts/4_world/classes/recipes/recipes/patchitem.c
  77. 3 3
      Scripts/4_world/classes/recipes/recipes/pluginrecipesmanagerbase.c
  78. 1 1
      Scripts/4_world/classes/recipes/recipes/repairwithtape.c
  79. 5 5
      Scripts/4_world/classes/recipes/recipes/sawwoodenlog.c
  80. 26 26
      Scripts/4_world/classes/recipes/recipes/splitbroom.c
  81. 7 7
      Scripts/4_world/classes/recipes/recipes/splitfirewood.c
  82. 26 26
      Scripts/4_world/classes/recipes/recipes/splitlongwoodenstick.c
  83. 1 1
      Scripts/4_world/classes/recoilbase/recoils/ak101recoil.c
  84. 1 1
      Scripts/4_world/classes/recoilbase/recoils/ak74recoil.c
  85. 2 2
      Scripts/4_world/classes/recoilbase/recoils/akmrecoil.c
  86. 1 1
      Scripts/4_world/classes/recoilbase/recoils/aks74urecoil.c
  87. 1 1
      Scripts/4_world/classes/recoilbase/recoils/augrecoil.c
  88. 1 1
      Scripts/4_world/classes/recoilbase/recoils/falrecoil.c
  89. 1 1
      Scripts/4_world/classes/recoilbase/recoils/famasrecoil.c
  90. 2 2
      Scripts/4_world/classes/recoilbase/recoils/m16a2recoil.c
  91. 2 2
      Scripts/4_world/classes/recoilbase/recoils/m4a1recoil.c
  92. 2 2
      Scripts/4_world/classes/recoilbase/recoils/mp5krecoil.c
  93. 28 0
      Scripts/4_world/classes/recoilbase/recoils/pm73rak.c
  94. 5 5
      Scripts/4_world/classes/recoilbase/recoils/pp19recoil.c
  95. 28 0
      Scripts/4_world/classes/recoilbase/recoils/r12recoil.c
  96. 4 4
      Scripts/4_world/classes/recoilbase/recoils/skorpionrecoil.c
  97. 1 1
      Scripts/4_world/classes/recoilbase/recoils/sksrecoil.c
  98. 5 5
      Scripts/4_world/classes/recoilbase/recoils/ump45recoil.c
  99. 107 88
      Scripts/4_world/classes/shockhandler.c
  100. 1 1
      Scripts/4_world/classes/soundevents/playersoundevents/playersoundeventhandler.c

+ 34 - 0
Scripts/1_core/proto/dbgui.c

@@ -89,6 +89,40 @@ class DbgUI
 	
 	static proto native void Begin(string windowTitle, float x = 0, float y = 0);
     static proto native void End();
+	
+	//! Draw an "override" checkbox that unrolls into a slider in provided range when checked	
+	static bool FloatOverride(string id, inout float value, float min, float max, int precision = 1000, bool sameLine = true)
+	{
+		if (sameLine)
+			DbgUI.SameLine();
+		
+		bool enable;
+		
+		DbgUI.PushID_Str(id+"_override");
+		DbgUI.Check("override", enable);
+		DbgUI.PopID();
+		
+		if (enable)
+		{
+			DbgUI.SameLine();
+			float tmp = value * (float)precision;
+			
+			DbgUI.PushID_Str(id+"_slider");
+			DbgUI.SliderFloat("", tmp, min * (float)precision, max * (float)precision);
+			DbgUI.PopID();
+			
+			tmp = tmp / (float)precision;
+			DbgUI.SameSpot();
+			DbgUI.PushID_Str(id+"_slider_text");
+			DbgUI.Text(tmp.ToString());
+			DbgUI.PopID();
+			
+			value = tmp;
+			return true;
+		}
+		
+		return false;
+	}
 };
 //@}
 //@}

+ 19 - 11
Scripts/1_core/proto/endebug.c

@@ -140,11 +140,11 @@ enum ShapeFlags
 enum CollisionFlags
 {
 	FIRSTCONTACT,	//<In many cases only collided=true/false is enough
-	NEARESTCONTACT	//<We want only one, the nearest contact
-	ONLYSTATIC		//<Only static objects
-	ONLYDYNAMIC		//<Only dynamic objects
-	ONLYWATER		//<Only water components (legacy support for "walk on geometry")
-	ALLOBJECTS		//<Valid when CF_FIRST_CONTACT, we get first contact for each object
+	NEARESTCONTACT,	//<We want only one, the nearest contact
+	ONLYSTATIC,		//<Only static objects
+	ONLYDYNAMIC,	//<Only dynamic objects
+	ONLYWATER,		//<Only water components (legacy support for "walk on geometry")
+	ALLOBJECTS,		//<Valid when CF_FIRST_CONTACT, we get first contact for each object
 }
 		
 /*!
@@ -211,13 +211,21 @@ class Shape
 		return CreateLines(color, flags, pts, 7);
 	}
 
-	static void CreateMatrix(vector mat[4])
+	static void CreateMatrix(vector mat[4], float axisLength = 0.05, float arrowSize = 0.0)
 	{
-		vector org = mat[3];
-		int flags = ShapeFlags.NOZWRITE|ShapeFlags.DOUBLESIDE|ShapeFlags.TRANSP|ShapeFlags.ONCE;
-		Create(ShapeType.LINE, 0xffff0000, flags, org, mat[0] * 0.5 + org);
-		Create(ShapeType.LINE, 0xff00ff00, flags, org, mat[1] * 0.5 + org);
-		Create(ShapeType.LINE, 0xff0000ff, flags, org, mat[2] * 0.5 + org);
+		int flags = ShapeFlags.ONCE | ShapeFlags.NOZWRITE | ShapeFlags.DOUBLESIDE | ShapeFlags.TRANSP;
+		if (arrowSize <= 0)
+		{
+			Create(ShapeType.LINE, 0xFFFF0000, flags, mat[3], mat[3] + axisLength * mat[0]);
+			Create(ShapeType.LINE, 0xFF00FF00, flags, mat[3], mat[3] + axisLength * mat[1]);
+			Create(ShapeType.LINE, 0xFF0000FF, flags, mat[3], mat[3] + axisLength * mat[2]);
+		}
+		else
+		{
+			CreateArrow(mat[3], mat[3] + axisLength * mat[0], arrowSize, 0xFFFF0000, flags);
+			CreateArrow(mat[3], mat[3] + axisLength * mat[1], arrowSize, 0xFF00FF00, flags);
+			CreateArrow(mat[3], mat[3] + axisLength * mat[2], arrowSize, 0xFF0000FF, flags);
+		}
 	}
 };
 

+ 2 - 0
Scripts/1_core/proto/enentity.c

@@ -559,6 +559,8 @@ class IEntity: Managed
 	*/
 	proto native external bool RemoveChild(notnull IEntity child, bool keepTransform = false);
 	
+	proto native external Physics GetPhysics();
+
 	//! Returns if the hierarchy component was created with positionOnly
 	proto native bool IsHierarchyPositionOnly();
 

+ 13 - 1
Scripts/1_core/proto/enmath.c

@@ -732,7 +732,7 @@ class Math
 		\param inputMin \p float Minimal value of given input range
 		\param inputMax \p float Maximal value of given input range
 		\param outputMin \p float Minimal value of given output range
-		\param outputMax \p float Maximal value of given input range
+		\param outputMax \p float Maximal value of given output range
 		\param inputValue \p float Value we want to remap
 		\param clampedOutput\p bool If value should stay in that range, otherwise it will be extrapolated 
 		\return \p float - Remapped value
@@ -755,6 +755,18 @@ class Math
 		
 		return Vector(x, 0.0, z);
 	}
+
+	/**
+	\brief Returns if given vectors are equal with given tolerance
+		\param v1 \p float First vector for comparison
+		\param v2 \p float Second vector for comparison
+		\param tolerance \p float Range in which given vectors can differ
+		\return \p bool - True if Vectors are equal; otherwise false
+	*/	
+	static bool VectorIsEqual(vector v1, vector v2, float tolerance)
+	{
+	    return (Math.AbsFloat(v1[0] - v2[0]) <= tolerance && Math.AbsFloat(v1[1] - v2[1]) <= tolerance && Math.AbsFloat(v1[2] - v2[2]) <= tolerance);
+	}
 }
 
 //@}

+ 8 - 0
Scripts/1_core/proto/enmath3d.c

@@ -493,5 +493,13 @@ class Math3D
 		\param[out] rightPoint \p vector Right point of the cone
 	*/
 	proto static void ConePoints(vector origin, float length, float halfAngle, float angleOffset, out vector leftPoint, out vector rightPoint);
+	
+	/*!
+		Output 2D blend space weights for `inPositions` when sampled at `samplePosition`
+		\param samplePosition [X, Y, unused] coordinate to sample the 2d space
+		\param inPositions [X, Y, unused] positions in the 2d space
+		\param outWeights Output weights for individual nodes 1:1 to inPositions
+	*/
+	proto static void BlendCartesian(vector samplePosition, notnull array<vector> inPositions, notnull array<float> outWeights);
 };
 //@}

+ 49 - 13
Scripts/1_core/proto/enphysics.c

@@ -301,28 +301,64 @@ proto native float dJointSliderGetAngularPos(dJoint joint);
 //-----------------------------------------------------------------
 typedef int[] dMaterial;
 
-class Contact
+typedef int[] Physics;
+class Physics
+{
+	protected void Physics() {};
+	protected void ~Physics() {};
+
+	// PHYSICS_TODO: Duplicate above functions into here
+
+	proto int GetNumBones();
+	proto Physics GetBone(int index);
+
+};
+
+typedef int[] SurfaceProperties;
+class SurfaceProperties
+{
+	protected void SurfaceProperties() {};
+	protected void ~SurfaceProperties() {};
+
+};
+
+sealed class Contact
 {
 	private void Contact() {}
 	private void ~Contact() {}
-	
-	dMaterial	Material1;
-	dMaterial	Material2;
-	int			MaterialIndex1;
-	int			MaterialIndex2;
-	int			Index1;
-	int			Index2;
 
-	float		PenetrationDepth;
+	Physics Physics1;
+	Physics Physics2;
+	//! Surface properties of Object1
+	SurfaceProperties Material1; 
+	//! Surface properties of Object2
+	SurfaceProperties Material2;
+	//! Impulse applied to resolve the collision
+	float Impulse;
+	//! Index of collider on Object1
+	int ShapeIndex1;
+	//! Index of collider on Object2
+	int ShapeIndex2;
+	//! Collision axis at the contact point
+	vector	Normal;
+	//! Position of the contact point (world space)
+	vector	Position;
+	//! Penetration depth on Object1
+	float PenetrationDepth;
 
-	float		Impulse;
 	float		RelativeNormalVelocityBefore;
 	float		RelativeNormalVelocityAfter;
-
-	vector		Normal;
-	vector		Position;
 	vector		RelativeVelocityBefore;
 	vector		RelativeVelocityAfter;
+	
+	//! Velocity of Object1 before collision (world space)
+	vector	VelocityBefore1;
+	//! Velocity of Object2 before collision (world space)
+	vector	VelocityBefore2;
+	//! Velocity of Object1 after collision (world space)
+	vector	VelocityAfter1;
+	//! Velocity of Object2 after collision (world space)
+	vector	VelocityAfter2;
 
 	proto native vector GetNormalImpulse();
 	proto native float GetRelativeVelocityBefore(vector vel);

+ 24 - 0
Scripts/1_core/proto/enscript.c

@@ -341,6 +341,30 @@ string String(string s)
 	return s;
 }
 
+/*!
+Marks method as obsolete. When is the method used, compiler just throw a compile-time
+warning, but method is called normally.
+\code
+	[Obsolete("Use different method!")]
+	void Hello()
+	{
+	}
+
+	void Test()
+	{
+		Hello(); // throws compile warning on this line: 'Hello' is obsolete: use different method!
+	}
+\endcode
+*/
+class Obsolete: Managed
+{
+	string m_Msg;
+	void Obsolete(string msg = "")
+	{
+		m_Msg = msg;
+	}
+}
+
 //!Helper for printing out string expression. Example: PrintString("Hello " + var);
 void PrintString(string s)
 {

+ 1 - 1
Scripts/1_core/proto/enworld.c

@@ -157,7 +157,7 @@ proto native int ClearLightFlags(HLIGHT light, LightFlags flags);
 proto native bool SetLightCone(HLIGHT light, float cone);
 
 /*!
-scene multiplicator of light (based on measured scene light levels) - preexposure of light
+scene light multiplier (based on measured scene light levels) - pre-exposure of light
 */
 proto native float GetSceneHDRMul(int camera);
 //@}

+ 30 - 4
Scripts/3_game/cfggameplaydatajson.c

@@ -65,10 +65,11 @@ class ITEM_GeneralData : ITEM_DataBase
 
 class ITEM_PlayerData : ITEM_DataBase
 {
-	ref ITEM_StaminaData StaminaData			 	= new ITEM_StaminaData;
-	ref ITEM_ShockHandlingData ShockHandlingData 	= new ITEM_ShockHandlingData;
-	ref ITEM_MovementData MovementData 				= new ITEM_MovementData;
-	ref ITEM_DrowningData DrowningData 				= new ITEM_DrowningData;
+	ref ITEM_StaminaData StaminaData			 			= new ITEM_StaminaData;
+	ref ITEM_ShockHandlingData ShockHandlingData 			= new ITEM_ShockHandlingData;
+	ref ITEM_MovementData MovementData 						= new ITEM_MovementData;
+	ref ITEM_DrowningData DrowningData 						= new ITEM_DrowningData;
+	ref ITEM_WeaponObstructionData WeaponObstructionData	= new ITEM_WeaponObstructionData;
 	
 	override void InitServer()
 	{
@@ -359,6 +360,31 @@ class ITEM_DrowningData : ITEM_DataBase
 	float shockDepletionSpeed = 10;
 }
 
+//--------------------------------------------------------------------------------------------------------------------------------------------------
+enum EWeaponObstructionMode
+{
+	DISABLED = 0, // Obstruction disallowed. Weapon doesn't obstruct, but still lifts.
+	ENABLED  = 1, // Obstruction allowed.    Weapon first obstructs and then lifts.
+	ALWAYS   = 2, // Obstruction always.     Weapon obstructs and never lifts.
+}
+
+class ITEM_WeaponObstructionData : ITEM_DataBase
+{
+	override void InitServer()
+	{
+	}
+	
+	override bool ValidateServer()
+	{
+		return true;
+	}
+	
+	//-------------------------------------------------------------------------------------------------
+	//!!! all member variables must correspond with the cfggameplay.json file contents !!!!
+	EWeaponObstructionMode staticMode  = EWeaponObstructionMode.ENABLED;
+	EWeaponObstructionMode dynamicMode = EWeaponObstructionMode.ENABLED;
+}
+
 //--------------------------------------------------------------------------------------------------------------------------------------------------
 
 class ITEM_VehicleData : ITEM_DataBase

+ 10 - 0
Scripts/3_game/cfggameplayhandler.c

@@ -259,6 +259,16 @@ class CfgGameplayHandler
 		return Math.Max(0.01, m_Data.PlayerData.MovementData.rotationSpeedSprint);
 	}
 	//----------------------------------------------------------------------------------
+	static EWeaponObstructionMode GetWeaponObstructionModeStatic()
+	{
+		return m_Data.PlayerData.WeaponObstructionData.staticMode;
+	}
+	//----------------------------------------------------------------------------------
+	static EWeaponObstructionMode GetWeaponObstructionModeDynamic()
+	{
+		return m_Data.PlayerData.WeaponObstructionData.dynamicMode;
+	}
+	//----------------------------------------------------------------------------------
 	static bool GetDisable2dMap()
 	{
 		return m_Data.PlayerData.disable2dMap;

+ 2 - 0
Scripts/3_game/constants.c

@@ -417,6 +417,8 @@ class SoundConstants
 	const int ITEM_PLACE = 1;
 	const int ITEM_DEPLOY_LOOP = 2;
 	const int ITEM_DEPLOY = 3;
+	const int ITEM_FOLD_LOOP = 4;
+	const int ITEM_FOLD = 5;
 	
 	const int ITEM_BARREL_OPEN = 20;
 	const int ITEM_BARREL_CLOSE = 21;

+ 1 - 1
Scripts/3_game/dayzplayer.c

@@ -1315,7 +1315,7 @@ class DayZPlayer extends Human
 	
 	//! returns true if player is currently in one of the stances specified by stance mask 
 	//! IsPlayerInStance(STANCEMASK_ERECT | STANCEMASK_CROUCH) returns true if player is standing or crouching and not raised (aimed)
-	//! IsPlayerInStance(STANCEMASK_PRONE | STANCEIDX_RAISEDPRONE) returns true if player is in or in prone (both raised or nonraised)
+	//! IsPlayerInStance(STANCEMASK_PRONE | STANCEMASK_RAISEDPRONE) returns true if player is in prone (both raised or nonraised)
 	//! IsPlayerInStance(STANCEMASK_ALL) returns true always 
 	//! IsPlayerInStance(STANCEMASK_RAISEDERECT | STANCEMASK_RAISEDCROUCH | STANCEMASK_RAISEDPRONE) returns true if player has raised hands
 	proto native 	bool	IsPlayerInStance(int pStanceMask);		// STANCEMASK_ERECT | STANCEMASK_CROUCH 

+ 27 - 2
Scripts/3_game/effect.c

@@ -37,6 +37,8 @@ class Effect : Managed
 	protected bool				m_IsPlaying;
 	//! Cached parent
 	protected Object 			m_ParentObject;
+	//! Cached parent pivot id
+	protected int				m_PivotIndex = -1;
 	//! Cached world position
 	protected vector    		m_Position;
 	//@}
@@ -387,6 +389,19 @@ class Effect : Managed
 	*/
 	//@{
 	
+	/**
+	\brief Set parent of the Effect
+		\note Same as SetAttachmentParent, but more generic name
+		\warning Only sets the cached variable, for immediate effect use SetCurrent variant
+		\param parent_obj \p Object The parent
+		\param pivot \p int The pivot index
+	*/
+	void SetParent(Object parent_obj, int pivot)
+	{
+		m_ParentObject = parent_obj;
+		m_PivotIndex = pivot;
+	}
+
 	/**
 	\brief Set parent of the Effect
 		\note Same as SetAttachmentParent, but more generic name
@@ -395,7 +410,7 @@ class Effect : Managed
 	*/
 	void SetParent(Object parent_obj)
 	{
-		m_ParentObject = parent_obj;
+		SetParent(parent_obj, -1);
 	}
 	
 	/**
@@ -408,6 +423,16 @@ class Effect : Managed
 	{
 		return m_ParentObject;
 	}
+
+	/**
+	\brief Get parent pivot of the Effect, only valid when there is some GetParent
+		\warning Only gets the cached variable
+		\return \p int The parent pivot of the Effect
+	*/
+	int GetPivotIndex()
+	{
+		return m_PivotIndex;
+	}
 	
 	/**
 	\brief Set current parent of the managed effect
@@ -616,4 +641,4 @@ class Effect : Managed
 	}
 	
 	//@}
-}
+}

+ 18 - 5
Scripts/3_game/effects/effectsound.c

@@ -164,7 +164,7 @@ class EffectSound : Effect
 					m_SoundObjectBuilder.AddEnvSoundVariables(GetPosition());
 					m_SoundObject = m_SoundObjectBuilder.BuildSoundObject();
 					m_SoundObject.SetKind( m_SoundWaveKind );
-					m_SoundObject.SetParent( m_ParentObject );
+					m_SoundObject.SetParent( m_ParentObject, m_PivotIndex );
 				}
 				
 				if ( m_SoundObject )
@@ -325,7 +325,7 @@ class EffectSound : Effect
 			if ( m_SoundObject )
 			{
 				m_SoundObject.SetKind( m_SoundWaveKind );
-				m_SoundObject.SetParent( m_ParentObject );
+				m_SoundObject.SetParent( m_ParentObject, m_PivotIndex );
 			}
 			else
 			{
@@ -651,13 +651,13 @@ class EffectSound : Effect
 	\brief Set parent for the sound to follow
 		\param parent_obj \p Object The parent for the sound to follow
 	*/
-	override void SetParent(Object parent_obj)
+	override void SetParent(Object parent_obj, int pivot)
 	{
-		super.SetParent(parent_obj); // ...
+		super.SetParent(parent_obj, pivot); // ...
 
 		if (m_SoundObject)
 		{
-			m_SoundObject.SetParent(parent_obj);
+			m_SoundObject.SetParent(parent_obj, pivot);
 		}
 	}
 	
@@ -673,6 +673,19 @@ class EffectSound : Effect
 			return super.GetParent();
 	}
 	
+	/**
+	\brief Get parent pivot of the Effect, only valid when there is some GetParent
+		\warning Only gets the cached variable
+		\return \p int The parent pivot of the Effect
+	*/
+	override int GetPivotIndex()
+	{
+		if (m_SoundObject)
+			return m_SoundObject.GetHierarchyPivot();
+		else
+			return super.GetPivotIndex();
+	}
+	
 	/**
 	\brief Get parent for the EffectSound
 		\note There is no real parenting with sound, so the setters and getters for parents do the exact same

+ 3 - 3
Scripts/3_game/entities/building.c

@@ -167,14 +167,14 @@ class Building extends EntityAI
 	override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
 	{
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_OUTPUT_LOG, "Output Door Log", FadeColors.LIGHT_GREY));
-		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, " --- ", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
 
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_PLAY_DOOR_SOUND, "Play Door Sound", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_OPEN_DOOR, "Open Door", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_CLOSE_DOOR, "Close Door", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_LOCK_DOOR, "Lock Door", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.BUILDING_UNLOCK_DOOR, "Unlock Door", FadeColors.LIGHT_GREY));
-		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, " --- ", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
 		
 		super.GetDebugActions(outputList);
 	}
@@ -232,7 +232,7 @@ class Building extends EntityAI
 	}
 
 	ref TIntArray m_InteractActions;
-	
+
 	void Building()
 	{
 		m_InteractActions = new TIntArray;

+ 7 - 0
Scripts/3_game/entities/camera.c

@@ -89,6 +89,13 @@ class FreeDebugCamera extends Camera
 	*/
 	static proto native FreeDebugCamera GetInstance();
 	
+	/**
+	\brief Returns if player movement is allowed while in free camera
+	\note See 'Game > Player move in FrCam' diag option
+	\return bool \p true if player movement is allowed
+	*/
+	proto native bool IsPlayerMove();
+
 	/**
 	\brief Sets cameras freeze state
 	\param freezed \p true = don't allow camera movement

+ 44 - 0
Scripts/3_game/entities/dayzanimal.c

@@ -122,6 +122,48 @@ class DayZCreature extends EntityAI
 		return true;
 	}
 
+	override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
+	{
+		super.GetDebugActions(outputList);
+		
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.DELETE, "Delete", FadeColors.RED));
+		if (Gizmo_IsSupported())
+			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GIZMO_OBJECT, "Gizmo Object", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GIZMO_PHYSICS, "Gizmo Physics (SP Only)", FadeColors.LIGHT_GREY)); // intentionally allowed for testing physics desync
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
+	}
+	
+	override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
+	{
+		if (super.OnAction(action_id, player, ctx))
+			return true;
+
+		if (GetGame().IsClient() || !GetGame().IsMultiplayer())
+		{
+			switch (action_id)
+			{
+				case EActions.GIZMO_OBJECT:
+					GetGame().GizmoSelectObject(this);
+					return true;
+				case EActions.GIZMO_PHYSICS:
+					GetGame().GizmoSelectPhysics(GetPhysics());
+					return true;
+			}
+		}
+	
+		if (GetGame().IsServer())
+		{
+			switch (action_id)
+			{
+				case EActions.DELETE:
+					Delete();
+					return true;
+			}
+		}
+	
+		return false;
+	}
+
 	//-------------------------------------------------------------
 	//!
 	//! ModOverrides
@@ -644,6 +686,8 @@ class DayZAnimal extends DayZCreatureAI
 		
 		//! sets default hit position and cache it here (mainly for impact particles)
 		m_DefaultHitPosition = SetDefaultHitPosition(GetDefaultHitPositionComponent());
+
+		SetEventMask(EntityEvent.CONTACT);
 	}
 	
 	override bool IsHealthVisible()

+ 4 - 0
Scripts/3_game/entities/entity.c

@@ -14,6 +14,10 @@ class Entity extends ObjectTyped
 	//! Process animation on object. Animation is defined in config file. Wanted animation phase is set to phase.
 	proto native void SetAnimationPhase(string animation, float phase);
 	
+	proto int GetNumUserAnimationSourceNames();
+
+	proto string GetUserAnimationSourceName(int index);
+
 	//! Same as SetAnimationPhase, only ignores any animation and sets the phase immediately
 	void SetAnimationPhaseNow(string animation, float phase)
 	{

+ 8 - 3
Scripts/3_game/entities/entityai.c

@@ -1522,7 +1522,7 @@ class EntityAI extends Entity
 	bool CanReceiveItemIntoCargo(EntityAI item)
 	{
 		if (GetInventory() && GetInventory().GetCargo())
-			return GetInventory().GetCargo().CanReceiveItemIntoCargo(item));
+			return GetInventory().GetCargo().CanReceiveItemIntoCargo(item);
 		
 		return true;
 	}
@@ -1558,7 +1558,7 @@ class EntityAI extends Entity
 	bool CanSwapItemInCargo (EntityAI child_entity, EntityAI new_entity)
 	{
 		if (GetInventory() && GetInventory().GetCargo())
-			return GetInventory().GetCargo().CanSwapItemInCargo(child_entity, new_entity));
+			return GetInventory().GetCargo().CanSwapItemInCargo(child_entity, new_entity);
 		
 		return true;
 	}
@@ -1807,7 +1807,7 @@ class EntityAI extends Entity
 
 	EntityAI FindAttachmentBySlotName(string slot_name)
 	{
-		if ( GetGame() )
+		if (g_Game && GetInventory())
 		{
 			int slot_id = InventorySlots.GetSlotIdFromString(slot_name);
 			if (slot_id != InventorySlots.INVALID)
@@ -2202,6 +2202,8 @@ class EntityAI extends Entity
 		return 0;
 	}
 	
+	void SetQuantityNormalized(float value, bool destroy_config = true, bool destroy_forced = false);
+			
 	float GetQuantityNormalized()
 	{
 		return 0;
@@ -3599,13 +3601,16 @@ class EntityAI extends Entity
 	{
 		//fix entity
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FIX_ENTITY, "Fix Entity", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
 	
 		//weight
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GET_TOTAL_WEIGHT, "Print Weight", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GET_TOTAL_WEIGHT_RECALC, "Print Weight Verbose", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GET_PLAYER_WEIGHT, "Print Player Weight", FadeColors.LIGHT_GREY));
 		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GET_PLAYER_WEIGHT_RECALC, "Print Player Weight Verbose", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
 	}
+
 	bool OnAction(int action_id, Man player, ParamsReadContext ctx)
 	{
 		if (action_id == EActions.FIX_ENTITY)

+ 36 - 8
Scripts/3_game/entities/man.c

@@ -218,7 +218,8 @@ class Man extends EntityAI
 		GetHumanInventory().LocalDestroyEntity(GetHumanInventory().GetEntityInHands());
 		UpdateInventoryMenu();
 	}
-
+	
+	//! ToDo: Old system method. Might should be adjusted to new system at some point
 	void PredictiveMoveItemFromHandsToInventory ()
 	{
 		if (LogManager.IsSyncLogEnable()) syncDebugPrint("[inv] " + GetDebugName(this) + " STS = " + GetSimulationTimeStamp() + " Stash IH=" + GetHumanInventory().GetEntityInHands());
@@ -229,25 +230,41 @@ class Man extends EntityAI
 		}
 		
 		InventoryMode invMode = InventoryMode.PREDICTIVE;
+		EntityAI entityInHands = GetHumanInventory().GetEntityInHands();
 		
-		if (NeedInventoryJunctureFromServer( GetHumanInventory().GetEntityInHands(), this, this))
+		if (NeedInventoryJunctureFromServer(entityInHands, this, this))
 			invMode = InventoryMode.JUNCTURE;
 
 		//! returns item to previous location, if available
-		if (GetHumanInventory().GetEntityInHands().m_OldLocation && GetHumanInventory().GetEntityInHands().m_OldLocation.IsValid())
+		if (entityInHands.m_OldLocation && entityInHands.m_OldLocation.IsValid())
 		{
 			InventoryLocation invLoc = new InventoryLocation;
-			GetHumanInventory().GetEntityInHands().GetInventory().GetCurrentInventoryLocation(invLoc);
-			//old location is somewhere on player
-			if (GetHumanInventory().GetEntityInHands().m_OldLocation.GetParent() && GetHumanInventory().GetEntityInHands().m_OldLocation.GetParent().GetHierarchyRootPlayer())
+			entityInHands.GetInventory().GetCurrentInventoryLocation(invLoc);
+			
+			//! Check if old strored location is somewhere on this player
+			if (entityInHands.m_OldLocation.GetParent() && entityInHands.m_OldLocation.GetParent().GetHierarchyRootPlayer())
 			{
-				if (GetHumanInventory().LocationCanMoveEntity(invLoc, GetHumanInventory().GetEntityInHands().m_OldLocation))
+				GetHumanInventory().ClearInventoryReservation(entityInHands, entityInHands.m_OldLocation);
+				if (GetHumanInventory().LocationCanMoveEntity(invLoc, entityInHands.m_OldLocation))
 				{
-					if (GetHumanInventory().TakeToDst(invMode, invLoc,GetHumanInventory().GetEntityInHands().m_OldLocation))
+					EntityAI oldLocEntity = GetHumanInventory().LocationGetEntity(entityInHands.m_OldLocation);
+					if (!oldLocEntity && GetHumanInventory().TakeToDst(invMode, invLoc, entityInHands.m_OldLocation))
 					{
 						UpdateInventoryMenu();
 						return;
 					}
+					else //! This should not happen after clearing inventory reservation but just in case handle also getting a new location if the old location is obscured by an item.
+					{
+						InventoryLocation newLocation = new InventoryLocation;
+						if (GetHumanInventory().FindFreeLocationFor(entityInHands, FindInventoryLocationType.CARGO, newLocation))
+						{
+							if (GetHumanInventory().TakeToDst(invMode, invLoc, newLocation))
+							{
+								UpdateInventoryMenu();
+								return;
+							}
+						}
+					}
 				}
 			}
 		}
@@ -934,4 +951,15 @@ class Man extends EntityAI
 	void SetProcessUIWarning(bool state);
 	void OnGameplayDataHandlerSync(); //depricated, sync now happens before the player is created, calling of this event still happens for legacy reasons
 	bool CanPlaceItem(EntityAI item);
+	
+	/*!
+		Called when 2D optics are about to be drawn.
+		\return Collection of optics to be drawn. 
+		        Draws the provided optics when of `ItemOptics` type.
+		        Draws optic of current muzzle when of `Weapon_Base` type.
+	*/
+	protected array<InventoryItem> OnDrawOptics2D()
+	{
+		return null;
+	}
 };

+ 32 - 29
Scripts/3_game/entities/object.c

@@ -852,36 +852,19 @@ class Object extends IEntity
 	*/
 	void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx);
 
-	vector GetSelectionPositionOld(string name)
-	{
-		return GetGame().ObjectGetSelectionPosition(this, name);
-	}
-	
-	vector GetSelectionPositionLS(string name)
-	{
-		return GetGame().ObjectGetSelectionPositionLS(this, name);
-	}
-	
-	vector GetSelectionPositionMS(string name)
-	{
-		return GetGame().ObjectGetSelectionPositionMS(this, name);
-	}
-	
-	vector GetSelectionPositionWS(string name)
-	{
-		return GetGame().ObjectGetSelectionPositionWS(this, name);
-	}
+	proto vector GetSelectionPositionOld(string name);	
+	proto vector GetSelectionPositionLS(string name);
+	proto vector GetSelectionPositionMS(string name);
+	proto vector GetSelectionPositionWS(string name);
+		
+	/**
+  \brief Get the position of the selection with no animations applied
+		@param name selection name of the selection (component, proxy)
+	*/
+	proto vector GetSelectionBasePositionLS(string name);
 
-	
-	vector ModelToWorld(vector modelPos)
-	{
-		return GetGame().ObjectModelToWorld(this, modelPos);
-	}
-	
-	vector WorldToModel(vector worldPos)
-	{
-		return GetGame().ObjectWorldToModel(this, worldPos);
-	}
+	proto vector ModelToWorld(vector modelPos);
+	proto vector WorldToModel(vector worldPos);
 
 	// config class API	
 	
@@ -1429,6 +1412,26 @@ class Object extends IEntity
 	void OnSpawnByObjectSpawner(ITEM_SpawnerObject item)
 	{}
 
+	bool Gizmo_IsSupported()
+	{
+		return !GetGame().IsMultiplayer();
+	}
+
+	void Gizmo_SetWorldTransform(vector transform[4], bool finalize)
+	{			
+		SetPosition(transform[3]);
+		SetDirection(transform[2]);
+
+		SetTransform(transform);
+			
+		// TODO: RPC or something when finalize == true to send the new transform to the server, don't forget to make 'Gizmo_IsSupported' return true
+	}
+
+	void Gizmo_GetWorldTransform(vector transform[4])
+	{
+		GetTransform(transform);
+	}
+
 	//Debug
 	//----------------------------------------------
 	/*void DbgAddPxyPhy(string slot)

+ 4 - 1
Scripts/3_game/entities/pawn.c

@@ -141,7 +141,10 @@ enum NetworkMoveStrategy
 	NONE,
 
 	//! Places the move as the last input inside 'NetworkInput' and simulates with the input message
-	LATEST
+	LATEST,
+
+	//! Sends over a fixed buffer of moves and re-simulates the physics steps on correction as a static scene
+	PHYSICS,
 };
 
 /**

+ 45 - 4
Scripts/3_game/entities/scriptedentity.c

@@ -40,9 +40,9 @@ class ScriptedEntity extends EntityAI
 		@endcode
 	*/
 	proto native void SetCollisionSphere(float radius);
-	
+
 	/**
-	\brief Sets collision cylinder for object
+	\brief Sets collision cylinder for object, representing cylinder from origin(center) up to defined positive height
 		\param radius \p float Radius of cylinder
 		\param height \p float Height of cylinder
 		\note Automatically sets TriggerShape.CYLINDER
@@ -51,7 +51,48 @@ class ScriptedEntity extends EntityAI
 			SetCollisionCylinder(3, 6);
 		@endcode
 	*/
-	proto native void SetCollisionCylinder(float radius, float height);	
+	proto native void SetCollisionCylinder(float radius, float height);
+	
+	/**
+	\brief Sets collision cylinder for object, representing cylinder from origin(center), height can be defined in both directions
+		\param radius \p float Radius of cylinder
+		\param negativeHeigh \p float Negative height of cylinder
+		\param positiveHeight \p float Positive height of cylinder
+		\note Automatically sets TriggerShape.CYLINDER
+		\n usage :
+		@code
+			SetCollisionCylinderTwoWay(3, -3, 3);
+		@endcode
+	*/	
+	private proto native void SetCollisionCylinderTwoWayNative(float radius, float negativeHeight, float positiveHeight);
+	
+	/**
+	\brief Input value validated version of SetCollisionCylinderTwoWay
+		\param radius \p float Radius of cylinder
+		\param negativeHeigh \p float Negative height of cylinder
+		\param positiveHeight \p float Positive height of cylinder
+		\note Automatically sets TriggerShape.CYLINDER
+		\n usage :
+		@code
+			SetCollisionCylinderTwoWayValidated(3, -3, 3);
+		@endcode
+	*/	
+	void SetCollisionCylinderTwoWay(float radius, float negativeHeight, float positiveHeight)
+	{
+		if (radius <=0)
+		{
+			ErrorEx("Radius has to >= 0");
+			return;
+		}
+
+		if (negativeHeight > positiveHeight)
+		{
+			ErrorEx("Negative height cannot be higher than positive height");
+			return;
+		}
+		
+		SetCollisionCylinderTwoWayNative(radius, negativeHeight, positiveHeight);
+	}
 		
 	//! Set the TriggerShape to be used, default is TriggerShape.BOX
 	proto native void SetTriggerShape(TriggerShape shape);
@@ -63,4 +104,4 @@ class ScriptedEntity extends EntityAI
 	{
 		return false;
 	}
-};
+}

+ 2 - 0
Scripts/3_game/enums/eactions.c

@@ -30,6 +30,8 @@ enum EActions
 	FOOD_NUTRITIONS_DATA,
 	MAKE_SPECIAL,
 	DELETE,
+	GIZMO_OBJECT,
+	GIZMO_PHYSICS,
 	SEPARATOR,
 	ACTIVATE_ENTITY,
 	DEACTIVATE_ENTITY,

+ 9 - 9
Scripts/3_game/enums/eagents.c

@@ -2,13 +2,13 @@
 enum eAgents
 {
 	//agent list
-	CHOLERA 		= 1;
-	INFLUENZA 		= 2;
-	SALMONELLA		= 4;
-	BRAIN 			= 8;
-	FOOD_POISON		= 16;
-	CHEMICAL_POISON	= 32;
-	WOUND_AGENT		= 64;
-	NERVE_AGENT		= 128;
-	HEAVYMETAL		= 256;
+	CHOLERA 		= 1,
+	INFLUENZA 		= 2,
+	SALMONELLA		= 4,
+	BRAIN 			= 8,
+	FOOD_POISON		= 16,
+	CHEMICAL_POISON	= 32,
+	WOUND_AGENT		= 64,
+	NERVE_AGENT		= 128,
+	HEAVYMETAL		= 256,
 }

+ 4 - 4
Scripts/3_game/enums/econtaminationtypes.c

@@ -1,7 +1,7 @@
 enum EContaminationTypes
 {
-	ITEM_BADGE_CONTAMINATED = 1;
-	ITEM_BADGE_POISONED 	= 2;
-	ITEM_BADGE_NERVE_GAS	= 4;
-	ITEM_BADGE_DIRTY 		= 8;
+	ITEM_BADGE_CONTAMINATED = 1,
+	ITEM_BADGE_POISONED 	= 2,
+	ITEM_BADGE_NERVE_GAS	= 4,
+	ITEM_BADGE_DIRTY 		= 8,
 }

+ 1 - 0
Scripts/3_game/enums/ecrewmemberstate.c

@@ -1,3 +1,4 @@
+[Obsolete("no replacement")]
 enum ECrewMemberState
 {
 	DEFAULT			= 0,

+ 2 - 0
Scripts/3_game/enums/ediagmenuids.c

@@ -130,6 +130,8 @@ enum DiagMenuIDs
 		WEAPON_BURST_VERSION,
 		WEAPON_CLAYMORE_DEBUG,
 		WEAPON_LIFT_DEBUG,
+		WEAPON_FORCEALLOW_OBSTRUCTION,
+		WEAPON_DISABLE_OBSTRUCTION_INTERPOLATION,
 	
 	BLEEDING_MENU,
 		BLEEDING_SOURCES,

+ 1 - 0
Scripts/3_game/enums/edynamicmusicplayercategory.c

@@ -2,6 +2,7 @@ enum EDynamicMusicPlayerCategory
 {
 	NONE = -1,
 	MENU = 0,
+	CREDITS,
 	TIME,
 	LOCATION_STATIC,
 	LOCATION_STATIC_PRIORITY,

+ 56 - 1
Scripts/3_game/global/game.c

@@ -2,7 +2,7 @@
  *  Game Class provide most "world" or global engine API functions.
  */
 
-static int GAME_STORAGE_VERSION = 141;
+static int GAME_STORAGE_VERSION = 142;
 
 class CGame
 {
@@ -794,6 +794,61 @@ class CGame
 	//! If physics extrapolation is enabled, always true on retail release builds
 	proto native bool		IsPhysicsExtrapolationEnabled();
 	
+	/**
+	 * Return the number of gizmos
+	 */
+	proto native int GizmoGetCount();
+
+	/**
+	 * Return the instance passed in for the gizmo
+	 */
+	proto native Class GizmoGetInstance(int index);
+
+	/**
+	 * Return the tracker passed in for the gizmo
+	 */
+	proto native Managed GizmoGetTracker(int index);
+
+	/**
+	 * Returned index is invalid if any other Gizmo function is called
+	 */
+	proto native int GizmoFindByTracker(Managed tracker);
+
+	/**
+	 * Clear the gizmo
+	 */
+	proto native void GizmoClear(int index);
+	
+	/**
+	 * Clear all gizmos 
+	 */
+	proto native void GizmoClearAll();
+
+	/**
+	 * Applies impulses to set the position when dynamic, otherwise sets the transform in the physics scene
+	 * 
+	 * Tracker for 'GizmoFind' is the passed in object
+	 */
+	proto native void GizmoSelectObject(Object object);
+
+	/**
+	 * Applies impulses to set the position when dynamic, otherwise sets the transform in the physics scene. Doesn't work in multiplayer
+	 * 
+	 * Tracker for 'GizmoFind' is the owned Entity
+	 * 
+	 * Note: GizmoGet doesn't work due as 'Physics' can't be compared against 'Class'
+	 */
+	proto native void GizmoSelectPhysics(Physics physics);
+
+	/**
+	 * Scripted controls, requires the following methods to be implemented in the class
+	 * 	void Gizmo_SetWorldTransform(vector[4] transform, bool finalize)
+	 * 	void Gizmo_GetWorldTransform(vector[4] transform)
+	 * 
+	 * Tracker for 'GizmoFind' is the passed in instance
+	 */
+	proto native void GizmoSelectUser(Managed instance);
+
 	/**
 	\brief Returns average FPS of last 16 frames
 	@return fps in milliseconds

+ 19 - 3
Scripts/3_game/human.c

@@ -882,12 +882,16 @@ enum WeaponActionChamberingTypes
 	CHAMBERING_ONEBULLET_UNIQUE_OPENED 			= 3,		//
 	CHAMBERING_ONEBULLET_UNIQUE_CLOSED			= 4,		//
 	CHAMBERING_TWOBULLETS_START					= 6,		//  plays one bullet, then second, then ends, when CHAMBERING_TWOBULLETS_END arise, it's canceled
-	CHAMBERING_TWOBULLETS_END					= 7,		//  - one bullet reload with closed mechanism		
+	CHAMBERING_TWOBULLETS_END					= 7,		//  - one bullet reload with closed mechanism	
+	
+	CHAMBERING_STARTLOOPABLE_CLOSED_EXTRA		= 9,	
 	CHAMBERING_STARTLOOPABLE_CLOSED				= 10,		// start loop chambering
 	CHAMBERING_ENDLOOPABLE						= 11,		// end loop chambering
 	CHAMBERING_STARTLOOPABLE_CLOSED_KEEP		= 12,		// start loop chambering and keep last bullet
 	CHAMBERING_STARTLOOPABLE_OPENED				= 13,		// 
 	
+	CHAMBERING_STARTLOOPABLE_CLOSED_WITHCHAMBER	= 14,
+	
 	CHAMBERING_STARTLOOPABLE_SHOTGUN_UNCOCKED	= 15,
 	CHAMBERING_STARTLOOPABLE_SHOTGUN_COCKED		= 16, 
 	
@@ -980,7 +984,7 @@ enum WeaponEvents
 	SLIDER_OPEN,
 	UNJAMMED,
 	HAMMER_UNCOCKED,
-	HAMMER_COCKED
+	HAMMER_COCKED,
 	CHANGE_HIDE,
 	CHANGE_SHOW,
 	CYLINDER_ROTATE,
@@ -1022,9 +1026,18 @@ class HumanCommandWeapons
 	//! sets head tilt to optics
 	proto native	void		SetADS(bool pState);
 
-	//! command for lifting weapon near obstacled (works only when weapon is raised)
+	//! command for lifting weapon near obstacles (works only when weapon is raised)
 	proto native	void		LiftWeapon(bool pState);
 	
+	//! return if lifting weapon is active
+	proto native	bool		IsWeaponLifted();
+	
+	//! command for obstruction weapon near obstacles
+	proto native	void		ObstructWeapon(float pState01);
+	
+	//! return obstruction value
+	proto native	float		GetWeaponObstruction();
+	
 	//! returns aiming hands up/down (y) offset angle
 	proto native	float		GetAimingHandsOffsetUD();
 	
@@ -1662,4 +1675,7 @@ class Human extends Man
 	
 	void	OnVehicleSeatDriverEnter();
 	void	OnVehicleSeatDriverLeft();
+
+	proto native bool IsControllingVehicle();
+
 }

+ 5 - 5
Scripts/3_game/impacteffects.c

@@ -1,10 +1,10 @@
 enum ImpactTypes
 {
-	UNKNOWN;
-	STOP;
-	PENETRATE;
-	RICOCHET;
-	MELEE;
+	UNKNOWN,
+	STOP,
+	PENETRATE,
+	RICOCHET,
+	MELEE,
 }
 
 class ImpactEffectsData

+ 4 - 0
Scripts/3_game/particles/particlelist.c

@@ -21,6 +21,7 @@ class ParticleList
 	static const int NONE							= 0; // 0 does not exist either, valid particle starts from 1
 	static const int PARTICLE_TEST					= RegisterParticle("_test_orientation");
 	static const int DEBUG_DOT						= RegisterParticle("debug_dot");
+	static const int DEBUG_DOT5M					= RegisterParticle("debug_dot5m");
 	
 	// FIREPLACE	
 	// Normal fireplace
@@ -291,6 +292,7 @@ class ParticleList
 	// BONFIRE 
 	static const int BONFIRE_FIRE 					= RegisterParticle("fire_bonfire");
 	static const int BONFIRE_SMOKE		 			= RegisterParticle("smoke_bonfire");
+	static const int TIREPILE_FIRE 					= RegisterParticle("fire_tirepile");
 	static const int SPOOKY_MIST		 			= RegisterParticle("spooky_mist");
 	
 	static const int VOMIT_BLOOD					= RegisterParticle("character_vomitBlood_01");
@@ -301,6 +303,7 @@ class ParticleList
 	static const int CONTAMINATED_AREA_GAS_BIGASS	= RegisterParticle("contaminated_area_gas_bigass");
 	static const int CONTAMINATED_AREA_GAS_GROUND	= RegisterParticle("contaminated_area_gas_ground");
 	static const int CONTAMINATED_AREA_GAS_SHELL	= RegisterParticle("contaminated_area_gas_shell");
+	static const int CONTAMINATED_AREA_GAS_DEBUG	= RegisterParticle("contaminated_area_gas_bigass_debug");
 	
 	//Fireworks
 	static const int FIREWORKS_SHOT					= RegisterParticle("fireworks_small_01");
@@ -344,6 +347,7 @@ class ParticleList
 	static const int HOTPSRING_WATERVAPOR			= RegisterParticle("hotspring_watervapor");
 	static const int GEYSER_NORMAL					= RegisterParticle("geyser_normal");
 	static const int GEYSER_STRONG					= RegisterParticle("geyser_strong");
+	static const int GEYSER_SPLASH					= RegisterParticle("geyser_strong_splash");
 	static const int GEYSER_BUBBLES					= RegisterParticle("geyser_bubbles");
 	static const int VOLCANO						= RegisterParticle("volcano_smoke");
 	

+ 5 - 5
Scripts/3_game/persistentflag.c

@@ -4,9 +4,9 @@
 
 enum PersistentFlag
 {
-	AREA_PRESENCE = 1;
-	//ADDITIONAL_FLAG_1 = 2
-	//ADDITIONAL_FLAG_2 = 4
-	//ADDITIONAL_FLAG_3 = 8
-	//ADDITIONAL_FLAG_4 = 16
+	AREA_PRESENCE = 1,
+	//ADDITIONAL_FLAG_1 = 2,
+	//ADDITIONAL_FLAG_2 = 4,
+	//ADDITIONAL_FLAG_3 = 8,
+	//ADDITIONAL_FLAG_4 = 16,
 }

+ 5 - 5
Scripts/3_game/ppemanager/ppeconstants.c

@@ -27,11 +27,11 @@ enum PostProcessPrioritiesCamera
 
 enum PPERequesterCategory
 {
-	NONE = 0;
-	GAMEPLAY_EFFECTS = 2;
-	MENU_EFFECTS = 4;
-	MISC_EFFECTS = 8;
-	ALL = 14; //GAMEPLAY_EFFECTS|MENU_EFFECTS|MISC_EFFECTS
+	NONE = 0,
+	GAMEPLAY_EFFECTS = 2,
+	MENU_EFFECTS = 4,
+	MISC_EFFECTS = 8,
+	ALL = 14, //GAMEPLAY_EFFECTS|MENU_EFFECTS|MISC_EFFECTS
 };
 
 /** 

+ 60 - 2
Scripts/3_game/ppemanager/requesters/pperunconeffects.c

@@ -1,14 +1,72 @@
 class PPERequester_UnconEffects extends PPERequester_GameplayBase
 {
+	protected const float FADING_TIME_DEFAULT = 3.0;
+	
 	protected float m_Intensity;
+	protected float m_FadeoutStartIntensity;
+	protected bool m_Stopping;
+	protected bool m_StopNext;
+	protected float m_FadeOutTimeTarget;
+	protected float m_FadingTimeElapsed;
+	protected float m_FadingProgress;
 	
 	override protected void OnStart(Param par = null)
 	{
 		super.OnStart();
 		
 		m_Intensity = Param1<float>.Cast(par).param1;
-		
-		SetTargetValueFloat(PostProcessEffectType.Glow,PPEGlow.PARAM_VIGNETTE,false,m_Intensity,PPEGlow.L_25_UNCON,PPOperators.ADD);
+		m_Stopping = false;
+		m_StopNext = false;
+		SetTargetValueFloat(PostProcessEffectType.Glow,PPEGlow.PARAM_VIGNETTE,false,m_Intensity,PPEGlow.L_25_UNCON,PPOperators.HIGHEST);
 		SetTargetValueColor(PostProcessEffectType.Glow,PPEGlow.PARAM_VIGNETTECOLOR,{0.0,0.0,0.0,0.0},PPEGlow.L_26_UNCON,PPOperators.LOWEST);
 	}
+	
+	override protected void OnStop(Param par = null)
+	{
+		super.OnStop(par);
+		
+		m_FadingProgress = 0.0;
+		m_Stopping = false;
+		m_StopNext = false;
+	}
+	
+	override protected void OnUpdate(float delta)
+	{
+		if (m_StopNext)
+		{
+			if (m_IsRunning)
+				Stop();
+			return;
+		}
+		
+		if (m_IsRunning && m_Stopping)
+			ProcessFading(delta);
+		
+		super.OnUpdate(delta);
+		
+		if (m_IsRunning && m_Stopping)
+			SetRequesterUpdating(true);
+	}
+	
+	void FadeOutEffect(float targetTime = FADING_TIME_DEFAULT)
+	{
+		m_Stopping = true;
+		m_FadeoutStartIntensity = m_Intensity;
+		m_FadeOutTimeTarget = targetTime;
+		m_FadingTimeElapsed = 0.0;
+		
+		if (m_IsRunning)
+			SetRequesterUpdating(true);
+	}
+	
+	protected void ProcessFading(float delta)
+	{
+		m_FadingTimeElapsed += delta;
+		m_FadingProgress = m_FadingTimeElapsed/m_FadeOutTimeTarget;
+		
+		m_StopNext = m_FadingProgress >= 1.0;
+		
+		m_Intensity = Math.Lerp(0,m_FadeoutStartIntensity,Easing.EaseInOutQuad(1 - m_FadingProgress));
+		SetTargetValueFloat(PostProcessEffectType.Glow,PPEGlow.PARAM_VIGNETTE,false,m_Intensity,PPEGlow.L_25_UNCON,PPOperators.HIGHEST);
+	}
 }

+ 3 - 3
Scripts/3_game/surfaceinfo.c

@@ -5,10 +5,10 @@ typedef int[] SurfaceInfo;
 	Lifetime is managed in code so don't store handles of this type yourself.
 	Any created 'SurfaceInfo' is destroyed during 'Game' ScriptModule destruction.
 */
-class SurfaceInfo
+class SurfaceInfo : SurfaceProperties
 {
-	private void SurfaceInfo() {};
-	private void ~SurfaceInfo() {};
+	protected void SurfaceInfo() {};
+	protected void ~SurfaceInfo() {};
 	
 	//! Warning: O(n) time complexity where n is the total number of loaded surfaces
 	//! Note: Will load the surface if not loaded

+ 130 - 48
Scripts/3_game/systems/dynamicmusicplayer/dynamicmusicplayer.c

@@ -48,7 +48,7 @@ class DynamicMusicTrackData
 	string m_SoundSet;
 
 	EDynamicMusicPlayerCategory m_Category;
-	
+
 	ref array<ref array<vector>> locationBoundaries = new array<ref array<vector>>();
 	ref array<vector> vertices = new array<vector>();
 	
@@ -64,6 +64,20 @@ class DynamicMusicPlayerTrackHistoryLookupType
 	const int BUFFER 	= 1;
 }
 
+/*
+ * \brief Structure for setting of Category playback
+ * @param m_Forced Force play of category (DEPRECATED)
+ * @param m_FadeOut If true, previously playing track will be faded out and stopped. Otherwise stopped only.
+ * @param m_Category Selected category that is going to be played
+ */
+class DynamicMusicPlayerCategoryPlaybackData
+{
+	bool m_Forced 	= false;
+	bool m_FadeOut 	= false;
+
+	EDynamicMusicPlayerCategory m_Category = EDynamicMusicPlayerCategory.NONE;
+}
+
 class DynamicMusicPlayer
 {
 	#ifdef DMP_DEBUG_SETTINGS
@@ -125,8 +139,9 @@ class DynamicMusicPlayer
 
 		m_LastPlayedTrackBufferPerCategory = new map<EDynamicMusicPlayerCategory, ref SimpleCircularBuffer<int>>;
 		
-		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.MENU] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
-		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.TIME] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
+		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.MENU] 	= new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
+		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.CREDITS] = new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
+		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.TIME] 	= new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
 
 		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.LOCATION_STATIC] 			= new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
 		m_LastPlayedTrackBufferPerCategory[EDynamicMusicPlayerCategory.LOCATION_STATIC_PRIORITY] 	= new SimpleCircularBuffer<int>(TRACKS_BUFFER_HISTORY_SIZE, -1);
@@ -155,13 +170,13 @@ class DynamicMusicPlayer
 	{
 		if (m_DynamicMusicPlayerRegistry == null)
 			return;
-
-		m_TickTimeOfDateElapsed 				+= timeslice;
-		m_TickLocationCacheUpdateElapsed		+= timeslice;
-		m_TickLocationUpdateElapsed 			+= timeslice;
-		m_TickPriorityLocationUpdateElapsed 	+= timeslice;
-		m_TickFadeOutProcessingElapsed 			+= timeslice;
 		
+		m_TickTimeOfDateElapsed 				= Math.Clamp(m_TickTimeOfDateElapsed + timeslice, 0.0, TICK_TIME_OF_DATE_UPDATE_SECONDS);
+		m_TickLocationCacheUpdateElapsed 		= Math.Clamp(m_TickLocationCacheUpdateElapsed + timeslice, 0.0, TICK_LOCATION_CACHE_UPDATE_SECONDS);
+		m_TickLocationUpdateElapsed 			= Math.Clamp(m_TickLocationUpdateElapsed + timeslice, 0.0, TICK_LOCATION_UPDATE_SECONDS);
+		m_TickPriorityLocationUpdateElapsed 	= Math.Clamp(m_TickPriorityLocationUpdateElapsed + timeslice, 0.0, TICK_PRIORITY_LOCATION_UPDATE_SECONDS);
+		m_TickFadeOutProcessingElapsed 			= Math.Clamp(m_TickFadeOutProcessingElapsed + timeslice, 0.0, TICK_FADEOUT_PROCESSOR_SECONDS);
+
 		//! handle fadeouts
 		if (m_FadeoutInProgress && m_TickFadeOutProcessingElapsed >= TICK_FADEOUT_PROCESSOR_SECONDS)
 		{
@@ -179,7 +194,7 @@ class DynamicMusicPlayer
 		}
 		else
 		{
-			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU)
+			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
 			{
 				//! caching of locations based on distance from player (<= LOCATION_DISTANCE_MAX)
 				if (m_TickLocationCacheUpdateElapsed >= TICK_LOCATION_CACHE_UPDATE_SECONDS)
@@ -226,10 +241,14 @@ class DynamicMusicPlayer
 					
 					//! works as default category selector
 					if (!IsPlaybackActive() || !IsPriotitizedCategorySelected())
-						SetCategory(EDynamicMusicPlayerCategory.TIME, false);
+					{
+						DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+						playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
+						SetCategory(playbackData);
+					}
 				}
 			}
-			else //! menu only
+			else
 			{
 				if (!IsPlaybackActive())
 					DetermineTrackByCategory(m_CategorySelected);
@@ -245,15 +264,26 @@ class DynamicMusicPlayer
 		}
 		#endif
 	}
-
-	void SetCategory(EDynamicMusicPlayerCategory category, bool forced)
+	
+	/*
+	 * \brief Set playback category
+	 * @param playbackData Structure holding information about category playback settings
+	 */
+	void SetCategory(DynamicMusicPlayerCategoryPlaybackData playbackData)
 	{
 		if (m_DynamicMusicPlayerRegistry == null)
 			return;
-
-		m_CategorySelected = category;
 		
-		OnCategorySet(category, forced);
+		m_CategorySelected = playbackData.m_Category;
+		
+		if (!playbackData.m_FadeOut)
+		{		
+			OnCategorySet(playbackData.m_Category, playbackData.m_Forced);
+			return;
+		}
+		
+		//! pass to fadeout handler
+		FadeoutTrack(GetPreviousTrackFadeoutSeconds(playbackData.m_Category));
 	}
 	
 	void RegisterDynamicLocation(notnull Entity caller, int locationType, float locationSize)
@@ -280,7 +310,10 @@ class DynamicMusicPlayer
 		if (eventTypeId == MPSessionPlayerReadyEventTypeID)
 		{
 			SetTimeOfDate();
-			SetCategory(EDynamicMusicPlayerCategory.TIME, false);
+			
+			DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+			playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
+			SetCategory(playbackData);
 		}
 	}
 	
@@ -306,6 +339,13 @@ class DynamicMusicPlayer
 				g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
 				break;
 			
+			case EDynamicMusicPlayerCategory.CREDITS:
+				if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksCredits, DynamicMusicPlayerTrackHistoryLookupType.BUFFER))
+					break;
+			
+				g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(DetermineTrackByCategory, 5000, false, category);
+				break;
+			
 			case EDynamicMusicPlayerCategory.TIME:
 				if (SetSelectedTrackFromCategory(category, m_DynamicMusicPlayerRegistry.m_TracksTime, DynamicMusicPlayerTrackHistoryLookupType.BUFFER))
 					break;
@@ -358,7 +398,7 @@ class DynamicMusicPlayer
 	
 	protected void OnCategorySet(EDynamicMusicPlayerCategory category, bool forced)
 	{
-		#ifdef DIAG_DEVELOPER
+		#ifdef ENABLE_LOGGING
 		DMPDebugPrint(string.Format(
 			"OnCategorySet() - category: %1, forced: %2",
 			EnumTools.EnumToString(EDynamicMusicPlayerCategory, category),
@@ -371,7 +411,7 @@ class DynamicMusicPlayer
 	
 	protected void OnTrackEnded()
 	{
-		#ifdef DIAG_DEVELOPER
+		#ifdef ENABLE_LOGGING
 		if (m_CurrentTrack)
 			DMPDebugPrint(string.Format("Track END - %1", m_CurrentTrack.m_SoundSet));
 		#endif
@@ -384,7 +424,7 @@ class DynamicMusicPlayer
 	protected void OnTrackStopped()
 	{
 		//! stopped only by fadeouts
-		#ifdef DIAG_DEVELOPER
+		#ifdef ENABLE_LOGGING
 		if (m_CurrentTrack)
 			DMPDebugPrint(string.Format("Track STOP - %1", m_CurrentTrack.m_SoundSet));
 		#endif
@@ -402,7 +442,7 @@ class DynamicMusicPlayer
 		if (m_Created)
 			m_Created = false;
 
-		#ifdef DIAG_DEVELOPER
+		#ifdef ENABLE_LOGGING
 		DMPDebugPrint(string.Format(
 			"WaitTime set to %1s, deferring playback of \"%2\"",
 			(int)waitTime,
@@ -416,15 +456,19 @@ class DynamicMusicPlayer
 	
 	protected void OnLocationMatched(EDynamicMusicPlayerCategory category, bool isPriorityLocation)
 	{
-		#ifdef DIAG_DEVELOPER
+		#ifdef ENABLE_LOGGING
 		string messagePriority;
 		if (isPriorityLocation)
 			messagePriority = "(with priority)";
 		DMPDebugPrint(string.Format("Location matched %1", messagePriority));
 		#endif
 
+		DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+		playbackData.m_Category = category;
+
 		if (isPriorityLocation)
 		{
+			playbackData.m_Forced 	= isPriorityLocation;
 			if (!IsPriotitizedCategorySelected())
 			{
 				m_CategorySelected = category;
@@ -432,15 +476,19 @@ class DynamicMusicPlayer
 					ResetWaitingQueue();
 				
 				if (m_SoundPlaying)
-					FadeoutTrack(GetPreviousTrackFadeoutSeconds(category));
-
-				SetCategory(category, isPriorityLocation);
+				{
+					playbackData.m_FadeOut = true;
+					SetCategory(playbackData);
+					return;
+				}
+					
+				SetCategory(playbackData);
 			}
 			else
-				SetCategory(category, true); //! play prio location track (no fadeout)
+				SetCategory(playbackData); //! play prio location track (no fadeout)
 		}
 		else
-			SetCategory(category, false); //! play location track (no fadeout) 
+			SetCategory(playbackData); //! play location track (no fadeout) 
 	}
 	
 	protected void OnFadeoutFinished(EDynamicMusicPlayerCategory category)
@@ -449,7 +497,11 @@ class DynamicMusicPlayer
 			m_SoundPlaying.GetEvents().Event_OnSoundWaveEnded.Remove(OnTrackEnded);
 
 		StopTrack();
-		SetCategory(category, IsPriotitizedCategorySelected());
+
+		DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+		playbackData.m_Category = category;
+		playbackData.m_Forced = IsPriotitizedCategorySelected();
+		SetCategory(playbackData);
 	}
 	//! --------------------------------------------------------------------------------
 	
@@ -480,7 +532,6 @@ class DynamicMusicPlayer
 			m_WaitingForPlayback = false;
 			m_CurrentTrack 		 = null;
 		}
-		
 	}
 	
 	private void StopTrack()
@@ -503,10 +554,12 @@ class DynamicMusicPlayer
 	{
 		if (m_FadeoutInProgress)
 			return;
+		
+		ResetWaitingQueue();
 
 		if (m_CurrentTrack && m_SoundPlaying)
 		{
-			#ifdef DIAG_DEVELOPER
+			#ifdef ENABLE_LOGGING
 			DMPDebugPrint(string.Format("Stopping currently played track %1", m_CurrentTrack.m_SoundSet));
 			DMPDebugPrint(string.Format("-- Setting fadeout to %1", fadeoutSeconds));
 			#endif
@@ -542,7 +595,7 @@ class DynamicMusicPlayer
 								if (m_TracksLocationMatchedPlayerInside.Find(track) == INDEX_NOT_FOUND)
 									m_TracksLocationMatchedPlayerInside.Insert(track);
 			
-								#ifdef DIAG_DEVELOPER
+								#ifdef ENABLE_LOGGING
 								DMPDebugPrint(string.Format("Player inside location <%1, %2>", bounds[0], bounds[1]));
 								#endif
 							}
@@ -554,7 +607,7 @@ class DynamicMusicPlayer
 							if (m_TracksLocationMatchedPlayerInside.Find(track) == INDEX_NOT_FOUND)
 								m_TracksLocationMatchedPlayerInside.Insert(track);					
 
-							#ifdef DIAG_DEVELOPER
+							#ifdef ENABLE_LOGGING
 							DMPDebugPrint(string.Format("Player inside polygon location at <%1>", m_PlayerPosition));
 							#endif	
 						}
@@ -574,7 +627,7 @@ class DynamicMusicPlayer
 			{
 				if (Math.IsPointInRectangle(location.m_Min, location.m_Max, m_PlayerPosition))
 				{
-					#ifdef DIAG_DEVELOPER
+					#ifdef ENABLE_LOGGING
 					DMPDebugPrint(string.Format("Player inside location <%1, %2>", location.m_Min, location.m_Max));
 					#endif
 					return true;
@@ -664,7 +717,7 @@ class DynamicMusicPlayer
 					return INDEX_NOT_FOUND;
 			}
 		}
-		
+	
 		return INDEX_NOT_FOUND;
 	}
 	
@@ -773,7 +826,7 @@ class DynamicMusicPlayer
 			DbgUI.Text(string.Format("  waiting: %1", m_WaitingForPlayback.ToString()));
 			DbgUI.Text(string.Format("Selected Category: %1", EnumTools.EnumToString(EDynamicMusicPlayerCategory, m_CategorySelected)));
 
-			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU)
+			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
 			{
 				DbgUI.Text("Update timers:");
 				DbgUI.Text(string.Format("  TimeOfDay: %1(%2)", TICK_TIME_OF_DATE_UPDATE_SECONDS, TICK_TIME_OF_DATE_UPDATE_SECONDS - (int)m_TickTimeOfDateElapsed));
@@ -782,7 +835,7 @@ class DynamicMusicPlayer
 				DbgUI.Text(string.Format("  Location Cache: %1(%2)", TICK_LOCATION_CACHE_UPDATE_SECONDS, TICK_LOCATION_CACHE_UPDATE_SECONDS - (int)m_TickLocationCacheUpdateElapsed));
 			}
 
-			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU)
+			if (m_CategorySelected != EDynamicMusicPlayerCategory.MENU && m_CategorySelected != EDynamicMusicPlayerCategory.CREDITS)
 			{			
 				DbgUI.Text("Player:");
 				DbgUI.Text(string.Format("  position: %1", m_PlayerPosition.ToString()));
@@ -790,8 +843,11 @@ class DynamicMusicPlayer
 			}
 			
 			DbgUI.Text("Tracks counts:");
-			if (m_CategorySelected == EDynamicMusicPlayerCategory.MENU)
+			if (m_CategorySelected == EDynamicMusicPlayerCategory.MENU || m_CategorySelected == EDynamicMusicPlayerCategory.CREDITS)
+			{
 				DbgUI.Text(string.Format("  Menu: %1", m_DynamicMusicPlayerRegistry.m_TracksMenu.Count()));
+				DbgUI.Text(string.Format("  Credits: %1", m_DynamicMusicPlayerRegistry.m_TracksCredits.Count()));
+			}
 			else
 			{
 				DbgUI.Text(string.Format("  Time: %1", m_DynamicMusicPlayerRegistry.m_TracksTime.Count()));
@@ -829,26 +885,42 @@ class DynamicMusicPlayer
 			if (DbgUI.Button("Reset Waiting"))
 				ResetWaitingQueue();
 			
+			DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+			playbackData.m_Category = EDynamicMusicPlayerCategory.TIME;
+			
 			DbgUI.Text("Set Category:\n");
 			if (DbgUI.Button("Time"))
-				SetCategory(EDynamicMusicPlayerCategory.TIME, false);
+				SetCategory(playbackData);
 			if (DbgUI.Button("Location"))
-				SetCategory(EDynamicMusicPlayerCategory.LOCATION_STATIC, false);
+			{
+				playbackData.m_Category = EDynamicMusicPlayerCategory.LOCATION_STATIC;
+				SetCategory(playbackData);
+			}	
+			if (DbgUI.Button("Menu"))
+			{
+				playbackData.m_Category = EDynamicMusicPlayerCategory.MENU;
+				SetCategory(playbackData);
+			}
+			if (DbgUI.Button("Credits"))
+			{
+				playbackData.m_Category = EDynamicMusicPlayerCategory.CREDITS;
+				SetCategory(playbackData);
+			}
 			
 			DbgUI.Text("Reset Timers\n");
 			if (DbgUI.Button("Timer ALL"))
 			{
-				m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1;
-				m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1;
-				m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1;
+				m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1.0;
+				m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1.0;
+				m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1.0;
 			}
 			
 			if (DbgUI.Button("Timer Daytime"))
-				m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1;
+				m_TickTimeOfDateElapsed = TICK_TIME_OF_DATE_UPDATE_SECONDS - 1.0;
 			if (DbgUI.Button("Timer Location"))
-				m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1;
+				m_TickLocationUpdateElapsed = TICK_LOCATION_UPDATE_SECONDS - 1.0;
 			if (DbgUI.Button("Timer Location(prio)"))
-				m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1;
+				m_TickPriorityLocationUpdateElapsed = TICK_PRIORITY_LOCATION_UPDATE_SECONDS - 1.0;
 			
 		}
 		DbgUI.End();
@@ -947,20 +1019,30 @@ class DynamicMusicPlayer
 		m_DebugShapesLocationsVertices.Insert(Debug.DrawLine(current, first, COLOR_WHITE, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE|ShapeFlags.ONCE));
 	}
 	
+	#ifdef ENABLE_LOGGING
 	private void DMPDebugPrint(string message)
 	{
 		#ifdef DMP_DEBUG_PRINT
 		Debug.Log(message);
 		#endif
 	}
+	#endif
+	#endif
 	
 	//!DEPRECATED
 	private void CleanupDebugShapes(array<Shape> shapesArr)
 	{
 		Debug.CleanupDrawShapes(shapesArr);
 	}
-
-	#endif
+	
+	void SetCategory(EDynamicMusicPlayerCategory category, bool forced)
+	{
+		DynamicMusicPlayerCategoryPlaybackData playbackData = new DynamicMusicPlayerCategoryPlaybackData();
+		playbackData.m_Category = category;
+		playbackData.m_Forced 	= forced;
+		
+		SetCategory(playbackData);
+	}
 }
 
 

+ 34 - 1
Scripts/3_game/systems/dynamicmusicplayer/dynamicmusicplayerregistry.c

@@ -3,6 +3,7 @@ class DynamicMusicPlayerRegistry
 	ref map<EDynamicMusicPlayerCategory, ref DynamicMusicPlayerSettings> m_SettingsByCategory;
 	
 	ref array<ref DynamicMusicTrackData> m_TracksMenu;
+	ref array<ref DynamicMusicTrackData> m_TracksCredits;
 	ref array<ref DynamicMusicTrackData> m_TracksTime;
 	ref array<ref DynamicMusicTrackData> m_TracksLocationStatic;
 	ref array<ref DynamicMusicTrackData> m_TracksLocationStaticPrioritized;
@@ -22,6 +23,7 @@ class DynamicMusicPlayerRegistry
 		RegisterCategorySettings();
 
 		RegisterTracksMenu();
+		RegisterTracksCredits();
 		RegisterTracksTime();
 		RegisterTracksLocationStatic();
 		RegisterTracksLocationDynamic();
@@ -35,6 +37,7 @@ class DynamicMusicPlayerRegistry
 
 		RegisterGlobalSettings();
 		RegisterMenuSettings();
+		RegisterCreditsSettings();
 		RegisterTimeSettings();
 		RegisterLocationStaticSettings();
 		RegisterLocationDynamicSettings();
@@ -56,8 +59,21 @@ class DynamicMusicPlayerRegistry
 		settings.m_MinWaitTimeSeconds = 1.0;
 		settings.m_MaxWaitTimeSeconds = 3.0;
 		
+		settings.m_PreviousTrackFadeoutSeconds = 2.0;
+		
 		m_SettingsByCategory[EDynamicMusicPlayerCategory.MENU] = settings;
 	}
+
+	private void RegisterCreditsSettings()
+	{
+		DynamicMusicPlayerSettings settings = new DynamicMusicPlayerSettings();
+		settings.m_MinWaitTimeSeconds = 1.0;
+		settings.m_MaxWaitTimeSeconds = 3.0;
+		
+		settings.m_PreviousTrackFadeoutSeconds = 2.0;
+		
+		m_SettingsByCategory[EDynamicMusicPlayerCategory.CREDITS] = settings;
+	}
 	
 	private void RegisterTimeSettings()
 	{
@@ -101,7 +117,13 @@ class DynamicMusicPlayerRegistry
 		RegisterTrackMenu("Music_Menu_2_SoundSet");
 		RegisterTrackMenu("Music_Menu_3_SoundSet");
 		RegisterTrackMenu("Music_Menu_4_SoundSet");
-		RegisterTrackMenu("Music_Menu_subtitles_remake_SoundSet");
+	}
+//===============================================================================================================================================	
+	protected void RegisterTracksCredits()
+	{
+		m_TracksCredits = new array<ref DynamicMusicTrackData>();
+
+		RegisterTrackCredits("Music_Menu_subtitles_remake_SoundSet");
 	}
 //____________________________________________Day Time setup___________________________________________
 
@@ -157,6 +179,17 @@ class DynamicMusicPlayerRegistry
 		m_TracksMenu.Insert(track);
 	}
 	
+	//! --------------------------------------------------------------------------------
+	protected void RegisterTrackCredits(string soundSetName, bool hasPriority = false)
+	{
+		DynamicMusicTrackData track = new DynamicMusicTrackData();
+		track.m_Category 	= EDynamicMusicPlayerCategory.CREDITS;
+		track.m_SoundSet 	= soundSetName;
+		track.m_HasPriority	= hasPriority;
+
+		m_TracksCredits.Insert(track);
+	}
+	
 	protected void RegisterTrackTime(string soundSetName, int timeOfDay = DynamicMusicPlayerTimeOfDay.ANY)
 	{
 		DynamicMusicTrackData track = new DynamicMusicTrackData();

+ 15 - 9
Scripts/3_game/systems/inventory/debug.c

@@ -1,26 +1,32 @@
 void syncDebugPrint (string s)
 {
-#ifdef INV_DEBUG
+#ifdef LOG_TO_RPT
 	PrintToRPT("" + s); // comment/uncomment to hide/see debug logs
-#else
-	//Print("" + s); // comment/uncomment to hide/see debug logs
+#endif
+	
+#ifdef LOG_TO_SCRIPT
+	Print(string.Format("%1", s));
 #endif
 }
 
 void actionDebugPrint (string s)
 {
-#ifdef INV_DEBUG
+#ifdef LOG_TO_RPT
 	PrintToRPT("" + s); // comment/uncomment to hide/see debug logs
-#else
-	Print("" + s); // comment/uncomment to hide/see debug logs
+#endif
+	
+#ifdef LOG_TO_SCRIPT
+	Print(string.Format("%1", s));
 #endif
 }
 
 void inventoryDebugPrint (string s)
 {
-#ifdef INV_DEBUG
+#ifdef LOG_TO_RPT
 	PrintToRPT("" + s); // comment/uncomment to hide/see debug logs
-#else
-	//Print("" + s); // comment/uncomment to hide/see debug logs
+#endif
+	
+#ifdef LOG_TO_SCRIPT
+	Print(string.Format("%1", s));
 #endif
 }

+ 36 - 10
Scripts/3_game/systems/inventory/hand_events.c

@@ -218,7 +218,7 @@ class HandEventTake extends HandEventBase
 
 	override bool CheckRequestSrc ()
 	{
-		if (false == GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
+		if (!GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
 		{
 			Debug.InventoryHFSMLog("CANNOT perform", typename.EnumToString(HandEventID, GetEventID()) , "n/a", "CheckRequestSrc", m_Player.ToString() );
 			if (LogManager.IsSyncLogEnable()) syncDebugPrint("[cheat] HandleInputData man=" + Object.GetDebugName(m_Player) + " failed src1 check with cmd=" + typename.EnumToString(HandEventID, GetEventID()) + " src1=" + InventoryLocation.DumpToStringNullSafe(GetSrc()));
@@ -289,7 +289,7 @@ class HandEventMoveTo extends HandEventBase
 	
 	override bool CheckRequestSrc ()
 	{
-		if (false == GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
+		if (!GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
 		{
 			if (LogManager.IsSyncLogEnable()) syncDebugPrint("[cheat] HandleInputData man=" + Object.GetDebugName(m_Player) + " failed src1 check with cmd=" + typename.EnumToString(HandEventID, GetEventID()) + " src1=" + InventoryLocation.DumpToStringNullSafe(GetSrc()));
 			return false; // stale packet
@@ -304,7 +304,7 @@ class HandEventMoveTo extends HandEventBase
 	
 	override bool CanPerformEvent ()
 	{
-		if (false == GameInventory.LocationCanMoveEntity(GetSrc(), GetDst()))
+		if (!GameInventory.LocationCanMoveEntity(GetSrc(), GetDst()))
 		{
 			#ifdef ENABLE_LOGGING
 			if ( LogManager.IsInventoryHFSMLogEnable() )
@@ -346,7 +346,7 @@ class HandEventRemove extends HandEventBase
 
 	override bool CheckRequestSrc ()
 	{
-		if (false == GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
+		if (!GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
 		{
 			#ifdef ENABLE_LOGGING
 			if ( LogManager.IsInventoryHFSMLogEnable() )
@@ -366,11 +366,36 @@ class HandEventRemove extends HandEventBase
 	
 	override bool CanPerformEvent ()
 	{
-		if (false == GameInventory.LocationCanMoveEntity(GetSrc(), GetDst()))
+		InventoryLocation src = GetSrc();
+		InventoryLocation dst = GetDst();
+
+		if (!dst)
 		{
 			#ifdef ENABLE_LOGGING
 			if ( LogManager.IsInventoryHFSMLogEnable() )
-			{	
+			{
+				Debug.InventoryHFSMLog("CANNOT perform. Dst location is NULL!", typename.EnumToString(HandEventID, GetEventID()) , "n/a", "CanPerformEvent", m_Player.ToString() );
+			}
+			#endif
+			return false;
+		}
+		
+		if (dst.GetType() == InventoryLocationType.UNKNOWN)
+		{
+			#ifdef ENABLE_LOGGING
+			if ( LogManager.IsInventoryHFSMLogEnable() )
+			{
+				Debug.InventoryHFSMLog("CANNOT perform. Dst location type is UNKNOWN!", typename.EnumToString(HandEventID, GetEventID()) , "n/a", "CanPerformEvent", m_Player.ToString() );
+			}
+			#endif
+			return false;
+		}
+		
+		if (!GameInventory.LocationCanMoveEntity(src, dst))
+		{
+			#ifdef ENABLE_LOGGING
+			if ( LogManager.IsInventoryHFSMLogEnable() )
+			{
 				Debug.InventoryHFSMLog("CANNOT perform", typename.EnumToString(HandEventID, GetEventID()) , "n/a", "CanPerformEvent", m_Player.ToString() );
 			}
 			#endif
@@ -406,12 +431,13 @@ class HandEventDrop extends HandEventRemove
 	{
 		super.ReadFromContext(ctx);
 
-		ctx.Read(m_CanPerformDrop);
-		OptionalLocationReadFromContext(m_Dst, ctx);
 		if (!m_Dst)
 		{
 			m_Dst = new InventoryLocation();
 		}
+		
+		ctx.Read(m_CanPerformDrop);
+		OptionalLocationReadFromContext(m_Dst, ctx);
 	}
 
 	override void WriteToContext(ParamsWriteContext ctx)
@@ -580,12 +606,12 @@ class HandEventSwap extends HandEventBase
 	{
 		//return false;
 
-		if (false == GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
+		if (!GameInventory.CheckRequestSrc(m_Player, GetSrc(), GameInventory.c_MaxItemDistanceRadius))
 		{
 			if (LogManager.IsSyncLogEnable()) syncDebugPrint("[cheat] HandleInputData man=" + Object.GetDebugName(m_Player) + " failed src1 check with cmd=" + typename.EnumToString(HandEventID, GetEventID()) + " src1=" + InventoryLocation.DumpToStringNullSafe(GetSrc()));
 			return false; // stale packet
 		}
-		if (false == GameInventory.CheckRequestSrc(m_Player, m_Src2, GameInventory.c_MaxItemDistanceRadius))
+		if (!GameInventory.CheckRequestSrc(m_Player, m_Src2, GameInventory.c_MaxItemDistanceRadius))
 		{
 			if (LogManager.IsSyncLogEnable()) syncDebugPrint("[cheat] HandleInputData man=" + Object.GetDebugName(m_Player) + " failed src2 check with cmd=" + typename.EnumToString(HandEventID, GetEventID()) + " src2=" + InventoryLocation.DumpToStringNullSafe(m_Src2));
 			return false; // stale packet

+ 2 - 2
Scripts/3_game/systems/universaltemperaturesource/universaltemperaturesourcelambdabase.c

@@ -12,8 +12,8 @@ class UniversalTemperatureSourceLambdaBase
 	void ~UniversalTemperatureSourceLambdaBase();
 	void Execute(UniversalTemperatureSourceSettings pSettings, UniversalTemperatureSourceResult resultValues);
 	void DryItemsInVicinity(UniversalTemperatureSourceSettings pSettings);
-	void DryItemsInVicinity(UniversalTemperatureSourceSettings pSettings, vector position, out notnull array<Object> nearestObjects);
-	void WarmAndCoolItemsInVicinity(UniversalTemperatureSourceSettings pSettings, vector position, out notnull array<Object> nearestObjects);
+	void DryItemsInVicinity(UniversalTemperatureSourceSettings pSettings, vector position, out notnull array<EntityAI> nearestObjects);
+	void WarmAndCoolItemsInVicinity(UniversalTemperatureSourceSettings pSettings, vector position, out notnull array<EntityAI> nearestObjects);
 	
 	void OnBeforeExecute()
 	{

+ 66 - 0
Scripts/3_game/tools/blend2d.c

@@ -0,0 +1,66 @@
+//----------------------------------------------------------------------------------------
+/*
+	Allows weighted blending of values defined by their 2D position in space.
+*/
+class Blend2D<Class T>
+{
+	private ref array<vector> m_Positions;
+	private ref array<T>      m_Values;
+	private ref array<float>  m_Weights;
+
+	//----------------------------------------------------------------------------------------
+	/*!
+		Create new blend structure.
+	*/
+	void Blend2D()
+	{
+		m_Positions = {};
+		m_Weights   = {};
+		m_Values    = {};
+	}
+
+	//----------------------------------------------------------------------------------------
+	/*!
+		Insert new `value` at coordinate [posX, posY].
+	*/
+	void Insert(float posX, float posY, T value)
+	{
+		m_Positions.Insert(Vector(posX, posY, 0));
+		m_Values.Insert(value);
+		m_Weights.Insert(0);
+	}
+
+	//----------------------------------------------------------------------------------------
+	/*!
+		Empty the blend structure.
+	*/
+	void Clear()
+	{
+		m_Positions.Clear();
+		m_Values.Clear();
+		m_Weights.Clear();
+	}
+
+	//----------------------------------------------------------------------------------------
+	/*
+		Evaluate and return the result of the blend sampled at coordinate [posX, posY].
+	*/
+	T Blend(float posX, float posY)
+	{
+		vector samplePosition = Vector(posX, posY, 0);
+		Math3D.BlendCartesian(samplePosition, m_Positions, m_Weights);
+
+		T result;
+		int numValues = m_Values.Count();
+		for (int v = 0; v < numValues; ++v)
+		{
+			result += (m_Values[v] * m_Weights[v]);
+		}
+
+		return result;
+	}
+
+}
+
+//----------------------------------------------------------------------------------------
+typedef  Blend2D<vector> Blend2DVector;

+ 1 - 1
Scripts/3_game/tools/component/componentenergymanager.c

@@ -761,7 +761,7 @@ class ComponentEnergyManager : Component
 			return m_CanWork;
 		}
 		
-		if (m_ThisEntityAI && m_ThisEntityAI.IsRuined())
+		if (!m_ThisEntityAI || m_ThisEntityAI.IsRuined())
 		{
 			return false;
 		}

+ 8 - 0
Scripts/3_game/tools/input.c

@@ -104,6 +104,14 @@ class Input
 	proto native void	EnableMouseAndKeyboard(bool enable);
 	//! @return state of support mouse and keyboard (on consoles)
 	proto native bool	IsEnabledMouseAndKeyboard();
+	
+	//! Enable gamepad (on PC)
+	proto native void	EnableGamepad(bool enable);
+	//! @return state of support gamepad (on PC) 
+	// NOTE: not actually supported, just keeping naming consistent. 
+	// Required as we need to disable gamepad on windows when in the server browser to prevent 
+	// the client from freezing on gamepad queries while refreshing the server list .
+	proto native bool	IsEnabledGamepad();
 	/*!
 	@return state of support mouse and keyboard. If client playing on server 
 	where mouse and keyboard is disabled, then return false. (on consoles)

+ 1 - 0
Scripts/3_game/tools/uiscriptedmenu.c

@@ -14,6 +14,7 @@ class UIMenuPanel: Managed
 	proto native void DestroySubmenu();
 	proto native bool IsAnyMenuVisible();
 	proto native bool IsVisible();
+	proto native bool IsClosing();
 
 #ifdef FEATURE_CURSOR
 	//! Signal when the menu is created through 'UIManager.CreateScriptedMenu'

+ 29 - 31
Scripts/3_game/vehicles/boat.c

@@ -17,13 +17,13 @@ enum BoatFluid
 
 class BoatOwnerState : TransportOwnerState
 {
-
 };
 
 class BoatMove : TransportMove
 {
 };
 
+//!	Native class for boats - handles physics simulation
 class Boat extends Transport
 {
 	//!
@@ -38,41 +38,23 @@ class Boat extends Transport
 		return BoatMove;
 	}
 
-	//!	Returns the actual throttle value in range <0, 1>.
-	proto native float GetThrottle();
-	
-	//! Sets the future throttle value.
-	proto native void SetThrottle(float value);
-
 	//!	Returns the actual steering value in range <-1, 1>.
 	proto native float GetSteering();
 
 	//! Sets the future steering value.
 	proto native void SetSteering(float value);
 
-	//! Returns the number of gears.
-	proto native int GetGearCount();
-
-	//! Returns the index of the neutral gear.
-	proto native int GetNeutralGear();
-
-	//! Returns the index of the future gear, -1 if there is no engine.
-	proto native int GetGear();
-
-	//! Returns the index of the current gear, -1 if there is no engine.
-	proto native int GetCurrentGear();
+	//!	Returns the actual throttle value in range <0, 1>.
+	proto native float GetThrottle();
+	
+	//! Sets the future throttle value.
+	proto native void SetThrottle(float value);
 
 	//! Returns the value of how much the clutch is disengaged.
 	proto native int GetClutch();
 
-	//! Shifts the future gear up, triggering gearbox simulation.
-	proto native void ShiftUp();
-
-	//! Shifts the future gear to selected gear, triggering gearbox simulation.
-	proto native void ShiftTo(int gear);
-
-	//! Shifts the future gear down, triggering gearbox simulation.
-	proto native void ShiftDown();
+	//! Sets the future clutch value.
+	proto native void SetClutch(float value);
 
 	//! Returns if there is an engine.
 	proto native bool HasEngine();
@@ -101,6 +83,27 @@ class Boat extends Transport
 	//! Stops the engine.
 	proto native void EngineStop();
 
+	//! Returns the index of the current gear, -1 if there is no engine.
+	proto native int GetCurrentGear();
+
+	//! Returns the index of the future gear, -1 if there is no engine.
+	proto native int GetGear();
+
+	//! Returns the index of the neutral gear.
+	proto native int GetNeutralGear();
+
+	//! Returns the number of gears.
+	proto native int GetGearCount();
+
+	//! Shifts the future gear up, triggering gearbox simulation.
+	proto native void ShiftUp();
+
+	//! Shifts the future gear to selected gear, triggering gearbox simulation.
+	proto native void ShiftTo(int gear);
+
+	//! Shifts the future gear down, triggering gearbox simulation.
+	proto native void ShiftDown();
+
 	//! Returns the propeller position in local space
 	proto native vector PropellerGetPosition();
 
@@ -131,11 +134,6 @@ class Boat extends Transport
 	//! Adds to the specified fluid the specified amount.
 	proto native void Fill(BoatFluid fluid, float amount);
 
-	/*!
-		Called every physics simulation step before inputs are applied.
-	*/
-	void OnInput(float dt) {}
-
 	/*!
 		Is called every time the game wants to start the engine.
 

+ 144 - 161
Scripts/3_game/vehicles/car.c

@@ -82,13 +82,28 @@ enum CarWheelWaterState
 	UNDER_WATER		//!< if the wheel is under a water plane
 };
 
+class CarOwnerState : TransportOwnerState
+{
+};
 
+class CarMove : TransportMove
+{
+};
 
-//!	Base native class for all motorized wheeled vehicles.
+//!	Native class for cars - handles physics simulation
 class Car extends Transport
 {
-	//!	DEPRECATED, left for backwards compatibility, the methods of this class are now directly accessible on Car itself
-	proto native CarController GetController();
+	//!
+	protected override event typename GetOwnerStateType()
+	{
+		return CarOwnerState;
+	}
+
+	//!
+	protected override event typename GetMoveType()
+	{
+		return CarMove;
+	}
 	
 	//!	Returns the current speed of the vehicle in km/h.
 	proto native float GetSpeedometer();
@@ -169,116 +184,40 @@ class Car extends Transport
 		return false;
 	}
 	
-//-----------------------------------------------------------------------------
-// controls
 
 	//!	Returns the current steering value in range <-1, 1>.
 	proto native float GetSteering();
-	/*!
-		Sets the steering value.
 
-		\param in     should be in range <-1, 1>
-		\param analog indicates if the input value was taken from analog controller
-	*/
-	proto native void SetSteering( float in, bool analog = false );
+	//! Sets the future steering value.
+	proto native void SetSteering(float value, bool unused0 = false);
 
-	//!	Returns the current thrust turbo modifier value in range <0, 1>.
-	proto native float GetThrustTurbo();
-	//!	Returns the current thrust gentle modifier value in range <0, 1>.
-	proto native float GetThrustGentle();
-	//!	Returns the current thrust value in range <0, 1>.
-	proto native float GetThrust();
-	/*!
-		Sets the thrust value.
+	//!	Returns the actual throttle value in range <0, 1>.
+	proto native float GetThrottle();
+	
+	//! Sets the future throttle value.
+	proto native void SetThrottle(float value);
 
-		\param in     should be in range <0, 1>
-		\param gentle should be in range <0, 1>, thrust modifier
-		\param turbo  should be in range <0, 1>, thrust modifier
-	*/
-	proto native void SetThrust( float in, float gentle = 0, float turbo = 0 );
+	//! Returns the value of how much the clutch is disengaged.
+	proto native int GetClutch();
+
+	//! Sets the future clutch value.
+	proto native void SetClutch(float value);
 
 	//! Returns the current brake value in range <0, 1>.
 	proto native float GetBrake();
-	/*!
-		Sets the brake value.
 
-		\param in should be in range <0, 1>
-		\param panic should be in range <0, 1>
-	*/
-	proto native void SetBrake( float in, float panic = 0, bool gentle = false );
+	//! Sets the future brake value
+	proto native void SetBrake(float value, float unused0 = 0, bool unused1 = false);
 	
 	//! Returns the current handbrake value in range <0, 1>.
 	proto native float GetHandbrake();
-	/*!
-		Sets the handbrake value.
 
-		\param in should be in range <0, 1>
-	*/
-	proto native void SetHandbrake( float in );
+	//! Sets the future handbrake value
+	proto native void SetHandbrake(float value);
 	
-	/*!
-		Sets if brakes should activate without a driver present
-	*/
-	proto native void SetBrakesActivateWithoutDriver( bool activate = true );
+	//! Sets if brakes should activate without a driver present
+	proto native void SetBrakesActivateWithoutDriver(bool activate = true);
 	
-	//! Returns the current clutch value in range <0, 1>.
-	proto native float GetClutch();
-	/*!
-		Sets the clutch state.
-	*/
-	proto native void SetClutchState( bool in );
-
-	//!	Returns index of the current gear.
-	proto native int GetGear();
-
-	proto native void ShiftUp();
-	proto native void ShiftTo( CarGear gear );
-	proto native void ShiftDown();
-
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-// fluids
-
-	/*!
-		Returns tank capacity for the specified vehicle's fluid.
-
-		\param fluid the specified fluid type
-	*/
-	proto native float GetFluidCapacity( CarFluid fluid );
-
-	/*!
-		Returns fraction value (in range <0, 1>)
-		of the current state of the specified vehicle's fluid.
-
-		\param[in] fluid the specified fluid type
-	*/
-	proto native float GetFluidFraction( CarFluid fluid );
-
-	//! Removes from the specified fluid the specified amount.
-	proto native void Leak( CarFluid fluid, float amount );
-
-	//! Removes all the specified fluid from vehicle.
-	proto native void LeakAll( CarFluid fluid );
-
-	//! Adds to the specified fluid the specified amount.
-	proto native void Fill( CarFluid fluid, float amount );
-
-	/*!
-		Is called every time when the specified vehicle's fluid level
-		changes eg. when vehicle is consuming fuel.
-
-		\param[in] fluid fluid identifier, \see CarFluid
-		\param[in] newValue new fluid level
-		\param[in] oldValue previous fluid level before change
-	*/
-	void OnFluidChanged( CarFluid fluid, float newValue, float oldValue ) {}
-//-----------------------------------------------------------------------------
-
-
-//-----------------------------------------------------------------------------
-// engine
-
 	//! Returns engine's min operating rpm
 	proto native float EngineGetRPMMin();
 	
@@ -300,39 +239,35 @@ class Car extends Transport
 	//! Starts the engine.
 	proto native void EngineStart();
 
-	/*!
-		Is called every time the game wants to start the engine.
-
-		\return true if the engine can start, false otherwise.
-	*/
-	bool OnBeforeEngineStart()
-	{
-		// engine can start by default
-		return true;
-	}
-
-	//! Is called every time the engine starts.
-	void OnEngineStart() {}
-
 	//! Stops the engine.
 	proto native void EngineStop();
 
-	//! Is called every time the engine stops.
-	void OnEngineStop() {}
-
 	//! Get actual position of engine (model space)
 	proto native vector GetEnginePos();
+	
 	//! Override the position of engine (model space)
 	proto native void SetEnginePos(vector pos);
 
-//-----------------------------------------------------------------------------
+	//! Returns the index of the current gear, -1 if there is no engine.
+	proto native int GetCurrentGear();
 
+	//! Returns the index of the future gear, -1 if there is no engine.
+	proto native int GetGear();
 
-//-----------------------------------------------------------------------------
-// gearbox
+	//! Returns the index of the neutral gear.
+	proto native int GetNeutralGear();
 
-	//! Returns total number of gears.
-	proto native int GetGearsCount();
+	//! Returns the number of gears.
+	proto native int GetGearCount();
+
+	//! Shifts the future gear up, triggering gearbox simulation.
+	proto native void ShiftUp();
+
+	//! Shifts the future gear to selected gear, triggering gearbox simulation.
+	proto native void ShiftTo(int gear);
+
+	//! Shifts the future gear down, triggering gearbox simulation.
+	proto native void ShiftDown();
 
 	//! Returns gearbox type. See CarGearboxType enum for more info.
 	proto native CarGearboxType GearboxGetType();
@@ -340,71 +275,65 @@ class Car extends Transport
 	//! Returns gearbox mode. This is useful when car has automatic gearbox.
 	proto native CarAutomaticGearboxMode GearboxGetMode();
 
-	/*!
-		Is called every time when the simulation changed gear.
-
-		\param[in] newGear new gear level
-		\param[in] oldGear previous gear level before gear shift
-	*/
-	void OnGearChanged( int newGear, int oldGear )
-	{
-	}
-//-----------------------------------------------------------------------------
-
-
-//-----------------------------------------------------------------------------
-// wheels
-
 	//! Returns true if any of the wheels are locked in terms of its movement.
 	proto native bool WheelIsAnyLocked();
+
 	/*!
 		Returns the raw angular velocity of the wheel, unstable value
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native float WheelGetAngularVelocity( int wheelIdx );
+
 	/*!
 		Returns true if given wheel is making any contact
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native bool WheelHasContact( int wheelIdx );
+
 	/*!
 		Returns the position of contact in world space, only valid if there was an actual contact
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native vector WheelGetContactPosition( int wheelIdx );
+
 	/*!
 		Returns the normal of contact in world space, only valid if there was an actual contact
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native vector WheelGetContactNormal( int wheelIdx );
+
 	/*!
 		Returns the direction pointing forwards that the wheel is facing
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native vector WheelGetDirection( int wheelIdx );
+
 	/*!
 		Returns the surface that the wheel is nearby
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native SurfaceInfo WheelGetSurface( int wheelIdx );
+
 	/*!
 		Returns the state that the wheel is in with water
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native CarWheelWaterState WheelGetWaterState( int wheelIdx );
+
 	/*!
 		Returns the entity attached that represents the wheel
 
 		\param[in] wheelIdx index of the wheel, they are counted from left-front to rear-right
 	*/
 	proto native EntityAI WheelGetEntity( int wheelIdx );
+
 	/*!
 		Returns true if given wheel is locked in terms of its movement.
 
@@ -418,21 +347,70 @@ class Car extends Transport
 	//! Number of actually attached wheels (hubs only)
 	proto native int WheelCountPresent();
 
-//-----------------------------------------------------------------------------
+	/*!
+		Returns tank capacity for the specified vehicle's fluid.
+
+		\param fluid the specified fluid type
+	*/
+	proto native float GetFluidCapacity(CarFluid fluid);
+
+	/*!
+		Returns fraction value (in range <0, 1>)
+		of the current state of the specified vehicle's fluid.
+
+		\param[in] fluid the specified fluid type
+	*/
+	proto native float GetFluidFraction(CarFluid fluid);
+
+	//! Removes from the specified fluid the specified amount.
+	proto native void Leak(CarFluid fluid, float amount);
+
+	//! Removes all the specified fluid from vehicle.
+	proto native void LeakAll(CarFluid fluid);
+
+	//! Adds to the specified fluid the specified amount.
+	proto native void Fill(CarFluid fluid, float amount);
 
+	/*!
+		Is called every time the game wants to start the engine.
 
-//-----------------------------------------------------------------------------
-// events
+		\return true if the engine can start, false otherwise.
+	*/
+	bool OnBeforeEngineStart()
+	{
+		// engine can start by default
+		return true;
+	}
 
 	/*!
-		Is called every time when vehicle collides with other object.
+		Is called every time the engine starts.
+	*/
+	void OnEngineStart() {}
 
-		\param[in] zoneName configured vehicle's zone that was hit
-		\param[in] localPos position where the vehicle was hit in vehicle's space
-		\param[in] other object with which the vehicle is colliding
-		\param[in] data contact properties
+	/*!
+		Is called every time the engine stops.
 	*/
-	void OnContact( string zoneName, vector localPos, IEntity other, Contact data ) {}
+	void OnEngineStop() {}
+
+	/*!
+		Is called every time when the simulation changed gear.
+
+		\param[in] newGear new gear level
+		\param[in] oldGear previous gear level before gear shift
+	*/
+	void OnGearChanged(int newGear, int oldGear)
+	{
+	}
+
+	/*!
+		Is called every time when the specified vehicle's fluid level changes. 
+		This callback is called on owner only.
+
+		\param[in] fluid fluid identifier, \see CarFluid
+		\param[in] newValue new fluid level
+		\param[in] oldValue previous fluid level before change
+	*/
+	void OnFluidChanged(CarFluid fluid, float newValue, float oldValue) {}
 
 	/*!
 		Is called every sound simulation step.
@@ -442,34 +420,39 @@ class Car extends Transport
 		\param[in] oldValue already computed value by the game code
 		\return new value of the specified sound controller.
 	*/
-	float OnSound( CarSoundCtrl ctrl, float oldValue )
+	float OnSound(CarSoundCtrl ctrl, float oldValue)
 	{
 		// just use the computed value by the game code
 		return oldValue;
 	}
 
-	/*!
-		Is called after every input simulation step.
+	[Obsolete("no replacement")]
+	proto native void ForcePosition(vector pos);
+	
+	[Obsolete("no replacement")]
+	proto native void ForceDirection(vector dir);
 
-		Note that the player character and other systems can always change the internal state.
-		It is highly recommended to store state of custom inputs elsewhere and call Setters here.
+	[Obsolete("Use methods directly on Car")]
+	proto native CarController GetController();
 
-		\param[in] dt frame time in seconds
-	*/
-	void OnInput( float dt ) {}
+	[Obsolete("Use Car.IsTurbo")]
+	proto native float GetThrustTurbo();
 
-	/*!
-		Is called every game frame.
-		\param[in] dt frame time in seconds
-	*/
-	void OnUpdate( float dt ) {}
-//-----------------------------------------------------------------------------
+	[Obsolete("Use Car.IsGentle")]
+	proto native float GetThrustGentle();
 
+	[Obsolete("Use Car.GetThrottle")]
+	proto native float GetThrust();
+	
+	[Obsolete("Use Car.SetThrottle/Car.SetTurbo/Car.SetGentle")]
+	proto native void SetThrust(float in, float gentle = 0, float turbo = 0);
+
+	[Obsolete("no replacement")];
+	proto native void SetClutchState(bool in);
+
+	[Obsolete("Use Car.GetGearCount")]
+	proto native int GetGearsCount();
 
-	// implemented only in internal configuration
-	proto native void ForcePosition( vector pos );
-	// implemented only in internal configuration
-	proto native void ForceDirection( vector dir );
 };
 
 

+ 116 - 19
Scripts/3_game/vehicles/transport.c

@@ -142,7 +142,75 @@ class Transport extends EntityAI
 	
 	//! Handles death of player in vehicle and awakes its physics if needed
 	proto native void CrewDeath( int posIdx );
-	
+
+	/*!
+		Is called when the crew member enters the driver seat
+		\param[in] player The player that entered the driver seat
+	*/
+	void OnDriverEnter(Human player) {}
+
+	/*!
+		Is called when the crew member leaves the driver seat
+		\param[in] player The player that left the driver seat
+	*/
+	void OnDriverExit(Human player) {}
+	
+	//! ---------------- deterministic random numbers ------------------------
+
+	/*!
+		\brief Random number in range of <0,0xffffffff> - !!! use this only during deterministic simulation (EOnSimulate/EOnPostSimulate)
+		\return int value in range of <0,0xffffffff>
+	*/
+	proto native int Random();
+		
+	/*!
+		\brief Random number in range of <0,pRange-1> - !!! use this only during deterministic simulation (EOnSimulate/EOnPostSimulate)
+		@param pRange upper bounds of random number
+		\return int value in range of <0,pRange-1>
+	*/
+	proto native float RandomRange(int pRange);
+
+	/*!
+		\brief Random number in range of <0,1> - !!! use this only during deterministic simulation (EOnSimulate/EOnPostSimulate)
+		\return float value in range of <0,1>
+	*/
+	proto native float Random01();
+
+	/*!
+		\brief Random number in range of <min,max> - !!! use this only during deterministic simulation (EOnSimulate/EOnPostSimulate)
+		\return float value in range of <min,max>
+	*/
+	float RandomFloat(float min, float max)
+	{
+		return Random01() * (max - min) + min;
+	}
+
+	/*!
+		Is called every time when vehicle collides with other object.
+
+		\param[in] zoneName configured vehicle's zone that was hit
+		\param[in] localPos position where the vehicle was hit in vehicle's space
+		\param[in] other object with which the vehicle is colliding
+		\param[in] data contact properties
+	*/
+	void OnContact(string zoneName, vector localPos, IEntity other, Contact data) {}
+
+	/*!
+		Called after every input simulation step.
+
+		Note that the player character and other systems can always change the internal state.
+		It is highly recommended to store state of custom inputs elsewhere and call Setters here.
+
+		\param[in] dt delta time since last called in seconds
+	*/
+	void OnInput(float dt) {}
+
+	/*!
+		Is called every game frame on client, fixed rate on server
+		\param[in] dt delta time since last called in seconds
+	*/
+	void OnUpdate(float dt) {}
+
 	override bool IsTransport()
 	{
 		return true;
@@ -327,24 +395,8 @@ class Transport extends EntityAI
 		return 4.0;
 	}
 	
-	void MarkCrewMemberUnconscious(int crewMemberIndex)
-	{
-		set<int> crewMemberIndicesCopy = new set<int>();
-		crewMemberIndicesCopy.Copy(m_UnconsciousCrewMemberIndices);
-		crewMemberIndicesCopy.Insert(crewMemberIndex);
-
-		m_UnconsciousCrewMemberIndices = crewMemberIndicesCopy;
-	}
-	
-	void MarkCrewMemberDead(int crewMemberIndex)
-	{
-		set<int> crewMemberIndicesCopy = new set<int>();
-		crewMemberIndicesCopy.Copy(m_DeadCrewMemberIndices);
-		crewMemberIndicesCopy.Insert(crewMemberIndex);
-
-		m_DeadCrewMemberIndices = crewMemberIndicesCopy;
-	}
-	protected void HandleByCrewMemberState(ECrewMemberState state);
+	void MarkCrewMemberUnconscious(int crewMemberIndex);	
+	void MarkCrewMemberDead(int crewMemberIndex);
 
 	vector GetTransportCameraOffset()
 	{
@@ -475,6 +527,48 @@ class Transport extends EntityAI
 	{
 		return m_EngineZoneReceivedHit;
 	}
+
+	override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
+	{
+		super.GetDebugActions(outputList);
+		
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.DELETE, "Delete", FadeColors.RED));
+		if (Gizmo_IsSupported())
+			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GIZMO_OBJECT, "Gizmo Object", FadeColors.LIGHT_GREY));
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.GIZMO_PHYSICS, "Gizmo Physics (SP Only)", FadeColors.LIGHT_GREY)); // intentionally allowed for testing physics desync
+		outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
+	}
+	
+	override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
+	{
+		if (super.OnAction(action_id, player, ctx))
+			return true;
+
+		if (GetGame().IsClient() || !GetGame().IsMultiplayer())
+		{
+			switch (action_id)
+			{
+				case EActions.GIZMO_OBJECT:
+					GetGame().GizmoSelectObject(this);
+					return true;
+				case EActions.GIZMO_PHYSICS:
+					GetGame().GizmoSelectPhysics(GetPhysics());
+					return true;
+			}
+		}
+
+		if (GetGame().IsServer())
+		{
+			switch (action_id)
+			{
+				case EActions.DELETE:
+					Delete();
+					return true;
+			}
+		}
+	
+		return false;
+	}
 	
 	bool IsAreaAtDoorFree( int currentSeat, float maxAllowedObjHeight, inout vector extents, out vector transform[4] )
 	{
@@ -613,4 +707,7 @@ class VehicleFlippedContext
 		return m_bIsDebug;
 	}
 #endif
+	
+	[Obsolete("no replacement")]
+	protected void HandleByCrewMemberState(ECrewMemberState state);
 };

+ 15 - 1
Scripts/3_game/weather.c

@@ -126,10 +126,13 @@ class WeatherPhenomenon
 	{
 		// check if mission forces use of custom weather
 		Weather weather = g_Game.GetWeather();
-		
+				
 		if ( weather.GetMissionWeather() )
 			return false;
 		
+		if (weather.GetWeatherUpdateFrozen())
+			return true;
+		
 		// check for active worlddata with custom onbeforechange behaviour
 		Mission currentMission = g_Game.GetMission();
 
@@ -164,6 +167,7 @@ typedef WeatherPhenomenon WindMagnitude;
 class Weather
 {
 	protected bool m_missionWeather;
+	protected bool m_UpdateFrozen;
 	
 	private void Weather()
 	{
@@ -386,6 +390,16 @@ class Weather
 		return m_missionWeather;
 	}
 	
+	void SetWeatherUpdateFreeze(bool state)
+	{
+		m_UpdateFrozen = state;
+	}
+	
+	bool GetWeatherUpdateFrozen()
+	{
+		return m_UpdateFrozen;
+	}
+	
 	// Noise reduction due to environmental conditions, used for AI noise evaluation
 	float GetNoiseReductionByWeather()
 	{

+ 1 - 1
Scripts/4_world/classes/basebuilding/construction.c

@@ -25,7 +25,7 @@ class Construction
 	//============================================	
 	void Construction( BaseBuildingBase parent )
 	{
-		m_ConstructionParts = new ref map<string, ref ConstructionPart>;
+		m_ConstructionParts = new map<string, ref ConstructionPart>;
 		
 		//set parent object
 		SetParent( parent );

+ 3 - 3
Scripts/4_world/classes/basebuilding/constructionactiondata.c

@@ -27,11 +27,11 @@ class ConstructionActionData
 
 	void ConstructionActionData()
 	{
-		m_BuildParts = new ref array<ConstructionPart>;
-		m_BuildPartsNoTool = new ref array<ConstructionPart>;
+		m_BuildParts = new array<ConstructionPart>;
+		m_BuildPartsNoTool = new array<ConstructionPart>;
 		m_PartIndex = 0;
 		
-		m_Attachments = new ref array<EntityAI>;
+		m_Attachments = new array<EntityAI>;
 		m_AttachmentsIndex = 0;
 
 		if ( GetGame().IsClient() || !GetGame().IsMultiplayer() )

+ 10 - 6
Scripts/4_world/classes/bleedingsources/bleedingsource.c

@@ -24,6 +24,10 @@ class BleedingSource
 	bool m_DeleteRequested;
 	eBleedingSourceType m_Type = eBleedingSourceType.NORMAL;
 	
+	#ifdef DIAG_DEVELOPER
+	float m_DiagTimeStart; //for debug purposes only
+	#endif
+	
 	void BleedingSource(PlayerBase player, int bit, string bone, vector orientation, vector offset,int max_time, float flow_modifier, string particle_name)
 	{
 		//m_Position = position;
@@ -86,7 +90,7 @@ class BleedingSource
 		m_BleedingEffect = EffectParticle.Cast(m_ParticleName.ToType().Spawn());
 		if (m_BleedingEffect)
 		{
-			SEffectManager.PlayInWorld( m_BleedingEffect, "0 0 0" );
+			SEffectManager.PlayInWorld(m_BleedingEffect, "0 0 0");
 			m_BloodParticle = m_BleedingEffect.GetParticle();
 			m_BloodParticle.SetOrientation(m_Orientation);
 			vector pos;
@@ -111,7 +115,7 @@ class BleedingSource
 		SEffectManager.DestroyEffect(m_BleedingEffect);
 	}
 
-	void OnUpdateServer(float deltatime, float blood_scale, bool no_blood_loss )
+	void OnUpdateServer(float deltatime, float blood_scale, bool no_blood_loss)
 	{
 		m_ActiveTime += deltatime;
 		
@@ -123,10 +127,10 @@ class BleedingSource
 				m_DeleteRequested = true;
 			}
 		}
-		if ( !no_blood_loss )
+		if (!no_blood_loss)
 		{
 			float flow = m_FlowModifier;
-			switch ( m_Type )
+			switch (m_Type)
 			{
 				case eBleedingSourceType.NORMAL:
 				{
@@ -138,7 +142,7 @@ class BleedingSource
 					flow *= PlayerConstants.BLEEDING_SOURCE_BURN_MODIFIER;
 				}
 			}
-			m_Player.AddHealth("GlobalHealth","Blood", (PlayerConstants.BLEEDING_SOURCE_BLOODLOSS_PER_SEC * blood_scale * deltatime * flow) );
+			m_Player.AddHealth("GlobalHealth","Blood", (PlayerConstants.BLEEDING_SOURCE_BLOODLOSS_PER_SEC * blood_scale * deltatime * flow));
 		}
 	}
 	
@@ -161,7 +165,7 @@ class BleedingSource
 	
 	void StopSourceBleedingIndication(bool instant = false)
 	{
-		if ( m_Player && m_Player.IsControlledPlayer() && GetGame() && (!GetGame().IsDedicatedServer()) )
+		if (m_Player && m_Player.IsControlledPlayer() && GetGame() && (!GetGame().IsDedicatedServer()))
 		{
 			Param4<bool,int,vector,bool> par = new Param4<bool,int,vector,bool>(false,m_Bit,"0 0 0",instant);
 			GetGame().GetMission().GetEffectWidgets().UpdateWidgets(EffectWidgetsTypes.BLEEDING_LAYER,0,par);

+ 30 - 43
Scripts/4_world/classes/bleedingsources/bleedingsourcesmanagerremote.c

@@ -54,11 +54,11 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 		Init();
 		int bit_offset = 0;
 		
-		for (int i = 0; i < BIT_INT_SIZE; i++)
+		for (int i = 0; i < BIT_INT_SIZE; ++i)
 		{
 			int bit = 1 << bit_offset;
 			bit_offset++;
-			if ( (bit & m_BleedingBits) != 0 )
+			if ((bit & m_BleedingBits) != 0)
 			{
 				RemoveBleedingSource(bit);
 				AddBleedingSource(bit);
@@ -70,6 +70,9 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 	override protected void AddBleedingSource(int bit)
 	{
 		super.AddBleedingSource(bit);
+		#ifdef DIAG_DEVELOPER
+		m_BleedingSources.Get(bit).m_DiagTimeStart = GetGame().GetTickTime();
+		#endif
 		if (GetGame().IsMultiplayer())
 			m_Player.OnBleedingSourceAdded();
 	}
@@ -87,15 +90,15 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 
 	void OnBleedingBitsUpdate(int old_mask, int new_mask)
 	{
-		for (int i = 0; i < 32; i++)
+		for (int i = 0; i < 32; ++i)
 		{
 			int compare_bit = 1 << i;
 			int new_compare_result_bit = compare_bit & new_mask;
 			int old_compare_result_bit = compare_bit & old_mask;
 			
-			if ( new_compare_result_bit )
+			if (new_compare_result_bit)
 			{
-				if ( !(new_compare_result_bit & old_mask))
+				if (!(new_compare_result_bit & old_mask))
 				{
 					//a different active bit in the new mask
 					AddBleedingSource(new_compare_result_bit);
@@ -103,7 +106,7 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 			}
 			else
 			{
-				if ( new_compare_result_bit != old_compare_result_bit )
+				if (new_compare_result_bit != old_compare_result_bit)
 				{
 					RemoveBleedingSource(old_compare_result_bit);
 				}
@@ -116,11 +119,11 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 		int bleeding_source_count = 0;
 		int pow = 0;
 		
-		for (int i = 0; i < BIT_INT_SIZE ; i++)
+		for (int i = 0; i < BIT_INT_SIZE ; ++i)
 		{
 			int bit = Math.Pow(2, pow);
 			pow++;
-			if ( (m_BleedingBits & bit) != 0)
+			if ((m_BleedingBits & bit) != 0)
 			{
 				bleeding_source_count++;
 			}
@@ -132,32 +135,6 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 	void SetDiag(bool value)
 	{
 		m_ShowDiag = value;
-		return;
-		
-		int boneIdx = m_Player.GetBoneIndexByName("RightArmExtra");
-
-		if ( boneIdx != -1 )
-		{
-		  Object linkedObject = GetGame().CreateObject("Ammo_ArrowBolt", "0 0 0");
-		
-		//linkedObject.SetPosition("0 1 0");
-		//linkedObject.SetOrientation("0 90 0");
-		  /*
-		
-		  Set local space transform for linked object
-		
-		  */
-			
-			BleedingSourceEffect eff = new BleedingSourceEffect();
-			eff.SetAutodestroy(true);
-			SEffectManager.PlayInWorld( eff, "0 0 0" );
-			Particle p = eff.GetParticle();
-			//p.SetOrientation("0 90 0");
-			m_Player.AddChild(p, boneIdx);
-		
-		 	 m_Player.AddChild(linkedObject, boneIdx);
-		}
-	
 	}
 	
 	void OnUpdate()
@@ -168,7 +145,7 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 			DisplayDebug();
 			DisplayVisualDebug();
 		}
-		else if ( m_ShowingDiag || m_ShowingDiagDraw )
+		else if (m_ShowingDiag || m_ShowingDiagDraw)
 		{
 			if (m_ShowingDiag)
 				CleanDebug();
@@ -181,32 +158,42 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 	void DisplayDebug()
 	{	
 		m_ShowingDiag = true;
-		
 		DbgUI.BeginCleanupScope();
         DbgUI.Begin("Bleeding Sources", 50, 50);
 		
 		int pow = 0;
-		
 		bool anyBleedingSourceActive = false;
 		
-		for (int i = 0; i < BIT_INT_SIZE ; i++)
+		for (int i = 0; i < BIT_INT_SIZE ; ++i)
 		{
 			int bit = Math.Pow(2, pow);
 			pow++;
-			if ( (m_BleedingBits & bit) != 0)
+			if ((m_BleedingBits & bit) != 0)
 			{
 				BleedingSourceZone bsz = GetBleedingSourceMeta(bit);
-				
 				string name = GetSelectionNameFromBit(bit);
 				string slot_name =  InventorySlots.GetSlotName(bsz.GetInvLocation());
-				DbgUI.Text(name + "| slot name: "+ slot_name);
+				float timeRemaining = -1;
+				
+				#ifdef DIAG_DEVELOPER
+				BleedingSource bsi = m_BleedingSources.Get(bit);
+				timeRemaining = bsz.GetMaxTime() + bsi.m_DiagTimeStart - GetGame().GetTickTime();
+				timeRemaining = Math.Round(timeRemaining);
+				#endif
+				
+				DbgUI.Text(string.Format("zone: %1 | closest inv. slot: %2 | time remaining: %3", name, slot_name, timeRemaining.ToString()));
 				anyBleedingSourceActive = true;
 			}
 		}
 		
 		if (!anyBleedingSourceActive)
 		{
-			DbgUI.Text("Currently no bleeding sources are active.");
+			DbgUI.Text("No bleeding sources are active.");
+		}
+		else
+		{
+			DbgUI.Text("");
+			DbgUI.Text("Note: BleedingSourcesManagerServer only updates active sources every 3s, displayed times are client estimates.");
 		}
 		
         DbgUI.End();
@@ -226,7 +213,7 @@ class BleedingSourcesManagerRemote extends BleedingSourcesManagerBase
 	void DisplayVisualDebug()
 	{
 		/*
-		if(m_Point) 
+		if (m_Point) 
 		{
 			Debug.RemoveShape(m_Point);
 		}

+ 58 - 51
Scripts/4_world/classes/bleedingsources/bleedingsourcesmanagerserver.c

@@ -5,6 +5,8 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	bool	m_DisableBloodLoss = false;
 	ref array<int> m_DeleteList = new array<int>;
 	
+	protected bool	m_ProcessSourcesRemoval = false; //to avoid constant array counting
+	
 	const int STORAGE_VERSION = 103;
 	
 	protected BleedingSourceZone GetBleedingSourceZone(int bit)
@@ -19,7 +21,11 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	
 	void RequestDeletion(int bit)
 	{
-		m_DeleteList.Insert(bit);
+		if (m_DeleteList.Find(bit) == -1) //avoids multiple removal of the same bit/source
+		{
+			m_DeleteList.Insert(bit);
+			m_ProcessSourcesRemoval = true;
+		}
 	}
 	
 	override protected void AddBleedingSource(int bit)
@@ -29,43 +35,39 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 			return;
 		#endif
 		
-		m_Player.SetBleedingBits(m_Player.GetBleedingBits() | bit );
+		m_Player.SetBleedingBits(m_Player.GetBleedingBits() | bit);
 		super.AddBleedingSource(bit);
 		m_Player.OnBleedingSourceAdded();
 	}
 	
 	override protected bool RemoveBleedingSource(int bit)
 	{
-		if(!super.RemoveBleedingSource(bit))
-		{
-			Error("Failed to remove bleeding source:" + bit);
-		}
-		else
+		if (super.RemoveBleedingSource(bit))
 		{
 			m_Player.OnBleedingSourceRemovedEx(m_Item);
-		}
-		
-		int inverse_bit_mask = ~bit;
-		m_Player.SetBleedingBits(m_Player.GetBleedingBits() & inverse_bit_mask );
-		
-		
-		//infection moved here to allow proper working in singleplayer
-		float chanceToInfect;
-		
-		if (m_Item)
-		{
-			chanceToInfect = m_Item.GetInfectionChance(0, CachedObjectsParams.PARAM1_BOOL);
+			
+			float chanceToInfect;
+			if (m_Item)
+			{
+				chanceToInfect = m_Item.GetInfectionChance(0, CachedObjectsParams.PARAM1_BOOL);
+			}
+			else
+			{
+				chanceToInfect = PlayerConstants.BLEEDING_SOURCE_CLOSE_INFECTION_CHANCE;
+			}
+			float diceRoll = Math.RandomFloat01();
+			if (diceRoll < chanceToInfect)
+			{
+				m_Player.InsertAgent(eAgents.WOUND_AGENT);
+			}
 		}
 		else
 		{
-			chanceToInfect = PlayerConstants.BLEEDING_SOURCE_CLOSE_INFECTION_CHANCE;
-		}
-		float diceRoll = Math.RandomFloat01();
-		if (diceRoll < chanceToInfect)
-		{
-			m_Player.InsertAgent(eAgents.WOUND_AGENT);
+			ErrorEx("Failed to remove bleeding source:" + bit,ErrorExSeverity.INFO);
 		}
 		
+		int inverse_bit_mask = ~bit;
+		m_Player.SetBleedingBits(m_Player.GetBleedingBits() & inverse_bit_mask);
 		m_Item = null;//reset, so that next call, if induced by self-healing, will have no item
 		
 		return true;
@@ -82,7 +84,7 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	void RemoveMostSignificantBleedingSource()
 	{
 		int bit = GetMostSignificantBleedingSource();
-		if( bit != 0)
+		if (bit != 0)
 			RemoveBleedingSource(bit);
 	}
 	
@@ -100,16 +102,16 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 		int highest_flow_bit;
 		int bit_offset;
 		
-		for(int i = 0; i < BIT_INT_SIZE; i++)
+		for (int i = 0; i < BIT_INT_SIZE; ++i)
 		{
 			int bit = 1 << bit_offset;
 			
-			if( (bit & bleeding_sources_bits) != 0 )
+			if ((bit & bleeding_sources_bits) != 0)
 			{
 				BleedingSourceZone meta = GetBleedingSourceMeta(bit);
-				if(meta)
+				if (meta)
 				{
-					if( meta.GetFlowModifier() > highest_flow )
+					if (meta.GetFlowModifier() > highest_flow)
 					{
 						highest_flow = meta.GetFlowModifier();
 						highest_flow_bit = bit;
@@ -125,20 +127,25 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	void OnTick(float delta_time)
 	{
 		m_Tick += delta_time;
-		if( m_Tick > TICK_INTERVAL_SEC )
+		
+		if (m_ProcessSourcesRemoval)
 		{
-			while( m_DeleteList.Count() > 0 )
+			while (m_DeleteList.Count() > 0)
 			{
 				RemoveBleedingSource(m_DeleteList.Get(0));
 				m_DeleteList.Remove(0);
 			} 
-			
-			float blood_scale = Math.InverseLerp(PlayerConstants.BLOOD_THRESHOLD_FATAL, PlayerConstants.BLEEDING_LOW_PRESSURE_BLOOD, m_Player.GetHealth( "GlobalHealth", "Blood" ));
-			blood_scale = Math.Clamp( blood_scale, PlayerConstants.BLEEDING_LOW_PRESSURE_MIN_MOD, 1 );
+			m_ProcessSourcesRemoval = false;
+		}
+		
+		if (m_Tick > TICK_INTERVAL_SEC)
+		{
+			float blood_scale = Math.InverseLerp(PlayerConstants.BLOOD_THRESHOLD_FATAL, PlayerConstants.BLEEDING_LOW_PRESSURE_BLOOD, m_Player.GetHealth("GlobalHealth", "Blood"));
+			blood_scale = Math.Clamp(blood_scale, PlayerConstants.BLEEDING_LOW_PRESSURE_MIN_MOD, 1);
 
-			for(int i = 0; i < m_BleedingSources.Count(); i++)
+			for (int i = 0; i < m_BleedingSources.Count(); ++i)
 			{
-				m_BleedingSources.GetElement(i).OnUpdateServer( m_Tick, blood_scale, m_DisableBloodLoss );
+				m_BleedingSources.GetElement(i).OnUpdateServer(m_Tick, blood_scale, m_DisableBloodLoss);
 			}
 			m_Tick = 0;
 		}
@@ -146,10 +153,10 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	
 	void ActivateAllBS()
 	{
-		for(int i = 0; i < m_BleedingSourceZone.Count(); i++)
+		for (int i = 0; i < m_BleedingSourceZone.Count(); ++i)
 		{
 			int bit = m_BleedingSourceZone.GetElement(i).GetBit();
-			if( CanAddBleedingSource(bit) )
+			if (CanAddBleedingSource(bit))
 			{
 				AddBleedingSource(bit);
 			}
@@ -187,7 +194,7 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 				createBleedingSource = true;
 			}
 		}
-		else if (damage > (dmg_max * (1 - bleed_threshold)) )
+		else if (damage > (dmg_max * (1 - bleed_threshold)))
 		{
 			createBleedingSource = true;
 		}
@@ -208,11 +215,11 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 	{
 		RemoveAllSources();
 		
-		if(source >= m_BleedingSourceZone.Count() || !m_BleedingSourceZone.GetElement(source)) return;
+		if (source >= m_BleedingSourceZone.Count() || !m_BleedingSourceZone.GetElement(source)) return;
 		
 		int bit = m_BleedingSourceZone.GetElement(source).GetBit();
 		
-		if( bit && CanAddBleedingSource(bit) )
+		if (bit && CanAddBleedingSource(bit))
 		{
 			AddBleedingSource(bit);
 		}
@@ -223,17 +230,17 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 		m_DisableBloodLoss = status;
 	}
 	
-	void OnStoreSave( ParamsWriteContext ctx )
+	void OnStoreSave(ParamsWriteContext ctx)
 	{
 		//int count = m_BleedingSources.Count();
 		int active_bits = m_Player.GetBleedingBits();
 		ctx.Write(active_bits);
 		
 		int bit_offset = 0;
-		for(int i = 0; i < BIT_INT_SIZE; i++)
+		for (int i = 0; i < BIT_INT_SIZE; ++i)
 		{
 			int bit = 1 << bit_offset;
-			if( (bit & active_bits) != 0 )
+			if ((bit & active_bits) != 0)
 			{
 				int active_time = GetBleedingSourceActiveTime(bit);
 				eBleedingSourceType type = GetBleedingSourceType(bit);
@@ -245,23 +252,23 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 		}
 	}
 
-	bool OnStoreLoad( ParamsReadContext ctx, int version )
+	bool OnStoreLoad(ParamsReadContext ctx, int version)
 	{
 		int active_bits;
-		if(!ctx.Read(active_bits))
+		if (!ctx.Read(active_bits))
 		{
 			return false;
 		}
 	
 		int bit_offset = 0;
-		for(int i = 0; i < BIT_INT_SIZE; i++)
+		for (int i = 0; i < BIT_INT_SIZE; ++i)
 		{
 			int bit = 1 << bit_offset;
-			if( (bit & active_bits) != 0 && CanAddBleedingSource(bit))
+			if ((bit & active_bits) != 0 && CanAddBleedingSource(bit))
 			{
 				AddBleedingSource(bit);
 				int active_time = 0;
-				if(!ctx.Read(active_time))
+				if (!ctx.Read(active_time))
 				{
 					return false;
 				}
@@ -269,7 +276,7 @@ class BleedingSourcesManagerServer extends BleedingSourcesManagerBase
 				{
 					SetBleedingSourceActiveTime(bit,active_time);
 				}
-				if( version >= 121 )//type was added in this version
+				if (version >= 121)//type was added in this version
 				{
 					eBleedingSourceType type = eBleedingSourceType.NORMAL;
 					if (!ctx.Read(type))

+ 20 - 24
Scripts/4_world/classes/contaminatedarea/contaminatedarea.c

@@ -3,12 +3,14 @@ class ContaminatedArea_Base : EffectArea
 	override void OnPlayerEnterServer(PlayerBase player, EffectTrigger trigger)
 	{
 		super.OnPlayerEnterServer(player, trigger);
+
 		player.IncreaseContaminatedAreaCount();
 	}
 	
 	override void OnPlayerExitServer(PlayerBase player, EffectTrigger trigger)
 	{
 		super.OnPlayerExitServer(player, trigger);
+
 		player.DecreaseContaminatedAreaCount();
 	}
 	
@@ -34,43 +36,30 @@ class ContaminatedArea_Static : ContaminatedArea_Base
 	// 				INITIAL SETUP
 	// ----------------------------------------------
 	
-	override void SetupZoneData(  EffectAreaParams params )
-	{
-		super.SetupZoneData( params );
-	}
-	
 	override void EEInit()
 	{
-		// We make sure we have the particle array
-		if ( !m_ToxicClouds )
-			m_ToxicClouds = new array<Particle>;
+		if (!m_ToxicClouds)
+			m_ToxicClouds = new array<Particle>();
 		
 		SetSynchDirty();
 		
-		#ifdef DEVELOPER
-		// Debugs when placing entity by hand using internal tools
-		if ( GetGame().IsServer() && !GetGame().IsMultiplayer() )
-		{
-			Debug.Log("YOU CAN IGNORE THE FOLLOWING DUMP");
-			InitZone();
-			Debug.Log("YOU CAN USE FOLLOWING DATA PROPERLY");
-		}
-		#endif
-		
-		if ( GetGame().IsClient() && GetGame().IsMultiplayer() )
-			InitZone();
-		
 		super.EEInit();
 	}
 	
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		InitZone();		
+	}
 	
 	override void InitZoneServer()
 	{
 		super.InitZoneServer();
 		
 		// We create the trigger on server
-		if ( m_TriggerType != "" )
-			CreateTrigger( m_Position, m_Radius );
+		if (m_TriggerType != "")
+			CreateTrigger(m_PositionTrigger, m_Radius);
 	}
 	
 	override void InitZoneClient()
@@ -78,6 +67,13 @@ class ContaminatedArea_Static : ContaminatedArea_Base
 		super.InitZoneClient();
 		
 		// We spawn VFX on client
-		PlaceParticles( GetWorldPosition(), m_Radius, m_InnerRings, m_InnerSpacing, m_OuterRingToggle, m_OuterSpacing, m_OuterRingOffset, m_ParticleID );
+		FillWithParticles(m_Position, m_Radius, m_OuterRingOffset, m_InnerSpacing, m_ParticleID);
+	}
+	
+	override void OnDebugSpawn()
+	{
+		super.OnDebugSpawn();
+		
+		InitZone();
 	}
 }

+ 70 - 221
Scripts/4_world/classes/contaminatedarea/contaminatedarea_dynamic.c

@@ -1,14 +1,5 @@
-enum eAreaDecayStage
-{
-	INIT 			= 1, // The dynamic area is initializing
-	START 			= 2, // The dynamic area is starting
-	LIVE 			= 3, // The dynamic area is live
-	DECAY_START 	= 4, // The dynamic area decay has started
-	DECAY_END 		= 5, // The dynamic area will soon be deleted
-}
-
 // The parameters for the explosion light when creating dynamic area
-class ShellLight extends PointLightBase
+class ShellLight : PointLightBase
 {
 	protected float m_DefaultBrightness = 10;
 	protected float m_DefaultRadius = 100;
@@ -28,14 +19,13 @@ class ShellLight extends PointLightBase
 }
 
 // The dynamic Contaminated area, using it's own default settings
-class ContaminatedArea_Dynamic : ContaminatedArea_Base
+class ContaminatedArea_Dynamic : ContaminatedArea_DynamicBase
 {
+	protected vector 		m_OffsetPos; 				// This will be the position at which we spawn all future airborne FX
 	protected ref Timer 	m_StartupTimer;
 	protected ref Timer 	m_FXTimer;
 	protected FlareLight 	m_FlareLight;
 	protected ShellLight	m_ShellLight; 				// Light used upon ariborne shell detonation
-	protected vector 		m_OffsetPos; 				// This will be the position at which we spawn all future airborne FX
-	protected int 			m_DecayState 				= eAreaDecayStage.INIT; // The current state in which the area is
 	
 	// Constants used for startup events
 	const int 				AIRBORNE_EXPLOSION_DELAY 	= 20;
@@ -43,100 +33,76 @@ class ContaminatedArea_Dynamic : ContaminatedArea_Base
 	const float 			AIRBORNE_FX_OFFSET 			= 50;
 	const float 			ARTILLERY_SHELL_SPEED		= 100; // Units per second
 	
-	// Constants used for dissapearing events
-	const float				DECAY_START_PART_SIZE 		= 32;
-	const int				DECAY_START_PART_BIRTH_RATE = 1;
-	const float				DECAY_END_PART_SIZE 		= 17;
-	const int				DECAY_END_PART_BIRTH_RATE 	= 1;
-	const float 			START_DECAY_LIFETIME		= 900;
-	const float 			FINISH_DECAY_LIFETIME		= 300;
-	
 	// Item Spawning upon area creation, the 4 arrays bellow have to have the same amount of elements
 	const ref array<string> 	SPAWN_ITEM_TYPE 		= {"Grenade_ChemGas"};//item classnames
 	const ref array<int>		SPAWN_ITEM_COUNT 		= {Math.RandomIntInclusive(2,5)};//how many of each type
 	const ref array<float> 		SPAWN_ITEM_RAD_MIN 		= {5};//min distance the item will be spawned from the area position(epicenter)
 	const ref array<float> 		SPAWN_ITEM_RAD_MAX 		= {15};//max distance the item will be spawned from the area position(epicenter)
 	
-	
-	void ContaminatedArea_Dynamic()
-	{
-		RegisterNetSyncVariableInt("m_DecayState");
-	}
-	
 	override void EEOnCECreate()
 	{
 		// We get the PPE index for future usage and synchronization ( we must do it here for dynamic as it is not read through file )
-		if ( GetGame().IsServer() )
-			m_PPERequesterIdx = GetRequesterIndex(m_PPERequesterType);
-		
-		SetSynchDirty();
+		m_PPERequesterIdx = GetRequesterIndex(m_PPERequesterType);
 		
 		// If this is the first initialization, we delay it in order to play cool effects
-		if ( m_DecayState == eAreaDecayStage.INIT )
+		if (m_DecayState == eAreaDecayStage.INIT)
 		{
 			vector areaPos = GetPosition();
 			m_OffsetPos = areaPos;
 			m_OffsetPos[1] = m_OffsetPos[1] + AIRBORNE_FX_OFFSET;
+			vector closestPoint = areaPos;
 			
 			// play artillery sound, sent to be played for everyone on server
 			array<vector> artilleryPoints = GetGame().GetMission().GetWorldData().GetArtyFiringPos();
-			vector closestPoint = areaPos;
-			int dist = 0;
-			int temp;
 			int index = 0;
-			for ( int i = 0; i < artilleryPoints.Count(); i++ )
+			foreach (int i, vector artilleryPoint : artilleryPoints)
 			{
-				temp = vector.DistanceSq( artilleryPoints.Get( i ), areaPos );
-				if ( temp < dist || dist == 0 )
+				int dist = 0;
+				int temp = vector.DistanceSq(artilleryPoint, areaPos);
+				if (temp < dist || dist == 0)
 				{
 					dist = temp;
 					index = i;
 				}
 			}
 			
-			closestPoint = artilleryPoints.Get( index );
+			closestPoint = artilleryPoints.Get(index);
 			
 			// We calculate the delay depending on distance from firing position to simulate shell travel time
-			float delay = vector.Distance( closestPoint, areaPos );
+			float delay = vector.Distance(closestPoint, areaPos);
 			delay = delay / ARTILLERY_SHELL_SPEED;
 			delay += AIRBORNE_EXPLOSION_DELAY; // We add the base, minimum time ( no area can start before this delay )
 			
-			Param3<vector, vector, float> pos; // The value to be sent through RPC
-			array<ref Param> params; // The RPC params
-			
-			// We prepare to send the message
-			pos = new Param3<vector, vector, float>( closestPoint, areaPos, delay );
-			params = new array<ref Param>;
-			
+			Param3<vector, vector, float> pos = new Param3<vector, vector, float>(closestPoint, areaPos, delay);
+			array<ref Param> params = new array<ref Param>();
 			// We send the message with this set of coords
-			params.Insert( pos );
-			GetGame().RPC( null, ERPCs.RPC_SOUND_ARTILLERY_SINGLE, params, true );
+			params.Insert(pos);
+			GetGame().RPC(null, ERPCs.RPC_SOUND_ARTILLERY_SINGLE, params, true);
 			
-			m_FXTimer = new Timer( CALL_CATEGORY_GAMEPLAY );
-			m_FXTimer.Run( delay, this, "PlayFX" );	
+			m_FXTimer = new Timer(CALL_CATEGORY_GAMEPLAY);
+			m_FXTimer.Run(delay, this, "PlayFX");	
 			
 			delay += AREA_SETUP_DELAY; // We have an additional delay between shell detonation and finalization of area creation
 			// setup zone
-			m_StartupTimer = new Timer( CALL_CATEGORY_GAMEPLAY );
-			m_StartupTimer.Run( delay, this, "InitZone" );
+			m_StartupTimer = new Timer(CALL_CATEGORY_GAMEPLAY);
+			m_StartupTimer.Run(delay, this, "InitZone");
 		}
+		
+		SetSynchDirty();
 	}
 	
-	float GetRemainingTime()
-	{
-		return GetLifetime();
-	}
-	
-	float GetStartDecayLifetime()
-	{
-		return START_DECAY_LIFETIME;
-	}
-	
-	float GetFinishDecayLifetime()
+	override void OnVariablesSynchronized()
 	{
-		return FINISH_DECAY_LIFETIME;
+		super.OnVariablesSynchronized();
+
+		switch (m_DecayState)
+		{
+			case eAreaDecayStage.START:
+				PlayExplosionLight();
+			break;
+		}
 	}
-	
+
 	override void Tick()
 	{
 		if ( GetRemainingTime() < GetFinishDecayLifetime() )
@@ -152,97 +118,67 @@ class ContaminatedArea_Dynamic : ContaminatedArea_Base
 		
 	}
 	
-	// Set the new state of the Area
-	void SetDecayState( int newState )
+	override void SetupZoneData(EffectAreaParams params)
 	{
-		if (m_DecayState != newState)
-		{
-			m_DecayState = newState;
+		params.m_ParamName			= string.Format("Dynamic area (%1)", m_Position.ToString());
+		params.m_ParamPartId 		= ParticleList.CONTAMINATED_AREA_GAS_BIGASS;
+		params.m_ParamAroundPartId 	= ParticleList.CONTAMINATED_AREA_GAS_AROUND;
+		params.m_ParamTinyPartId 	= ParticleList.CONTAMINATED_AREA_GAS_TINY;
+		params.m_ParamPosHeight 	= 7;
+		params.m_ParamNegHeight 	= 10;
+		params.m_ParamRadius 		= 120;
+		params.m_ParamInnerRings 	= 1;
+		params.m_ParamInnerSpace 	= 40;
+		params.m_ParamOuterSpace 	= 30;
+		params.m_ParamOuterOffset 	= 0;
+		params.m_ParamTriggerType 	= "ContaminatedTrigger_Dynamic";
 		
-			// We update the trigger state values as we also want to update player bound effects
-			if ( m_Trigger )
-				ContaminatedTrigger_Dynamic.Cast( m_Trigger ).SetAreaState( m_DecayState );
-			
-			SetSynchDirty();
-		}
+		super.SetupZoneData(params);
 	}
 	
-	override void EEInit()
+	override void DeferredInit()
 	{
+		super.DeferredInit();
+
 		// We make sure we have the particle array
-		if ( !m_ToxicClouds )
-			m_ToxicClouds = new array<Particle>;
-		
-		// We set the values for dynamic area as these are not set through JSON and are standardized
-		m_Name = "Default Dynamic";
-		m_Radius = 120;
-		m_PositiveHeight = 7;
-		m_NegativeHeight = 10;
-		m_InnerRings = 1;
-		m_InnerSpacing = 40;
-		m_OuterSpacing = 30;
-		m_OuterRingOffset = 0;
-		m_Type = eZoneType.DYNAMIC;
-		m_TriggerType = "ContaminatedTrigger_Dynamic";
-		
-		SetSynchDirty();
-		
-		#ifdef DEVELOPER
-		// Debugs when placing entity by hand using internal tools
-		/*if ( GetGame().IsServer() && !GetGame().IsMultiplayer() )
-		{
-			Debug.Log("YOU CAN IGNORE THE FOLLOWING DUMP");
-			InitZone();
-			Debug.Log("YOU CAN USE FOLLOWING DATA PROPERLY");
-		}*/
-		#endif
+		if (!m_ToxicClouds)
+			m_ToxicClouds = new array<Particle>();
 		
-		m_OffsetPos = GetPosition();
+		m_Position = GetPosition();
+		m_OffsetPos = m_Position;
 		m_OffsetPos[1] = m_OffsetPos[1] + AIRBORNE_FX_OFFSET;
 		
+		SetupZoneData(new EffectAreaParams);
+		
 		// If a player arrives slightly later during the creation process we check if playing the flare FX is relevant
-		if ( m_DecayState == eAreaDecayStage.INIT )
+		if (m_DecayState == eAreaDecayStage.INIT)
 			PlayFlareVFX();
 		
 		if ( m_DecayState == eAreaDecayStage.LIVE )
 			InitZone(); // If it has already been created, we simply do the normal setup, no cool effects, force the LIVE state
-		else if ( GetGame().IsClient() && m_DecayState > eAreaDecayStage.LIVE )
-			InitZoneClient(); // Same as before but without state forcing
-		
-		super.EEInit();
-	}
-	
-	// We spawn particles and setup trigger
-	override void InitZone()
-	{
-		m_DecayState = eAreaDecayStage.LIVE;
-		SetSynchDirty();
 		
-		super.InitZone();
+		super.DeferredInit();
 	}
 	
 	override void InitZoneServer()
 	{
-		super.InitZoneServer();
-		
 		SpawnItems();
-		// We create the trigger on server
-		if ( m_TriggerType != "" )
-			CreateTrigger( m_Position, m_Radius );
+
+		super.InitZoneServer();
 	}
 	
 	void SpawnItems()
 	{
 		//Print("---------============ Spawning items at pos:"+m_Position);
-		foreach (int j, string type:SPAWN_ITEM_TYPE)
+		foreach (int j, string type : SPAWN_ITEM_TYPE)
 		{
 			//Print("----------------------------------");
-			for (int i = 0; i < SPAWN_ITEM_COUNT[j]; i++)
+			for (int i = 0; i < SPAWN_ITEM_COUNT[j]; ++i)
 			{
 				vector randomDir2d = vector.RandomDir2D();
 				float randomDist = Math.RandomFloatInclusive(SPAWN_ITEM_RAD_MIN[j],SPAWN_ITEM_RAD_MAX[j]);
 				vector spawnPos = m_Position + (randomDir2d * randomDist);
-				InventoryLocation il = new InventoryLocation;
+				InventoryLocation il = new InventoryLocation();
 				vector mat[4];
 				Math3D.MatrixIdentity4(mat);
 				mat[3] = spawnPos;
@@ -253,42 +189,9 @@ class ContaminatedArea_Dynamic : ContaminatedArea_Base
 		}
 	}
 	
-	override void InitZoneClient()
+	override void CreateTrigger(vector pos, int radius)
 	{
-		super.InitZoneClient();
-		
-		if ( !m_ToxicClouds )
-			m_ToxicClouds = new array<Particle>;
-		
-		// We spawn VFX on client
-		PlaceParticles( GetWorldPosition(), m_Radius, m_InnerRings, m_InnerSpacing, m_OuterRingToggle, m_OuterSpacing, m_OuterRingOffset, m_ParticleID );		
-	}
-	
-	override void OnParticleAllocation(ParticleManager pm, array<ParticleSource> particles)
-	{
-		super.OnParticleAllocation(pm, particles);
-		
-		if ( m_DecayState > eAreaDecayStage.LIVE )
-		{
-			foreach ( ParticleSource p : particles )
-			{
-				if ( m_DecayState == eAreaDecayStage.DECAY_END )
-				{
-					p.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_END_PART_BIRTH_RATE );
-					p.SetParameter( 0, EmitorParam.SIZE, DECAY_END_PART_SIZE );
-				}
-				else
-				{
-					p.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_START_PART_BIRTH_RATE );
-					p.SetParameter( 0, EmitorParam.SIZE, DECAY_START_PART_SIZE );
-				}
-			}
-		}
-	}
-	
-	override void CreateTrigger( vector pos, int radius )
-	{
-		super.CreateTrigger( pos, radius );
+		super.CreateTrigger(pos, radius);
 		
 		// This handles the specific case of dynamic triggers as some additionnal parameters are present
 		ContaminatedTrigger_Dynamic dynaTrigger = ContaminatedTrigger_Dynamic.Cast( m_Trigger );
@@ -301,22 +204,18 @@ class ContaminatedArea_Dynamic : ContaminatedArea_Base
 	
 	void PlayFX()
 	{
-		if ( GetGame().IsServer() )
+		if (GetGame().IsServer())
 		{
-			Param1<vector> pos; // The value to be sent through RPC
-			array<ref Param> params; // The RPC params
-			
-			// We prepare to send the message
-			pos = new Param1<vector>( vector.Zero );
-			params = new array<ref Param>;
+			Param1<vector> pos = new Param1<vector>(vector.Zero); 	// The value to be sent through RPC
+			array<ref Param> params = new array<ref Param>(); 		// The RPC params
 			
 			// We send the message with this set of coords
 			pos.param1 = m_OffsetPos;
-			params.Insert( pos );
-			GetGame().RPC( null, ERPCs.RPC_SOUND_CONTAMINATION, params, true );
+			params.Insert(pos);
+			GetGame().RPC(null, ERPCs.RPC_SOUND_CONTAMINATION, params, true);
 			
 			// We go to the next stage
-			m_DecayState = eAreaDecayStage.START;
+			SetDecayState(eAreaDecayStage.START);
 			SetSynchDirty();
 		}
 	}
@@ -337,54 +236,4 @@ class ContaminatedArea_Dynamic : ContaminatedArea_Base
 			m_FlareLight = FlareLightContamination.Cast(ScriptedLightBase.CreateLight( FlareLightContamination, m_OffsetPos ));
 		}
 	}
-	
-	override void EEDelete( EntityAI parent )
-	{
-		super.EEDelete( parent );
-	}
-	
-	override void OnVariablesSynchronized()
-	{
-		super.OnVariablesSynchronized();
-		
-		if ( !m_ToxicClouds )
-			m_ToxicClouds = new array<Particle>;
-		
-		switch ( m_DecayState )
-		{
-			case eAreaDecayStage.START:
-				PlayExplosionLight();
-			break;
-			case eAreaDecayStage.LIVE:
-				InitZoneClient();
-			
-			break;
-			case eAreaDecayStage.DECAY_START:
-			{
-				// We go through all the particles bound to this area and update relevant parameters
-				//Debug.Log("We start decay");
-				foreach ( Particle p : m_ToxicClouds )
-				{
-					p.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_START_PART_BIRTH_RATE );
-					p.SetParameter( 0, EmitorParam.SIZE, DECAY_START_PART_SIZE );
-				}
-				
-				break;
-			}
-			case eAreaDecayStage.DECAY_END:
-			{
-				// We go through all the particles bound to this area and update relevant parameters
-				//Debug.Log("We finish decay");
-				foreach ( Particle prt : m_ToxicClouds )
-				{
-					prt.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_END_PART_BIRTH_RATE );
-					prt.SetParameter( 0, EmitorParam.SIZE, DECAY_END_PART_SIZE );
-				}
-				
-				break;
-			}
-			default:
-			break;
-		}
-	}
 }

+ 160 - 0
Scripts/4_world/classes/contaminatedarea/contaminatedarea_dynamicbase.c

@@ -0,0 +1,160 @@
+enum eAreaDecayStage
+{
+	INIT 			= 1, // The dynamic area is initializing
+	START 			= 2, // The dynamic area is starting
+	LIVE 			= 3, // The dynamic area is live
+	DECAY_START 	= 4, // The dynamic area decay has started
+	DECAY_END 		= 5, // The dynamic area will soon be deleted
+}
+
+class ContaminatedArea_DynamicBase : ContaminatedArea_Base
+{
+	protected int 			m_DecayState 				= eAreaDecayStage.INIT; // The current state in which the area is
+	
+	// Constants used for dissapearing events
+	const float				DECAY_START_PART_SIZE 		= 32;
+	const int				DECAY_START_PART_BIRTH_RATE = 1;
+	const float				DECAY_END_PART_SIZE 		= 17;
+	const int				DECAY_END_PART_BIRTH_RATE 	= 1;
+	const float 			START_DECAY_LIFETIME		= 900;
+	const float 			FINISH_DECAY_LIFETIME		= 300;
+
+	void ContaminatedArea_DynamicBase()
+	{
+		m_Type = eZoneType.DYNAMIC;
+
+		RegisterNetSyncVariableInt("m_DecayState");
+	}
+
+	
+	float GetRemainingTime()
+	{
+		return GetLifetime();
+	}
+	
+	float GetStartDecayLifetime()
+	{
+		return START_DECAY_LIFETIME;
+	}
+	
+	float GetFinishDecayLifetime()
+	{
+		return FINISH_DECAY_LIFETIME;
+	}
+	
+	// Set the new state of the Area
+	void SetDecayState(int newState)
+	{
+		if (m_DecayState != newState)
+		{
+			m_DecayState = newState;
+		
+			// We update the trigger state values as we also want to update player bound effects
+			if ( m_Trigger )
+				ContaminatedTrigger_Dynamic.Cast( m_Trigger ).SetAreaState( m_DecayState );
+			
+			SetSynchDirty();
+		}
+	}
+	
+	// We spawn particles and setup trigger
+	override void InitZone()
+	{		
+		SetDecayState(eAreaDecayStage.LIVE);
+		
+		super.InitZone();
+	}
+	
+	override void InitZoneClient()
+	{
+		super.InitZoneClient();
+		
+		// We spawn VFX on client
+		PlaceParticles(m_Position, m_Radius, m_InnerRings, m_InnerSpacing, m_OuterRingToggle, m_OuterSpacing, m_OuterRingOffset, m_ParticleID);		
+	}
+	
+	override void InitZoneServer()
+	{
+		super.InitZoneServer();
+		
+		// We create the trigger on server
+		if (m_TriggerType != "")
+			CreateTrigger(m_PositionTrigger, m_Radius);
+	}
+	
+	override void OnParticleAllocation(ParticleManager pm, array<ParticleSource> particles)
+	{
+		super.OnParticleAllocation(pm, particles);
+		
+		if (m_DecayState > eAreaDecayStage.LIVE)
+		{
+			foreach (ParticleSource p : particles)
+			{
+				if (m_DecayState == eAreaDecayStage.DECAY_END)
+				{
+					p.SetParameter(0, EmitorParam.BIRTH_RATE, DECAY_END_PART_BIRTH_RATE);
+					p.SetParameter(0, EmitorParam.SIZE, DECAY_END_PART_SIZE);
+				}
+				else
+				{
+					p.SetParameter(0, EmitorParam.BIRTH_RATE, DECAY_START_PART_BIRTH_RATE);
+					p.SetParameter(0, EmitorParam.SIZE, DECAY_START_PART_SIZE);
+				}
+			}
+		}
+	}
+	
+	override void CreateTrigger(vector pos, int radius)
+	{
+		super.CreateTrigger(pos, radius);
+		
+		// This handles the specific case of dynamic triggers as some additionnal parameters are present
+		ContaminatedTrigger_Dynamic dynaTrigger = ContaminatedTrigger_Dynamic.Cast( m_Trigger );
+		if (dynaTrigger)
+		{
+			dynaTrigger.SetLocalEffects( m_AroundParticleID, m_TinyParticleID, m_PPERequesterIdx );
+			dynaTrigger.SetAreaState( m_DecayState );
+		}
+	}
+	
+	override void OnVariablesSynchronized()
+	{
+		super.OnVariablesSynchronized();
+		
+		if (!m_ToxicClouds)
+			m_ToxicClouds = new array<Particle>();
+		
+		switch ( m_DecayState )
+		{
+			case eAreaDecayStage.LIVE:
+				InitZoneClient();
+				break;
+			case eAreaDecayStage.DECAY_START:
+			{
+				// We go through all the particles bound to this area and update relevant parameters
+				//Debug.Log("We start decay");
+				foreach ( Particle p : m_ToxicClouds )
+				{
+					p.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_START_PART_BIRTH_RATE );
+					p.SetParameter( 0, EmitorParam.SIZE, DECAY_START_PART_SIZE );
+				}
+				
+				break;
+			}
+			case eAreaDecayStage.DECAY_END:
+			{
+				// We go through all the particles bound to this area and update relevant parameters
+				//Debug.Log("We finish decay");
+				foreach ( Particle prt : m_ToxicClouds )
+				{
+					prt.SetParameter( 0, EmitorParam.BIRTH_RATE, DECAY_END_PART_BIRTH_RATE );
+					prt.SetParameter( 0, EmitorParam.SIZE, DECAY_END_PART_SIZE );
+				}
+				
+				break;
+			}
+			default:
+			break;
+		}
+	}
+}

+ 25 - 16
Scripts/4_world/classes/contaminatedarea/contaminatedarea_local.c

@@ -1,23 +1,20 @@
-class ContaminatedArea_Local : ContaminatedArea_Dynamic
+class ContaminatedArea_Local : ContaminatedArea_DynamicBase
 {
 	const float TICK_RATE 	= 1;
 	ref Timer 	m_Timer1 	= new Timer;
 	float 		m_Lifetime 	= 360;
-	// ----------------------------------------------
-	// 				INITIAL SETUP
-	// ----------------------------------------------
 	
 	void ContaminatedArea_Local()
 	{
 		m_EffectsPriority = -10;
 	}
 	
-	override void SetupZoneData(  EffectAreaParams params )
+	override void SetupZoneData(EffectAreaParams params)
 	{
 		params.m_ParamPartId 		= ParticleList.CONTAMINATED_AREA_GAS_AROUND;
 		params.m_ParamInnerRings 	= 0;
 		params.m_ParamPosHeight 	= 3;
-		params.m_ParamNegHeight 	= 5;
+		params.m_ParamNegHeight 	= 3;
 		params.m_ParamRadius 		= 10;
 		params.m_ParamOuterToggle 	= false;
 		params.m_ParamTriggerType 	= "ContaminatedTrigger_Local";
@@ -25,21 +22,37 @@ class ContaminatedArea_Local : ContaminatedArea_Dynamic
 		params.m_ParamAroundPartId 	= 0;
 		params.m_ParamTinyPartId 	= 0;
 		
-		super.SetupZoneData( params );
+		super.SetupZoneData(params);
+		
+		InitZone();
 	}
 	
 	override void EEInit()
 	{
 		if (GetGame().IsServer() || !GetGame().IsMultiplayer())
-		{
-			SetupZoneData(new EffectAreaParams);
 			m_Timer1.Run(TICK_RATE, this, "Tick", NULL, true);
-		}
+	}
+
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		if (!m_ToxicClouds)
+			m_ToxicClouds = new array<Particle>();
+
+		SetupZoneData(new EffectAreaParams);
 	}
 	
-	override void SpawnItems()
+	override void SpawnParticles(ParticlePropertiesArray props, vector centerPos, vector partPos, inout int count)
 	{
-		// override base funcionality as we don't want any items spawned here
+		partPos[1] = GetGame().SurfaceRoadY(partPos[0], partPos[2]);	// Snap particles to ground
+		
+		// We make sure that spawned particle is inside the trigger	
+		if (!Math.IsInRange(partPos[1], centerPos[1] - m_NegativeHeight, centerPos[1] + m_PositiveHeight))				
+			partPos[1] = centerPos[1];
+		
+		props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, GetGame().GetSurfaceOrientation( partPos[0], partPos[2] ), this));
+		++count;
 	}
 	
 	override float GetStartDecayLifetime()
@@ -61,10 +74,6 @@ class ContaminatedArea_Local : ContaminatedArea_Dynamic
 	{
 		m_Lifetime -= TICK_RATE;
 		if (m_Lifetime <= 0)
-		{
 			Delete();
-		}
 	}
-
-	
 }

+ 25 - 17
Scripts/4_world/classes/contaminatedarea/contaminatedarealoader.c

@@ -2,22 +2,22 @@
 class EffectAreaLoader
 {
 	private static string m_Path = "$mission:cfgeffectarea.json";
-	
+
 	static void CreateZones()
 	{
 		JsonDataContaminatedAreas effectAreaData;
 		
 		// We confirm the contaminated area configuration file exists in mission folder
-		if ( !FileExist( m_Path ) )
+		if (!FileExist(m_Path))
 		{
 			// We fallback to check in data and notify user file was not found in mission
 			PrintToRPT("[WARNING] :: [EffectAreaLoader CreateZones] :: No contaminated area file found in MISSION folder, your path is " + m_Path + " Attempting DATA folder"); // If the path is invalid, we warn the user
 			
 			m_Path = "";
-			GetGame().GetWorldName( m_Path );
-			m_Path = string.Format("dz/worlds/%1/ce/cfgeffectarea.json", m_Path );
+			GetGame().GetWorldName(m_Path);
+			m_Path = string.Format("dz/worlds/%1/ce/cfgeffectarea.json", m_Path);
 			
-			if ( !FileExist( m_Path ) )
+			if (!FileExist(m_Path))
 			{
 				PrintToRPT("[WARNING] :: [EffectAreaLoader CreateZones] :: No contaminated area file found in DATA folder, your path is " + m_Path); // If the path is invalid, we warn the user
 				return; // Nothing could be read, just end here
@@ -26,12 +26,12 @@ class EffectAreaLoader
 		
 		// We load the data from file, in case of failure we notify user
 		effectAreaData = EffectAreaLoader.GetData();
-		if ( effectAreaData )
+		if (effectAreaData)
 		{
 			// Now that we have extracted the data we go through every declared area
 			//Debug.Log("Contaminated area JSON contains : " + effectAreaData.Areas.Count());
 			
-			for ( int i = 0; i < effectAreaData.Areas.Count(); i++ )
+			for (int i = 0; i < effectAreaData.Areas.Count(); ++i)
 			{
 				EffectAreaParams params = new EffectAreaParams();
 				
@@ -43,9 +43,17 @@ class EffectAreaLoader
 				
 				// World level area data ( Trigger info, world particles, etc... )
 				vector pos = Vector( data.Pos[0], data.Pos[1], data.Pos[2] );
+				if (data.Radius <= 0)
+				{
+					ErrorEx(string.Format("Radius cannot be <= 0. Fix [%1] area definition in cfgeffectarea.json", params.m_ParamName));
+					continue;
+				}
+				
 				params.m_ParamRadius = data.Radius;
+				
 				params.m_ParamPosHeight = data.PosHeight;
 				params.m_ParamNegHeight = data.NegHeight;
+
 				params.m_ParamInnerRings = data.InnerRingCount;
 				params.m_ParamInnerSpace = data.InnerPartDist;
 				params.m_ParamOuterToggle = data.OuterRingToggle;
@@ -69,27 +77,27 @@ class EffectAreaLoader
 					params.m_ParamPartId = ParticleList.GetParticleID( particleName );
 				
 				if (aroundPartName != "")
-					params.m_ParamAroundPartId = ParticleList.GetParticleID( aroundPartName );
+					params.m_ParamAroundPartId = ParticleList.GetParticleID(aroundPartName);
 				
 				if (tinyPartName != "")
-					params.m_ParamTinyPartId = ParticleList.GetParticleID( tinyPartName );
+					params.m_ParamTinyPartId = ParticleList.GetParticleID(tinyPartName);
 				
 				params.m_ParamPpeRequesterType = ppeRequesterType;
 
 				EffectArea newZone; // Zones MUST inherit from EffectArea
-				
+
 				// We snap item position to ground before creating if specified Y is 0
-				if ( pos[1] == 0 )
+				if (pos[1] == 0)
 				{
-					pos[1] = GetGame().SurfaceRoadY( pos[0], pos[2] );
-					Class.CastTo( newZone, GetGame().CreateObjectEx( areaType, pos, ECE_PLACE_ON_SURFACE ) );
+					pos[1] = GetGame().SurfaceRoadY(pos[0], pos[2]);
+					newZone = EffectArea.Cast(GetGame().CreateObjectEx(areaType, pos, ECE_PLACE_ON_SURFACE));
 				}
 				else
-					Class.CastTo( newZone, GetGame().CreateObjectEx( areaType, pos, ECE_NONE ) );
+					newZone = EffectArea.Cast(GetGame().CreateObjectEx(areaType, pos, ECE_NONE));
 				
 				// We created a new zone, we feed in the data to finalize setup
-				if ( newZone )
-					newZone.SetupZoneData( params );
+				if (newZone)
+					newZone.SetupZoneData(params);
 				else
 					Error("[WARNING] :: [EffectAreaLoader CreateZones] :: Cast failed, are you sure your class ( 'Type:' ) inherits from EffectArea and that there are no Typos?");
 			}
@@ -97,7 +105,7 @@ class EffectAreaLoader
 		else
 			Error("[WARNING] :: [EffectAreaLoader CreateZones] :: Data could not be read, please check data and syntax"); // Most JSON related errors should be handled, but we have an extra check in case data could not be read
 	}
-	
+
 	static JsonDataContaminatedAreas GetData()
 	{
 		string errorMessage;

+ 216 - 101
Scripts/4_world/classes/contaminatedarea/effectarea.c

@@ -1,3 +1,5 @@
+// #define EFFECT_AREA_VISUAL_DEBUG
+
 // Mostly used for better readability
 enum eZoneType
 {
@@ -48,7 +50,8 @@ class EffectArea : House
 	// Area Data
 	string					m_Name = "Default setup"; 				// The user defined name of the area
 	int						m_Type = eZoneType.STATIC; 				// If the zone is static or dynamic
-	vector 					m_Position; 							// World position of Area
+	vector 					m_Position; 							// World position of area snapped to ground on creation (see: EffectAreaLoader)
+	vector 					m_PositionTrigger; 						// World position adjusted according to trigger pivot (pivot is cylinder center)
 	int 					m_EffectInterval;						// If non persisent effect: determines intervals between effect activation
 	int						m_EffectDuration;						// If non persisent effect: determines duration of effect
 	bool 					m_EffectModifier;						// Flag for modification of internal behavior of the effect
@@ -78,12 +81,13 @@ class EffectArea : House
 	int 					m_PPERequesterIdx = -1;
 	int						m_EffectsPriority;						// When multiple areas overlap, only the area with the highest priority will play its effects
 	
+	const int 				PARTICLES_MAX = 1000;					// Better safe than sorry
+	
 	// Other values and storage
 	string 					m_TriggerType = "ContaminatedTrigger"; 	// The trigger class used by this zone
 	EffectTrigger			m_Trigger; 								// The trigger used to determine if player is inside toxic area
 
 	ref array<Particle> 	m_ToxicClouds; 							// All static toxic clouds in ContaminatedArea
-	
 
 	// ----------------------------------------------
 	// 				INITIAL SETUP
@@ -160,19 +164,26 @@ class EffectArea : House
 		}
 		// We get the PPE index for future usage and synchronization
 		
-		
 		// DEVELOPER NOTE :
 		// If you cannot register a new requester, add your own indexation and lookup methods to get an index and synchronize it
 		// EXAMPLE : m_PPERequesterIdx = MyLookupMethod()
 		
+		#ifdef ENABLE_LOGGING
+		Debug.Log(">>>> SetupZoneData: Finished: " + m_Name);
+		#endif
+		
 		// We sync our data
 		SetSynchDirty();
-		
-		// Now that everything is ready, we finalize setup
-		InitZone();
 	}
 
-	void Tick() {};
+	void Tick()
+	{
+		#ifdef DIAG_DEVELOPER
+		#ifdef EFFECT_AREA_VISUAL_DEBUG
+		CleanupDebugShapes(m_DebugTargets);
+		#endif
+		#endif
+	}
 	
 		
 	// Through this we will evaluate the resize of particles
@@ -184,29 +195,25 @@ class EffectArea : House
 	
 	void InitZone()
 	{
-		//Debug.Log("------------------------------------------");
-		//Debug.Log( "We have created the zone : " + m_Name );
+	//	Debug.Log("------------------------------------------");
+	//	Debug.Log("InitZone: " + m_Name);
 		
-		m_Position = GetWorldPosition();
+		m_Position 				= GetPosition();
+		m_PositionTrigger		= m_Position;
+		m_PositionTrigger[1] 	= m_Position[1] + ((m_PositiveHeight - m_NegativeHeight) * 0.5); // Cylinder trigger pivot correction
 		
-		if ( !GetGame().IsDedicatedServer() )
-		{
+		if (!GetGame().IsDedicatedServer())
 			InitZoneClient();
-		}
 		
-		if ( GetGame().IsServer() )
-		{
+		if (GetGame().IsServer())
 			InitZoneServer();
-		}
 		
-		//Debug.Log("------------------------------------------");
+	//	Debug.Log("------------------------------------------");
 	}
 	
 	// The following methods are to be overriden to execute specifc logic
-	// Each method is executed where it says it will so no need to check for server or client ;) 
-	void InitZoneServer() {};
-	
-	void InitZoneClient() {};
+	void InitZoneServer();
+	void InitZoneClient();
 	
 	// ----------------------------------------------
 	// 				INTERACTION SETUP
@@ -238,70 +245,44 @@ class EffectArea : House
 	// Used to position all particles procedurally
 	void PlaceParticles( vector pos, float radius, int nbRings, int innerSpacing, bool outerToggle, int outerSpacing, int outerOffset, int partId )
 	{
-#ifdef NO_GUI
+	//	Debug.Log("PlaceParticles: " + pos);
+		
+	#ifdef NO_GUI
 		return; // do not place any particles if there is no GUI
-#endif
+	#endif	
 		if (partId == 0)
 		{
 			Error("[WARNING] :: [EffectArea PlaceParticles] :: no particle defined, skipping area particle generation" );
 			return;
 		}
-		// Determine if we snap first layer to ground
-		bool snapFirstLayer = true; 
-		if ( m_Type == eZoneType.STATIC && pos[1] != GetGame().SurfaceRoadY( pos[0], pos[2] ) )
-			snapFirstLayer = false;
-		
-		// BEGINNING OF SAFETY NET
-		// We want to prevent divisions by 0
 		if ( radius == 0 )
 		{
-			// In specific case of radius, we log an error and return as it makes no sense
 			Error("[WARNING] :: [EffectArea PlaceParticles] :: Radius of contaminated zone is set to 0, this should not happen");
 			return;
 		}
-		
 		if ( outerToggle && radius == outerOffset )
 		{
 			Error("[WARNING] :: [EffectArea PlaceParticles] :: Your outerOffset is EQUAL to your Radius, this will result in division by 0");
 			return;
 		}
 		
-		// Inner spacing of 0 would cause infinite loops as no increment would happen
-		if ( innerSpacing == 0 )
-			innerSpacing = 1;
-		
-		// END OF SAFETY NET
-		
-		int partCounter = 0; // Used for debugging, allows one to know how many emitters are spawned in zone
-		int numberOfEmitters = 1; // We always have the central emitter
-		
-		//Debug.Log("We have : " + nbRings + " rings");
-		//Debug.Log("We have : " + m_VerticalLayers + " layers");
-		
-		float angle = 0; // Used in for loop to know where we are in terms of angle spacing ( RADIANS )
+		int partCount = 0;	// Number of spawned emitters
 		
 		ParticlePropertiesArray props = new ParticlePropertiesArray();
 		
-		// We also populate vertically, layer 0 will be snapped to ground, subsequent layers will see particles floating and relevant m_VerticalOffset
-		for ( int k = 0; k <= m_VerticalLayers; k++ )
+		// Inner spacing of 0 would cause infinite loops as no increment would happen
+		if (innerSpacing == 0)
+			innerSpacing = 1;
+			
+		// For each concentric ring, we place a particle emitter at a set offset
+		for ( int i = 0; i <= nbRings + outerToggle; ++i )
 		{
-			vector partPos = pos;
-			// We prevent division by 0
-			// We don't want to tamper with ground layer vertical positioning
-			if ( k != 0 )
+			if (i == 0)						// Skipping 0, we want to start by placing a particle at center of area
 			{
-				partPos[1] = partPos[1] + ( m_VerticalOffset * k );
-			}
-			
-			// We will want to start by placing a particle at center of area
-			props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, vector.Zero, this));
-			partCounter++;
-			
-			// For each concentric ring, we place a particle emitter at a set offset
-			for ( int i = 1; i <= nbRings + outerToggle; i++ )
+				SpawnParticles(props, pos, pos, partCount);
+			}	
+			else
 			{
-				//Debug.Log("We are on iteration I : " + i );
-				
 				// We prepare the variables to use later in calculation
 				float angleIncrement; 		// The value added to the offset angle to place following particle
 				float ab; 					// Length of a side of triangle used to calculate particle positionning
@@ -329,48 +310,130 @@ class EffectArea : House
 					
 					//Debug.Log("Radius of inner circle " + i + " is : " + ab);
 				}
-				
+
 				for ( int j = 0; j <= ( Math.PI2 / angleIncrement ); j++ )
 				{
 					// Determine position of particle emitter
 					// Use offset of current ring for vector length
-					// Use accumulated angle for vector direction
-					
-					float sinAngle = Math.Sin( angle );
-					float cosAngle = Math.Cos( angle );
-				
-					partPos = vector.RotateAroundZero( temp, vector.Up, cosAngle, sinAngle );
+
+					float sinAngle 	= Math.Sin(angleIncrement * j);
+					float cosAngle 	= Math.Cos(angleIncrement * j);
+
+					vector partPos = vector.RotateAroundZero( temp, vector.Up, cosAngle, sinAngle );
 					partPos += pos;
-					
-					// We snap first layer to ground if specified
-					if ( k == 0 && snapFirstLayer == true )
-						partPos[1] = GetGame().SurfaceY( partPos[0], partPos[2] );
-					else if ( k == 0 && snapFirstLayer == false )
-						partPos[1] = partPos[1] - m_NegativeHeight;
-					
-					// We check the particle is indeed in the trigger to make it consistent
-					if ( partPos[1] <= pos[1] + m_PositiveHeight && partPos[1] >= pos[1] - m_NegativeHeight )
-					{
-						// Place emitter at vector end ( coord )
-						props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, GetGame().GetSurfaceOrientation( partPos[0], partPos[2] ), this));
-						
-						++partCounter;
-					}
 
-					// Increase accumulated angle
-					angle += angleIncrement;
+					SpawnParticles(props, pos, partPos, partCount);
 				}
+			}			
+		}
+		
+		InsertParticles(props, partCount, partId);
+	}
+	
+	// Fill the radius with particle emitters using the Circle packing in a circle method
+	void FillWithParticles(vector pos, float areaRadius, float outwardsBleed, float partSize, int partId)
+	{
+	//	Debug.Log("FillWithParticles: " + pos);
+		
+	#ifdef NO_GUI
+		return; // do not place any particles if there is no GUI
+	#endif
+		if (partId == 0)
+			return;
+ 
+		if (partSize <= 0)
+			partSize = 1;
+		
+		int 	partCount 	= 0;								// Number of spawned emitters
+		int 	ringCount	= 0;								// Number of area rings
+		float	ringDist 	= 0;								// Distance between rings
+
+		float 	radiusMax	= areaRadius + outwardsBleed; 		// Visual radius of the area
+		float	radiusPart	= partSize / 2;						// Particle radius
 				
-				angle = 0; // We reset our accumulated angle for the next ring
+		bool 	centerPart	= true;								// Spawn central particle?
+		
+		ParticlePropertiesArray props = new ParticlePropertiesArray();	
+
+		// Debug.Log("Area radius: " + radiusMax + "m, Particle radius: " + radiusPart + "m");
+		
+		if (radiusMax > radiusPart * 1.5)						// Area has to be larger than particle, plus some margin
+		{
+			if (radiusMax < radiusPart * 2.5)					// Area fits one ring of particles, but no center particle (minus some overlap margin)
+			{
+				ringDist	= radiusMax - radiusPart;			// Snap the particles to outer edge
+				ringCount 	= 1;
+				centerPart 	= false;
 			}
+			else 												// Area fits all
+			{
+				radiusMax  -= radiusPart;						// Snap the particles to outer edge
+				ringCount 	= Math.Ceil(radiusMax / partSize);	// Get number of inner rings
+				ringDist 	= radiusMax / ringCount;			// Adjust ring distance after rounding
+			}	
 		}
+							
+		// Debug.Log("We have : " + ringCount + " rings, " + ringDist + "m apart, center: " + centerPart);
+		// Debug.Log("We have : " + m_VerticalLayers + " layers, " + m_VerticalOffset + "m apart");
+		
+		// For each concentric ring, we place a particle emitter at a set offset
+		for (int ring = 0; ring <= ringCount; ++ring)
+		{
+			if (ring == 0 && centerPart)						// We start by placing particle at center of area
+			{
+				SpawnParticles(props, pos, pos, partCount);
+			}	
+			else if (ring > 0)
+			{
+				float ringRadius = ringDist * ring;
+				float circumference = 2 * Math.PI2 * ringRadius;
+				
+				int count = Math.Floor(circumference / partSize);				// Get number of particles on ring (roughly)
+				float angleInc = Math.PI2 / count;								// Get angle between particles on ring
+			
+				for (int i = 0; i < count; ++i)		// Insert particles around the ring
+				{
+					vector partPos = pos;
+					float x = ringRadius * Math.Sin(angleInc * i);
+					float z = ringRadius * Math.Cos(angleInc * i);
+					
+					partPos[0] = partPos[0] + x;
+					partPos[2] = partPos[2] + z;
+					
+					SpawnParticles(props, pos, partPos, partCount);
+				}				
+			}
+		}
+		
+		InsertParticles(props, partCount, partId);
+ 	}
+	
+	protected void SpawnParticles(ParticlePropertiesArray props, vector centerPos, vector partPos, inout int count)
+	{
+		partPos[1] = GetGame().SurfaceY(partPos[0], partPos[2]);	// Snap particles to ground
 		
-		m_ToxicClouds.Reserve(partCounter);
+		// We also populate vertically, layer 0 will be snapped to ground, subsequent layers will see particles floating by m_VerticalOffset
+		for (int layer = 0; layer <= m_VerticalLayers; ++layer)
+		{
+			partPos[1] = partPos[1] + (m_VerticalOffset * layer);
+		
+			// We check that spawned particle is inside the trigger	
+			if (count < PARTICLES_MAX && Math.IsInRange(partPos[1], centerPos[1] - m_NegativeHeight, centerPos[1] + m_PositiveHeight))
+			{
+				props.Insert(ParticleProperties(partPos, ParticlePropertiesFlags.PLAY_ON_CREATION, null, GetGame().GetSurfaceOrientation( partPos[0], partPos[2] ), this));
+				++count;
+			}
+		}
+	}	
+	
+	private void InsertParticles(ParticlePropertiesArray props, int count, int partId)
+	{
+		m_ToxicClouds.Reserve(count);
 		
 		ParticleManager gPM = ParticleManager.GetInstance();
 		
-		array<ParticleSource> createdParticles = gPM.CreateParticlesByIdArr(partId, props, partCounter);
-		if (createdParticles.Count() != partCounter)
+		array<ParticleSource> createdParticles = gPM.CreateParticlesByIdArr(partId, props, count);
+		if (createdParticles.Count() != count)
 		{
 			if (gPM.IsFinishedAllocating())
 			{
@@ -387,15 +450,17 @@ class EffectArea : House
 			OnParticleAllocation(gPM, createdParticles);
 		}
 		
-		//Debug.Log("Emitter count : " + partCounter );
-	}
-	
+		// Debug.Log("Emitter count: " + count);
+	}	
+		
 	void OnParticleAllocation(ParticleManager pm, array<ParticleSource> particles)
 	{
 		foreach (ParticleSource p : particles)
 		{
 			if (p.GetOwner() == this) // Safety, since it can be unrelated particles when done through event
+			{
 				m_ToxicClouds.Insert(p);
+			}
 		}
 	}
 	
@@ -414,23 +479,59 @@ class EffectArea : House
 	// ----------------------------------------------
 	// 				TRIGGER SETUP
 	// ----------------------------------------------
-	
-	void CreateTrigger( vector pos, int radius )
+	void CreateTrigger(vector pos, int radius)
 	{
-		// The trigger pos is based on lwer end, but we want to stretch downwards
-		pos[1] = pos[1] - m_NegativeHeight;
+		#ifdef DIAG_DEVELOPER
+		#ifdef EFFECT_AREA_VISUAL_DEBUG 
+		Shape dbgShape;
+		CleanupDebugShapes(m_DebugTargets);
+		#endif
+		#endif
 		
 		// Create new trigger of specified type
-		if ( Class.CastTo( m_Trigger, GetGame().CreateObjectEx( m_TriggerType, pos, ECE_NONE ) ) )
+		if (Class.CastTo(m_Trigger, GetGame().CreateObjectEx(m_TriggerType, pos, ECE_NONE)))
 		{
 			// We finalize trigger dimension setup
-			m_Trigger.SetCollisionCylinder( radius, ( m_NegativeHeight + m_PositiveHeight ) );
+			float centerHeightCorrection = (m_PositiveHeight - m_NegativeHeight) * 0.5;
+			
+			m_Trigger.SetCollisionCylinderTwoWay(radius, -(m_NegativeHeight + centerHeightCorrection), (m_PositiveHeight - centerHeightCorrection));
+			m_Trigger.SetPosition(pos);
+			m_Trigger.Update();
+			
+			#ifdef DIAG_DEVELOPER
+			#ifdef EFFECT_AREA_VISUAL_DEBUG
+			/*
+			vector cubePos = pos;
+			cubePos[0] = cubePos[0] + radius;
+			cubePos[1] = cubePos[1] + (m_PositiveHeight - centerHeightCorrection);
+			cubePos[2] = cubePos[2] + radius;
+			m_DebugTargets.Insert(Debug.DrawCube(cubePos, 0.5, 0x1fff0000));
+			*/
+			
+			vector colliderPosDebug = pos;
+			//! upper limit
+			colliderPosDebug[1] = pos[1] + (m_PositiveHeight - centerHeightCorrection);
+			m_DebugTargets.Insert(Debug.DrawSphere(colliderPosDebug, 0.15, 0x1f0000ff, ShapeFlags.NOZWRITE));
+			//m_DebugTargets.Insert(Debug.DrawLine(cubePos, colliderPosDebug, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
+			//! center
+			m_DebugTargets.Insert(Debug.DrawSphere(pos, 0.15, 0x1fff0000, ShapeFlags.NOZWRITE));
+			//m_DebugTargets.Insert(Debug.DrawLine(cubePos, pos, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
+			
+			//! bottom limit
+			colliderPosDebug[1] = pos[1] - (m_NegativeHeight + centerHeightCorrection);
+			m_DebugTargets.Insert(Debug.DrawSphere(colliderPosDebug, 0.15, 0x1f00ff00, ShapeFlags.NOZWRITE));
+			//m_DebugTargets.Insert(Debug.DrawLine(cubePos, colliderPosDebug, 0x1fff0000, ShapeFlags.NOZWRITE)); // connector
+			
+			float triggerHeight = (m_PositiveHeight + m_NegativeHeight);
+			m_DebugTargets.Insert(Debug.DrawCylinder(pos, radius, triggerHeight, 0x1f0000ff, ShapeFlags.TRANSP|ShapeFlags.NOZWRITE));
+			#endif
+			#endif
 			
 			// If the trigger is lower in hierarchy and can see it's local effects customized, we pass the new parameters
-			if ( m_Trigger.IsInherited( EffectTrigger ) )
+			if ( m_Trigger.IsInherited(EffectTrigger))
 			{
 				//Debug.Log("We override area local effects");
-				EffectTrigger.Cast( m_Trigger ).SetLocalEffects( m_AroundParticleID, m_TinyParticleID, m_PPERequesterIdx );
+				EffectTrigger.Cast(m_Trigger).SetLocalEffects(m_AroundParticleID, m_TinyParticleID, m_PPERequesterIdx);
 			}
 			m_Trigger.Init(this, m_EffectsPriority);
 			//Debug.Log("We created the trigger at : " + m_Trigger.GetWorldPosition() );
@@ -468,4 +569,18 @@ class EffectArea : House
 	{
 		player.DecreaseEffectAreaCount();
 	}
+	
+	#ifdef DIAG_DEVELOPER
+	#ifdef EFFECT_AREA_VISUAL_DEBUG
+	protected ref array<Shape> m_DebugTargets = new array<Shape>();
+	
+	protected void CleanupDebugShapes(array<Shape> shapes)
+	{
+		foreach (Shape shape : shapes)
+			Debug.RemoveShape(shape);
+
+		shapes.Clear();
+	}
+	#endif
+	#endif
 }

+ 82 - 30
Scripts/4_world/classes/contaminatedarea/geyserarea.c

@@ -9,13 +9,25 @@ enum EGeyserState
 class GeyserArea : EffectArea
 {
 	protected const int 	UPDATE_RATE 			= 1000; // ms
-	protected const float 	PRE_ERUPTION_DURATION 	= 5; 	// length of the pre eruption phase in seconds
+	protected const float 	PRE_ERUPTION_DURATION 	= 10; 	// delay before the geysier activates (sec)
+	protected const float 	ERUPTION_TALL_DURATION 	= 3; 	// lenght of secondary eruption (sec)
+	protected const float 	ERUPTION_TALL_DELAY 	= 3; 	// min delay between secondary eruptions (sec)
+	
+	protected bool 			m_SecondaryActive;	
 	
 	protected int 			m_TimeElapsed;			// seconds
+	protected int 			m_TimeSecondaryElapsed;	// seconds
 	protected float 		m_RandomizedInterval;	// randomized interval to 80% - 120% of the set value
 	protected float 		m_RandomizedDuration;	// randomized duration to 80% - 120% of the set value
 	protected GeyserTrigger m_GeyserTrigger;
 	
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		InitZone();		
+	}
+	
 	override void EEDelete( EntityAI parent )
 	{		
 		if (GetGame().IsClient() && m_GeyserTrigger)
@@ -30,60 +42,100 @@ class GeyserArea : EffectArea
 				
 		if ( m_TriggerType != "" )
 		{
-			CreateTrigger( m_Position, m_Radius );
+			CreateTrigger(m_PositionTrigger, m_Radius);
 			m_GeyserTrigger = GeyserTrigger.Cast(m_Trigger);
 		}
 		
 		GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(TickState, UPDATE_RATE, true);
 		
-		m_RandomizedInterval = Math.RandomInt(m_EffectInterval * 0.8, m_EffectInterval * 1.2);
+		RandomizeIntervals();
 	}	
 		
 	void TickState()
 	{
 		m_TimeElapsed += UPDATE_RATE * 0.001;
+	
+		if (m_GeyserTrigger.CheckGeyserState(EGeyserState.DORMANT))
+		{
+			if (m_TimeElapsed > PRE_ERUPTION_DURATION)
+			{
+				#ifdef ENABLE_LOGGING
+				Debug.Log(m_Name + ": ERUPTION_SOON, interval: " + m_RandomizedInterval + " sec");
+				#endif
 				
-		if (m_GeyserTrigger.GetGeyserState() == EGeyserState.DORMANT)
+				m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTION_SOON);
+				
+				m_TimeElapsed = 0;
+			}
+		}
+		else if (m_GeyserTrigger.CheckGeyserState(EGeyserState.ERUPTION_SOON))
 		{
 			if (m_TimeElapsed > m_RandomizedInterval)
 			{
+				#ifdef ENABLE_LOGGING
+				Debug.Log(m_Name + ": ERUPTING_PRIMARY, interval: " + m_RandomizedDuration + " sec");
+				#endif
+				
+				m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTION_SOON);
+				m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTING_PRIMARY);
+				
+				KillEntitiesInArea();
+				
+				m_TimeSecondaryElapsed = -1;
+				m_SecondaryActive = false;
 				m_TimeElapsed = 0;
-				m_RandomizedDuration = Math.RandomInt(m_EffectDuration * 0.8, m_EffectDuration * 1.2);
-				m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTION_SOON);
 			}
 		}
-		else 
+		else if (m_GeyserTrigger.CheckGeyserState(EGeyserState.ERUPTING_PRIMARY))
 		{
-			if (m_GeyserTrigger.GetGeyserState() & EGeyserState.ERUPTION_SOON)
-			{
-				if (m_TimeElapsed > PRE_ERUPTION_DURATION)
-				{
-					m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTION_SOON);
-					m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTING_PRIMARY);
-					KillEntitiesInArea();
-					return;
-				}
+			if (m_TimeElapsed > m_RandomizedDuration)
+			{	
+				RandomizeIntervals();
+				
+				#ifdef ENABLE_LOGGING
+				Debug.Log(m_Name + ": ERUPTION_SOON, interval: " + m_RandomizedInterval + " sec");
+				#endif
+				
+				m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTING_PRIMARY);
+				m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTING_SECONDARY);
+				m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTION_SOON);
+
+				m_TimeElapsed = 0;
 			}
-			
-			if (m_GeyserTrigger.GetGeyserState() & EGeyserState.ERUPTING_PRIMARY)
+			else if (Math.IsInRange(m_TimeElapsed, ERUPTION_TALL_DELAY, m_RandomizedDuration - ERUPTION_TALL_DURATION)) 	// Ensure burst do not overlap with state transitions
 			{
-				if (m_TimeElapsed > m_RandomizedDuration)
+				if (!m_SecondaryActive && m_TimeSecondaryElapsed < 0) 
 				{
-					m_TimeElapsed = 0;
-					m_RandomizedInterval = Math.RandomInt(m_EffectInterval * 0.8, m_EffectInterval * 1.2);
-					m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTING_PRIMARY | EGeyserState.ERUPTING_SECONDARY);
-					return;
+					if (Math.RandomBool())	// 50% chance to start secondary eruption every update
+					{
+						m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTING_SECONDARY);
+						
+						m_TimeSecondaryElapsed = 0;
+						m_SecondaryActive = true;
+					}
 				}
-				
-				if (m_GeyserTrigger.GetGeyserState() & EGeyserState.ERUPTING_SECONDARY)
-				{
-					if (Math.RandomBool())	// 50% chance to end secondary eruption every update
+				else if (m_TimeSecondaryElapsed >= 0) 
+				{	
+					m_TimeSecondaryElapsed += UPDATE_RATE * 0.001;
+					
+					if (m_SecondaryActive && m_TimeSecondaryElapsed > ERUPTION_TALL_DURATION)
+					{
 						m_GeyserTrigger.RemoveGeyserState(EGeyserState.ERUPTING_SECONDARY);
+						m_SecondaryActive = false;
+					}
+					else if (m_TimeSecondaryElapsed > (ERUPTION_TALL_DURATION + ERUPTION_TALL_DELAY))
+					{
+						m_TimeSecondaryElapsed = -1;
+					}
 				}
-				else if (Math.RandomBool())	// 50% chance to start secondary eruption every update
-					m_GeyserTrigger.AddGeyserState(EGeyserState.ERUPTING_SECONDARY);
 			}
-		}
+		}	
+	}
+	
+	private void RandomizeIntervals()
+	{
+		m_RandomizedInterval = Math.RandomInt(m_EffectInterval * 0.8, m_EffectInterval * 1.2);
+		m_RandomizedDuration = Math.RandomInt(m_EffectDuration * 0.8, m_EffectDuration * 1.2);
 	}
 	
 	void KillEntitiesInArea()

+ 8 - 1
Scripts/4_world/classes/contaminatedarea/hotspringarea.c

@@ -5,6 +5,13 @@ class HotSpringArea : EffectArea
 	protected ref UniversalTemperatureSourceSettings m_UTSSettings;
 	protected ref UniversalTemperatureSourceLambdaConstant m_UTSLConstant;
 	
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		InitZone();		
+	}
+
 	override void InitZoneServer()
 	{
 		super.InitZoneServer();
@@ -23,7 +30,7 @@ class HotSpringArea : EffectArea
 		m_UTSource 		= new UniversalTemperatureSource(this, m_UTSSettings, m_UTSLConstant);
 		
 		if ( m_TriggerType != "" )
-			CreateTrigger( m_Position, m_Radius );
+			CreateTrigger(m_PositionTrigger, m_Radius);
 		
 		m_UTSource.SetActive(true);
 	}

+ 12 - 18
Scripts/4_world/classes/contaminatedarea/spookyarea.c

@@ -4,37 +4,31 @@ class SpookyArea : EffectArea
 	// ----------------------------------------------
 	// 				INITIAL SETUP
 	// ----------------------------------------------
+	
 	override void EEInit()
 	{
-		// We make sure we have the particle array
-		if ( !m_ToxicClouds )
-			m_ToxicClouds = new array<Particle>;
+		if (!m_ToxicClouds)
+			m_ToxicClouds = new array<Particle>();
 		
 		SetSynchDirty();
 		
-		#ifdef DEVELOPER
-		// Debugs when placing entity by hand using internal tools
-		if ( GetGame().IsServer() && !GetGame().IsMultiplayer() )
-		{
-			Debug.Log("YOU CAN IGNORE THE FOLLOWING DUMP");
-			InitZone();
-			Debug.Log("YOU CAN USE FOLLOWING DATA PROPERLY");
-		}
-		#endif
-		
-		if ( GetGame().IsClient() && GetGame().IsMultiplayer() )
-			InitZone();
-		
 		super.EEInit();
 	}
 	
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		InitZone();		
+	}
+	
 	override void InitZoneServer()
 	{
 		super.InitZoneServer();
 		
 		// We create the trigger on server
 		if ( m_TriggerType != "" )
-			CreateTrigger( m_Position, m_Radius );
+			CreateTrigger(m_PositionTrigger, m_Radius);
 	}
 	
 	override void InitZoneClient()
@@ -42,7 +36,7 @@ class SpookyArea : EffectArea
 		super.InitZoneClient();
 		
 		// We spawn VFX on client
-		PlaceParticles( GetWorldPosition(), m_Radius, m_InnerRings, m_InnerSpacing, m_OuterRingToggle, m_OuterSpacing, m_OuterRingOffset, m_ParticleID );
+		PlaceParticles(m_Position, m_Radius, m_InnerRings, m_InnerSpacing, m_OuterRingToggle, m_OuterSpacing, m_OuterRingOffset, m_ParticleID);
 	}
 }
 

+ 8 - 1
Scripts/4_world/classes/contaminatedarea/volcanicarea.c

@@ -5,6 +5,13 @@ class VolcanicArea : EffectArea
 	protected ref UniversalTemperatureSourceSettings m_UTSSettings;
 	protected ref UniversalTemperatureSourceLambdaConstant m_UTSLConstant;
 	
+	override void DeferredInit()
+	{
+		super.DeferredInit();
+		
+		InitZone();		
+	}
+	
 	override void InitZoneServer()
 	{
 		super.InitZoneServer();
@@ -23,7 +30,7 @@ class VolcanicArea : EffectArea
 		m_UTSource 		= new UniversalTemperatureSource(this, m_UTSSettings, m_UTSLConstant);
 		
 		if ( m_TriggerType != "" )
-			CreateTrigger( m_Position, m_Radius );
+			CreateTrigger(m_PositionTrigger, m_Radius);
 		
 		m_UTSource.SetActive(true);
 	}

+ 5 - 8
Scripts/4_world/classes/emoteclasses/emoteclasses.c

@@ -448,17 +448,14 @@ class EmoteSurrender extends EmoteBase
 	
 	override bool EmoteStartOverride(typename callbacktype, int id, int mask, bool fullbody)
 	{
-		bool surrendered = m_Player.GetEmoteManager().m_IsSurrendered;
-		if (!surrendered)
+		bool surrenderTargetState = !m_Player.GetEmoteManager().m_IsSurrendered;
+		if (!surrenderTargetState && m_Player.GetItemInHands())
 		{
-			m_Player.GetEmoteManager().PlaySurrenderInOut(true);
-		}
-		else
-		{
-			if (m_Player.GetItemInHands())
-				m_Player.GetItemInHands().DeleteSafe();
+			m_Player.GetItemInHands().DeleteSafe();
 		}
 		
+		m_Player.GetEmoteManager().PlaySurrenderInOut(surrenderTargetState);
+		
 		return true;
 	}
 }

+ 75 - 29
Scripts/4_world/classes/emotemanager.c

@@ -111,7 +111,7 @@ class EmoteManager
 	EmoteCB					m_Callback;
 	HumanInputController 	m_HIC;
 	ref array<string> 		m_InterruptInputs;
-	ref array<UAInput> 		m_InterruptInputDirect;
+	ref array<UAIDWrapper> 	m_InterruptInputDirect;
 	ref InventoryLocation 	m_HandInventoryLocation;
 	ref EmoteLauncher 		m_MenuEmote;
 	bool					m_bEmoteIsRequestPending;
@@ -279,7 +279,7 @@ class EmoteManager
 		
 		if (m_ItemToBeCreated)
 		{
-			if (!m_Player.GetItemInHands() && GetGame().IsServer())
+			if (GetGame().IsServer() && m_Callback && !m_Player.GetItemInHands())
 			{
 				m_Player.GetHumanInventory().CreateInHands("SurrenderDummyItem");
 			}
@@ -305,13 +305,16 @@ class EmoteManager
 			m_DeferredEmoteExecution = CALLBACK_CMD_INVALID;
 			m_InstantCancelEmote = false;
 			m_bEmoteIsRequestPending = false;
-			SetEmoteLockState(false);
+			if (m_IsSurrendered)
+				ClearSurrenderState();
+			else
+				SetEmoteLockState(false);
 		}
 		else if (m_CancelEmote) //'soft' cancel
 		{
 			if (m_IsSurrendered)
 			{
-				EndSurrenderRequest(new SurrenderData);
+				ClearSurrenderState();
 			}
 			else if (m_Callback)
 			{
@@ -431,8 +434,8 @@ class EmoteManager
 				PlaySurrenderInOut(false);
 				return;
 			}
-			// getting out of surrender state - hard cancel
-			else if (m_IsSurrendered && (m_HIC.IsSingleUse() || m_HIC.IsContinuousUseStart() || m_HIC.IsWeaponRaised()))
+			// getting out of surrender state
+			else if (m_IsSurrendered && m_Player.GetItemInHands() && (m_HIC.IsSingleUse() || m_HIC.IsContinuousUseStart() || m_HIC.IsWeaponRaised()))
 			{
 				if (m_Player.GetItemInHands())
 					m_Player.GetItemInHands().DeleteSafe();//Note, this keeps item 'alive' until it is released by all the systems (inventory swapping etc.)
@@ -441,8 +444,7 @@ class EmoteManager
 			// fallback in case lock does not end properly
 			else if (m_IsSurrendered && (!m_Player.GetItemInHands() || (m_Player.GetItemInHands() && m_Player.GetItemInHands().GetType() != "SurrenderDummyItem" && m_EmoteLockState)))
 			{
-				m_IsSurrendered = false;
-				SetEmoteLockState(false);
+				ClearSurrenderState();
 				return;
 			}
 			//actual emote launch
@@ -472,22 +474,22 @@ class EmoteManager
 			return;
 		}
 		
-		//surrender "state" switch
+		//surrender "state" OFF switch only
 		if (m_CurrentGestureID == EmoteConstants.ID_EMOTE_SURRENDER)
 		{
-			m_IsSurrendered = !m_IsSurrendered;
-			SetEmoteLockState(m_IsSurrendered);
+			if (m_IsSurrendered && !m_Player.GetItemInHands())
+			{
+				m_IsSurrendered = false;
+			}
 		}
 		
 		m_CurrentGestureID = 0;
-		
 		m_bEmoteIsPlaying = false;
 		m_bEmoteIsRequestPending = false;
 		
 		if (m_IsSurrendered)
-		{
 			return;
-		}
+		
 		m_GestureInterruptInput = false;
 		SetEmoteLockState(false);
 
@@ -823,8 +825,8 @@ class EmoteManager
 		if (GetGame().IsMultiplayer() && GetGame().IsClient())
 		{
 			bool canProceed = true; //running callbacks in certain state can block additional actions
-			EmoteBase emoteData;
-			if (m_Callback && m_NameEmoteMap.Find(m_CurrentGestureID,emoteData))
+			EmoteBase emoteData = m_NameEmoteMap.Get(m_CurrentGestureID);
+			if (m_Callback && emoteData)
 			{
 				canProceed = emoteData.CanBeCanceledNormally(m_Callback);
 			}
@@ -980,22 +982,36 @@ class EmoteManager
 	{
 		m_PreviousGestureID = m_CurrentGestureID;
 		m_CurrentGestureID = EmoteConstants.ID_EMOTE_SURRENDER;
+		
 		if (state)
 		{
-			if (m_Player.GetItemInHands() && !m_Player.CanDropEntity(m_Player.GetItemInHands()))
-				return;
-			
-			if (m_Player.GetItemInHands() && GetGame().IsClient())
+			ItemBase item = m_Player.GetItemInHands();
+			if (item)
 			{
+				if (!m_Player.CanDropEntity(item))
+					return;
+				
 				if (m_Player.GetInventory().HasInventoryReservation(null, m_HandInventoryLocation))
 					m_Player.GetInventory().ClearInventoryReservationEx(null, m_HandInventoryLocation);
-				m_Player.PhysicalPredictiveDropItem(m_Player.GetItemInHands());
+				
+				if (GetGame().IsMultiplayer())
+				{
+					if (GetGame().IsServer())
+						m_Player.ServerDropEntity(item);
+				}
+				else
+				{
+					m_Player.PhysicalPredictiveDropItem(item); //SP only
+				}
 			}
 			
 			CreateEmoteCallback(EmoteCB,DayZPlayerConstants.CMD_GESTUREFB_SURRENDERIN,DayZPlayerConstants.STANCEMASK_ALL,true);
 			
 			if (m_Callback)
+			{
 				m_Callback.RegisterAnimationEvent("ActionExec", UA_ANIM_EVENT);
+				m_IsSurrendered = true; //sets state immediately on anim start
+			}
 		}
 		else
 		{
@@ -1020,6 +1036,9 @@ class EmoteManager
 			m_InventoryAccessLocked = state;
 		}
 		
+		if (GetGame().IsClient() && m_InventoryAccessLocked && GetGame().GetUIManager().FindMenu(MENU_INVENTORY))
+			m_Player.CloseInventoryMenu();
+		
 		//Movement lock in fullbody anims
 		if (state && m_Callback && m_Callback.m_IsFullbody)
 			m_controllsLocked = true;
@@ -1066,17 +1085,44 @@ class EmoteManager
 		}
 	}
 	
-	//! directly force-ends surrender state from outside of normal flow
+	//! directly force-ends surrender state AND requests hard cancel
 	void EndSurrenderRequest(SurrenderData data = null)
 	{
 		if (m_IsSurrendered && data)
 		{
-			if (m_Player.GetItemInHands())
-				m_Player.GetItemInHands().DeleteSafe();//Note, this keeps item 'alive' until it is released by all the systems (inventory swapping etc.)
-			
+			PostSurrenderRequestServer();
+			data.End();
+		}
+	}
+	
+	//! clears surrender state only
+	protected void ClearSurrenderState()
+	{
+		if (m_IsSurrendered)
+		{
+			SurrenderDummyItem dummyItem = SurrenderDummyItem.Cast(m_Player.GetItemInHands());
+			if (dummyItem)
+				dummyItem.DeleteSafe();
 			m_IsSurrendered = false;
 			SetEmoteLockState(IsEmotePlaying());
-			data.End();
+		}
+	}
+	
+	void ForceSurrenderState(bool state)
+	{
+		m_IsSurrendered = state;
+		SetEmoteLockState(IsEmotePlaying());
+	}
+	
+	//! server only
+	protected void PostSurrenderRequestServer()
+	{
+		if ((GetGame().IsMultiplayer() && GetGame().IsServer()) || !GetGame().IsMultiplayer())
+		{
+			ScriptJunctureData pCtx = new ScriptJunctureData;
+			pCtx.Write(CALLBACK_CMD_INSTACANCEL);
+			pCtx.Write(EmoteLauncher.FORCE_ALL);
+			m_Player.SendSyncJuncture(DayZPlayerSyncJunctures.SJ_GESTURE_REQUEST, pCtx);
 		}
 	}
 	
@@ -1094,12 +1140,12 @@ class EmoteManager
 		//init pass
 		if (!m_InterruptInputDirect)
 		{
-			m_InterruptInputDirect = new array<UAInput>;
+			m_InterruptInputDirect = new array<UAIDWrapper>;
 			m_InterruptInputsCount = m_InterruptInputs.Count();
 			
 			for (int i = 0; i < m_InterruptInputsCount; i++)
 			{
-				m_InterruptInputDirect.Insert(GetUApi().GetInputByName(m_InterruptInputs[i]));
+				m_InterruptInputDirect.Insert(GetUApi().GetInputByName(m_InterruptInputs[i]).GetPersistentWrapper());
 			}
 		}
 		
@@ -1109,7 +1155,7 @@ class EmoteManager
 		
 		for (int idx = 0; idx < m_InterruptInputsCount; idx++)
 		{
-			if (m_InterruptInputDirect[idx].LocalPress())
+			if (m_InterruptInputDirect[idx].InputP().LocalPress())
 			{
 				return true;
 			}

+ 326 - 114
Scripts/4_world/classes/environment/environment.c

@@ -1,3 +1,6 @@
+/**
+ * \brief Categories that are changing behavior of Heat comfort processing
+ */
 enum EEnvironmentHeatcomfortBehaviorCategory
 {
 	DEFAULT,
@@ -10,6 +13,16 @@ class EnvironmentSnapshotData
 	float m_TargetHeatComfort;
 }
 
+class EnvironmentDrynessData
+{
+	bool m_UseTemperatureSources 		= false;
+	float m_TemperatureSourceDistance	= 1.0;
+}
+
+/**
+ * \brief Simulates influence of environment to character
+ *		Takes input data from WorldData, Weather system and entities simulating temperature and wetness.
+ */
 class Environment
 {
 	const float RAIN_LIMIT_LOW		= 0.05;
@@ -83,7 +96,9 @@ class Environment
 	
 	#ifdef DIAG_DEVELOPER
 	bool m_Debug = false;
+	#endif
 
+	#ifdef ENABLE_LOGGING
 	bool m_DebugLogDryWet = false;
 	bool m_DebugLogItemHeat = false;
 	#endif
@@ -185,8 +200,11 @@ class Environment
 		m_Initialized = true;
 	}
 
-	
-	// Calculates heatisolation of clothing, process its wetness, collects heat from heated items and calculates player's heat comfort
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief Main loop that runs calculations for various Environment parts (wetness, heatcomfort, watercontact, etc.)
+	 */
 	void Update(float pDelta)
 	{
 		if (m_Player && m_Initialized)
@@ -211,8 +229,8 @@ class Environment
 				CheckWaterContact(m_WaterLevel);
 				CollectAndSetPlayerData();
 				CollectAndSetEnvironmentData();
-				GatherTemperatureSources();
 
+				GatherTemperatureSources();
 				ProcessTemperatureSources();
 				
 				//! Process temperatures
@@ -259,39 +277,56 @@ class Environment
 			}
 		}
 	}
-	
+
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief For safe-guards, waiting for the proper temperature to be set
+	 */	
 	bool IsTemperatureSet()
 	{
 		return m_IsTempSet;
 	}
 
-	//! Returns heat player generated based on player's movement speed (for now)
+	/**
+	 * \brief Character's heat (calculated from movement speed multiplied by constant)
+	 * \return generated heat value
+	 */
 	protected float GetPlayerHeat()
 	{
 		float heat = m_PlayerSpeed * GameConstants.ENVIRO_DEFAULT_ENTITY_HEAT;
 		return heat;
 	}
-	
+
+	/**
+	 * \brief Is character under roof (periodically checked - GameConstants.ENVIRO_TICK_ROOF_RC_CHECK).
+	 *		Runs when player is not inside of building
+	 */	
 	bool IsUnderRoof()
 	{
 		return m_IsUnderRoof;
 	}
-	
-	protected bool IsWaterContact()
-	{
-		return m_IsInWater;
-	}
-	
+
+	/**
+	 * \brief Is character inside building? (periodically checked - GameConstants.ENVIRO_TICK_ROOF_RC_CHECK).
+	 */	
 	bool IsInsideBuilding()
 	{
-		return m_Player && m_Player.IsSoundInsideBuilding();
+		return m_Player.IsSoundInsideBuilding();
 	}
-
-	protected bool IsInsideVehicle()
+	
+	/**
+	 * \brief Is character in contact with water body? (periodically checked - GameConstants.ENVIRO_TICK_RATE).
+	 */
+	protected bool IsWaterContact()
 	{
-		return m_Player && m_Player.IsInVehicle();
+		return m_IsInWater;
 	}
 	
+	/**
+	 * \brief Returns true if character is child of given parent type(s)
+	 * @param typenames list of types to check against
+	 */
 	private bool IsChildOfType(array<typename> typenames)
 	{
 		Object parent = Object.Cast(m_Player.GetParent());
@@ -300,22 +335,37 @@ class Environment
 		
 		return false;
 	}
-	
+
+	/**
+	 * \brief Is character under building's roof (periodically checked - GameConstants.ENVIRO_TICK_ROOF_RC_CHECK).
+	 *		There might be situations where the surface under is external, but character is inside building
+	 */	
 	private bool IsUnderRoofBuilding()
 	{
 		return m_IsUnderRoofBuilding;
 	}
-	
+
+	/**
+	 * \brief Rain phenomenon actual value > RAIN_LIMIT_LOW
+	 */		
 	protected bool IsRaining()
 	{
 		return m_Rain > RAIN_LIMIT_LOW;
 	}
-	
+
+	/**
+	 * \brief Snowfall phenomenon actual value > SNOWFALL_LIMIT_LOW
+	 */			
 	protected bool IsSnowing()
 	{
 		return m_Snowfall > SNOWFALL_LIMIT_LOW;
 	}
 
+	/**
+	 * \brief Changes Heat Comfort curve behavior based on where the character is
+	 *
+	 * \return true if the behavior is altered otherwise false
+	 */
 	protected bool DetermineHeatcomfortBehavior()
 	{
 		if (IsChildOfType({Car}))
@@ -333,10 +383,14 @@ class Environment
 		return false;
 	}
 	
-	//! Checks whether Player is sheltered
+	// --------------------------------------------------------------------------------
+	
+	/**
+	 * \brief Checks whether character is sheltered and sets the information
+	 */
 	protected void CheckUnderRoof()
 	{
-		// if inside vehicle return immediatelly
+		//! if inside vehicle return immediatelly
 		if (IsChildOfType({Car}))
 		{
 			m_IsUnderRoof = false;
@@ -356,6 +410,14 @@ class Environment
 		m_IsUnderRoofBuilding = hitObject && hitObject.IsInherited(House);
 	}
 	
+	// --------------------------------------------------------------------------------
+	
+	/**
+	 * \brief Checks player's contanct with water
+	 * @param[out] pWaterLevel water level height
+	 *
+	 * \return Nothing 
+	 */
 	protected void CheckWaterContact(out float pWaterLevel)
 	{
 		string surfType;
@@ -416,7 +478,14 @@ class Environment
 		m_LiquidType = liquidType;
 
 	}
+	
+	// --------------------------------------------------------------------------------
 
+	/**
+	 * \brief Wind intensity (influence) modifier of temperature value
+	 *
+	 * \return float modifier used in temperature calculations
+	 */
 	float GetWindModifierPerSurface()
 	{
 		if (IsUnderRoofBuilding())
@@ -435,7 +504,16 @@ class Environment
 		return m_TargetHeatComfort;
 	}
 	
-	// Calculates and return temperature of environment
+	// --------------------------------------------------------------------------------
+	
+	/**
+	 * \brief Calculations of temperarute for different situations
+	 *		- in water
+	 *		- inside building / inside vehicle / under roof
+	 *		- influence of UniversalTemperatureSource
+	 *
+	 * \return Resulting temperature of the environment
+	 */
 	protected float GetEnvironmentTemperature()
 	{
 		float temperature = m_WorldData.GetTemperature(m_Player, EEnvironmentTemperatureComponent.ALTITUDE | EEnvironmentTemperatureComponent.OVERCAST);
@@ -475,7 +553,13 @@ class Environment
 		return temperature;
 	}
 	
-	// Calculates wet/drying delta based on player's location and weather
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief Calculates soaking/drying delta based on character's location and weather
+	 *
+	 * \return Resulting wet delta modifier
+	 */
 	float GetWetDelta()
 	{
 		float wetDelta = 0;
@@ -527,9 +611,15 @@ class Environment
 
 		return wetDelta;
 	}
+	
+	// --------------------------------------------------------------------------------
 
-	// EXPOSURE
-	// Each tick updates current entity member variables
+	/**
+	 * \brief Sets character related value for furher use
+	 * 		- position
+	 *  	- movement speed
+	 * 		- player heat (actual speed * heat constant)
+	 */
 	protected void CollectAndSetPlayerData()
 	{
 		vector playerPos = m_Player.GetPosition();
@@ -545,6 +635,9 @@ class Environment
 	}
 	
 	// Each tick updates current environment member variables
+	/**
+	 * \brief Sets actual weather related values for further use (rain, snow, wind, etc.)
+	 */
 	protected void CollectAndSetEnvironmentData()
 	{
 		Weather weather	= g_Game.GetWeather();
@@ -558,6 +651,8 @@ class Environment
 		SetEnvironmentTemperature();
 		SetAreaGenericColdness();
 	}
+
+	// --------------------------------------------------------------------------------
 	
 	void SetEnvironmentTemperature()
 	{
@@ -565,14 +660,19 @@ class Environment
 		m_EnvironmentTemperature = GetEnvironmentTemperature();
 	}
 	
-	//! Determines whether player is in cold area which restricts use of some actions (digging)
+	/**
+	 * \brief Determines whether player is in cold area which restricts use of some actions (digging)
+	 */
 	void SetAreaGenericColdness()
 	{			
 		float heigthCorrectedTemp = m_WorldData.GetBaseEnvTemperatureAtObject(m_Player);
 		m_Player.SetInColdArea(heigthCorrectedTemp <= GameConstants.COLD_AREA_TEMPERATURE_THRESHOLD);
 	}
 
-	//! process attachments by water depth
+	/**
+	 * \brief Processes items wetness in player possession based on the current water level (the character is in)
+	 * @param pWaterLevel Water level height
+	 */
 	protected void ProcessWetnessByWaterLevel(float pWaterLevel)
 	{
 		if (pWaterLevel >= WATER_LEVEL_HIGH)
@@ -584,15 +684,22 @@ class Environment
 		else if (pWaterLevel >= WATER_LEVEL_NONE && pWaterLevel < WATER_LEVEL_LOW)
 			ProcessItemsWetness(m_SlotIdsLower);
 	}
+	
+	// --------------------------------------------------------------------------------
 
-	// Wets or dry items once in given time
+	/**
+	 * \brief Soak items at specific Slot ID(s)
+	 * @param pSlotIds Inventory Slot IDs to process
+	 */
 	protected void ProcessItemsWetness(array<int> pSlotIds)
 	{
 		EntityAI attachment;
 		
 		int playerAttachmentCount = m_Player.GetInventory().AttachmentCount();
 		
+		#ifdef ENABLE_LOGGING
 		LogDryWetProcess(string.Format("Environment :: ProcessItemsWetness (update interval=%1s)", GameConstants.ENVIRO_TICK_RATE));
+		#endif
 		for (int attIdx = 0; attIdx < playerAttachmentCount; ++attIdx)
 		{
 			attachment = m_Player.GetInventory().GetAttachmentFromIndex(attIdx);
@@ -617,46 +724,9 @@ class Environment
 		if (m_Player.GetItemInHands())
 			ApplyWetnessToItem(m_Player.GetItemInHands());
 
+		#ifdef ENABLE_LOGGING
 		LogDryWetProcess("==========");
-
-	}
-
-	protected void ProcessItemsDryness()
-	{
-		EntityAI attachment;
-		ItemBase item;
-		
-		int attCount = m_Player.GetInventory().AttachmentCount();
-		
-		LogDryWetProcess(string.Format("Environment :: ProcessItemsDryness (update interval=%1s)", GameConstants.ENVIRO_TICK_RATE));
-		EnvironmentDrynessData drynessData = new EnvironmentDrynessData();
-		drynessData.m_UseTemperatureSources = m_HasTemperatureSources;
-
-		if (m_HasTemperatureSources)
-		{
-			float distance = vector.Distance(m_UTemperatureSources[0].GetPosition(), m_Player.GetPosition());
-			distance = Math.Max(distance, 0.1);
-			drynessData.m_TemperatureSourceDistance = distance;
-			LogDryWetProcess(string.Format("distance to heatsource: %1 m", distance));
-		}
-		
-		for (int attIdx = 0; attIdx < attCount; ++attIdx)
-		{
-			attachment = m_Player.GetInventory().GetAttachmentFromIndex(attIdx);
-			if (attachment && attachment.IsItemBase())
-			{
-				item = ItemBase.Cast(attachment);
-				if (item)
-					ApplyDrynessToItemEx(item, drynessData);
-			}
-		}
-
-		if (m_Player.GetItemInHands())
-		{
-			ApplyDrynessToItemEx(m_Player.GetItemInHands(), drynessData);
-		}
-		
-		LogDryWetProcess("==========");
+		#endif
 	}
 
 	protected void ApplyWetnessToItem(ItemBase pItem)
@@ -696,19 +766,25 @@ class Environment
 				if (parentContainsLiquid)
 				{
 					soakingCoef = pItem.GetSoakingIncrement("parentWithLiquid");
+					#ifdef ENABLE_LOGGING
 					LogDryWetProcess(string.Format("%1 (soak coef=%2/s, current wetness=%3) [parent contains liquid]", pItem.GetDisplayName(), soakingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+					#endif
 				}
 				else if (isParentWet && parentItem)
 				{
 					if (pItem.GetWet() < parentItem.GetWet())
 						soakingCoef = GetWetDelta();
 
+					#ifdef ENABLE_LOGGING
 					LogDryWetProcess(string.Format("%1 (soak coef=%2/s, current wetness=%3) [parent wet]", pItem.GetDisplayName(), soakingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+					#endif
 				}
 				else
 				{
 					soakingCoef = GetWetDelta();
+					#ifdef ENABLE_LOGGING
 					LogDryWetProcess(string.Format("%1 (soak coef=%2/s, current wetness=%3) [normal]", pItem.GetDisplayName(), soakingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+					#endif
 				}
 
 				pItem.AddWet(soakingCoef);
@@ -738,6 +814,54 @@ class Environment
 			}
 		}
 	}
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief Dry items in player possession
+	 */
+	protected void ProcessItemsDryness()
+	{
+		EntityAI attachment;
+		ItemBase item;
+		
+		int attCount = m_Player.GetInventory().AttachmentCount();
+		
+		#ifdef ENABLE_LOGGING
+		LogDryWetProcess(string.Format("Environment :: ProcessItemsDryness (update interval=%1s)", GameConstants.ENVIRO_TICK_RATE));
+		#endif
+		EnvironmentDrynessData drynessData = new EnvironmentDrynessData();
+		drynessData.m_UseTemperatureSources = m_HasTemperatureSources;
+
+		if (m_HasTemperatureSources)
+		{
+			float distance = vector.Distance(m_UTemperatureSources[0].GetPosition(), m_Player.GetPosition());
+			distance = Math.Max(distance, 0.1);
+			drynessData.m_TemperatureSourceDistance = distance;
+			#ifdef ENABLE_LOGGING
+			LogDryWetProcess(string.Format("distance to heatsource: %1 m", distance));
+			#endif
+		}
+		
+		for (int attIdx = 0; attIdx < attCount; ++attIdx)
+		{
+			attachment = m_Player.GetInventory().GetAttachmentFromIndex(attIdx);
+			if (attachment && attachment.IsItemBase())
+			{
+				item = ItemBase.Cast(attachment);
+				if (item)
+					ApplyDrynessToItemEx(item, drynessData);
+			}
+		}
+
+		if (m_Player.GetItemInHands())
+		{
+			ApplyDrynessToItemEx(m_Player.GetItemInHands(), drynessData);
+		}
+		
+		#ifdef ENABLE_LOGGING
+		LogDryWetProcess("==========");
+		#endif
+	}
 	
 	protected void ApplyDrynessToItem(ItemBase pItem)
 	{
@@ -786,7 +910,9 @@ class Environment
 				dryingCoef = (-1 * GameConstants.ENVIRO_TICK_RATE * dryingIncrement) / pDrynessData.m_TemperatureSourceDistance;
 				if (pItem.GetWet() >= GameConstants.STATE_DAMP)
 				{
+					#ifdef ENABLE_LOGGING
 					LogDryWetProcess(string.Format("%1 (dry coef=%2/s, current wetness=%3) [normal]", pItem.GetDisplayName(), dryingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+					#endif
 					pItem.AddWet(dryingCoef);			
 				}
 				
@@ -818,7 +944,9 @@ class Environment
 			{
 				//! adds wetness to item inside parent item containing liquid
 				dryingCoef = (GameConstants.ENVIRO_TICK_RATE * pItem.GetSoakingIncrement("parentWithLiquid")) / pDrynessData.m_TemperatureSourceDistance;
+				#ifdef ENABLE_LOGGING
 				LogDryWetProcess(string.Format("%1 (dry coef=%2/s, current wetness=%3) [parent contains liquid]", pItem.GetDisplayName(), dryingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+				#endif
 				pItem.AddWet(dryingCoef);
 			}
 	
@@ -826,14 +954,19 @@ class Environment
 			{
 				//! adds wetness to item inside wet parent item
 				dryingCoef = (GameConstants.ENVIRO_TICK_RATE * pItem.GetSoakingIncrement("wetParent")) / pDrynessData.m_TemperatureSourceDistance;
+				#ifdef ENABLE_LOGGING
 				LogDryWetProcess(string.Format("%1 (dry coef=%2/s, current wetness=%3) [parent wet]", pItem.GetDisplayName(), dryingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null);
+				#endif
 				pItem.AddWet(dryingCoef);
 			}
 		}
 	}
 
-	// HEAT COMFORT
-	//! Calculates and process player's heatcomfort related to body parts
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief Calculates and process player's heatcomfort related to defined body parts
+	 */
 	protected void ProcessHeatComfort()
 	{
 		float hcPenaltyTotal //! Heat Comfort Penalty
@@ -845,7 +978,9 @@ class Environment
 		float heatComfortSum = 0.0;
 		float heatItems = 0.0;
 		
+		#ifdef ENABLE_LOGGING
 		LogItemHeat("====================");
+		#endif
 		BodyPartHeatProperties(InventorySlots.HEADGEAR, GameConstants.ENVIRO_HEATCOMFORT_HEADGEAR_WEIGHT, hcBodyPart, hBodyPart);
 		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
 		BodyPartHeatProperties(InventorySlots.MASK, GameConstants.ENVIRO_HEATCOMFORT_MASK_WEIGHT, hcBodyPart, hBodyPart);
@@ -961,6 +1096,8 @@ class Environment
  			m_Player.GetStatHeatComfort().Set(dynamicHeatComfort);
 		}
 	}
+	
+	// --------------------------------------------------------------------------------
 
 	protected void ProcessHeatBuffer(EnvironmentSnapshotData data)
 	{
@@ -972,12 +1109,14 @@ class Environment
 			float heatBufferCap = Math.InverseLerp(0.0, GameConstants.ENVIRO_HEATCOMFORT_WEIGHT_SUMMARY, data.m_ClothingHeatComfort);
 			float heatBufferMax = GameConstants.ENVIRO_PLAYER_HEATBUFFER_CAPACITY_MIN + heatBufferCap * (1 - GameConstants.ENVIRO_PLAYER_HEATBUFFER_CAPACITY_MIN);
 			m_Player.SetHeatBufferDynamicMax(heatBufferMax);
+			
+			PlayerStat<float> heatBuffer = m_Player.GetStatHeatBuffer();
 
 			//! deplete the heat buffer if there is difference in HB capacity (eg.: cloths were removed)
 			if (heatBufferCap < m_HeatBufferCapPrevious)
 			{
 				float heatBufferValueCorrection = GameConstants.ENVIRO_PLAYER_HEATBUFFER_INCREASE / (heatBufferMax * ((-GameConstants.ENVIRO_PLAYER_HEATBUFFER_TEMP_AFFECT * data.m_TargetHeatComfort) + 1 ));
-				m_Player.GetStatHeatBuffer().Add(-heatBufferValueCorrection);
+				heatBuffer.Add(-heatBufferValueCorrection);
 				m_HeatBufferCapPrevious = heatBufferCap;
 			}
 			
@@ -1027,7 +1166,7 @@ class Environment
 				if (m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
 				{
 					if (m_HeatBufferTimer >= 1.0)
-						m_Player.GetStatHeatBuffer().Add(-decreaseRate);
+						heatBuffer.Add(-decreaseRate);
 					else
 						m_HeatBufferTimer = 1.0;
 				}
@@ -1035,9 +1174,9 @@ class Environment
 				{
 					m_HeatBufferTimer = 0.0;
 					if (applicableHeatbuffer > 0.0)
-						m_Player.GetStatHeatBuffer().Add(-decreaseRate);
+						heatBuffer.Add(-decreaseRate);
 					else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
-						m_Player.GetStatHeatBuffer().Set(0.0);
+						heatBuffer.Set(0.0);
 				}			
 			}
 			else
@@ -1046,14 +1185,14 @@ class Environment
 				{
 					if (applicableHeatbuffer < heatBufferMax)
 					{
-						m_Player.GetStatHeatBuffer().Add(increaseRate);
+						heatBuffer.Add(increaseRate);
 						m_HeatBufferCapPrevious = heatBufferCap;
 					}
 				}
 				else if (applicableHeatbuffer > 0.0)
-					m_Player.GetStatHeatBuffer().Add(-decreaseRate);
+					heatBuffer.Add(-decreaseRate);
 				else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
-					m_Player.GetStatHeatBuffer().Set(0.0);
+					heatBuffer.Set(0.0);
 	
 				m_HeatBufferTimer = 0.0;
 			}
@@ -1062,11 +1201,18 @@ class Environment
 	
 	protected float GetApplicableHeatbuffer()
 	{
-		float applicableHeatbuffer = Math.Round((m_Player.GetStatHeatBuffer().Get() / m_Player.GetStatHeatBuffer().GetMax()) * 1000) * 0.001;
+		PlayerStat<float> heatBuffer = m_Player.GetStatHeatBuffer();
+		float applicableHeatbuffer = Math.Round((heatBuffer.Get() / heatBuffer.GetMax()) * 1000) * 0.001;
+
 		return applicableHeatbuffer;
 	}
+	
+	// --------------------------------------------------------------------------------
 
-	//! go through all items in player's possession cool/warm them to neutral temperature
+	/**
+	 * \brief Iterate through items in player posession (by given body parts) and cool/warm them to neutral temparature
+	 * @param pBodyPartIds List of body parts to iterate through (see InventorySlots)
+	 */
 	protected void ProcessItemsTemperature(array<int> pBodyPartIds)
 	{
 		EntityAI attachment;
@@ -1087,14 +1233,17 @@ class Environment
 					float heatPermCoef = item.GetHeatPermeabilityCoef();
 					//first handle the item itself, if necessary
 					if (item.CanHaveTemperature() && !item.IsSelfAdjustingTemperature())
-						SetProcessedItemTemperature(item,heatPermCoef);
+						SetProcessedItemTemperature(item, heatPermCoef);
 					
-					ProcessItemHierarchyRecursive(item,heatPermCoef);
+					ProcessItemHierarchyRecursive(item, heatPermCoef);
 				}
 			}
 		}
 	}
-	
+
+	/**
+	 * \brief Process temperature of item in character hands and cool/warm it to neutral temparature
+	 */	
 	protected void ProcessItemsInHandsTemperature()
 	{
 		ItemBase item = m_Player.GetItemInHands();
@@ -1164,6 +1313,7 @@ class Environment
 	{
 		float targetTemperature = GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_MIDDLE;
 		bool globalCooling = true;
+		//! swimming special behavior
 		if (m_Player.IsSwimming())
 		{
 			SetItemHeatingCoef(GameConstants.TEMP_COEF_SWIMMING);
@@ -1186,12 +1336,20 @@ class Environment
 		}
 	}
 	
+	// --------------------------------------------------------------------------------
+	
 	protected float EnvTempToCoef(float pTemp)
 	{
 		return (pTemp - GameConstants.ENVIRO_PLAYER_COMFORT_TEMP) / GameConstants.ENVIRO_TEMP_EFFECT_ON_PLAYER;
 	}
 
-	//! returns enhanced heat comfort for given body part
+	/**
+	 * \brief Iterate through given body part and calculates heatcofort and item heat value
+	 * @param pBodyPartIds List of body parts to iterate through (see InventorySlots)
+	 * @param pCoef Multiplier used for heatcomfort enhancing
+	 * @param[out] pHeatComfort overall heatcomfort from items of given body part
+	 * @param[out] pHeat overall heat from items for given body part
+	 */
 	protected void BodyPartHeatProperties(int pBodyPartId, float pCoef, out float pHeatComfort, out float pHeat)
 	{
 		pHeatComfort = 0;
@@ -1208,7 +1366,9 @@ class Environment
 
 				if (attachmentSlot == pBodyPartId)
 				{
+					#ifdef ENABLE_LOGGING
 					LogItemHeat(string.Format("BodyPartHeatProperties (%1)", EnumTools.EnumToString(InventorySlots, pBodyPartId)));
+					#endif
 
 					float itemHeatcomfort = 0;
 					float itemTemperature = 0;
@@ -1217,7 +1377,9 @@ class Environment
 					int inventoryAttCount = item.GetInventory().AttachmentCount();
 					if (inventoryAttCount > 0)
 					{
+						#ifdef ENABLE_LOGGING
 						LogItemHeat(string.Format("attachments:"), false);
+						#endif
 						for (int inAttIdx = 0; inAttIdx < inventoryAttCount; ++inAttIdx)
 						{
 							EntityAI inAttachment = item.GetInventory().GetAttachmentFromIndex(inAttIdx);
@@ -1228,7 +1390,9 @@ class Environment
 								if (itemTemperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT || itemTemperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
 								{
 									itemHeatcomfort = NormalizedTemperature(itemTemperature) * attachmentItem.GetQuantityNormalizedScripted() * attachmentItem.GetTemperaturePerQuantityWeight();
+									#ifdef ENABLE_LOGGING
 									LogItemHeat(string.Format("%1: temperature=%2 heat=%3", attachmentItem, itemTemperature, pHeat), true);
+									#endif
 									pHeat += itemHeatcomfort;
 								}
 							}
@@ -1240,7 +1404,9 @@ class Environment
 						
 						if (inventoryItemCount > 0)
 						{
+							#ifdef ENABLE_LOGGING
 							LogItemHeat(string.Format("cargo:"), false);
+							#endif
 							for (int j = 0; j < inventoryItemCount; ++j)
 							{
 								ItemBase inventoryItem = ItemBase.Cast(item.GetInventory().GetCargo().GetItem(j));
@@ -1250,7 +1416,9 @@ class Environment
 									if (itemTemperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT || itemTemperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
 									{
 										itemHeatcomfort = NormalizedTemperature(itemTemperature) * inventoryItem.GetQuantityNormalizedScripted() * inventoryItem.GetTemperaturePerQuantityWeight();
+										#ifdef ENABLE_LOGGING
 										LogItemHeat(string.Format("%1: temperature=%2 heat=%3", inventoryItem, itemTemperature, itemHeatcomfort), true);
+										#endif
 										pHeat += itemHeatcomfort;
 									}
 								}
@@ -1260,8 +1428,10 @@ class Environment
 					
 					pHeatComfort = MiscGameplayFunctions.GetCurrentItemHeatIsolation(item) * pCoef;
 					
+					#ifdef ENABLE_LOGGING
 					LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat));
 					LogItemHeat("");
+					#endif
 
 					break;
 				}
@@ -1269,6 +1439,12 @@ class Environment
 		}
 	}
 
+	/**
+	 * \brief Calculates penalty value for heatcomfort - this simulates uncovered body part reaction
+	 * @param pBodyPartSlotId InventorySlot ID to check
+	 * @param pCoef external coefficient used for that slot
+	 * \return Value of penalty for given slot ID
+	 */
 	protected float NakedBodyPartHeatComfortPenalty(int pBodyPartSlotId, float pCoef)
 	{
 		float penalty = 0.0;
@@ -1284,26 +1460,37 @@ class Environment
 		
 		return penalty;
 	}
-	
+
+	// --------------------------------------------------------------------------------
+
+	/**
+	 * \brief Checks characters proximity for usable Universal Temperature Sources and register them
+	 */
 	protected void GatherTemperatureSources()
 	{
 		m_UTemperatureSources.Clear();
+		
+		// Calculate min and max positions of the box
+		vector pos = m_Player.GetPosition();
+		vector minPos = pos - Vector(GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS, GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS / 2, GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS);
+		vector maxPos = pos + Vector(GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS, GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS / 2, GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS);
 
-		array<Object> nearestObjects = new array<Object>();
-		GetGame().GetObjectsAtPosition(m_Player.GetPosition(), GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS, nearestObjects, null);
-
-		foreach (Object nearestObject : nearestObjects)
+		array<EntityAI> nearestObjects = {};
+		DayZPlayerUtils.SceneGetEntitiesInBox(minPos, maxPos, nearestObjects, QueryFlags.STATIC|QueryFlags.DYNAMIC); //STATIC catches area effects and other static (or 'static') sources
+		
+		foreach (EntityAI nearestEntity : nearestObjects)
 		{
-			EntityAI ent = EntityAI.Cast(nearestObject);
-			if (ent && ent.IsUniversalTemperatureSource() && ent != m_Player)
+			if (nearestEntity.IsUniversalTemperatureSource() && nearestEntity != m_Player)
 			{
-				//! next temp source is too far
-				if (vector.DistanceSq(m_Player.GetPosition(), ent.GetPosition()) > Math.SqrFloat(ent.GetUniversalTemperatureSource().GetMaxRange()))
+				//! skip - Temperature Source is not affecting player entities
+				if (!nearestEntity.GetUniversalTemperatureSource().GetLambda().AffectsPlayer())
 					continue;
-				
-				//! skip - this TS is not affecting player entities
-				if (ent.GetUniversalTemperatureSource().GetLambda().AffectsPlayer())
-					m_UTemperatureSources.Insert(ent.GetUniversalTemperatureSource());
+
+				//! skip - Temperature Source is too far
+				if (vector.DistanceSq(pos, nearestEntity.GetPosition()) > Math.SqrFloat(nearestEntity.GetUniversalTemperatureSource().GetMaxRange()))
+					continue;
+
+				m_UTemperatureSources.Insert(nearestEntity.GetUniversalTemperatureSource());
 			}
 		}
 
@@ -1311,11 +1498,9 @@ class Environment
 			m_UTemperatureSources.Insert(m_Player.GetItemInHands().GetUniversalTemperatureSource());
 	}
 	
-	protected void SetItemHeatingCoef(float val)
-	{
-		m_ItemTemperatureCoef = val;
-	}
-	
+	/**
+	 * \brief Processes registered UTSources and calculates resulting m_UTSAverageTemperature
+	 */
 	protected void ProcessTemperatureSources()
 	{
 		int UTScount = m_UTemperatureSources.Count();
@@ -1361,7 +1546,19 @@ class Environment
 		m_HasTemperatureSources = true;
 	}
 	
+	protected void SetItemHeatingCoef(float val)
+	{
+		m_ItemTemperatureCoef = val;
+	}
+	
+	/**
+	 * \brief Event fired when characters enters into UTSource proximity
+	 */
 	protected void OnTemperatureSourcesEnter();	
+	
+	/**
+	 * \brief Event fired when characters leave the UTSource proximity
+	 */
 	protected void OnTemperatureSourcesLeft();
 	
 	float GetUniversalSourcesTemperageAverage()
@@ -1388,9 +1585,11 @@ class Environment
 
 		return temperature;
 	}
+	
+	// --------------------------------------------------------------------------------
 
 	//! debug
-#ifdef DIAG_DEVELOPER
+	#ifdef DIAG_DEVELOPER
 	EnvDebugData GetEnvDebugData()
 	{
 		EnvDebugData data = new EnvDebugData();
@@ -1452,7 +1651,7 @@ class Environment
 		data.m_Fog = m_Fog;
 		data.m_Clouds = m_Clouds;
 	}
-#endif
+	#endif
 
 	string GetDebugMessage()
 	{
@@ -1527,9 +1726,9 @@ class Environment
 		return 0.0;
 	}
 	
+	#ifdef ENABLE_LOGGING
 	private void LogDryWetProcess(string message, bool indented = false)
 	{
-		#ifdef DIAG_DEVELOPER
 		if (m_DebugLogDryWet)
 		{
 			string indentation = "";
@@ -1538,12 +1737,12 @@ class Environment
 
 			Debug.Log(string.Format("%1 %2", indentation, message));
 		}
-		#endif
 	}
-	
+	#endif
+
+	#ifdef ENABLE_LOGGING
 	private void LogItemHeat(string message, bool indented = false)
 	{
-		#ifdef DIAG_DEVELOPER
 		if (m_DebugLogItemHeat)
 		{
 			string indentation = "";
@@ -1552,8 +1751,10 @@ class Environment
 
 			Debug.Log(string.Format("%1 %2", indentation, message));
 		}
-		#endif	
-	}	
+	}
+	#endif
+	
+	// --------------------------------------------------------------------------------
 	
 	//! DEPRECATED
 	protected float 				m_HeatSourceTemp;	
@@ -1594,7 +1795,9 @@ class Environment
 		
 		if (pBodyPartIds.Count() > 0)
 		{
+			#ifdef ENABLE_LOGGING
 			LogItemHeat(string.Format("BodyPartHeatProperties (%1)", EnumTools.EnumToString(InventorySlots, pBodyPartIds[0])));
+			#endif
 	
 			int attCount = m_Player.GetInventory().AttachmentCount();
 			for (int attIdx = 0; attIdx < attCount; ++attIdx)
@@ -1625,7 +1828,9 @@ class Environment
 							int inventoryAttCount = item.GetInventory().AttachmentCount();
 							if (inventoryAttCount > 0)
 							{
+								#ifdef ENABLE_LOGGING
 								LogItemHeat(string.Format("attachments:"), false);
+								#endif
 								for (int inAttIdx = 0; inAttIdx < inventoryAttCount; ++inAttIdx)
 								{
 									EntityAI inAttachment = item.GetInventory().GetAttachmentFromIndex(inAttIdx);
@@ -1636,8 +1841,10 @@ class Environment
 										if (itemTemperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT || itemTemperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
 										{
 											itemHeatcomfort = NormalizedTemperature(itemTemperature) * attachmentItem.GetQuantityNormalizedScripted() * attachmentItem.GetTemperaturePerQuantityWeight();
+											#ifdef ENABLE_LOGGING 
 											LogItemHeat(string.Format("%1: temperature=%2 heat=%3", attachmentItem, itemTemperature, pHeat), true);
 											pHeat += itemHeatcomfort;
+											#endif
 										}
 									}
 								}
@@ -1648,7 +1855,9 @@ class Environment
 								
 								if (inventoryItemCount > 0)
 								{
+									#ifdef ENABLE_LOGGING
 									LogItemHeat(string.Format("cargo:"), false);
+									#endif
 									for (int j = 0; j < inventoryItemCount; ++j)
 									{
 										ItemBase inventoryItem = ItemBase.Cast(item.GetInventory().GetCargo().GetItem(j));
@@ -1658,7 +1867,9 @@ class Environment
 											if (itemTemperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT || itemTemperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
 											{
 												itemHeatcomfort = NormalizedTemperature(itemTemperature) * inventoryItem.GetQuantityNormalizedScripted() * inventoryItem.GetTemperaturePerQuantityWeight();
+												#ifdef ENABLE_LOGGING
 												LogItemHeat(string.Format("%1: temperature=%2 heat=%3", inventoryItem, itemTemperature, itemHeatcomfort), true);
+												#endif
 												pHeat += itemHeatcomfort;
 											}
 										}
@@ -1672,8 +1883,10 @@ class Environment
 
 			pHeatComfort += (pHeatComfort / pBodyPartIds.Count()) * pCoef;
 			
+			#ifdef ENABLE_LOGGING
 			LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat));
 			LogItemHeat("");
+			#endif
 		}
 	}
 	
@@ -1709,12 +1922,11 @@ class Environment
 	{
 		return m_DayOrNight;
 	}
-}
-
-class EnvironmentDrynessData
-{
-	bool m_UseTemperatureSources 		= false;
-	float m_TemperatureSourceDistance	= 1.0;
+	
+	protected bool IsInsideVehicle()
+	{
+		return IsChildOfType({Car});
+	}
 }
 
 #ifdef DIAG_DEVELOPER

+ 1 - 1
Scripts/4_world/classes/explosions/flashbangeffect.c

@@ -45,7 +45,7 @@ class FlashbangEffect
 			m_Requester.Start();
 		}
 		
-		m_DeferAttenuation = new ref Timer();
+		m_DeferAttenuation = new Timer();
 		m_DeferAttenuation.Run(SOUND_DEFER_TIME, this, "PlaySound", null, false);
 		
 		//! naive time of the day selector

+ 16 - 37
Scripts/4_world/classes/heatcomfortanimhandler.c

@@ -12,11 +12,11 @@ class HeatComfortAnimHandler
 	float m_EventTimeHot = -1;
 	protected ref HumanMovementState m_MovementState = new HumanMovementState();
 	
-	const float TIME_INTERVAL_HC_MINUS_LOW_MIN = 5; const float TIME_INTERVAL_HC_MINUS_LOW_MAX = 12;
-	const float TIME_INTERVAL_HC_MINUS_HIGH_MIN = 15; const float TIME_INTERVAL_HC_MINUS_HIGH_MAX = 25;
+	const float TIME_INTERVAL_HC_MINUS_LOW_MIN 	= 12; 	const float TIME_INTERVAL_HC_MINUS_LOW_MAX 	= 20;
+	const float TIME_INTERVAL_HC_MINUS_HIGH_MIN = 25; 	const float TIME_INTERVAL_HC_MINUS_HIGH_MAX = 40;
 	
-	const float TIME_INTERVAL_HC_PLUS_LOW_MIN = 5; const float TIME_INTERVAL_HC_PLUS_LOW_MAX = 12;
-	const float TIME_INTERVAL_HC_PLUS_HIGH_MIN = 15; const float TIME_INTERVAL_HC_PLUS_HIGH_MAX = 25;
+	const float TIME_INTERVAL_HC_PLUS_LOW_MIN 	= 5; 	const float TIME_INTERVAL_HC_PLUS_LOW_MAX 	= 12;
+	const float TIME_INTERVAL_HC_PLUS_HIGH_MIN 	= 15;	const float TIME_INTERVAL_HC_PLUS_HIGH_MAX 	= 25;
 	
 	void HeatComfortAnimHandler(PlayerBase player)
 	{
@@ -34,13 +34,13 @@ class HeatComfortAnimHandler
 		}
 	}
 	
-	float GetEventTime(float hc_value ,float threshold_low, float threshold_high, float low_min, float high_min, float low_max, float high_max)
+	float GetEventTime(float hc_value, float threshold_low, float threshold_high, float low_min, float high_min, float low_max, float high_max)
 	{
 		float inv_value = Math.InverseLerp(threshold_low, threshold_high, hc_value);
-		float value_min = Math.Lerp(low_min, high_min,inv_value);
-		float value_max = Math.Lerp(low_max,high_max,inv_value);
+		float value_min = Math.Lerp(low_min, high_min, inv_value);
+		float value_max = Math.Lerp(low_max, high_max, inv_value);
 
-		return Math.RandomFloatInclusive(value_min,value_max);
+		return Math.RandomFloatInclusive(value_min, value_max);
 	}
 	
 	
@@ -48,19 +48,14 @@ class HeatComfortAnimHandler
 	{
 		if( GetGame().IsServer() )
 		{
-			
 			float hc = m_Player.GetStatHeatComfort().Get();
-			float inv_value;
-			float value_min;
-			float value_max;
-			float offset_time;
 			
-			if ( hc <= PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_CRITICAL )
+			if ( hc <= PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_CRITICAL )		// Deep blue zone, rattling sounds are layerd on top of freezing sounds
 			{
 				m_ProcessTimeAccuFreezeRattle++;
 				
 				if (m_EventTimeFreezeRattle < 0)
-					m_EventTimeFreezeRattle = GetEventTime(hc, -1 ,PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_CRITICAL, TIME_INTERVAL_HC_MINUS_LOW_MIN, TIME_INTERVAL_HC_MINUS_HIGH_MIN, TIME_INTERVAL_HC_MINUS_LOW_MAX, TIME_INTERVAL_HC_MINUS_HIGH_MAX);
+					m_EventTimeFreezeRattle = GetEventTime(hc, -1, PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_CRITICAL, TIME_INTERVAL_HC_MINUS_LOW_MIN, TIME_INTERVAL_HC_MINUS_HIGH_MIN, TIME_INTERVAL_HC_MINUS_LOW_MAX, TIME_INTERVAL_HC_MINUS_HIGH_MAX);
 				
 				if( m_ProcessTimeAccuFreezeRattle > m_EventTimeFreezeRattle )
 				{
@@ -70,13 +65,13 @@ class HeatComfortAnimHandler
 				}
 			}
 			
-			if ( hc <= PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING )
+			if ( hc <= PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING )		// Light blue zone
 			{
 				m_ProcessTimeAccuFreeze++;
 				
-				if(m_EventTimeFreeze < 0)//if not set
+				if(m_EventTimeFreeze < 0)	//if not set
 				{
-					m_EventTimeFreeze = GetEventTime(hc, PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_EMPTY,PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING, TIME_INTERVAL_HC_MINUS_LOW_MIN, TIME_INTERVAL_HC_MINUS_HIGH_MIN, TIME_INTERVAL_HC_MINUS_LOW_MAX, TIME_INTERVAL_HC_MINUS_HIGH_MAX);
+					m_EventTimeFreeze = GetEventTime(hc, PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_EMPTY, PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING, TIME_INTERVAL_HC_MINUS_LOW_MIN, TIME_INTERVAL_HC_MINUS_HIGH_MIN, TIME_INTERVAL_HC_MINUS_LOW_MAX, TIME_INTERVAL_HC_MINUS_HIGH_MAX);
 				}
 				
 				if( m_ProcessTimeAccuFreeze > m_EventTimeFreeze )
@@ -84,21 +79,13 @@ class HeatComfortAnimHandler
 					m_ProcessTimeAccuFreeze = 0;
 					m_EventTimeFreeze = -1;
 					m_Player.GetSymptomManager().QueueUpPrimarySymptom(SymptomIDs.SYMPTOM_FREEZE);
-					/*
-					Print("-----======== freezing ========-------");
-					Print(inv_value);
-					Print(value_min);
-					Print(value_max);
-					Print(offset_time);
-					Print("-----======== freezing ========-------");
-					*/
 				}	
 			}
-			else if ( hc >= PlayerConstants.THRESHOLD_HEAT_COMFORT_PLUS_WARNING )
+			else if ( hc >= PlayerConstants.THRESHOLD_HEAT_COMFORT_PLUS_WARNING )	// Yellow zone
 			{
 				m_ProcessTimeAccuHot++;
 				
-				if(m_EventTimeHot < 0)//if not set
+				if(m_EventTimeHot < 0)	//if not set
 				{
 					m_EventTimeHot = GetEventTime(hc, PlayerConstants.THRESHOLD_HEAT_COMFORT_PLUS_EMPTY,PlayerConstants.THRESHOLD_HEAT_COMFORT_PLUS_WARNING, TIME_INTERVAL_HC_PLUS_LOW_MIN, TIME_INTERVAL_HC_PLUS_LOW_MIN, TIME_INTERVAL_HC_PLUS_LOW_MAX, TIME_INTERVAL_HC_PLUS_HIGH_MAX);
 				}
@@ -107,15 +94,7 @@ class HeatComfortAnimHandler
 				{
 					m_ProcessTimeAccuHot = 0;
 					m_EventTimeHot = -1;
-					m_Player.GetSymptomManager().QueueUpPrimarySymptom(SymptomIDs.SYMPTOM_HOT);
-					
-					//Print("-----======== freezing ========-------");
-					//Print(inv_value);
-					//Print(value_min);
-					//Print(value_max);
-					//Print(offset_time);
-					//Print("-----======== freezing ========-------");
-					
+					m_Player.GetSymptomManager().QueueUpPrimarySymptom(SymptomIDs.SYMPTOM_HOT);					
 				}	
 			}
 		}

+ 11 - 0
Scripts/4_world/classes/playergearspawn/cfgplayerspawnhandler.c

@@ -55,6 +55,17 @@ class PlayerSpawnHandler
 		}
 		
 		return m_Data.presets.Get(weightedPresetIndexes.GetRandomElement());
+	}
+	
+	static PlayerSpawnPreset GetCharacterPresetByName(string presetName)
+	{
+		foreach (PlayerSpawnPreset preset : m_Data.presets)
+		{
+			if (preset.IsValid() && preset.name == presetName)
+				return preset;
+		}
+		
+		return null;
 	} 
 	
 	//! equips character with the chosen preset

+ 2 - 2
Scripts/4_world/classes/playermodifiers/modifiers/diseases/salmonella.c

@@ -34,12 +34,12 @@ class SalmonellaMdfr: ModifierBase
 	
 	override protected bool ActivateCondition(PlayerBase player)
 	{
-		return player.GetSingleAgentCount(eAgents.SALMONELLA) >= AGENT_THRESHOLD_ACTIVATE);
+		return player.GetSingleAgentCount(eAgents.SALMONELLA) >= AGENT_THRESHOLD_ACTIVATE;
 	}
 	
 	override protected bool DeactivateCondition(PlayerBase player)
 	{
-		return player.GetSingleAgentCount(eAgents.SALMONELLA) <= AGENT_THRESHOLD_DEACTIVATE);
+		return player.GetSingleAgentCount(eAgents.SALMONELLA) <= AGENT_THRESHOLD_DEACTIVATE;
 	}
 
 	override protected void OnActivate(PlayerBase player)

+ 57 - 22
Scripts/4_world/classes/recipes/recipebase.c

@@ -2,12 +2,28 @@ const int MAX_NUMBER_OF_INGREDIENTS = 2;
 const int MAXIMUM_RESULTS = 10;
 const float DEFAULT_SPAWN_DISTANCE = 0.6;
 
+class RecipeAnimationInfo
+{
+	string m_IngredientName;
+	int m_AnimationUID;
+	bool m_ItemVisible;
+	
+	void RecipeAnimationInfo(string ingredient, int animationID, bool itemVisible)
+	{
+		m_IngredientName = ingredient;
+		m_AnimationUID = animationID;
+		m_ItemVisible = itemVisible;
+	}
+}
+
 class RecipeBase
 {
+	protected const int BASE_CRAFT_ANIMATION_ID = DayZPlayerConstants.CMD_ACTIONFB_CRAFTING;
+	
 	string m_ItemsToCreate[MAXIMUM_RESULTS];
 	ref array<string> m_Ingredients[MAX_NUMBER_OF_INGREDIENTS];
 	ref array<string> m_SoundCategories[MAX_NUMBER_OF_INGREDIENTS];
-	protected ref array<int> m_AnimationUIDs = new array<int>();	// used for overriding animation based on ingredient
+	protected ref array<ref RecipeAnimationInfo> m_AnimationInfos = new array<ref RecipeAnimationInfo>();	// used for overriding animation based on ingredient
 	
 	ItemBase m_Items[MAX_NUMBER_OF_INGREDIENTS];
 	
@@ -18,7 +34,7 @@ class RecipeBase
 	
 	int	m_ID;
 	int m_NumberOfResults;
-	int m_RecipeUID;
+	int m_RecipeUID;//obsolete
 	float m_AnimationLength = 1;//animation length in relative time units
 	float m_Specialty = 0;// value > 0 for roughness, value < 0 for precision
 	bool m_IsInstaRecipe;//should this recipe be performed instantly without animation
@@ -65,7 +81,7 @@ class RecipeBase
 		m_NumberOfResults = 0;
 		
 		m_Name = "RecipeBase default name";
-		m_RecipeUID = DayZPlayerConstants.CMD_ACTIONFB_CRAFTING;
+		m_RecipeUID = BASE_CRAFT_ANIMATION_ID;
 		Init();
 	}
 	
@@ -140,19 +156,27 @@ class RecipeBase
 		}
 	}
 
-	void InsertIngredient(int index, string ingredient, DayZPlayerConstants uid = DayZPlayerConstants.CMD_ACTIONFB_CRAFTING)
+	void InsertIngredient(int index, string ingredient, DayZPlayerConstants uid = BASE_CRAFT_ANIMATION_ID, bool showItem = false)
 	{
-		InsertIngredientEx(index, ingredient, "", uid);
+		InsertIngredientEx(index, ingredient, "", uid, showItem);
 	}
 	
-	void InsertIngredientEx(int index, string ingredient, string soundCategory, DayZPlayerConstants uid = DayZPlayerConstants.CMD_ACTIONFB_CRAFTING)
+	void InsertIngredientEx(int index, string ingredient, string soundCategory, DayZPlayerConstants uid = BASE_CRAFT_ANIMATION_ID, bool showItem = false)
 	{
 		array<string> ptr = m_Ingredients[index];
 		ptr.Insert(ingredient);
 		m_SoundCategories[index].Insert(soundCategory);
-		if(index == 0)
+
+		if(uid != BASE_CRAFT_ANIMATION_ID)
 		{
-			m_AnimationUIDs.Insert(uid);
+			RecipeAnimationInfo rai = new RecipeAnimationInfo(ingredient, uid, showItem);
+			int animationIndex;
+			for(animationIndex = 0; animationIndex < m_AnimationInfos.Count(); animationIndex++)
+			{
+				if(GetGame().IsKindOf(ingredient, m_AnimationInfos[animationIndex].m_IngredientName))
+					break;
+			}
+			m_AnimationInfos.InsertAt(rai, animationIndex);
 		}
 	}
 	
@@ -477,16 +501,6 @@ class RecipeBase
 		return false;
 	}
 	
-	protected void CheckIngredientAnimOverride()
-	{
-		array<string> tempArray = m_Ingredients[0];
-		for ( int i; i < m_AnimationUIDs.Count(); i++ )
-		{
-			if (m_IngredientsSorted[0].ClassName() == tempArray[i] || m_IngredientsSorted[1].ClassName() == tempArray[i])
-				SetAnimation(m_AnimationUIDs[i]);
-		}
-	}
-	
 	void OnSelectedRecipe(ItemBase item1, ItemBase item2, PlayerBase player)
 	{
 		if (item1 == NULL || item2 == NULL)
@@ -623,12 +637,33 @@ class RecipeBase
 		}
 		return mask;
 	}
+
+	int GetAnimationCommandUID()//obsolete
+	{
+		return BASE_CRAFT_ANIMATION_ID;
+	}
 	
-	int GetAnimationCommandUID()
+	RecipeAnimationInfo GetRecipeAnimationInfo(PlayerBase player, ItemBase mainItem, ItemBase target)
 	{
-		if (m_AnimationUIDs.Count() > 0)
-			CheckIngredientAnimOverride();
+		RecipeAnimationInfo recipeAnimationInfo;
 		
-		return m_RecipeUID;
+		int found = false;
+		
+		for(int i = 0; i < m_AnimationInfos.Count();i++)
+		{
+			recipeAnimationInfo = m_AnimationInfos[i];
+			if(GetGame().IsKindOf(mainItem.GetType(), recipeAnimationInfo.m_IngredientName))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if(!found)
+		{
+			recipeAnimationInfo = new RecipeAnimationInfo("ItemBase", BASE_CRAFT_ANIMATION_ID, false);
+		}
+
+		return recipeAnimationInfo;
 	}
 }

+ 8 - 20
Scripts/4_world/classes/recipes/recipes/cleanweapon.c

@@ -1,4 +1,4 @@
-class CleanWeapon extends RecipeBase
+class CleanWeapon : RecipeBase
 {	
 	override void Init()
 	{
@@ -24,7 +24,7 @@ class CleanWeapon extends RecipeBase
 		
 		//INGREDIENTS
 		//ingredient 1
-		InsertIngredient(0,"WeaponCleaningKit",DayZPlayerConstants.CMD_ACTIONFB_CLEANING_WEAPON);//you can insert multiple ingredients this way
+		InsertIngredient(0,"WeaponCleaningKit");//you can insert multiple ingredients this way
 		
 		m_IngredientAddHealth[0] = 0;// 0 = do nothing
 		m_IngredientSetHealth[0] = -1; // -1 = do nothing
@@ -33,7 +33,7 @@ class CleanWeapon extends RecipeBase
 		m_IngredientUseSoftSkills[0] = true;// set 'true' to allow modification of the values by softskills on this ingredient
 		
 		//ingredient 2
-		InsertIngredient(1,"DefaultWeapon");
+		InsertIngredient(1,"DefaultWeapon",DayZPlayerConstants.CMD_ACTIONFB_CLEANING_WEAPON, true);
 		InsertIngredient(1,"DefaultMagazine");
 		InsertIngredient(1,"ItemSuppressor");
 		
@@ -46,30 +46,18 @@ class CleanWeapon extends RecipeBase
 
 	override bool CanDo(ItemBase ingredients[], PlayerBase player)//final check for recipe's validity
 	{
-		PluginRepairing module_repairing;
-		Class.CastTo(module_repairing, GetPlugin(PluginRepairing));
-		ItemBase ingredient1;
-		Class.CastTo(ingredient1, ingredients[0]);
-		ItemBase ingredient2;
-		Class.CastTo(ingredient2, ingredients[1]);
-		return module_repairing.CanRepair(ingredient1,ingredient2);
+		PluginRepairing moduleRepairing = PluginRepairing.Cast(GetPlugin(PluginRepairing));
+		return moduleRepairing.CanRepair(ingredients[0], ingredients[1]);
 	}
 
 	override void Do(ItemBase ingredients[], PlayerBase player,array<ItemBase> results, float specialty_weight)//gets called upon recipe's completion
 	{
-		PluginRepairing module_repairing;
-		Class.CastTo(module_repairing, GetPlugin(PluginRepairing));
-		PlayerBase playerPB;
-		Class.CastTo(playerPB, player);
-		ItemBase ingredient1;
-		Class.CastTo(ingredient1, ingredients[0]);
-		ItemBase ingredient2;
-		Class.CastTo(ingredient2, ingredients[1]);
-		module_repairing.Repair(playerPB, ingredient1,ingredient2,m_Specialty);
+		PluginRepairing moduleRepairing = PluginRepairing.Cast(GetPlugin(PluginRepairing));
+		moduleRepairing.Repair(player, ingredients[0], ingredients[1], m_Specialty);
 	}
 	
 	override bool IsRepeatable()
 	{
 		return true;
 	}
-};
+}

+ 2 - 2
Scripts/4_world/classes/recipes/recipes/craftwoodenplank.c

@@ -28,8 +28,8 @@ class CraftWoodenPlank extends RecipeBase
 		m_IngredientDestroy[0]			= false;		// true = destroy, false = do nothing
 		
 		//ingredient 2
-		InsertIngredient(1,"Hacksaw");
-		InsertIngredient(1,"HandSaw");
+		InsertIngredient(1,"Hacksaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HandSaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
 		
 		m_IngredientAddHealth[1]		= -7;
 		m_IngredientSetHealth[1]		= -1;

+ 3 - 3
Scripts/4_world/classes/recipes/recipes/patchitem.c

@@ -24,9 +24,9 @@ class PatchItem extends RecipeBase
 		
 		//INGREDIENTS
 		//ingredient 1
-		InsertIngredient(0,"LeatherSewingKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_LEATHER_SEWING_KIT);//you can insert multiple ingredients this way
-		InsertIngredient(0,"SewingKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_SEWING);//you can insert multiple ingredients this way
-		InsertIngredient(0,"TireRepairKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_TIRE);//you can insert multiple ingredients this way
+		InsertIngredient(0,"LeatherSewingKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_LEATHER_SEWING_KIT, false);//you can insert multiple ingredients this way
+		InsertIngredient(0,"SewingKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_SEWING, false);//you can insert multiple ingredients this way
+		InsertIngredient(0,"TireRepairKit", DayZPlayerConstants.CMD_ACTIONFB_PATCHING_TIRE, false);//you can insert multiple ingredients this way
 		
 		m_IngredientAddHealth[0] = 0;// 0 = do nothing
 		m_IngredientSetHealth[0] = -1; // -1 = do nothing

+ 3 - 3
Scripts/4_world/classes/recipes/recipes/pluginrecipesmanagerbase.c

@@ -2,7 +2,7 @@ enum Ingredient
 {
 	FIRST = 1,
 	SECOND = 2,
-	BOTH = 3;
+	BOTH = 3,
 }
 
 class PluginRecipesManagerBase extends PluginBase
@@ -38,8 +38,8 @@ class PluginRecipesManagerBase extends PluginBase
 		RegisterRecipe(new CraftSuppressor);
 		RegisterRecipe(new CleanWeapon);
 		RegisterRecipe(new RepairWithTape);
-		RegisterRecipe(new RepairWithRags);
-		RegisterRecipe(new RepairEyePatch);
+		//RegisterRecipe(new RepairWithRags);
+		//RegisterRecipe(new RepairEyePatch);
 		RegisterRecipe(new CraftBoneKnife);
 		//RegisterRecipe(new CraftArrow);
 		//RegisterRecipe(new CraftArrowBone);

+ 1 - 1
Scripts/4_world/classes/recipes/recipes/repairwithtape.c

@@ -24,7 +24,7 @@ class RepairWithTape extends RecipeBase
 		
 		//INGREDIENTS
 		//ingredient 1
-		InsertIngredient(0,"DuctTape",DayZPlayerConstants.CMD_ACTIONFB_PATCHING_DUCTTAPE);//you can insert multiple ingredients this way
+		InsertIngredient(0,"DuctTape",DayZPlayerConstants.CMD_ACTIONFB_PATCHING_DUCTTAPE, false);//you can insert multiple ingredients this way
 		
 		m_IngredientAddHealth[0] = 0;// 0 = do nothing
 		m_IngredientSetHealth[0] = -1; // -1 = do nothing

+ 5 - 5
Scripts/4_world/classes/recipes/recipes/sawwoodenlog.c

@@ -20,11 +20,11 @@ class SawWoodenLog extends RecipeBase
 		//----------------------------------------------------------------------------------------------------------------------
 		//INGREDIENTS
 		//ingredient 1
-		InsertIngredient(0,"Hacksaw");
-		InsertIngredient(0,"HandSaw");
-		InsertIngredient(0,"WoodAxe",DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD);
-		InsertIngredient(0,"Hatchet",DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD);
-		InsertIngredient(0,"FirefighterAxe",DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD);
+		InsertIngredient(0,"Hacksaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(0,"HandSaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(0,"WoodAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(0,"Hatchet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(0,"FirefighterAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
 
 		m_IngredientAddHealth[0]		= -10;
 		m_IngredientSetHealth[0]		= -1;

+ 26 - 26
Scripts/4_world/classes/recipes/recipes/splitbroom.c

@@ -34,32 +34,32 @@ class SplitBroom extends RecipeBase
 		m_IngredientUseSoftSkills[0] = false;// set 'true' to allow modification of the values by softskills on this ingredient
 		
 		//ingredient 2
-		InsertIngredient(1,"Sickle");//you can insert multiple ingredients this way
-		InsertIngredient(1,"KukriKnife");
-		InsertIngredient(1,"FangeKnife");
-		InsertIngredient(1,"Hacksaw");
-		InsertIngredient(1,"HandSaw");
-		InsertIngredient(1,"KitchenKnife");
-		InsertIngredient(1,"SteakKnife");
-		InsertIngredient(1,"HayHook");
-		InsertIngredient(1,"StoneKnife");
-		InsertIngredient(1,"Cleaver");
-		InsertIngredient(1,"CombatKnife");
-		InsertIngredient(1,"HuntingKnife");
-		InsertIngredient(1,"Machete");
-		InsertIngredient(1,"CrudeMachete");
-		InsertIngredient(1,"OrientalMachete");
-		InsertIngredient(1,"Crowbar");
-		InsertIngredient(1,"Pickaxe");
-		InsertIngredient(1,"WoodAxe");
-		InsertIngredient(1,"Hatchet");
-		InsertIngredient(1,"FirefighterAxe");
-		InsertIngredient(1,"Sword");
-		InsertIngredient(1,"AK_Bayonet");
-		InsertIngredient(1,"M9A1_Bayonet");
-		InsertIngredient(1,"Mosin_Bayonet");
-		InsertIngredient(1,"SKS_Bayonet");
-		InsertIngredient(1,"BoneKnife");	
+		InsertIngredient(1,"Sickle", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);//you can insert multiple ingredients this way
+		InsertIngredient(1,"KukriKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"FangeKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Hacksaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HandSaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"KitchenKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"SteakKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HayHook", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"StoneKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Cleaver", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"CombatKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HuntingKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Machete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"CrudeMachete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"OrientalMachete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Crowbar", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Pickaxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"WoodAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Hatchet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"FirefighterAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Sword", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"AK_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"M9A1_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Mosin_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"SKS_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"BoneKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);	
 		
 		m_IngredientAddHealth[1] = -4;// 0 = do nothing
 		m_IngredientSetHealth[1] = -1; // -1 = do nothing

+ 7 - 7
Scripts/4_world/classes/recipes/recipes/splitfirewood.c

@@ -20,7 +20,7 @@ class SplitFirewood extends RecipeBase
 		//----------------------------------------------------------------------------------------------------------------------
 		//INGREDIENTS
 		//ingredient 1
-		InsertIngredient(0,"Firewood",DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD);	// you can insert multiple ingredients this way
+		InsertIngredient(0,"Firewood");	// you can insert multiple ingredients this way
 
 		m_IngredientAddHealth[0]		= 0;				// 0 = do nothing
 		m_IngredientSetHealth[0]		= -1;				// -1 = do nothing
@@ -28,12 +28,12 @@ class SplitFirewood extends RecipeBase
 		m_IngredientDestroy[0]			= false;			// true = destroy, false = do nothing
 
 		//ingredient 2
-		InsertIngredientEx(1,"Hacksaw"  , 		 "FirewoodSplit_Saw");
-		InsertIngredientEx(1,"HandSaw"  , 		 "FirewoodSplit_Saw");
-		InsertIngredientEx(1,"Pickaxe"  , 		 "FirewoodSplit_Axe");
-		InsertIngredientEx(1,"WoodAxe"  , 		 "FirewoodSplit_Axe");
-		InsertIngredientEx(1,"Hatchet"  ,  		 "FirewoodSplit_Axe");
-		InsertIngredientEx(1,"FirefighterAxe" ,  "FirewoodSplit_Axe");
+		InsertIngredientEx(1,"Hacksaw"  , 		 "FirewoodSplit_Saw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredientEx(1,"HandSaw"  , 		 "FirewoodSplit_Saw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredientEx(1,"Pickaxe"  , 		 "FirewoodSplit_Axe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredientEx(1,"WoodAxe"  , 		 "FirewoodSplit_Axe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredientEx(1,"Hatchet"  ,  		 "FirewoodSplit_Axe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredientEx(1,"FirefighterAxe" ,  "FirewoodSplit_Axe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
 
 		m_IngredientAddHealth[1]		= -6;
 		m_IngredientSetHealth[1]		= -1;

+ 26 - 26
Scripts/4_world/classes/recipes/recipes/splitlongwoodenstick.c

@@ -34,32 +34,32 @@ class SplitLongWoodenStick extends RecipeBase
 		m_IngredientUseSoftSkills[0] = false;// set 'true' to allow modification of the values by softskills on this ingredient
 		
 		//ingredient 2
-		InsertIngredient(1,"Sickle");//you can insert multiple ingredients this way
-		InsertIngredient(1,"KukriKnife");
-		InsertIngredient(1,"FangeKnife");
-		InsertIngredient(1,"Hacksaw");
-		InsertIngredient(1,"HandSaw");
-		InsertIngredient(1,"KitchenKnife");
-		InsertIngredient(1,"SteakKnife");
-		InsertIngredient(1,"HayHook");
-		InsertIngredient(1,"StoneKnife");
-		InsertIngredient(1,"Cleaver");
-		InsertIngredient(1,"CombatKnife");
-		InsertIngredient(1,"HuntingKnife");
-		InsertIngredient(1,"Machete");
-		InsertIngredient(1,"CrudeMachete");
-		InsertIngredient(1,"OrientalMachete");
-		InsertIngredient(1,"Crowbar");
-		InsertIngredient(1,"Pickaxe");
-		InsertIngredient(1,"WoodAxe");
-		InsertIngredient(1,"Hatchet");
-		InsertIngredient(1,"FirefighterAxe");
-		InsertIngredient(1,"Sword");
-		InsertIngredient(1,"AK_Bayonet");
-		InsertIngredient(1,"M9A1_Bayonet");
-		InsertIngredient(1,"Mosin_Bayonet");
-		InsertIngredient(1,"SKS_Bayonet");
-		InsertIngredient(1,"BoneKnife");		
+		InsertIngredient(1,"Sickle", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);//you can insert multiple ingredients this way
+		InsertIngredient(1,"KukriKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"FangeKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Hacksaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HandSaw", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"KitchenKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"SteakKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HayHook", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"StoneKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Cleaver", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"CombatKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"HuntingKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Machete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"CrudeMachete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"OrientalMachete", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Crowbar", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Pickaxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"WoodAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Hatchet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"FirefighterAxe", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Sword", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"AK_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"M9A1_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"Mosin_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"SKS_Bayonet", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);
+		InsertIngredient(1,"BoneKnife", DayZPlayerConstants.CMD_ACTIONFB_SPLITTING_FIREWOOD, true);		
 		
 		m_IngredientAddHealth[1] = -4;// 0 = do nothing
 		m_IngredientSetHealth[1] = -1; // -1 = do nothing

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/ak101recoil.c

@@ -15,7 +15,7 @@ class Ak101Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 45;//in degrees min
 		m_MouseOffsetRangeMax = 110;//in degrees max

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/ak74recoil.c

@@ -15,7 +15,7 @@ class Ak74Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 45;//in degrees min
 		m_MouseOffsetRangeMax = 110;//in degrees max

+ 2 - 2
Scripts/4_world/classes/recoilbase/recoils/akmrecoil.c

@@ -15,11 +15,11 @@ class AkmRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 45;//in degrees min
 		m_MouseOffsetRangeMax = 110;//in degrees max
-		m_MouseOffsetDistance = 1.6;//how far should the mouse travel
+		m_MouseOffsetDistance = 1.8;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.5;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
 		m_CamOffsetDistance = 0.0125;

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/aks74urecoil.c

@@ -15,7 +15,7 @@ class Aks74uRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 45;//in degrees min
 		m_MouseOffsetRangeMax = 110;//in degrees max

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/augrecoil.c

@@ -15,7 +15,7 @@ class AUGRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 75;//in degrees min
 		m_MouseOffsetRangeMax = 125;//in degrees max

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/falrecoil.c

@@ -15,7 +15,7 @@ class FALRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 65;//in degrees min
 		m_MouseOffsetRangeMax = 145;//in degrees max

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/famasrecoil.c

@@ -15,7 +15,7 @@ class FamasRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 80;//in degrees min
 		m_MouseOffsetRangeMax = 150;//in degrees max

+ 2 - 2
Scripts/4_world/classes/recoilbase/recoils/m16a2recoil.c

@@ -15,11 +15,11 @@ class M16A2Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 50;//in degrees min
 		m_MouseOffsetRangeMax = 120;//in degrees max
-		m_MouseOffsetDistance = 1.0;//how far should the mouse travel
+		m_MouseOffsetDistance = 1.2;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.5;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
 		m_CamOffsetDistance = 0.01;

+ 2 - 2
Scripts/4_world/classes/recoilbase/recoils/m4a1recoil.c

@@ -15,11 +15,11 @@ class M4a1Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 50;//in degrees min
 		m_MouseOffsetRangeMax = 120;//in degrees max
-		m_MouseOffsetDistance = 1.4;//how far should the mouse travel
+		m_MouseOffsetDistance = 1.5;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.5;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
 		m_CamOffsetDistance = 0.01;

+ 2 - 2
Scripts/4_world/classes/recoilbase/recoils/mp5krecoil.c

@@ -15,11 +15,11 @@ class Mp5kRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 0.35;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 60;//in degrees min
 		m_MouseOffsetRangeMax = 145;//in degrees max
-		m_MouseOffsetDistance = 1.25;//how far should the mouse travel
+		m_MouseOffsetDistance = 1;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.4;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
 		m_CamOffsetDistance = 0.0075;

+ 28 - 0
Scripts/4_world/classes/recoilbase/recoils/pm73rak.c

@@ -0,0 +1,28 @@
+class PM73RakRecoil: RecoilBase
+{
+	override void Init()
+	{
+		vector point_1;
+		vector point_2;
+		vector point_3;
+		vector point_4;
+		point_1[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,0.33,1.25); point_1[1] = 1.25; point_1[2] = 0;
+		point_2[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-1,1); point_2[1] = 1.75; point_2[2] = 0;
+		point_3[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-0.5,-1.5); point_3[1] = 1.5; point_3[2] = 0;
+		point_4[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-0.5,-0.1); point_4[1] = 1; point_4[2] = 0;
+		m_HandsCurvePoints.Insert(point_1);//forms a 2 dimensional spline(z is ignored)
+		m_HandsCurvePoints.Insert(point_2);
+		m_HandsCurvePoints.Insert(point_3);
+		m_HandsCurvePoints.Insert(point_4);
+		m_HandsCurvePoints.Insert("0 0 0");
+		m_HandsOffsetRelativeTime = 0.5;
+		
+		m_MouseOffsetRangeMin = 45;//in degrees min
+		m_MouseOffsetRangeMax = 100;//in degrees max
+		m_MouseOffsetDistance = 0.8;//how far should the mouse travel
+		m_MouseOffsetRelativeTime = 0.35;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
+	
+		m_CamOffsetDistance = 0.01;
+		m_CamOffsetRelativeTime = 1;
+	}
+}

+ 5 - 5
Scripts/4_world/classes/recoilbase/recoils/pp19recoil.c

@@ -15,14 +15,14 @@ class PP19Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.35;
 		
 		m_MouseOffsetRangeMin = 60;//in degrees min
 		m_MouseOffsetRangeMax = 125;//in degrees max
-		m_MouseOffsetDistance = 1.2;//how far should the mouse travel
+		m_MouseOffsetDistance = 0.75;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.4;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
-		m_CamOffsetDistance = 0.0075;
-		m_CamOffsetRelativeTime = 1;
+		m_CamOffsetDistance = 0.004;
+		m_CamOffsetRelativeTime = 0.5;
 	}
-}
+}	

+ 28 - 0
Scripts/4_world/classes/recoilbase/recoils/r12recoil.c

@@ -0,0 +1,28 @@
+class R12Recoil: RecoilBase
+{
+	override void Init()
+	{
+		vector point_1;
+		vector point_2;
+		vector point_3;
+		vector point_4;
+		point_1[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,1.0,1.5); point_1[1] = 3; point_1[2] = 0;
+		point_2[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-0.5,0.5); point_2[1] = 4; point_2[2] = 0;
+		point_3[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-1.5,-2); point_3[1] = 1; point_3[2] = 0;
+		point_4[0] = m_Player.GetRandomGeneratorSyncManager().GetRandomInRange(RandomGeneratorSyncUsage.RGSRecoil,-0.75,-0.25); point_4[1] = 0.75; point_4[2] = 0;
+		m_HandsCurvePoints.Insert(point_1);//forms a 2 dimensional spline(z is ignored)
+		m_HandsCurvePoints.Insert(point_2);
+		m_HandsCurvePoints.Insert(point_3);
+		m_HandsCurvePoints.Insert(point_4);
+		m_HandsCurvePoints.Insert("0 0 0");
+		m_HandsOffsetRelativeTime = 0.3;
+		
+		m_MouseOffsetRangeMin = 70;//in degrees min
+		m_MouseOffsetRangeMax = 110;//in degrees max
+		m_MouseOffsetDistance = 2.75;//how far should the mouse travel
+		m_MouseOffsetRelativeTime = 0.15;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
+	
+		m_CamOffsetDistance = 0.05; //0.0125;
+		m_CamOffsetRelativeTime = 0.5;
+	}
+}

+ 4 - 4
Scripts/4_world/classes/recoilbase/recoils/skorpionrecoil.c

@@ -15,14 +15,14 @@ class Cz61Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.2;
 		
 		m_MouseOffsetRangeMin = 45;//in degrees min
 		m_MouseOffsetRangeMax = 100;//in degrees max
-		m_MouseOffsetDistance = 0.75;//how far should the mouse travel
+		m_MouseOffsetDistance = 0.7;//how far should the mouse travel
 		m_MouseOffsetRelativeTime = 0.35;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
-		m_CamOffsetDistance = 0.0075;
-		m_CamOffsetRelativeTime = 1;
+		m_CamOffsetDistance = 0.0035;
+		m_CamOffsetRelativeTime = 0.5;
 	}
 }

+ 1 - 1
Scripts/4_world/classes/recoilbase/recoils/sksrecoil.c

@@ -15,7 +15,7 @@ class SKSRecoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 1;
+		m_HandsOffsetRelativeTime = 0.5;
 		
 		m_MouseOffsetRangeMin = 50;//in degrees min
 		m_MouseOffsetRangeMax = 120;//in degrees max

+ 5 - 5
Scripts/4_world/classes/recoilbase/recoils/ump45recoil.c

@@ -15,14 +15,14 @@ class Ump45Recoil: RecoilBase
 		m_HandsCurvePoints.Insert(point_3);
 		m_HandsCurvePoints.Insert(point_4);
 		m_HandsCurvePoints.Insert("0 0 0");
-		m_HandsOffsetRelativeTime = 0.4;
+		m_HandsOffsetRelativeTime = 0.3;
 		
 		m_MouseOffsetRangeMin = 55;//in degrees min
 		m_MouseOffsetRangeMax = 120;//in degrees max
-		m_MouseOffsetDistance = 1.2;//how far should the mouse travel
-		m_MouseOffsetRelativeTime = 0.4;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
+		m_MouseOffsetDistance = 0.8;//how far should the mouse travel
+		m_MouseOffsetRelativeTime = 0.3;//[0..1] a time it takes to move the mouse the required distance relative to the reload time of the weapon(firing mode)
 	
-		m_CamOffsetDistance = 0.0075;
-		m_CamOffsetRelativeTime = 1;
+		m_CamOffsetDistance = 0.005;
+		m_CamOffsetRelativeTime = 0.5;
 	}
 }

+ 107 - 88
Scripts/4_world/classes/shockhandler.c

@@ -1,95 +1,83 @@
 class ShockHandler
 {	
+	protected const float 					UPDATE_THRESHOLD = 3; //NOTE : The lower, the more precise but the more synchronization
+	const float 							VALUE_CHECK_INTERVAL = 0.95; //in seconds
+	private const int 						INTENSITY_FACTOR = 1; //How intense the vignette effect will be, the higher the value, the stronger the effect
+	private const int 						VIGNETTE_INTENSITY_MAX = 1; //Max BASE intensity of the vignette effect (w/o. bobbing). 0..2, where 2 is full screen coverage
+	private const int 						VIGNETTE_INTENSITY_MAX_TOTAL = 2; //Max TOTAL intensity of the vignette effect (w. bobbing). 0..2, where 2 is full screen coverage
+	private const float						SMOOTHING_MAX_INCR = 0.05; //max smoothing change INCREASE per update
+	private const float						SMOOTHING_MAX_DECR = 0.01; //max smoothing change DECREASE per update
+	//bobbing constants
+	private const float 					PULSE_PERIOD = 0.5; //The time it takes for pulse to do a full cycle
+	private const float 					PULSE_AMPLITUDE = 0.05; //This is a multiplier, keep below 1 or expect the unexpected
+	
 	protected float 						m_Shock;
+	protected float 						m_LastEffectIntensityValue; //for interpolation only
 	protected float 						m_ShockValueMax;
-	protected float 						m_ShockValueThreshold;
-	protected PlayerBase					m_Player;
-	
-	protected const float 					UPDATE_THRESHOLD = 3; //NOTE : The lower, the more precise but the more synchronization
-	const float 							VALUE_CHECK_INTERVAL = 0.95; //in seconds 
-	protected float 						m_CumulatedShock;
+	protected float 						m_CumulatedShock; //! works ok on server, but does nothing for client. See deprecated stuff.
 	private float							m_TimeSinceLastTick = VALUE_CHECK_INTERVAL + 1;
 	private float							m_ShockMultiplier = 1;
-	private float 							m_PrevVignette; //Previous vignette shock value
-	private	float 							m_LerpRes; //Lerp result
+	private float 							m_PrevVignette; //Previous shock-adjecent value (some normalization required). Client sets it only on regular ShockHandler update!
 	
-	private const int						LIGHT_SHOCK_HIT = 33;
-	private const int						MID_SHOCK_HIT = 67;
-	private const int						HEAVY_SHOCK_HIT = 100;
-	private const int 						INTENSITY_FACTOR = 1; //How intense the vignette effect will be, the higher the value, the stronger the effect
-	
-	//Pulsing effect
-	private const float 					PULSE_PERIOD = 0.5; //The time it takes for pulse to do a full cycle
-	private const float 					PULSE_AMPLITUDE = 0.05; //This is a multiplier, keep below 1 or expect the unexpected
-	private 	  float 					m_PulseTimer;
+	//bobbing effect
+	private float 							m_PulseTimer;
 	
+	//PPE
+	PPERequester_TunnelVisionEffects 		m_Requester;
 	protected ref Param1<float> 			m_Param;
 	
+	protected PlayerBase					m_Player;
+	
 	void ShockHandler(PlayerBase player)
 	{
 		m_Player = player;
 		m_Player.m_CurrentShock = m_Player.GetMaxHealth("", "Shock");
-		m_PrevVignette = m_Player.m_CurrentShock * 0.01; //Equivalent to divided by 100
 		m_ShockValueMax = m_Player.GetMaxHealth("", "Shock");
-		m_ShockValueThreshold = m_ShockValueMax * 0.95;
+		m_PrevVignette = m_Player.m_CurrentShock / m_ShockValueMax; //Equivalent to divided by 100
+		m_Requester = PPERequester_TunnelVisionEffects.Cast(PPERequesterBank.GetRequester(PPERequester_TunnelVisionEffects));
 		m_Param = new Param1<float>(0);
+		
+		//loegacy stuff
+		m_ShockValueThreshold = m_ShockValueMax * 0.95;
 	}
 
 	void Update(float deltaT)
 	{
 		m_TimeSinceLastTick += deltaT;
-		
 		m_PulseTimer += deltaT;
 		
-		if ( GetGame().IsClient() )
+		//periodical update, just in case
+		if (m_TimeSinceLastTick > VALUE_CHECK_INTERVAL)
 		{
-			//Deactivate tunnel vignette when player falls unconscious
-			if ( m_Player.IsUnconscious() )
-			{
-				PPERequesterBank.GetRequester(PPERequester_TunnelVisionEffects).Stop();
-				return;
-			}
-			
-			//Deactivate if above visible threshold (also stops "zero bobbing" being sent all the time
-			if ( m_Player.m_CurrentShock >= m_ShockValueThreshold)
+			if (GetGame().IsClient())
 			{
-				PPERequesterBank.GetRequester(PPERequester_TunnelVisionEffects).Stop();
-				return;
+				//ShockHitEffect(m_PrevVignette * m_ShockValueMax);
+				m_PrevVignette = m_Player.m_CurrentShock / m_ShockValueMax;
 			}
 			
-			//Add bobbing to create pulsing effect
-			float val = 0.0;
-			if ( m_Player.m_CurrentShock > m_ShockValueMax * 0.8)
-				val = MiscGameplayFunctions.Bobbing( PULSE_PERIOD, PULSE_AMPLITUDE, m_PulseTimer );
-			float val_adjusted;
-			
-			if ( m_Player.m_CurrentShock != (m_PrevVignette * 100) )
-			{
-				//Interpolate between previous level and currently synchronized shock level
-				m_LerpRes = LerpVignette( m_PrevVignette, NormalizeShockVal(m_Player.m_CurrentShock), m_TimeSinceLastTick );
-				
-				val_adjusted = 1 - Easing.EaseInQuart(m_LerpRes) + val;
-			}
-			else
-			{
-				val_adjusted = 1 - Easing.EaseInQuart( NormalizeShockVal(m_Player.m_CurrentShock)) + val;
-			}
-			
-			m_Param.param1 = val_adjusted;
-			PPERequesterBank.GetRequester(PPERequester_TunnelVisionEffects).Start(m_Param);
+			CheckValue(false);
+			m_TimeSinceLastTick = 0;
 		}
 		
-		if ( m_TimeSinceLastTick > VALUE_CHECK_INTERVAL )
+		if (GetGame().IsClient())
 		{
-			//Play the shock hit event (multiply prevVignette by 100 to "Un-Normalize" value)
-			if ( GetGame().IsClient() )
+			float valAdjusted = BaseEffectIntensityCalc();
+			
+			if (valAdjusted <= 0)
 			{
-				ShockHitEffect( m_PrevVignette * 100 );
-				m_PrevVignette = m_Player.m_CurrentShock * 0.01;
+				if (m_Requester.IsRequesterRunning())
+					m_Requester.Stop();
+				
+				return;
 			}
 			
-			CheckValue( false );
-			m_TimeSinceLastTick = 0;
+			//Print("dbgShock | valAdjusted: " + valAdjusted);
+			
+			//Add bobbing to create pulsing effect
+			valAdjusted = AddEffectBobbing(valAdjusted);
+			
+			m_Param.param1 = valAdjusted;
+			m_Requester.Start(m_Param);
 		}
 	}
 	
@@ -103,17 +91,17 @@ class ShockHandler
 		return m_Shock;
 	}
 	
-	void SetShock( float dealtShock )
+	void SetShock(float dealtShock)
 	{
 		m_Shock += dealtShock;
-		CheckValue( false );
+		CheckValue(false);
 	}
 	
 	//Inflict shock damage
 	private void DealShock()
 	{
-		if ( GetGame().IsServer() )
-			m_Player.GiveShock( -m_CumulatedShock );
+		if (GetGame().IsServer())
+			m_Player.GiveShock(-m_CumulatedShock);
 	}
 	
 	//Passing a value of FALSE will only check the values, a value of TRUE will force a synchronization (don't use too often)
@@ -121,18 +109,12 @@ class ShockHandler
 	{
 		m_CumulatedShock += m_Shock; // increment on both client and server
 		
-		if ( forceUpdate )
-			m_PrevVignette = NormalizeShockVal( m_Player.m_CurrentShock );
+		if (forceUpdate)
+			m_PrevVignette = NormalizeShockVal(m_Player.m_CurrentShock);
 		
-		if ( GetGame().IsServer() )
-		{	
-			m_Player.m_CurrentShock = m_Player.GetHealth("", "Shock");
-			
-			/*
-			if (m_Player.m_CurrentShock <= 0)
-				m_Player.SetHealthMax("", "Shock");
-			*/
-			if ( m_CumulatedShock >= UPDATE_THRESHOLD || forceUpdate )
+		if (GetGame().IsServer())
+		{
+			if (m_CumulatedShock >= UPDATE_THRESHOLD || forceUpdate)
 			{
 				m_CumulatedShock *= m_ShockMultiplier;
 				DealShock();
@@ -141,12 +123,41 @@ class ShockHandler
 			
 				Synchronize();
 			}
+			
+			m_Player.m_CurrentShock = m_Player.GetHealth("", "Shock");
 		}
 	}
 	
 	protected void Synchronize()
 	{
-		DayZPlayerSyncJunctures.SendShock( m_Player, m_Player.m_CurrentShock );
+		DayZPlayerSyncJunctures.SendShock(m_Player, m_Player.m_CurrentShock);
+	}
+	
+	protected float BaseEffectIntensityCalc()
+	{
+		float effectIntensity = 1 - Easing.EaseInQuart(NormalizeShockVal(m_Player.m_CurrentShock));
+		
+		//smoothing
+		if (effectIntensity != m_LastEffectIntensityValue)
+			effectIntensity = Math.Clamp(effectIntensity,m_LastEffectIntensityValue - SMOOTHING_MAX_DECR, m_LastEffectIntensityValue + SMOOTHING_MAX_INCR);
+		
+		m_LastEffectIntensityValue = effectIntensity;
+		
+		return effectIntensity;
+	}
+	
+	//! adds bobbing, also clamps to valid range
+	protected float AddEffectBobbing(float baseVal)
+	{
+		float ret = baseVal;
+		float bobbingVal = 0.0;
+		
+		if (m_Player.m_CurrentShock > m_ShockValueMax * 0.8)
+			bobbingVal = MiscGameplayFunctions.Bobbing(PULSE_PERIOD, PULSE_AMPLITUDE, m_PulseTimer);
+		ret += bobbingVal;
+		ret = Math.Clamp(ret,0,VIGNETTE_INTENSITY_MAX_TOTAL);
+		
+		return ret;
 	}
 	
 	float SetMultiplier(float mult)
@@ -155,33 +166,41 @@ class ShockHandler
 		return m_ShockMultiplier;
 	}
 	
-	private float NormalizeShockVal( float shock )
+	private float NormalizeShockVal(float shock)
 	{
-		float base = m_Player.GetMaxHealth("", "Shock") * INTENSITY_FACTOR;
+		float base = m_ShockValueMax * INTENSITY_FACTOR;
 		float normShock = shock / base;
 		return normShock;
 	}
 	
-	private float LerpVignette( float x, float y, float deltaT )
+	private float LerpVignette(float x, float y, float deltaT)
 	{
 		float output;
-		output = Math.Lerp( x, y, deltaT );
+		output = Math.Lerp(x, y, deltaT);
 		return output;
 	}
 	
-	//ONLY CLIENT SIDE
-	private void ShockHitEffect( float compareBase )
+	///////////////////////////
+	//Deprecated stuff below//
+	/////////////////////////
+	private const int						LIGHT_SHOCK_HIT = 33; //!Deprecated
+	private const int						MID_SHOCK_HIT = 67; //!Deprecated
+	private const int						HEAVY_SHOCK_HIT = 100; //!Deprecated
+	protected float 						m_ShockValueThreshold; //!Deprecated
+	private	float 							m_LerpRes; //!Deprecated
+	
+	//!Deprecated
+	private void ShockHitEffect(float compareBase)
 	{
 		float shockDifference = compareBase - m_Player.m_CurrentShock;
-		//Print(shockDifference);
-		if ( shockDifference >= UPDATE_THRESHOLD )
+		if (shockDifference >= UPDATE_THRESHOLD)
 		{
-			if ( m_CumulatedShock < 25 )
-				m_Player.SpawnShockEffect( MiscGameplayFunctions.Normalize( LIGHT_SHOCK_HIT, 100 ) ); 
-			else if ( m_CumulatedShock < 50 )
-				m_Player.SpawnShockEffect( MiscGameplayFunctions.Normalize( MID_SHOCK_HIT, 100 ) );
+			if (m_CumulatedShock < 25)
+				m_Player.SpawnShockEffect(MiscGameplayFunctions.Normalize(LIGHT_SHOCK_HIT, 100)); 
+			else if (m_CumulatedShock < 50)
+				m_Player.SpawnShockEffect(MiscGameplayFunctions.Normalize(MID_SHOCK_HIT, 100));
 			else
-				m_Player.SpawnShockEffect( MiscGameplayFunctions.Normalize( HEAVY_SHOCK_HIT, 100 ) );
+				m_Player.SpawnShockEffect(MiscGameplayFunctions.Normalize(HEAVY_SHOCK_HIT, 100));
 		}
 	}
 };

+ 1 - 1
Scripts/4_world/classes/soundevents/playersoundevents/playersoundeventhandler.c

@@ -46,7 +46,7 @@ class PlayerSoundEventHandler extends SoundEventHandler
 	PlayerBase m_Player;
 	const int SOUND_EVENTS_MAX = EPlayerSoundEventID.ENUM_COUNT;
 	static ref PlayerSoundEventBase m_AvailableStates[SOUND_EVENTS_MAX];
-	static ref map<int,int> m_ConfigIDToScriptIDmapping = new ref map<int,int> ;
+	static ref map<int,int> m_ConfigIDToScriptIDmapping = new map<int,int> ;
 	ref PlayerSoundEventBase m_CurrentState;
 	ref Timer m_UpdateTimer;
 	

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels