DIFF

Environment.c

/**
 * \brief Categories that are changing behavior of Heat comfort processing
 */
enum EEnvironmentHeatcomfortBehaviorCategory
{
	DEFAULT,
	CAR_ENGINE_ON
}

class EnvironmentSnapshotData
{
	float m_ClothingHeatComfort;
	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;
	
	protected const float SNOWFALL_LIMIT_LOW				= 0.5;
	protected const float SNOWFALL_WIND_COMBINED_THRESHOLD 	= 1.3;

	const float WATER_LEVEL_HIGH	= 1.5;
	const float WATER_LEVEL_MID 	= 1.2;
	const float WATER_LEVEL_LOW 	= 0.5;
	const float WATER_LEVEL_NONE	= 0.15;
	
	protected float					m_WetDryTick; //ticks passed since last clothing wetting or drying
	protected float					m_ItemsWetnessMax; //! keeps wetness of most wet item in player's possesion
	protected float					m_RoofCheckTimer; // keeps info about tick time

	//player
	protected PlayerBase 			m_Player;
	protected float					m_PlayerHeightPos; // y position of player above water level (meters)
	protected float 				m_PlayerSpeed; // 1-3 speed of player movement
	protected float 				m_PlayerTemperature; //34-44
	protected float 				m_PlayerHeat; //3-9 heatcomfort generated by entites movement
	protected float					m_HeatComfort;  		//! player's heatcomfort (buffered, stored in player stats)
	protected float					m_TargetHeatComfort;  	//! target value of heatcomfort (non-buffered)

	//environment
	protected float 				m_Rain = 0; // 0-1 amount of rain 
	protected float 				m_Snowfall = 0; // 0-1 amount of rain 
	protected float 				m_Wind = 0; // strength of wind
	protected float 				m_Fog = 0; // 0-1 how foggy it is
	protected float 				m_DayOrNight = 0; // 0-1 day(0) or night(1)
	protected float 				m_Clouds = 0; // 0-1 how cloudy it is
	protected float 				m_EnvironmentTemperature; //temperature of environment player is in
	protected float					m_Time = 0;
	protected string 				m_SurfaceType;
	protected int 					m_LiquidType;
	
	//item temperatures
	protected float 				m_ItemTemperatureCoef;
	
	//
	protected float					m_WaterLevel;
	protected bool 					m_IsUnderRoof;
	private bool					m_IsUnderRoofBuilding;
	protected bool 					m_IsInWater;
	protected bool					m_IsTempSet;
	//
	protected float 				m_HeatBufferTimer; //! reused as state toggle
	protected float 				m_HeatBufferCapPrevious;
	
	protected ref array<int> 		m_SlotIdsComplete;
	protected ref array<int> 		m_SlotIdsUpper;
	protected ref array<int> 		m_SlotIdsBottom;
	protected ref array<int> 		m_SlotIdsLower;
	
	protected ref array<int>		m_HeadParts;
	protected ref array<int>		m_BodyParts;
	protected ref array<int>		m_FeetParts;
	
	protected WorldData 			m_WorldData;

	protected bool m_HasTemperatureSources;
	protected float m_UTSAverageTemperature;
	protected ref array<UTemperatureSource> m_UTemperatureSources;
	protected ref SimpleMovingAverage<float> m_UTSAverageTemperatureBuffer;
	protected ref SimpleMovingAverage<float> m_AverageHeatComfortBuffer;
	
	protected int m_HeatComfortBehaviorCategory;
	
	private bool m_Initialized;
	
	#ifdef DIAG_DEVELOPER
	bool m_Debug = false;
	#endif

	#ifdef ENABLE_LOGGING
	bool m_DebugLogDryWet = false;
	bool m_DebugLogItemHeat = false;
	#endif
		
	void Environment(PlayerBase pPlayer)
	{
		m_Player = pPlayer;
	}
	
	void Init()
	{
		m_PlayerSpeed					= 0.0;
		m_WetDryTick					= 0.0;
		m_RoofCheckTimer				= 0.0;
		m_WaterLevel					= 0.0;
		m_HeatComfort					= m_Player.GetStatHeatComfort().Get();
		m_WorldData						= g_Game.GetMission().GetWorldData();
		m_EnvironmentTemperature		= m_WorldData.GetBaseEnvTemperatureAtObject(m_Player);
		
		m_IsUnderRoof					= false;
		m_IsInWater						= false;
		m_SurfaceType					= "cp_dirt";

		m_HeatBufferTimer 				= 0.0;
		
		m_UTSAverageTemperature			= 0.0;
		m_UTemperatureSources 			= new array<UTemperatureSource>();
		m_UTSAverageTemperatureBuffer	= new SimpleMovingAverage<float>(10, 0.0);
		m_AverageHeatComfortBuffer		= new SimpleMovingAverage<float>(20, m_HeatComfort);
		
		//! whole body slots		
		m_SlotIdsComplete 		= new array<int>();		
		m_SlotIdsComplete 		= {
			InventorySlots.HEADGEAR,
			InventorySlots.MASK,
			InventorySlots.EYEWEAR,
			InventorySlots.GLOVES,
			InventorySlots.ARMBAND,
			InventorySlots.BODY,
			InventorySlots.HIPS,
			InventorySlots.VEST,
			InventorySlots.BACK,
			InventorySlots.LEGS,
			InventorySlots.FEET
		};
		//! upper body part slots
		m_SlotIdsUpper 			= new array<int>();
		m_SlotIdsUpper 			= {
			InventorySlots.GLOVES,
			InventorySlots.ARMBAND,
			InventorySlots.BODY,
			InventorySlots.HIPS,
			InventorySlots.VEST,
			InventorySlots.BACK,
			InventorySlots.LEGS,
			InventorySlots.FEET
		};
		//! bottom body part slots
		m_SlotIdsBottom 		= new array<int>();
		m_SlotIdsBottom			= {
			InventorySlots.HIPS,
			InventorySlots.LEGS,
			InventorySlots.FEET
		};
		//! lower body part slots
		m_SlotIdsLower 			= new array<int>();
		m_SlotIdsLower			= {
			InventorySlots.FEET,
		};

		//! --------------------------
		//! heat comfort related slots
		m_HeadParts				= new array<int>();
		m_HeadParts				= {
			InventorySlots.HEADGEAR,
			InventorySlots.MASK,
		};
		
		m_BodyParts				= new array<int>();
		m_BodyParts				= {
			InventorySlots.GLOVES,
			InventorySlots.HIPS,
			InventorySlots.BODY,
			InventorySlots.BACK,
			InventorySlots.VEST,
			InventorySlots.MELEE,
			InventorySlots.SHOULDER
		};
		
		m_FeetParts				= new array<int>();
		m_FeetParts				= {
			InventorySlots.LEGS,
			InventorySlots.FEET,
		};
		
		m_HeatComfortBehaviorCategory 	= EEnvironmentHeatcomfortBehaviorCategory.DEFAULT;
		m_EnvironmentSnapshot 			= new EnvironmentSnapshotData();
		
		m_Initialized = true;
	}

	// --------------------------------------------------------------------------------

	/**
	 * \brief Main loop that runs calculations for various Environment parts (wetness, heatcomfort, watercontact, etc.)
	 */
	void Update(float pDelta)
	{
		if (m_Player && m_Initialized)
		{
			m_RoofCheckTimer += pDelta;
			//! check if player is under roof (only if the Building check is false)
			if ( m_RoofCheckTimer >= GameConstants.ENVIRO_TICK_ROOF_RC_CHECK )
			{
				if ( !IsInsideBuilding() )
					CheckUnderRoof();
				
				m_RoofCheckTimer = 0;
			}

			m_Time += pDelta;
			if (m_Time >= GameConstants.ENVIRO_TICK_RATE)
			{
				m_Time = 0;
				m_WetDryTick++; // Sets whether it is time to add wetness to items and clothing

				//! Updates data
				CheckWaterContact(m_WaterLevel);
				CollectAndSetPlayerData();
				CollectAndSetEnvironmentData();

				GatherTemperatureSources();
				ProcessTemperatureSources();
				
				//! Process temperatures
				ProcessItemsTemperature(m_HeadParts);
				ProcessItemsTemperature(m_BodyParts);
				ProcessItemsTemperature(m_FeetParts);
				ProcessItemsInHandsTemperature();

				//! heat comfort calculation
				if (DetermineHeatcomfortBehavior())
					SetHeatcomfortDirectly();
				else
					ProcessHeatComfort();

				//! Process item wetness/dryness
				if (m_WetDryTick >= GameConstants.ENVIRO_TICKS_TO_WETNESS_CALCULATION)
				{
					if (IsWaterContact())
					{
						ProcessWetnessByWaterLevel(m_WaterLevel);
					}
					else if ((IsRaining() || (IsSnowing() && MiscGameplayFunctions.GetCombinedSnowfallWindValue() > SNOWFALL_WIND_COMBINED_THRESHOLD)) && !IsInsideBuilding() && !IsUnderRoof() && !IsChildOfType({Car}))
					{
						ProcessItemsWetness(m_SlotIdsComplete);
					}
					else
					{
						ProcessItemsDryness();
					}

					//! setting of wetness/dryiness of player
					if ((m_ItemsWetnessMax < GameConstants.STATE_WET) && (m_Player.GetStatWet().Get() == 1))
					{
						m_Player.GetStatWet().Set(0);
					}
					else if ((m_ItemsWetnessMax >= GameConstants.STATE_WET) && (m_Player.GetStatWet().Get() == 0))
					{
						m_Player.GetStatWet().Set(1);
					}

					m_WetDryTick = 0;
					m_ItemsWetnessMax = 0; //! reset item wetness counter;
				}
			}
		}
	}

	// --------------------------------------------------------------------------------

	/**
	 * \brief For safe-guards, waiting for the proper temperature to be set
	 */	
	bool IsTemperatureSet()
	{
		return m_IsTempSet;
	}

	/**
	 * \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;
	}

	/**
	 * \brief Is character inside building? (periodically checked - GameConstants.ENVIRO_TICK_ROOF_RC_CHECK).
	 */	
	bool IsInsideBuilding()
	{
		return m_Player.IsSoundInsideBuilding();
	}
	
	/**
	 * \brief Is character in contact with water body? (periodically checked - GameConstants.ENVIRO_TICK_RATE).
	 */
	protected bool IsWaterContact()
	{
		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());
		if (parent)
			return parent.IsAnyInherited(typenames);
		
		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}))
		{
			CarScript car = CarScript.Cast(m_Player.GetParent());
			if (car && car.EngineIsOn())
			{
				m_HeatComfortBehaviorCategory = EEnvironmentHeatcomfortBehaviorCategory.CAR_ENGINE_ON;
				return true;
			}
		}
		
		m_HeatComfortBehaviorCategory = EEnvironmentHeatcomfortBehaviorCategory.DEFAULT;
		
		return false;
	}
	
	// --------------------------------------------------------------------------------
	
	/**
	 * \brief Checks whether character is sheltered and sets the information
	 */
	protected void CheckUnderRoof()
	{
		//! if inside vehicle return immediatelly
		if (IsChildOfType({Car}))
		{
			m_IsUnderRoof = false;
			m_IsUnderRoofBuilding = false;
			return;
		}
		
		float hitFraction;
		vector hitPosition, hitNormal;
		vector from = m_Player.GetPosition();
		vector to = from + "0 25 0";
		Object hitObject;
		PhxInteractionLayers collisionLayerMask = PhxInteractionLayers.ITEM_LARGE|PhxInteractionLayers.BUILDING|PhxInteractionLayers.VEHICLE;
		
		m_IsUnderRoof = DayZPhysics.RayCastBullet(from, to, collisionLayerMask, null, hitObject, hitPosition, hitNormal, hitFraction);

		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;
		int liquidType;

		m_IsInWater = false;
		
		if (m_Player.PhysicsGetLinkedEntity() || IsChildOfType({Transport}))
		{
			m_Player.SetInWater(m_IsInWater);
			return;
		}
		
		if (m_Player.IsSwimming())
		{
			g_Game.SurfaceUnderObjectByBoneCorrectedLiquid(m_Player, SurfaceAnimationBone.RightFrontLimb, surfType, liquidType);
			m_SurfaceType = surfType;
			m_LiquidType = liquidType;
			
			m_IsInWater = true;
			m_Player.SetInWater(m_IsInWater);
			
			HumanMovementState hms = new HumanMovementState();
			m_Player.GetMovementState(hms);
			
			pWaterLevel = WATER_LEVEL_MID;
			if (hms.m_iMovement >= DayZPlayerConstants.MOVEMENTIDX_WALK)
				pWaterLevel = WATER_LEVEL_HIGH;

			return;
		}
		
		if (m_Player.IsClimbingLadder())
		{
			pWaterLevel = 0.0;
			m_IsInWater = false;

			return;			
		}
		
		//! no valid surface under character
		if (IsUnderRoofBuilding())
		{
			m_IsInWater = false;
			return;
		}
		
		string impact;
		g_Game.SurfaceUnderObjectExCorrectedLiquid(m_Player, surfType, impact, liquidType);
		
		switch (liquidType)
		{
			case LIQUID_SALTWATER:
			case LIQUID_WATER:
			case LIQUID_RIVERWATER:
			case LIQUID_FRESHWATER:
			case LIQUID_STILLWATER:
			case LIQUID_HOTWATER:
				pWaterLevel = Math.Max(m_Player.GetCurrentWaterLevel(), 0.0);
				m_IsInWater = true;
			break;
		}

		//! sync info about water contact to player
		m_Player.SetInWater(m_IsInWater);

		//! update active surface
		m_SurfaceType = surfType;
		m_LiquidType = liquidType;

	}
	
	// --------------------------------------------------------------------------------

	/**
	 * \brief Wind intensity (influence) modifier of temperature value
	 *
	 * \return float modifier used in temperature calculations
	 */
	float GetWindModifierPerSurface()
	{
		if (IsUnderRoofBuilding())
			return 0.0;

		return g_Game.ConfigGetFloat("CfgSurfaces " + m_SurfaceType + " windModifier");
	}
	
	float GetTemperature()
	{
		return m_EnvironmentTemperature;
	}

	float GetTargetHeatComfort()
	{
		return m_TargetHeatComfort;
	}
	
	// --------------------------------------------------------------------------------
	
	/**
	 * \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);
		
		if (IsWaterContact())
		{
			float waterBodyTemperature = m_WorldData.GetLiquidTypeEnviroTemperature(m_LiquidType);
			temperature = waterBodyTemperature - m_WorldData.m_WaterContactTemperatureModifier;
			
			return temperature;
		}
		
		if (IsInsideBuilding() || m_IsUnderRoofBuilding)
		{
			temperature += m_WorldData.m_TemperatureInsideBuildingsModifier;
		}
		else if (IsChildOfType({Car}))
		{
			temperature += Math.AbsFloat(temperature * GameConstants.ENVIRO_TEMPERATURE_INSIDE_VEHICLE_COEF);
			return temperature;
		}
		else if (IsUnderRoof() && !m_IsUnderRoofBuilding)
		{
			temperature = m_WorldData.GetTemperature(m_Player, EEnvironmentTemperatureComponent.ALTITUDE | EEnvironmentTemperatureComponent.OVERCAST|EEnvironmentTemperatureComponent.FOG);
			temperature += WindEffectTemperatureValue(temperature) * GetWindModifierPerSurface() * GameConstants.ENVIRO_TEMPERATURE_UNDERROOF_COEF;
		}
		else
		{
			temperature = m_WorldData.GetTemperature(m_Player, EEnvironmentTemperatureComponent.ALTITUDE | EEnvironmentTemperatureComponent.OVERCAST|EEnvironmentTemperatureComponent.FOG);
			temperature += m_WorldData.GetTemperatureComponentValue(temperature, EEnvironmentTemperatureComponent.WIND) * GetWindModifierPerSurface();
		}

		float temperaturesDiff = temperature - m_UTSAverageTemperature;
		if (temperaturesDiff * (temperaturesDiff - temperature) > 0.0)
			temperature = m_UTSAverageTemperature;
		
		return temperature;
	}
	
	// --------------------------------------------------------------------------------

	/**
	 * \brief Calculates soaking/drying delta based on character's location and weather
	 *
	 * \return Resulting wet delta modifier
	 */
	float GetWetDelta()
	{
		float wetDelta = 0;
		if ( IsWaterContact() )
		{
			//! player is getting wet by movement/swimming in water (+differentiate wetDelta by water level)
			if (m_WaterLevel >= WATER_LEVEL_HIGH)
			{
				wetDelta = 1;
			}
			else if (m_WaterLevel >= WATER_LEVEL_MID && m_WaterLevel < WATER_LEVEL_HIGH)
			{
				wetDelta = 0.66;
			}
			else if (m_WaterLevel >= WATER_LEVEL_LOW && m_WaterLevel < WATER_LEVEL_MID)
			{
				wetDelta = 0.66;
			}
			else if (m_WaterLevel >= WATER_LEVEL_NONE && m_WaterLevel < WATER_LEVEL_LOW)
			{
				wetDelta = 0.33;
			}
		}
		else if (!IsInsideBuilding() && !IsUnderRoof() && !IsChildOfType({Car}))
		{
			if (IsRaining())
				wetDelta = GameConstants.ENVIRO_WET_INCREMENT * GameConstants.ENVIRO_TICKS_TO_WETNESS_CALCULATION * (m_Rain) * (1 + (GameConstants.ENVIRO_WIND_EFFECT * m_Wind));

			if (IsSnowing() && MiscGameplayFunctions.GetCombinedSnowfallWindValue() > SNOWFALL_WIND_COMBINED_THRESHOLD)
				wetDelta = GameConstants.ENVIRO_WET_INCREMENT * GameConstants.ENVIRO_TICKS_TO_WETNESS_CALCULATION * (m_Snowfall - SNOWFALL_LIMIT_LOW) * GameConstants.ENVIRO_SNOW_WET_COEF * (1 + (GameConstants.ENVIRO_WIND_EFFECT * m_Wind));
		}
		else
		{
			//! player is drying
			float tempEffect = Math.Max(m_PlayerHeat + GetEnvironmentTemperature(), 1.0);

			float weatherEffect = ((1 - (m_Fog * GameConstants.ENVIRO_FOG_DRY_EFFECT))) * (1 - (m_Clouds * GameConstants.ENVIRO_CLOUD_DRY_EFFECT));
			if (weatherEffect <= 0)
			{
				weatherEffect = 1.0;
			}
			
			wetDelta = -(GameConstants.ENVIRO_DRY_INCREMENT * weatherEffect * tempEffect);
			if (!IsInsideBuilding())
			{
				wetDelta *= 1 + (GameConstants.ENVIRO_WIND_EFFECT * m_Wind);
			}
		}

		return wetDelta;
	}
	
	// --------------------------------------------------------------------------------

	/**
	 * \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();
		m_PlayerHeightPos = playerPos[1];
		
		m_PlayerSpeed = 0.0;

		HumanCommandMove hcm = m_Player.GetCommand_Move();
		if (hcm)
			m_PlayerSpeed = hcm.GetCurrentMovementSpeed();
		
		m_PlayerHeat = GetPlayerHeat();

	}
	
	// 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();
		m_Rain			= weather.GetRain().GetActual();
		m_Snowfall		= weather.GetSnowfall().GetActual();
		m_DayOrNight 	= g_Game.GetMission().GetWorldData().GetDaytime();
		m_Fog 			= weather.GetFog().GetActual();
		m_Clouds 		= weather.GetOvercast().GetActual();
		m_Wind 			= weather.GetWindMagnitude().GetActual();

		SetEnvironmentTemperature();
		SetAreaGenericColdness();
	}

	// --------------------------------------------------------------------------------
	
	void SetEnvironmentTemperature()
	{
		m_IsTempSet = true;
		m_EnvironmentTemperature = GetEnvironmentTemperature();
	}
	
	/**
	 * \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);
	}

	/**
	 * \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)
			ProcessItemsWetness(m_SlotIdsComplete);
		else if (pWaterLevel >= WATER_LEVEL_MID && pWaterLevel < WATER_LEVEL_HIGH)
			ProcessItemsWetness(m_SlotIdsUpper);
		else if (pWaterLevel >= WATER_LEVEL_LOW && pWaterLevel < WATER_LEVEL_MID)
			ProcessItemsWetness(m_SlotIdsBottom);
		else if (pWaterLevel >= WATER_LEVEL_NONE && pWaterLevel < WATER_LEVEL_LOW)
			ProcessItemsWetness(m_SlotIdsLower);
	}
	
	// --------------------------------------------------------------------------------

	/**
	 * \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);
			if (attachment.IsItemBase())
			{
				int attachmentSlotsCount = attachment.GetInventory().GetSlotIdCount();
				for (int attachmentSlotId = 0; attachmentSlotId < attachmentSlotsCount; ++attachmentSlotId)
				{
					int attachmentSlot = attachment.GetInventory().GetSlotId(attachmentSlotId);
					for (int i = 0; i < pSlotIds.Count(); ++i)
					{
						if (attachmentSlot == pSlotIds.Get(i))
						{
							ApplyWetnessToItem(ItemBase.Cast(attachment));
							break;
						}
					}
				}
			}
		}
		
		if (m_Player.GetItemInHands())
			ApplyWetnessToItem(m_Player.GetItemInHands());

		#ifdef ENABLE_LOGGING
		LogDryWetProcess("==========");
		#endif
	}

	protected void ApplyWetnessToItem(ItemBase pItem)
	{
		if (pItem)
		{
			ItemBase parentItem;
			bool isParentWet 			= false;
			bool parentContainsLiquid 	= false;
			InventoryLocation iLoc 		= new InventoryLocation();

			if (pItem.GetInventory().GetCurrentInventoryLocation(iLoc))
			{
				EntityAI parent = iLoc.GetParent();
				if (parent)
				{
					parentItem = ItemBase.Cast(parent);
					if (parentItem)
					{
						if (parentItem.GetWet() >= GameConstants.STATE_SOAKING_WET)
							isParentWet = true;

						if ((parentItem.GetLiquidType() != 0) && (parentItem.GetQuantity() > 0))
							parentContainsLiquid = true;
					}
					else
						isParentWet = true;
					
					if ((pItem.GetWet() > m_ItemsWetnessMax) && (parent == m_Player))
						m_ItemsWetnessMax = pItem.GetWet();
				}
			}

			if (isParentWet || parentContainsLiquid)
			{
				float soakingCoef = 0;
				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);

				if (pItem.GetInventory().GetCargo())
				{
					int inItemCount = pItem.GetInventory().GetCargo().GetItemCount();
					for (int i = 0; i < inItemCount; ++i)
					{
						ItemBase inItem;
						if (Class.CastTo(inItem, pItem.GetInventory().GetCargo().GetItem(i)))
							ApplyWetnessToItem(inItem);
					}
				}
				
				int attCount = pItem.GetInventory().AttachmentCount();
				if (attCount > 0)
				{
					for (int attIdx = 0; attIdx < attCount; ++attIdx)
					{
						EntityAI attachment = pItem.GetInventory().GetAttachmentFromIndex(attIdx);
						ItemBase itemAtt = ItemBase.Cast(attachment);
						if (itemAtt)
							ApplyWetnessToItem(itemAtt);
					}
				}
			}
		}
	}
	// --------------------------------------------------------------------------------

	/**
	 * \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)
	{
		EnvironmentDrynessData drynessData = new EnvironmentDrynessData();
		ApplyDrynessToItemEx(pItem, drynessData);
	}
	
	protected void ApplyDrynessToItemEx(ItemBase pItem, EnvironmentDrynessData pDrynessData)
	{
		if (pItem)
		{
			float dryingIncrement = pItem.GetDryingIncrement("player");
			if (pDrynessData.m_UseTemperatureSources)
				dryingIncrement = pItem.GetDryingIncrement("playerHeatSource");
			
			ItemBase parentItem;
			bool isParentWet 			= false;
			bool parentContainsLiquid 	= false;
	
			InventoryLocation iLoc = new InventoryLocation();
			if (pItem.GetInventory().GetCurrentInventoryLocation(iLoc))
			{
				EntityAI parent = iLoc.GetParent();
				if (parent)
				{
					parentItem = ItemBase.Cast(parent);
					if (parentItem)
					{
						if (parentItem.GetWet() >= GameConstants.STATE_SOAKING_WET)
							isParentWet = true;
	
						if ((parentItem.GetLiquidType() != 0) && (parentItem.GetQuantity() > 0))
							parentContainsLiquid = true;
					}
					
					if ((pItem.GetWet() > m_ItemsWetnessMax) && (parent == m_Player))
						m_ItemsWetnessMax = pItem.GetWet();
				}
			}
	
			float dryingCoef = 0;
			
			if (!isParentWet && !parentContainsLiquid)
			{
				
				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);			
				}
				
				if (pItem.GetInventory().GetCargo())
				{
					int inItemCount = pItem.GetInventory().GetCargo().GetItemCount();
					for (int i = 0; i < inItemCount; ++i)
					{
						ItemBase inItem;
						if (Class.CastTo(inItem, pItem.GetInventory().GetCargo().GetItem(i)))
							ApplyDrynessToItemEx(inItem, pDrynessData);
					}
				}
				
				int attCount = pItem.GetInventory().AttachmentCount();
				if (attCount > 0)
				{
					for (int attIdx = 0; attIdx < attCount; ++attIdx)
					{
						EntityAI attachment = pItem.GetInventory().GetAttachmentFromIndex(attIdx);
						ItemBase itemAtt;
						if (ItemBase.CastTo(itemAtt, attachment))
							ApplyDrynessToItemEx(itemAtt, pDrynessData);
					}
				}
			}
			
			if (parentContainsLiquid)
			{
				//! 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);
			}
	
			if (isParentWet)
			{
				//! 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);
			}
		}
	}

	// --------------------------------------------------------------------------------

	/**
	 * \brief Calculates and process player's heatcomfort related to defined body parts
	 */
	protected void ProcessHeatComfort()
	{
		float hcPenaltyTotal //! Heat Comfort Penalty
		
		// NEW body parts => splitted
		float hcBodyPartTotal, hcBodyPart;
		float hBodyPartTotal, hBodyPart;
		
		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);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.VEST, GameConstants.ENVIRO_HEATCOMFORT_VEST_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.BODY, GameConstants.ENVIRO_HEATCOMFORT_BODY_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.BACK, GameConstants.ENVIRO_HEATCOMFORT_BACK_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.GLOVES, GameConstants.ENVIRO_HEATCOMFORT_GLOVES_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.LEGS, GameConstants.ENVIRO_HEATCOMFORT_LEGS_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.FEET, GameConstants.ENVIRO_HEATCOMFORT_FEET_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		BodyPartHeatProperties(InventorySlots.HIPS, GameConstants.ENVIRO_HEATCOMFORT_HIPS_WEIGHT, hcBodyPart, hBodyPart);
		hcBodyPartTotal += hcBodyPart; hBodyPartTotal += hBodyPart;
		
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.HEADGEAR, GameConstants.ENVIRO_HEATCOMFORT_HEADGEAR_WEIGHT);
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.MASK, GameConstants.ENVIRO_HEATCOMFORT_MASK_WEIGHT);
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.BODY, GameConstants.ENVIRO_HEATCOMFORT_BODY_WEIGHT);
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.GLOVES, GameConstants.ENVIRO_HEATCOMFORT_GLOVES_WEIGHT);
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.LEGS, GameConstants.ENVIRO_HEATCOMFORT_LEGS_WEIGHT);
		hcPenaltyTotal += NakedBodyPartHeatComfortPenalty(InventorySlots.FEET, GameConstants.ENVIRO_HEATCOMFORT_FEET_WEIGHT);

		heatItems = hBodyPartTotal;
		heatComfortSum = hcBodyPartTotal;
		heatComfortSum += hcPenaltyTotal; //! heatcomfort body parts penalties

		//! Stomach temperature influence to heatcomfort		
		{
			if (m_Player.GetStomach().GetStomachVolume() > 0.0)
			{
				float stomachContentTemperature = m_Player.GetStomach().GetStomachTemperature();
				if (stomachContentTemperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT)
				{
					stomachContentTemperature = Math.Remap(
						-10.0,
						GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT,
						-GameConstants.ENVIRO_STOMACH_WEIGHT,
						0.0,
						stomachContentTemperature,
					);
				}
				else if (stomachContentTemperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
				{
					stomachContentTemperature = Math.Remap(
						GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT,
						70.0,
						0.0,
						GameConstants.ENVIRO_STOMACH_WEIGHT,
						stomachContentTemperature,
					);
				}
				else
					stomachContentTemperature = 0.0;

				heatComfortSum += stomachContentTemperature * GameConstants.ENVIRO_STOMACH_WEIGHT;
			}
		}
		
		float targetHeatComfort = (heatComfortSum + heatItems + (m_PlayerHeat / 100)) + EnvTempToCoef(m_EnvironmentTemperature);
				
		//! uses the raw targetHeatComfort data
		m_EnvironmentSnapshot.m_ClothingHeatComfort = hcBodyPartTotal;
		m_EnvironmentSnapshot.m_TargetHeatComfort 	= targetHeatComfort;
		ProcessHeatBuffer(m_EnvironmentSnapshot);
		
		if (m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
			targetHeatComfort = Math.Clamp(targetHeatComfort, 0.0, m_Player.GetStatHeatComfort().GetMax());
 		else
 			targetHeatComfort = Math.Clamp(targetHeatComfort, m_Player.GetStatHeatComfort().GetMin(), m_Player.GetStatHeatComfort().GetMax());

		targetHeatComfort = Math.Round(targetHeatComfort * 100) * 0.01;
		
		float dynamicHeatComfort;
		
		{
			float direction = 1.0;
			if (targetHeatComfort < 0.0)
				direction = -1.0;
			
			if (Math.AbsFloat(targetHeatComfort - m_HeatComfort) <= GameConstants.ENVIRO_HEATCOMFORT_MAX_STEP_SIZE)
				dynamicHeatComfort = m_AverageHeatComfortBuffer.Add(targetHeatComfort);
			else
				dynamicHeatComfort = m_AverageHeatComfortBuffer.Add((Math.AbsFloat(targetHeatComfort) - GameConstants.ENVIRO_HEATCOMFORT_MAX_STEP_SIZE) * direction);
		}
		
		dynamicHeatComfort = Math.Round(dynamicHeatComfort * 100) * 0.01;

		m_HeatComfort = dynamicHeatComfort;

		SetTargetHeatComfort(targetHeatComfort);
		m_Player.GetStatHeatComfort().Set(m_HeatComfort);
	}
	
	protected void SetTargetHeatComfort(float value)
	{
		m_TargetHeatComfort = value;
	}
	
	protected void SetHeatcomfortDirectly()
	{
		if (m_HeatComfortBehaviorCategory == EEnvironmentHeatcomfortBehaviorCategory.CAR_ENGINE_ON)
		{
 			float targetHeatComfort = 0.0;
 			float dynamicHeatComfort = m_AverageHeatComfortBuffer.Add(targetHeatComfort);
 			
 			m_HeatComfort = dynamicHeatComfort;
 
 			SetTargetHeatComfort(0.0);
 			m_Player.GetStatHeatComfort().Set(dynamicHeatComfort);
		}
	}
	
	// --------------------------------------------------------------------------------

	protected void ProcessHeatBuffer(EnvironmentSnapshotData data)
	{
		if (m_HeatComfortBehaviorCategory == EEnvironmentHeatcomfortBehaviorCategory.DEFAULT)
		{
			float applicableHeatbuffer = GetApplicableHeatbuffer();
			
			//! dynamic HB cap based on actual heatcomfort (from cloths)
			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 ));
				heatBuffer.Add(-heatBufferValueCorrection);
				m_HeatBufferCapPrevious = heatBufferCap;
			}
			
			float increaseRate = 0.0;
			float decreaseRate = 0.0;

			{
				increaseRate = GameConstants.ENVIRO_PLAYER_HEATBUFFER_INCREASE / (heatBufferMax * (( -GameConstants.ENVIRO_PLAYER_HEATBUFFER_TEMP_AFFECT * data.m_TargetHeatComfort) + 1 ));
				decreaseRate = GameConstants.ENVIRO_PLAYER_HEATBUFFER_DECREASE / (heatBufferMax * (( GameConstants.ENVIRO_PLAYER_HEATBUFFER_TEMP_AFFECT * data.m_TargetHeatComfort) + 1 ));				
				
				float decreaseRateByHeatBufferStageCoef = 1;
				
				if (heatBufferMax > HeatBufferMdfr.STAGE_THRESHOLDS[1])
				{
					float heatBufferMaxInversed = Math.InverseLerp(HeatBufferMdfr.STAGE_THRESHOLDS[1], 1.0, heatBufferMax);
					switch (m_Player.GetHeatBufferStage())
					{
						case 2:
							decreaseRateByHeatBufferStageCoef = Math.Lerp(
								GameConstants.ENVIRO_PLAYER_HEATBUFFER_STAGE_RATELIMIT[2][0],
								GameConstants.ENVIRO_PLAYER_HEATBUFFER_STAGE_RATELIMIT[2][1],
								heatBufferMaxInversed,
							);
							break;
						case 1:
							decreaseRateByHeatBufferStageCoef = Math.Lerp(
								GameConstants.ENVIRO_PLAYER_HEATBUFFER_STAGE_RATELIMIT[1][0],
								GameConstants.ENVIRO_PLAYER_HEATBUFFER_STAGE_RATELIMIT[1][1],
								heatBufferMaxInversed,
							);
							break;
					}
				}
				else
				{
					decreaseRateByHeatBufferStageCoef = GameConstants.ENVIRO_PLAYER_HEATBUFFER_STAGE_RATELIMIT[1][0];
				}
				
				decreaseRate *= decreaseRateByHeatBufferStageCoef;
				
				if (m_IsInWater)
					decreaseRate *= GameConstants.ENVIRO_PLAYER_HEATBUFFER_WATEREFFECT * m_WaterLevel;
			}

			if (!m_HasTemperatureSources)
			{
				if (m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
				{
					if (m_HeatBufferTimer >= 1.0)
						heatBuffer.Add(-decreaseRate);
					else
						m_HeatBufferTimer = 1.0;
				}
				else
				{
					m_HeatBufferTimer = 0.0;
					if (applicableHeatbuffer > 0.0)
						heatBuffer.Add(-decreaseRate);
					else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
						heatBuffer.Set(0.0);
				}			
			}
			else
			{
				if (m_HeatComfort > PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING && m_UTSAverageTemperature > 0) // m_UTSAverageTemperature can be negative
				{
					if (applicableHeatbuffer < heatBufferMax)
					{
						heatBuffer.Add(increaseRate);
						m_HeatBufferCapPrevious = heatBufferCap;
					}
				}
				else if (applicableHeatbuffer > 0.0)
					heatBuffer.Add(-decreaseRate);
				else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER))
					heatBuffer.Set(0.0);
	
				m_HeatBufferTimer = 0.0;
			}
		}
	}
	
	protected float GetApplicableHeatbuffer()
	{
		PlayerStat<float> heatBuffer = m_Player.GetStatHeatBuffer();
		float applicableHeatbuffer = Math.Round((heatBuffer.Get() / heatBuffer.GetMax()) * 1000) * 0.001;

		return applicableHeatbuffer;
	}
	
	// --------------------------------------------------------------------------------

	/**
	 * \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;
		ItemBase item;
		
		int attCount = m_Player.GetInventory().AttachmentCount();
		for (int attIdx = 0; attIdx < attCount; ++attIdx)
		{
			attachment = m_Player.GetInventory().GetAttachmentFromIndex(attIdx);
			item = ItemBase.Cast(attachment);
			int attachmentSlot = attachment.GetInventory().GetSlotId(0);
			
			//! go through all body parts we've defined for that zone (ex.: head, body, feet)
			for (int i = 0; i < pBodyPartIds.Count(); ++i)
			{
				if (attachmentSlot == pBodyPartIds[i])
				{
					float heatPermCoef = item.GetHeatPermeabilityCoef();
					//first handle the item itself, if necessary
					if (item.CanHaveTemperature() && !item.IsSelfAdjustingTemperature())
						SetProcessedItemTemperature(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();
		if (item)
		{
			float heatPermCoef = item.GetHeatPermeabilityCoef();
			//first handle the item itself, if necessary
			if (item.CanHaveTemperature() && !item.IsSelfAdjustingTemperature())
				SetProcessedItemTemperature(item,heatPermCoef);
			
			ProcessItemHierarchyRecursive(item,heatPermCoef);
		}
	}
	
	protected void ProcessItemHierarchyRecursive(ItemBase item, float heatPermeabilityCoef = 1.0)
	{
		float heatPermCoef = heatPermeabilityCoef;
		
		// go through any attachments and cargo, recursive
		int inventoryAttCount = item.GetInventory().AttachmentCount();
		if (inventoryAttCount > 0)
		{
			ItemBase attachmentItem;
			for (int inAttIdx = 0; inAttIdx < inventoryAttCount; ++inAttIdx)
			{
				if (Class.CastTo(attachmentItem,item.GetInventory().GetAttachmentFromIndex(inAttIdx)))
				{
					heatPermCoef = heatPermeabilityCoef;
					heatPermCoef *= attachmentItem.GetHeatPermeabilityCoef();
					
					if (attachmentItem.CanHaveTemperature() && !attachmentItem.IsSelfAdjustingTemperature())
					{
						SetProcessedItemTemperature(attachmentItem,heatPermCoef);
					}
					
					ProcessItemHierarchyRecursive(attachmentItem,heatPermCoef);
				}
			}
		}
		
		if (item.GetInventory().GetCargo())
		{
			int inventoryItemCount = item.GetInventory().GetCargo().GetItemCount();
			if (inventoryItemCount > 0)
			{
				ItemBase inventoryItem;
				for (int j = 0; j < inventoryItemCount; ++j)
				{
					if (Class.CastTo(inventoryItem,item.GetInventory().GetCargo().GetItem(j)))
					{
						heatPermCoef = heatPermeabilityCoef;
						heatPermCoef *= inventoryItem.GetHeatPermeabilityCoef();
						
						if (inventoryItem.CanHaveTemperature() && !inventoryItem.IsSelfAdjustingTemperature())
						{
							SetProcessedItemTemperature(inventoryItem,heatPermCoef);
						}
						
						ProcessItemHierarchyRecursive(inventoryItem,heatPermCoef);
					}
				}
			}
		}
	}
	
	protected void SetProcessedItemTemperature(ItemBase item, float heatPermeabilityCoef = 1.0)
	{
		float targetTemperature = GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_MIDDLE;
		bool globalCooling = true;
		//! swimming special behavior
		if (m_Player.IsSwimming())
		{
			SetItemHeatingCoef(GameConstants.TEMP_COEF_SWIMMING);
			targetTemperature = m_WorldData.GetLiquidTypeEnviroTemperature(m_LiquidType); 
			globalCooling = false;
		}

		if (item.GetTemperature() != targetTemperature || !item.IsFreezeThawProgressFinished())
		{
			TemperatureDataInterpolated temperatureData = new TemperatureDataInterpolated(
				targetTemperature,
				ETemperatureAccessTypes.ACCESS_INVENTORY,
				GameConstants.ENVIRO_TICK_RATE,
				m_ItemTemperatureCoef,
				heatPermeabilityCoef,
			);
			temperatureData.m_UseGlobalCooling = globalCooling;

			item.SetTemperatureEx(temperatureData);
		}
	}
	
	// --------------------------------------------------------------------------------
	
	protected float EnvTempToCoef(float pTemp)
	{
		return (pTemp - GameConstants.ENVIRO_PLAYER_COMFORT_TEMP) / GameConstants.ENVIRO_TEMP_EFFECT_ON_PLAYER;
	}

	/**
	 * \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;
		pHeat = 0;

		GameInventory playerInventory = m_Player.GetInventory();
		int attCount = playerInventory.AttachmentCount();
		for (int attIdx = 0; attIdx < attCount; ++attIdx)
		{
			EntityAI attachment = playerInventory.GetAttachmentFromIndex(attIdx);
			if (attachment.IsClothing())
			{
				ItemBase item = ItemBase.Cast(attachment);
				int attachmentSlot = attachment.GetInventory().GetSlotId(0);

				if (attachmentSlot == pBodyPartId)
				{
					#ifdef ENABLE_LOGGING
					LogItemHeat(string.Format("BodyPartHeatProperties (%1)", EnumTools.EnumToString(InventorySlots, pBodyPartId)));
					#endif

					float itemHeatcomfort = 0;
					float itemTemperature = 0;
					
					// go through any attachments and cargo (only current level, ignore nested containers - they isolate)
					GameInventory itemInventory = item.GetInventory();
					int inventoryAttCount = itemInventory.AttachmentCount();
					if (inventoryAttCount > 0)
					{
						#ifdef ENABLE_LOGGING
						LogItemHeat(string.Format("attachments:"), false);
						#endif
						for (int inAttIdx = 0; inAttIdx < inventoryAttCount; ++inAttIdx)
						{
							EntityAI inAttachment = itemInventory.GetAttachmentFromIndex(inAttIdx);
							ItemBase attachmentItem = ItemBase.Cast(inAttachment);
							if (attachmentItem && attachmentItem.CanHaveTemperature())
							{
								itemTemperature = attachmentItem.GetTemperature();
								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;
								}
							}
						}
					}
					
					CargoBase inventoryItemCargo = itemInventory.GetCargo();
					if (inventoryItemCargo)
					{
						int inventoryItemCount = inventoryItemCargo.GetItemCount();
						
						if (inventoryItemCount > 0)
						{
							#ifdef ENABLE_LOGGING
							LogItemHeat(string.Format("cargo:"), false);
							#endif
							for (int j = 0; j < inventoryItemCount; ++j)
							{
								ItemBase inventoryItem = ItemBase.Cast(inventoryItemCargo.GetItem(j));
								if (inventoryItem && inventoryItem.CanHaveTemperature())
								{
									itemTemperature = inventoryItem.GetTemperature();
									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;
									}
								}
							}
						}
					}
					
					pHeatComfort = MiscGameplayFunctions.GetCurrentItemHeatIsolation(item) * pCoef;
					
					#ifdef ENABLE_LOGGING
					LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat));
					LogItemHeat("");
					#endif

					break;
				}
			}
		}
	}

	/**
	 * \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;
		
		if (!IsInsideBuilding() && !IsUnderRoof() && !IsChildOfType({Car}) && !IsWaterContact())
		{
			if (m_Rain > GameConstants.ENVIRO_NAKED_BODY_PENALTY_RAIN_MIN_VALUE || m_Snowfall > GameConstants.ENVIRO_NAKED_BODY_PENALTY_SNOWFALL_MIN_VALUE)
			{
				penalty += GameConstants.ENVIRO_ISOLATION_WETFACTOR_DRENCHED;
				penalty *= pCoef;
			}
		}
		
		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<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)
		{
			if (nearestEntity.IsUniversalTemperatureSource() && nearestEntity != m_Player)
			{
				//! skip - Temperature Source is not affecting player entities
				if (!nearestEntity.GetUniversalTemperatureSource().GetLambda().AffectsPlayer())
					continue;

				//! skip - Temperature Source is too far
				if (vector.DistanceSq(pos, nearestEntity.GetPosition()) > Math.SqrFloat(nearestEntity.GetUniversalTemperatureSource().GetMaxRange()))
					continue;

				m_UTemperatureSources.Insert(nearestEntity.GetUniversalTemperatureSource());
			}
		}

		ItemBase inHandsItem = m_Player.GetItemInHands();
		if (inHandsItem && inHandsItem.IsUniversalTemperatureSource())
			m_UTemperatureSources.Insert(inHandsItem.GetUniversalTemperatureSource());
	}
	
	/**
	 * \brief Processes registered UTSources and calculates resulting m_UTSAverageTemperature
	 */
	protected void ProcessTemperatureSources()
	{
		int UTScount = m_UTemperatureSources.Count();
		if (UTScount == 0)
		{
			m_HasTemperatureSources = false;
			OnTemperatureSourcesLeft();
			m_UTSAverageTemperatureBuffer.Add(0.0);
			m_UTSAverageTemperature = 0.0;
			SetItemHeatingCoef(GameConstants.TEMP_COEF_INVENTORY);

			return;
		}

		TFloatArray utsTemperatures = new TFloatArray();
		
		// get temperature from the source (based on distance), save it for min/max filtering
		float itemCoefAverage = 0.0;
		foreach (UTemperatureSource tempSource : m_UTemperatureSources)
		{
			utsTemperatures.Insert(CalcTemperatureFromTemperatureSource(tempSource));
			itemCoefAverage += tempSource.GetTemperatureItemCoef();
		}
		itemCoefAverage /= UTScount;
		SetItemHeatingCoef(itemCoefAverage);
		
		float min = MiscGameplayFunctions.GetMinValue(utsTemperatures);
		float max = MiscGameplayFunctions.GetMaxValue(utsTemperatures);

		if (max > 0 && min < 0)
		{
			//! adds average of 2 most significat sources to buffer
			m_UTSAverageTemperature = m_UTSAverageTemperatureBuffer.Add((max + min) * 0.5);
		}
		else
		{
			m_UTSAverageTemperature = m_UTSAverageTemperatureBuffer.Add(max);
		}

		if (m_HasTemperatureSources == false)
			OnTemperatureSourcesEnter();

		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()
	{
		return m_UTSAverageTemperature;
	}
	
	float CalcTemperatureFromTemperatureSource(notnull UTemperatureSource uts)
	{
		float distance 		= vector.Distance(m_Player.GetPosition(), uts.GetPosition());
		distance 			= Math.Max(distance, 0.1);	//min distance cannot be 0 (division by zero)
		float temperature	= 0;

		//! heat transfer through air to player (env temperature)
		if (distance > uts.GetFullRange())
		{
			float distFactor = Math.InverseLerp(uts.GetMaxRange(), uts.GetFullRange(), distance);
			temperature = uts.GetTemperatureCap() * distFactor;
		}
		else
		{
			temperature = uts.GetTemperatureCap();
		}

		return temperature;
	}
	
	// --------------------------------------------------------------------------------

	//! debug
	#ifdef DIAG_DEVELOPER
	EnvDebugData GetEnvDebugData()
	{
		EnvDebugData data = new EnvDebugData();
		data.Synch(this, m_Player);
		return data;
	}
	
	void ShowEnvDebugPlayerInfo(bool enabled)
	{
		EnvDebugData data = GetEnvDebugData();
		DisplayEnvDebugPlayerInfo(enabled, data);
	}
	
	static void DisplayEnvDebugPlayerInfo(bool enabled, EnvDebugData data)
	{
		int windowPosX = 10;
		int windowPosY = 200;

		Object obj;

		DbgUI.Begin("Player stats", windowPosX, windowPosY);
		if ( enabled )
		{
			DbgUI.Text(string.Format("Heat comfort(target): %1", data.m_PlayerData.m_HeatComfortTarget));
			DbgUI.Text(string.Format("Heat comfort(dynamic): %1", data.m_PlayerData.m_HeatComfortDynamic));
			DbgUI.Text(string.Format("Inside: %1 (%2)", data.m_PlayerData.m_Inside, data.m_PlayerData.m_Surface));
			DbgUI.Text(string.Format("Under roof: %1 (%2)", data.m_PlayerData.m_UnderRoof, data.m_PlayerData.m_UnderRoofTimer));
			if ( data.m_PlayerData.m_WaterLevel > 0 )
			{
				DbgUI.Text(string.Format("Water Level: %1", data.m_PlayerData.m_WaterLevel));
			}
			
		}
		DbgUI.End();
		
		DbgUI.Begin("Weather stats:", windowPosX, windowPosY + 200);
		if ( enabled )
		{
			DbgUI.Text(string.Format("Env temperature (base): %1", data.m_MiscData.m_TemperatureBase));
			DbgUI.Text(string.Format("Env temperature (height corrected): %1", data.m_MiscData.m_HeightCorrectedTemperature));
			DbgUI.Text(string.Format("Env temperature (modfied): %1", data.m_MiscData.m_TemperatureModified));
			DbgUI.Text(string.Format("Wind magnitude(surface mult): %1 (x%2)", data.m_WeatherData.m_Wind, data.m_WeatherData.m_WindModifier));
			DbgUI.Text(string.Format("Rain: %1", data.m_WeatherData.m_Rain));
			DbgUI.Text(string.Format("Snow: %1", data.m_WeatherData.m_Snowfall));
			DbgUI.Text(string.Format("Datetime: %1", WorldDataDaytime.ToString(g_Game.GetMission().GetWorldData().GetDaytime())));
			DbgUI.Text(string.Format("Fog: %1", data.m_WeatherData.m_Fog));
			DbgUI.Text(string.Format("Clouds: %1", data.m_WeatherData.m_Clouds));
			DbgUI.Text(string.Format("Wet delta: %1", data.m_MiscData.m_WetDelta));
		}
		DbgUI.End();
	}
	
	void FillDebugWeatherData(EnvDebugWeatherData data)
	{
		data.m_Wind = m_Wind;
		data.m_WindModifier = GetWindModifierPerSurface();
		data.m_Rain = m_Rain;
		data.m_Snowfall = m_Snowfall;
		data.m_Fog = m_Fog;
		data.m_Clouds = m_Clouds;
	}
	#endif

	string GetDebugMessage()
	{
		string message;
		message += "Player stats";
		message += "\nHeat comfort(target): " + GetTargetHeatComfort().ToString();
		message += "\nHeat comfort(dynamic): " + m_HeatComfort.ToString();
		
		int liquidType;
		string impact, surfaceType;
		g_Game.SurfaceUnderObjectExCorrectedLiquid(m_Player, surfaceType, impact, liquidType);
		
		message += "\nInside: " + IsInsideBuilding().ToString();
		message += "\nSurface: " + surfaceType;
		message += "\nLiquid: " + liquidType;
		message += "\nUnder roof: " + m_IsUnderRoof.ToString() + " (" + GetNextRoofCheck() + ")";
		if (IsWaterContact() && m_WaterLevel > WATER_LEVEL_NONE)
		{
			message += "\nWater Level: " + m_WaterLevel;
		}

		message += "\n\nWeather stats";
		message += "\nEnv temperature (base): " + m_WorldData.GetBaseEnvTemperature().ToString();
		message += "\nEnv temperature (height corrected): " + m_WorldData.GetBaseEnvTemperatureAtObject(m_Player);
		message += "\nEnv temperature (modified): " + m_EnvironmentTemperature.ToString();
		message += "\nWind: " + m_Wind.ToString() + " (x" + GetWindModifierPerSurface() + ")";
		message += "\nRain: " + m_Rain.ToString();
		message += "\nSnow: " + m_Snowfall.ToString();
		message += "\nDatetime: " + WorldDataDaytime.ToString(m_DayOrNight);
		message += "\nFog: " + m_Fog.ToString();
		message += "\nClouds: " + m_Clouds.ToString();
		message += "\nWet delta: " + GetWetDelta().ToString();
		
		return message;
	}
	
	int GetNextRoofCheck()
	{
		return (GameConstants.ENVIRO_TICK_ROOF_RC_CHECK - m_RoofCheckTimer) + 1;
	}
	
	float GetWaterLevel()
	{
		if (IsWaterContact() && m_WaterLevel > WATER_LEVEL_NONE)
		{
			return m_WaterLevel;
		}

		return 0.0;
	}
	
	private bool IsNeutralTemperature(float temperature, float lowerLimit = GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT, float upperLimit = GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
	{
		if (temperature >= lowerLimit && temperature <= upperLimit)
			return true;

		return false;
	}
	
	private float NormalizedTemperature(float temperature, float lowerLimit = GameConstants.ENVIRO_LOW_TEMP_LIMIT, float upperLimit = GameConstants.ENVIRO_HIGH_TEMP_LIMIT)
	{
		if (temperature >= GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT && temperature <= GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
			return 0.0;

		if (temperature < GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT)
			return Math.Clamp(Math.InverseLerp(lowerLimit, GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_LOWER_LIMIT, temperature), -1.0, -0.1);
		
		if (temperature > GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT)
			return Math.Clamp(Math.InverseLerp(GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_UPPER_LIMIT, upperLimit, temperature), 0.1, 1.0);
		
		// neutral zone
		return 0.0;
	}
	
	#ifdef ENABLE_LOGGING
	private void LogDryWetProcess(string message, bool indented = false)
	{
		if (m_DebugLogDryWet)
		{
			string indentation = "";
			if (indented)
				indentation = "|--";

			Debug.Log(string.Format("%1 %2", indentation, message));
		}
	}
	#endif

	#ifdef ENABLE_LOGGING
	private void LogItemHeat(string message, bool indented = false)
	{
		if (m_DebugLogItemHeat)
		{
			string indentation = "";
			if (indented)
				indentation = "|--";

			Debug.Log(string.Format("%1 %2", indentation, message));
		}
	}
	#endif
	
	// --------------------------------------------------------------------------------
	
	//! DEPRECATED
	protected float 				m_HeatSourceTemp;	
	protected ref SimpleMovingAverage<float> m_WindAverageBuffer;
	protected ref EnvironmentSnapshotData m_EnvironmentSnapshot; //! used for calculations before the data modification
	
	void Init(PlayerBase pPlayer)
	{
		Init();
	}

	protected bool OverridenHeatComfort(out float value);
	void AddToEnvironmentTemperature(float pTemperature);
	
	protected void ProcessItemsHeat()
	{
		// for backward combatibility only
		ProcessHeatComfort();
	}

	protected void ProcessWetnessByRain()
	{
		ProcessItemsWetness(m_SlotIdsComplete);
	}
	
	// Returns amount of deg C air temperature should be lowered by, based on player's height above water level
	float GetTemperatureHeightCorrection()
	{
		float temperature_reduction = Math.Max(0, (m_PlayerHeightPos * m_WorldData.m_TemperaturePerHeightReductionModifier));
		return temperature_reduction;
	}
	
	//! returns weighted avg heat comfort for bodypart
	protected void BodyPartHeatProperties(array<int> pBodyPartIds, float pCoef, out float pHeatComfort, out float pHeat)
	{
		pHeatComfort = 0;
		pHeat = 0;
		
		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)
			{
				EntityAI attachment = m_Player.GetInventory().GetAttachmentFromIndex(attIdx);
				if (attachment.IsClothing())
				{
					ItemBase item = ItemBase.Cast(attachment);
					int attachmentSlot = attachment.GetInventory().GetSlotId(0);
	
					//! go through all body parts we've defined for that zone (ex.: head, body, feet)
					for (int i = 0; i < pBodyPartIds.Count(); ++i)
					{
						if (attachmentSlot == pBodyPartIds[i])
						{
							float heatIsoMult = 1.0;
							if (attachmentSlot == InventorySlots.BACK)
								heatIsoMult = GameConstants.ENVIRO_HEATISOLATION_BACK_WEIGHT;
							else if (attachmentSlot == InventorySlots.VEST)
								heatIsoMult = GameConstants.ENVIRO_HEATISOLATION_VEST_WEIGHT;
	
							pHeatComfort += heatIsoMult * MiscGameplayFunctions.GetCurrentItemHeatIsolation(item);
							
							float itemHeatcomfort = 0;
							float itemTemperature = 0;
							
							// go through any attachments and cargo (only current level, ignore nested containers - they isolate)
							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);
									ItemBase attachmentItem = ItemBase.Cast(inAttachment);
									if (attachmentItem && attachmentItem.CanHaveTemperature())
									{
										itemTemperature = attachmentItem.GetTemperature();
										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
										}
									}
								}
							}
							if (item.GetInventory().GetCargo())
							{
								int inventoryItemCount = item.GetInventory().GetCargo().GetItemCount();
								
								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));
										if (inventoryItem && inventoryItem.CanHaveTemperature())
										{
											itemTemperature = inventoryItem.GetTemperature();
											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;
											}
										}
									}
								}
							}
						}
					}
				}
			}

			pHeatComfort += (pHeatComfort / pBodyPartIds.Count()) * pCoef;
			
			#ifdef ENABLE_LOGGING
			LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat));
			LogItemHeat("");
			#endif
		}
	}
	
	protected void SetEnvironmentSnapshotData()
	{
		EnvironmentSnapshotData data = new EnvironmentSnapshotData();
		data.m_TargetHeatComfort = m_TargetHeatComfort;
		
		m_EnvironmentSnapshot = data;
	}
	
	//! backward compatibility [<1.27]
	protected void ProcessHeatBuffer(float heatComfortCloths)
	{
		m_EnvironmentSnapshot.m_ClothingHeatComfort 	= heatComfortCloths;
		m_EnvironmentSnapshot.m_TargetHeatComfort 	= m_TargetHeatComfort;
		
		ProcessHeatBuffer(m_EnvironmentSnapshot);
	}
	
	//! backward compatibility [<1.28]
	protected float WindEffectTemperatureValue(float temperatureInput)
	{
		float output = 0.0;

		output = (temperatureInput - GameConstants.ENVIRO_WIND_CHILL_LIMIT) / (GameConstants.ENVIRO_WIND_EFFECT_SLOPE - GameConstants.ENVIRO_WIND_CHILL_LIMIT);
		output = output * m_Wind * m_WorldData.GetWindCoef();
		
		return -output;
	}
	
	float GetDayOrNight()
	{
		return m_DayOrNight;
	}
	
	protected bool IsInsideVehicle()
	{
		return IsChildOfType({Car});
	}
}

#ifdef DIAG_DEVELOPER
class EnvDebugPlayerData : Param
{
	float 	m_HeatComfortDynamic;
	float 	m_HeatComfortTarget;
	bool 	m_Inside;
	string 	m_Surface;
	bool 	m_UnderRoof;
	int 	m_UnderRoofTimer;
	float 	m_WaterLevel;
	
	void Synch(Environment env, PlayerBase player)
	{
		m_HeatComfortTarget = env.GetTargetHeatComfort();
		m_HeatComfortDynamic = player.GetStatHeatComfort().Get();
		m_Inside = env.IsInsideBuilding();
		m_Surface = player.GetSurfaceType();
		m_UnderRoof = env.IsUnderRoof();
		m_UnderRoofTimer = env.GetNextRoofCheck();
		m_WaterLevel = env.GetWaterLevel();
	}
	
	override bool Serialize(Serializer ctx)
	{
		return (
		ctx.Write(m_HeatComfortTarget) && ctx.Write(m_HeatComfortDynamic) && ctx.Write(m_Inside) && ctx.Write(m_Surface) && ctx.Write(m_UnderRoof) && ctx.Write(m_UnderRoofTimer) && ctx.Write(m_WaterLevel));
	}
	
	override bool Deserializer(Serializer ctx)
	{
		return ctx.Read(m_HeatComfortTarget) && ctx.Read(m_HeatComfortDynamic) && ctx.Read(m_Inside) && ctx.Read(m_Surface) && ctx.Read(m_UnderRoof) && ctx.Read(m_UnderRoofTimer) && ctx.Read(m_WaterLevel);
	}
}

class EnvDebugMiscData : Param
{
	float 	m_TemperatureBase;
	float 	m_TemperatureModified;
	float 	m_HeightCorrectedTemperature;
	float 	m_WetDelta;
	
	void Synch(Environment env)
	{
		m_TemperatureBase = g_Game.GetMission().GetWorldData().GetBaseEnvTemperature();
		m_TemperatureModified = env.GetTemperature();
		m_HeightCorrectedTemperature = m_TemperatureBase - env.GetTemperatureHeightCorrection();
		m_WetDelta = env.GetWetDelta();
	}
	
	override bool Serialize(Serializer ctx)
	{
		return ctx.Write(m_TemperatureBase) && ctx.Write(m_TemperatureModified) && ctx.Write(m_HeightCorrectedTemperature) && ctx.Write(m_WetDelta);
	}
	
	override bool Deserializer(Serializer ctx)
	{
		return ctx.Read(m_TemperatureBase) && ctx.Read(m_TemperatureModified) && ctx.Read(m_HeightCorrectedTemperature) && ctx.Read(m_WetDelta);
	}
}

class EnvDebugWeatherData : Param
{
	float 	m_Wind;
	float 	m_WindModifier;
	float 	m_Rain;
	float 	m_Snowfall;
	float 	m_Fog;
	float 	m_Clouds;
	
	void Synch(Environment env)
	{
		env.FillDebugWeatherData(this);
	}
	
	override bool Serialize(Serializer ctx)
	{
		return ctx.Write(m_Wind) && ctx.Write(m_WindModifier) && ctx.Write(m_Rain) && ctx.Write(m_Snowfall) && ctx.Write(m_Fog) && ctx.Write(m_Clouds);
	}
		
	override bool Deserializer(Serializer ctx)
	{
		return ctx.Read(m_Wind) && ctx.Read(m_WindModifier) && ctx.Read(m_Rain) && ctx.Read(m_Snowfall) && ctx.Read(m_Fog) && ctx.Read(m_Clouds);
	}
}

class EnvDebugData : Param
{
	ref EnvDebugPlayerData 		m_PlayerData = new EnvDebugPlayerData();
	ref EnvDebugMiscData 		m_MiscData = new EnvDebugMiscData();
	ref EnvDebugWeatherData 	m_WeatherData = new EnvDebugWeatherData();
	
	void Synch(Environment env, PlayerBase player)
	{
		m_PlayerData.Synch(env, player);
		m_MiscData.Synch(env);
		m_WeatherData.Synch(env);
	}
	
	override bool Serialize(Serializer ctx)
	{
		return m_PlayerData.Serialize(ctx) && m_MiscData.Serialize(ctx) && m_WeatherData.Serialize(ctx);
	}
		
	override bool Deserializer(Serializer ctx)
	{
		return m_PlayerData.Deserializer(ctx) && m_MiscData.Deserializer(ctx) && m_WeatherData.Deserializer(ctx);
	}
}
#endif