enum EEnvironmentHeatcomfortBehaviorCategory { DEFAULT, CAR_ENGINE_ON } class EnvironmentSnapshotData { float m_ClothingHeatComfort; float m_TargetHeatComfort; } 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 m_SlotIdsComplete; protected ref array m_SlotIdsUpper; protected ref array m_SlotIdsBottom; protected ref array m_SlotIdsLower; protected ref array m_HeadParts; protected ref array m_BodyParts; protected ref array m_FeetParts; protected WorldData m_WorldData; protected bool m_HasTemperatureSources; protected float m_UTSAverageTemperature; protected ref array m_UTemperatureSources; protected ref SimpleMovingAverage m_UTSAverageTemperatureBuffer; protected ref SimpleMovingAverage m_AverageHeatComfortBuffer; protected int m_HeatComfortBehaviorCategory; private bool m_Initialized; #ifdef DIAG_DEVELOPER bool m_Debug = false; 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(); m_UTSAverageTemperatureBuffer = new SimpleMovingAverage(10, 0.0); m_AverageHeatComfortBuffer = new SimpleMovingAverage(20, m_HeatComfort); //! whole body slots m_SlotIdsComplete = new array(); 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(); 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(); m_SlotIdsBottom = { InventorySlots.HIPS, InventorySlots.LEGS, InventorySlots.FEET }; //! lower body part slots m_SlotIdsLower = new array(); m_SlotIdsLower = { InventorySlots.FEET, }; //! -------------------------- //! heat comfort related slots m_HeadParts = new array(); m_HeadParts = { InventorySlots.HEADGEAR, InventorySlots.MASK, }; m_BodyParts = new array(); m_BodyParts = { InventorySlots.GLOVES, InventorySlots.HIPS, InventorySlots.BODY, InventorySlots.BACK, InventorySlots.VEST, InventorySlots.MELEE, InventorySlots.SHOULDER }; m_FeetParts = new array(); m_FeetParts = { InventorySlots.LEGS, InventorySlots.FEET, }; m_HeatComfortBehaviorCategory = EEnvironmentHeatcomfortBehaviorCategory.DEFAULT; m_EnvironmentSnapshot = new EnvironmentSnapshotData(); m_Initialized = true; } // Calculates heatisolation of clothing, process its wetness, collects heat from heated items and calculates player's heat comfort 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; } } } } bool IsTemperatureSet() { return m_IsTempSet; } //! Returns heat player generated based on player's movement speed (for now) protected float GetPlayerHeat() { float heat = m_PlayerSpeed * GameConstants.ENVIRO_DEFAULT_ENTITY_HEAT; return heat; } bool IsUnderRoof() { return m_IsUnderRoof; } protected bool IsWaterContact() { return m_IsInWater; } bool IsInsideBuilding() { return m_Player && m_Player.IsSoundInsideBuilding(); } protected bool IsInsideVehicle() { return m_Player && m_Player.IsInVehicle(); } private bool IsChildOfType(array typenames) { Object parent = Object.Cast(m_Player.GetParent()); if (parent) return parent.IsAnyInherited(typenames); return false; } private bool IsUnderRoofBuilding() { return m_IsUnderRoofBuilding; } protected bool IsRaining() { return m_Rain > RAIN_LIMIT_LOW; } protected bool IsSnowing() { return m_Snowfall > SNOWFALL_LIMIT_LOW; } 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; } //! Checks whether Player is sheltered 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); } protected void CheckWaterContact(out float pWaterLevel) { string surfType; int liquidType; m_IsInWater = false; if (m_Player.PhysicsGetLinkedEntity() || IsChildOfType({Transport})) 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; } //! 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 = m_Player.GetCurrentWaterLevel(); 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; } 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; } // Calculates and return temperature of 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(); } // incorporate temperature from temperature sources (buffer) if (Math.AbsFloat(m_UTSAverageTemperature) > 0.0 && m_UTSAverageTemperature > temperature) temperature = m_UTSAverageTemperature; return temperature; } // Calculates wet/drying delta based on player's location and weather 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; } // EXPOSURE // Each tick updates current entity member variables protected void CollectAndSetPlayerData() { vector playerPos = m_Player.GetPosition(); m_PlayerHeightPos = playerPos[1]; HumanCommandMove hcm = m_Player.GetCommand_Move(); if (hcm) { m_PlayerSpeed = hcm.GetCurrentMovementSpeed(); } m_PlayerHeat = GetPlayerHeat(); } // Each tick updates current environment member variables 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(); } //! 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 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); } // Wets or dry items once in given time protected void ProcessItemsWetness(array pSlotIds) { EntityAI attachment; int playerAttachmentCount = m_Player.GetInventory().AttachmentCount(); LogDryWetProcess(string.Format("Environment :: ProcessItemsWetness (update interval=%1s)", GameConstants.ENVIRO_TICK_RATE)); 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()); 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("=========="); } 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"); 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); } else if (isParentWet && parentItem) { if (pItem.GetWet() < parentItem.GetWet()) soakingCoef = GetWetDelta(); LogDryWetProcess(string.Format("%1 (soak coef=%2/s, current wetness=%3) [parent wet]", pItem.GetDisplayName(), soakingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null); } else { soakingCoef = GetWetDelta(); LogDryWetProcess(string.Format("%1 (soak coef=%2/s, current wetness=%3) [normal]", pItem.GetDisplayName(), soakingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null); } 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); } } } } } 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) { LogDryWetProcess(string.Format("%1 (dry coef=%2/s, current wetness=%3) [normal]", pItem.GetDisplayName(), dryingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null); 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; 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); pItem.AddWet(dryingCoef); } if (isParentWet) { //! adds wetness to item inside wet parent item dryingCoef = (GameConstants.ENVIRO_TICK_RATE * pItem.GetSoakingIncrement("wetParent")) / pDrynessData.m_TemperatureSourceDistance; LogDryWetProcess(string.Format("%1 (dry coef=%2/s, current wetness=%3) [parent wet]", pItem.GetDisplayName(), dryingCoef / GameConstants.ENVIRO_TICK_RATE, pItem.GetWet()), parentItem != null); pItem.AddWet(dryingCoef); } } } // HEAT COMFORT //! Calculates and process player's heatcomfort related to 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; LogItemHeat("===================="); 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 + (GetPlayerHeat() / 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); //! 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); 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) m_Player.GetStatHeatBuffer().Add(-decreaseRate); else m_HeatBufferTimer = 1.0; } else { m_HeatBufferTimer = 0.0; if (applicableHeatbuffer > 0.0) m_Player.GetStatHeatBuffer().Add(-decreaseRate); else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER)) m_Player.GetStatHeatBuffer().Set(0.0); } } else { if (m_HeatComfort > PlayerConstants.THRESHOLD_HEAT_COMFORT_MINUS_WARNING && m_UTSAverageTemperature > 0) // m_UTSAverageTemperature can be negative { if (applicableHeatbuffer < heatBufferMax) { m_Player.GetStatHeatBuffer().Add(increaseRate); m_HeatBufferCapPrevious = heatBufferCap; } } else if (applicableHeatbuffer > 0.0) m_Player.GetStatHeatBuffer().Add(-decreaseRate); else if (applicableHeatbuffer != 0.0 && !m_Player.GetModifiersManager().IsModifierActive(eModifiers.MDF_HEATBUFFER)) m_Player.GetStatHeatBuffer().Set(0.0); m_HeatBufferTimer = 0.0; } } } protected float GetApplicableHeatbuffer() { float applicableHeatbuffer = Math.Round((m_Player.GetStatHeatBuffer().Get() / m_Player.GetStatHeatBuffer().GetMax()) * 1000) * 0.001; return applicableHeatbuffer; } //! go through all items in player's possession cool/warm them to neutral temperature protected void ProcessItemsTemperature(array 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); } } } } 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; 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; } //! returns enhanced heat comfort for given body part protected void BodyPartHeatProperties(int pBodyPartId, float pCoef, out float pHeatComfort, out float pHeat) { pHeatComfort = 0; pHeat = 0; 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); if (attachmentSlot == pBodyPartId) { LogItemHeat(string.Format("BodyPartHeatProperties (%1)", EnumTools.EnumToString(InventorySlots, pBodyPartId))); 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) { LogItemHeat(string.Format("attachments:"), false); 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(); LogItemHeat(string.Format("%1: temperature=%2 heat=%3", attachmentItem, itemTemperature, pHeat), true); pHeat += itemHeatcomfort; } } } } if (item.GetInventory().GetCargo()) { int inventoryItemCount = item.GetInventory().GetCargo().GetItemCount(); if (inventoryItemCount > 0) { LogItemHeat(string.Format("cargo:"), false); 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(); LogItemHeat(string.Format("%1: temperature=%2 heat=%3", inventoryItem, itemTemperature, itemHeatcomfort), true); pHeat += itemHeatcomfort; } } } } } pHeatComfort = MiscGameplayFunctions.GetCurrentItemHeatIsolation(item) * pCoef; LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat)); LogItemHeat(""); break; } } } } 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; } protected void GatherTemperatureSources() { m_UTemperatureSources.Clear(); array nearestObjects = new array(); GetGame().GetObjectsAtPosition(m_Player.GetPosition(), GameConstants.ENVIRO_TEMP_SOURCES_LOOKUP_RADIUS, nearestObjects, null); foreach (Object nearestObject : nearestObjects) { EntityAI ent = EntityAI.Cast(nearestObject); if (ent && ent.IsUniversalTemperatureSource() && ent != m_Player) { //! next temp source is too far if (vector.DistanceSq(m_Player.GetPosition(), ent.GetPosition()) > Math.SqrFloat(ent.GetUniversalTemperatureSource().GetMaxRange())) continue; //! skip - this TS is not affecting player entities if (ent.GetUniversalTemperatureSource().GetLambda().AffectsPlayer()) m_UTemperatureSources.Insert(ent.GetUniversalTemperatureSource()); } } if (m_Player.GetItemInHands() && m_Player.GetItemInHands().IsUniversalTemperatureSource()) m_UTemperatureSources.Insert(m_Player.GetItemInHands().GetUniversalTemperatureSource()); } protected void SetItemHeatingCoef(float val) { m_ItemTemperatureCoef = val; } 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; } array utsTemperatures = new array(); // 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 OnTemperatureSourcesEnter(); 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; } private void LogDryWetProcess(string message, bool indented = false) { #ifdef DIAG_DEVELOPER if (m_DebugLogDryWet) { string indentation = ""; if (indented) indentation = "|--"; Debug.Log(string.Format("%1 %2", indentation, message)); } #endif } private void LogItemHeat(string message, bool indented = false) { #ifdef DIAG_DEVELOPER 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 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 pBodyPartIds, float pCoef, out float pHeatComfort, out float pHeat) { pHeatComfort = 0; pHeat = 0; if (pBodyPartIds.Count() > 0) { LogItemHeat(string.Format("BodyPartHeatProperties (%1)", EnumTools.EnumToString(InventorySlots, pBodyPartIds[0]))); 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) { LogItemHeat(string.Format("attachments:"), false); 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(); LogItemHeat(string.Format("%1: temperature=%2 heat=%3", attachmentItem, itemTemperature, pHeat), true); pHeat += itemHeatcomfort; } } } } if (item.GetInventory().GetCargo()) { int inventoryItemCount = item.GetInventory().GetCargo().GetItemCount(); if (inventoryItemCount > 0) { LogItemHeat(string.Format("cargo:"), false); 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(); LogItemHeat(string.Format("%1: temperature=%2 heat=%3", inventoryItem, itemTemperature, itemHeatcomfort), true); pHeat += itemHeatcomfort; } } } } } } } } } pHeatComfort += (pHeatComfort / pBodyPartIds.Count()) * pCoef; LogItemHeat(string.Format("overall heat from items=%1 (coef applied)", pHeat)); LogItemHeat(""); } } 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; } } class EnvironmentDrynessData { bool m_UseTemperatureSources = false; float m_TemperatureSourceDistance = 1.0; } #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.Write(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