enum FireplaceFireState
{
NO_FIRE = 1,
START_FIRE = 2,
SMALL_FIRE = 3,
NORMAL_FIRE = 4,
END_FIRE = 5,
EXTINGUISHING_FIRE = 6,
EXTINGUISHED_FIRE = 7,
REIGNITED_FIRE = 8,
COUNT = 9 //for net sync purposes
}
enum FirePlaceFailure
{
WIND = 0,
WET = 1,
}
class FireplaceBase : ItemBase
{
//State params
protected bool m_IsBurning = false;
protected bool m_HasAshes = false;
protected bool m_IsOven = false;
protected bool m_HasStoneCircle = false;
protected bool m_NoIgnite = false;
protected int m_OvenAttachmentsLockState = -1;
protected FireplaceFireState m_FireState = FireplaceFireState.NO_FIRE;
protected FireplaceFireState m_LastFireState = FireplaceFireState.NO_FIRE; //for synchronization purposes
protected vector m_HalfExtents;
//Fireplace params
protected float m_FuelBurnRateMP = 1.0; //! determines how fast will the fuel item burn before spending (lower is better)
//cooking
protected ref Cooking m_CookingProcess;
//temperature simulation constants
const float PARAM_SMALL_FIRE_TEMPERATURE = 150; //! maximum fireplace temperature of a small fire (degree Celsius)
const float PARAM_NORMAL_FIRE_TEMPERATURE = 1000; //! maximum fireplace temperature of a normal fire (degree Celsius)
const float PARAM_OUTDOOR_FIRE_TEMPERATURE = 500; //! maximum fireplace temperature of an outdoor fire (degree Celsius)
const float PARAM_MIN_FIRE_TEMPERATURE = 30; //! minimum fireplace temperature under which the fireplace is inactive (degree Celsius)
const float PARAM_TEMPERATURE_INCREASE = 10; //! how much will temperature increase when fireplace is burning (degree Celsius per second)
const float PARAM_TEMPERATURE_DECREASE = 3; //! how much will temperature decrease when fireplace is cooling (degree Celsius per second)
const float PARAM_MAX_WET_TO_IGNITE = 0.2; //! maximum wetness value when the fireplace can be ignited
const float PARAM_MIN_TEMP_TO_REIGNITE = 30; //! minimum fireplace temperature under which the fireplace can be reignited using air only (degree Celsius)
const float PARAM_IGNITE_RAIN_THRESHOLD = 0.1; //! maximum rain value when the fireplace can be ignited
const float PARAM_BURN_WET_THRESHOLD = 0.40; //! maximum wetness value when the fireplace is able to burn
const float PARAM_WET_HEATING_DECREASE_COEF = 0.01; //! value for calculating wetness loss during heating process
const float PARAM_WET_COOLING_DECREASE_COEF = 0.002; //! value for calculating wetness loss during cooling process
const float PARAM_FIRE_CONSUM_RATE_AMOUNT = 0.5; //! base value of fire consumption rate (how many base energy will be spent on each update)
const float PARAM_BURN_DAMAGE_COEF = 5.0; //! value for calculating damage on items located in fireplace cargo
const float PARAM_FULL_HEAT_RADIUS = 2.0; //! radius in which objects are fully heated by fire
const float PARAM_HEAT_RADIUS = 4.0; //! radius in which objects are heated by fire
const float PARAM_MAX_TRANSFERED_TEMPERATURE = 20; //! maximum value for temperature that will be transfered to player (environment)
const float PARAM_DRY_MODIFIER = 1.3;
//staging constants
const int MIN_STONES_TO_BUILD_CIRCLE = 8; //! minimum amount of stones for circle
const int MIN_STONES_TO_BUILD_OVEN = 16; //! minimum amount of stones for oven
const int MAX_TEMPERATURE_TO_DISMANTLE_OVEN = 40; //! maximum temperature for dismantling oven
const float MIN_CEILING_HEIGHT = 5; //! min height of ceiling for fire to be ignited
//fuel constants
const float FUEL_BURN_RATE_DEFAULT = 1.0;
const float FUEL_BURN_RATE_STONES = 0.92;
const float FUEL_BURN_RATE_OVEN = 0.85;
//! cooking
const float PARAM_COOKING_TEMP_THRESHOLD = 100; //! temperature threshold for starting coooking process (degree Celsius)
//! timer constants
const int TIMER_HEATING_UPDATE_INTERVAL = 3; //! update interval duration of heating process (seconds)
const int TIMER_COOLING_UPDATE_INTERVAL = 3; //! update interval duration of cooling process (seconds)
//! direct cooking slots
const int DIRECT_COOKING_SLOT_COUNT = 3;
const int SMOKING_SLOT_COUNT = 4;
const float SMOKING_SPEED = 1; // per second
// stage lifetimes
const int LIFETIME_FIREPLACE_STONE_CIRCLE = 172800;
const int LIFETIME_FIREPLACE_STONE_OVEN = 604800;
const float IGNITE_WIND_THRESHOLD = 0.8; //fireplace can not be ignited above this multiple of max wind
protected const float RAIN_EFFECT_LIMIT = 0.4; //! rain level that triggers fireplace to start soaking
protected const float SNOWFALL_EFFECT_LIMIT = 1.5; //! snowfall level that triggers fireplace to start soaking
protected const float RAIN_WETNESS_INCREASE = 0.02; //! value for calculating of wetness that fireplace gain when raining
protected const float SNOWFALL_WETNESS_INCREASE = 0.01; //! value for calculating of wetness that fireplace gain when raining
//Timers
protected ref Timer m_HeatingTimer;
protected ref Timer m_CoolingTimer;
// Light entity
protected FireplaceLight m_Light;
protected float m_LightDistance = 2000;
//Attachments
protected ItemBase m_CookingEquipment;
protected ItemBase m_DirectCookingSlots[DIRECT_COOKING_SLOT_COUNT];
protected ItemBase m_SmokingSlots[SMOKING_SLOT_COUNT];
protected ref FireConsumable m_ItemToConsume;
//Particles - default for FireplaceBase
protected int PARTICLE_FIRE_START = ParticleList.CAMP_FIRE_START;
protected int PARTICLE_OVEN_FIRE_START = ParticleList.CAMP_STOVE_FIRE_START;
protected int PARTICLE_SMALL_FIRE = ParticleList.CAMP_SMALL_FIRE;
protected int PARTICLE_NORMAL_FIRE = ParticleList.CAMP_NORMAL_FIRE;
protected int PARTICLE_OVEN_FIRE = ParticleList.CAMP_STOVE_FIRE;
protected int PARTICLE_SMALL_SMOKE = ParticleList.CAMP_SMALL_SMOKE;
protected int PARTICLE_NORMAL_SMOKE = ParticleList.CAMP_NORMAL_SMOKE;
protected int PARTICLE_FIRE_END = ParticleList.CAMP_FIRE_END;
protected int PARTICLE_OVEN_FIRE_END = ParticleList.CAMP_STOVE_FIRE_END;
protected int PARTICLE_STEAM_END = ParticleList.CAMP_STEAM_2END;
protected int PARTICLE_STEAM_EXTINGUISHING = ParticleList.CAMP_STEAM_EXTINGUISH_START;
protected int PARTICLE_NO_IGNITE_WIND = ParticleList.CAMP_NO_IGNITE_WIND;
//
protected Particle m_ParticleFireStart;
protected Particle m_ParticleSmallFire;
protected Particle m_ParticleNormalFire;
protected Particle m_ParticleSmallSmoke;
protected Particle m_ParticleNormalSmoke;
protected Particle m_ParticleFireEnd;
protected Particle m_ParticleSteamEnd;
protected Particle m_ParticleSteamExtinguishing;
//Sounds
const string SOUND_FIRE_HEAVY = "HeavyFire_SoundSet";
const string SOUND_FIRE_LIGHT = "LightFire_SoundSet";
const string SOUND_FIRE_NO_FIRE = "NoFuelFire_SoundSet";
const string SOUND_FIRE_EXTINGUISHING = "ExtinguishByWater_SoundSet";
const string SOUND_FIRE_EXTINGUISHED = "ExtinguishByWaterEnd_SoundSet";
//
protected EffectSound m_SoundFireLoop;
protected EffectSound m_SoundFire;
//
//Fuel
//Kindling / Fuel item types
protected static ref map<typename, ref FireConsumableType> m_FireConsumableTypes;
protected ref map<ItemBase, ref FireConsumable> m_FireConsumables;
//Player action / status messages
const string MESSAGE_IGNITE_IGNIT_ITEM_DAMAGED = "You cannot start fire. Fireplace is ruined.";
const string MESSAGE_IGNITE_NO_KINDLING = "There needs to be some kindling to start a fire.";
const string MESSAGE_IGNITE_TOO_WET = "The fireplace is too wet to be ignited.";
const string MESSAGE_IGNITE_RAIN = "The fire went out because of the rain.";
const string MESSAGE_IGNITE_WIND = "The fire went out because of the wind.";
const string MESSAGE_IGNITE_IN_WATER = "I cannot ignite the fireplace in the water.";
const string MESSAGE_IGNITE_UNDER_LOW_ROOF = "I cannot ignite the fireplace here, the is not enough room.";
const string MESSAGE_REIGNITE_NO_KINDLING = "There needs to be some kindling to start a fire.";
const string MESSAGE_REIGNITE_RAIN = "Cannot reignite fireplace in rain.";
const string MESSAGE_REIGNITE_TOO_WET = "The fireplace is too wet to be reignited.";
const string MESSAGE_BURY_ASHES_FAILED_NOT_EMPTY = "Cannot bury ashes. There are sill items in it.";
const string MESSAGE_BURY_ASHES_FAILED_SURFACE = "Cannot bury ashes on hard surface.";
const string MESSAGE_BURY_ASHES_FAILED_BURNING = "Cannot bury ashes, the fireplace is still burning.";
const string MESSAGE_BURY_ASHES_FAILED_TOO_HOT = "Cannot bury ashes, the fireplace is too hot.";
const string MESSAGE_CANNOT_BUILD_OVEN = "Cannot build oven.";
const string MESSAGE_CANNOT_DISMANTLE_OVEN = "Cannot dismantle oven.";
//Visual animations
const string ANIMATION_ASHES = "Ashes";
const string ANIMATION_KINDLING = "Kindling";
const string ANIMATION_STICKS = "Sticks";
const string ANIMATION_WOOD = "Wood";
const string ANIMATION_BURNT_WOOD = "BurntWood";
const string ANIMATION_STONE = "Stone";
const string ANIMATION_STONES = "StonesCircle";
const string ANIMATION_TRIPOD = "Tripod";
const string ANIMATION_OVEN = "Oven";
const string ANIMATION_INVENTORY = "Inventory";
const string ANIMATION_COOKWARE_HANDLE = "handleRotate";
const string ANIMATION_CAULDRON_HANDLE = "handle1Rotate";
//Visual textures and materials
const string MATERIAL_FIREPLACE_GLOW = "dz\\gear\\cooking\\data\\stoneground.rvmat";
const string MATERIAL_FIREPLACE_NOGLOW = "dz\\gear\\cooking\\data\\stonegroundnoemit.rvmat";
//Attachment types
//Fuel
typename ATTACHMENT_STICKS = WoodenStick;
typename ATTACHMENT_FIREWOOD = Firewood;
//Kindling
typename ATTACHMENT_RAGS = Rag;
typename ATTACHMENT_BANDAGE = BandageDressing;
typename ATTACHMENT_BOOK = ItemBook;
typename ATTACHMENT_BARK_OAK = Bark_Oak;
typename ATTACHMENT_BARK_BIRCH = Bark_Birch;
typename ATTACHMENT_PAPER = Paper;
typename ATTACHMENT_GIFTWRAP = GiftWrapPaper;
typename ATTACHMENT_PUNCHEDCARD = PunchedCard;
typename ATTACHMENT_EYEMASK_COLORBASE = EyeMask_ColorBase;
//Other
typename ATTACHMENT_TRIPOD = Tripod;
typename ATTACHMENT_COOKINGSTAND = CookingStand;
typename ATTACHMENT_STONES = Stone;
typename ATTACHMENT_COOKING_POT = Pot; //'IsCookware' used instead
typename ATTACHMENT_FRYING_PAN = FryingPan; //'IsCookware' used instead
typename ATTACHMENT_CAULDRON = Cauldron; //'IsCookware' used instead
//
protected const float PLACEMENT_HEIGHT_LIMIT = 0.1; // Y coord placement limit - this is important when server has collision checks disabled
const string OBJECT_CLUTTER_CUTTER = "ClutterCutterFireplace";
protected Object m_ClutterCutter;
//area damage
protected ref AreaDamageManager m_AreaDamage;
//quantity
protected float m_TotalEnergy;
// Noise
ref NoiseParams m_NoisePar;
private bool m_CanNoise = true; // Flip every fireplace update to limit amount of noise generation
// UniversalTemperatureSource
protected ref UniversalTemperatureSource m_UTSource;
protected ref UniversalTemperatureSourceSettings m_UTSSettings;
protected ref UniversalTemperatureSourceLambdaFireplace m_UTSLFireplace;
// surface wetness on heat modification
protected const float WET_SURFACE_INCREMENT = 0.06; //! size of wetness increment (per FP heating tick) added to overall FP wetness when ignited on wet surface
protected float m_SurfaceUnderWetnessModifier
protected bool m_ThawnSurfaceUnderSupport;
protected ref UnderObjectDecalSpawnSettings m_UnderObjectDecalSpawnSettings;
protected ref UnderObjectDecalSpawnComponent m_UnderObjectDecalSpawnComponent;
//================================================================
// INIT / STORE LOAD-SAVE
//================================================================
//Constructor
protected void FireplaceBase()
{
//STATIC: define kindling types
if (!m_FireConsumableTypes)
{
m_FireConsumableTypes = new map<typename, ref FireConsumableType>();
m_FireConsumableTypes.Insert(ATTACHMENT_RAGS, new FireConsumableType(ATTACHMENT_RAGS, 8, true, "Rags"));
m_FireConsumableTypes.Insert(ATTACHMENT_BANDAGE, new FireConsumableType(ATTACHMENT_BANDAGE, 8, true, "MedicalBandage"));
m_FireConsumableTypes.Insert(ATTACHMENT_BOOK, new FireConsumableType(ATTACHMENT_BOOK, 20, true, "Book"));
m_FireConsumableTypes.Insert(ATTACHMENT_BARK_OAK, new FireConsumableType(ATTACHMENT_BARK_OAK, 10, true, "OakBark"));
m_FireConsumableTypes.Insert(ATTACHMENT_BARK_BIRCH, new FireConsumableType(ATTACHMENT_BARK_BIRCH, 8, true, "BirchBark"));
m_FireConsumableTypes.Insert(ATTACHMENT_PAPER, new FireConsumableType(ATTACHMENT_PAPER, 5, true, "Paper"));
m_FireConsumableTypes.Insert(ATTACHMENT_GIFTWRAP, new FireConsumableType(ATTACHMENT_GIFTWRAP, 5, true, "GiftWrapPaper"));
m_FireConsumableTypes.Insert(ATTACHMENT_PUNCHEDCARD, new FireConsumableType(ATTACHMENT_PUNCHEDCARD, 5, true, "PunchedCard"));
m_FireConsumableTypes.Insert(ATTACHMENT_EYEMASK_COLORBASE, new FireConsumableType(ATTACHMENT_EYEMASK_COLORBASE, 5, true, "EyeMask_ColorBase"));
//define fuel types
m_FireConsumableTypes.Insert(ATTACHMENT_STICKS, new FireConsumableType(ATTACHMENT_STICKS, 30, false, "WoodenStick"));
m_FireConsumableTypes.Insert(ATTACHMENT_FIREWOOD, new FireConsumableType(ATTACHMENT_FIREWOOD, 100, false, "Firewood"));
}
//calculate total energy
CalcAndSetTotalEnergy();
//define fuel / kindling items (fire consumables)
m_FireConsumables = new map<ItemBase, ref FireConsumable>();
//default burning parameters
SetFuelBurnRateMP(FUEL_BURN_RATE_DEFAULT);
//synchronized variables
RegisterNetSyncVariableBool("m_IsBurning");
RegisterNetSyncVariableBool("m_HasAshes");
RegisterNetSyncVariableBool("m_IsOven");
RegisterNetSyncVariableBool("m_HasStoneCircle");
RegisterNetSyncVariableBool("m_RoofAbove");
RegisterNetSyncVariableInt("m_FireState", FireplaceFireState.NO_FIRE, FireplaceFireState.COUNT);
RegisterNetSyncVariableBool("m_NoIgnite");
m_HalfExtents = vector.Zero;
m_SurfaceUnderWetnessModifier = 0.0;
m_UTSSettings = new UniversalTemperatureSourceSettings();
m_UTSSettings.m_ManualUpdate = true;
m_UTSSettings.m_TemperatureItemCap = GameConstants.ITEM_TEMPERATURE_NEUTRAL_ZONE_MIDDLE;
m_UTSSettings.m_TemperatureCap = PARAM_MAX_TRANSFERED_TEMPERATURE;
m_UTSSettings.m_RangeFull = PARAM_FULL_HEAT_RADIUS;
m_UTSSettings.m_RangeMax = PARAM_HEAT_RADIUS;
m_UTSSettings.m_ItemDryModifier = PARAM_DRY_MODIFIER;
m_UTSSettings.m_EnableOnTemperatureControl = true;
m_UTSSettings.m_ActiveTemperatureThreshold = 250.0;
m_UTSSettings.m_InactiveTemperatureThreshold = 475.0;
m_UnderObjectDecalSpawnSettings = new UnderObjectDecalSpawnSettings();
m_UnderObjectDecalSpawnSettings.m_RandomizeRotation = true;
m_UnderObjectDecalSpawnSettings.m_PositionOffset = "0 0.05 0";
m_UnderObjectDecalSpawnSettings.m_ScaleMax = 1.0;
}
protected void ~FireplaceBase()
{
DestroyAreaDamage();
if (GetLightEntity())
GetLightEntity().FadeOut();
if (m_ThawnSurfaceUnderSupport && m_UnderObjectDecalSpawnComponent)
{
m_UnderObjectDecalSpawnComponent.RemoveDecal();
m_UnderObjectDecalSpawnComponent = null;
}
}
override void EEInit()
{
super.EEInit();
//refresh visual on init
RefreshFireplaceVisuals();
if (m_CookingProcess == null)
m_CookingProcess = new Cooking();
if (g_Game.IsServer() || !g_Game.IsMultiplayer())
{
InitializeTemperatureSources();
}
if (m_ThawnSurfaceUnderSupport && !m_UnderObjectDecalSpawnComponent)
{
m_UnderObjectDecalSpawnComponent = new UnderObjectDecalSpawnComponent(m_UnderObjectDecalSpawnSettings, this);
m_UnderObjectDecalSpawnComponent.SpawnDecal();
}
}
override void EEItemAttached(EntityAI item, string slot_name)
{
super.EEItemAttached(item, slot_name);
//cookware
if (item.IsCookware())
SetCookingEquipment(ItemBase.Cast(item));
}
override void OnItemLocationChanged(EntityAI old_owner, EntityAI new_owner)
{
super.OnItemLocationChanged(old_owner, new_owner);
//refresh physics after location change (with delay)
g_Game.GetCallQueue(CALL_CATEGORY_GAMEPLAY).Call(RefreshFireplacePhysics);
InventoryLocation loc = new InventoryLocation();
GetInventory().GetCurrentInventoryLocation(loc);
if (loc.GetType() == InventoryLocationType.GROUND)
{
if (IsOpen() && !IsOven())
CheckForRoofLimited(0); //roof check when moved to ground
if (m_ThawnSurfaceUnderSupport && !m_UnderObjectDecalSpawnComponent)
{
m_UnderObjectDecalSpawnComponent = new UnderObjectDecalSpawnComponent(m_UnderObjectDecalSpawnSettings, this);
m_UnderObjectDecalSpawnComponent.SpawnDecal();
}
}
else
{
if (m_ThawnSurfaceUnderSupport && m_UnderObjectDecalSpawnComponent)
{
m_UnderObjectDecalSpawnComponent.RemoveDecal();
m_UnderObjectDecalSpawnComponent = null;
}
DestroyAreaDamage();
}
m_SurfaceUnderWetnessModifier = GetSurfaceWetnessOnHeatModifier(this);
}
override void OnAttachmentRuined(EntityAI attachment)
{
switch (attachment.Type())
{
case ATTACHMENT_TRIPOD:
case ATTACHMENT_COOKINGSTAND:
int slot = InventorySlots.GetSlotIdFromString("CookingEquipment");
EntityAI ent = GetInventory().FindAttachment(slot);
if (ent && ent.IsCookware())
{
vector direction = ent.GetDirection();
float dot = vector.Dot(direction, vector.Forward);
float angle = Math.Acos(dot);
if (direction[0] < 0)
{
angle = -angle;
}
float cos = Math.Cos(angle);
float sin = Math.Sin(angle);
GetInventory().DropEntityInBounds(InventoryMode.SERVER, this, ent, "2 0 2", angle, cos, sin);
}
attachment.Delete();
break;
}
}
override void EEDelete(EntityAI parent)
{
super.EEDelete(parent);
SetFireState(FireplaceFireState.NO_FIRE);
// cleanup particles (for cases of leaving burning fplace and going back outside of network bubble)
#ifndef SERVER
StopAllParticlesAndSounds();
#endif
}
//on store save/load
override void OnStoreSave(ParamsWriteContext ctx)
{
super.OnStoreSave(ctx);
//Save ashes state
ctx.Write(m_HasAshes);
//Save fuel burn rate MP
ctx.Write(m_FuelBurnRateMP);
//Save burning state
ctx.Write(m_IsBurning);
}
override bool OnStoreLoad(ParamsReadContext ctx, int version)
{
if (!super.OnStoreLoad(ctx, version))
return false;
//--- Fireplace data ---
//Load ashes state
if (!ctx.Read(m_HasAshes))
{
m_HasAshes = false; //set default
return false;
}
//Load temperature loss MP
if (version < 139 && !ctx.Read(m_TemperatureLossMP))
{
m_TemperatureLossMP = 1.0; //set default
return false;
}
//Load fuel burn rate MP
if (!ctx.Read(m_FuelBurnRateMP))
{
m_FuelBurnRateMP = 1.0; //set default
return false;
}
//Load burning state, if true start fire
if (!ctx.Read(m_IsBurning))
{
m_IsBurning = false; //set default
return false;
}
//---
return true;
}
override void AfterStoreLoad()
{
super.AfterStoreLoad();
Synchronize();
//start fire
if (IsBurning())
{
if (g_Game && g_Game.IsServer())
{
StartFire(true); //will be auto-synchronized when starting fire
}
}
}
//================================================================
// SYNCHRONIZATION
//================================================================
void Synchronize()
{
if (g_Game && g_Game.IsServer())
{
SetSynchDirty();
if (g_Game.IsMultiplayer() && g_Game.IsServer())
{
//Refresh visuals (on server)
g_Game.GetCallQueue(CALL_CATEGORY_GAMEPLAY).Call(RefreshFireplaceVisuals);
}
}
}
override void OnVariablesSynchronized()
{
super.OnVariablesSynchronized();
RefreshFireplaceVisuals();
RefreshFireParticlesAndSounds(false);
if (IsBaseFireplace() && !IsOven())
{
if (m_IsBurning && !m_AreaDamage)
{
CreateAreaDamage();
}
else if (!m_IsBurning && m_AreaDamage)
{
DestroyAreaDamage();
}
}
if (m_ThawnSurfaceUnderSupport && m_UnderObjectDecalSpawnComponent)
m_UnderObjectDecalSpawnComponent.UpdateSize(Math.InverseLerp(0.0, GetTemperatureMax() * 0.6, GetTemperature()));
}
//================================================================
// BASE
//================================================================
//States
//check fireplace types
override bool IsFireplace()
{
return true;
}
bool IsBaseFireplace()
{
return false;
}
bool IsBarrelWithHoles()
{
return false;
}
bool IsFireplaceIndoor()
{
return false;
}
bool IsIndoorOven()
{
return false;
}
override bool CanHaveWetness()
{
return true;
}
override bool CanHaveTemperature()
{
return true;
}
override bool GetCookingTargetTemperature(out float temperature)
{
temperature = GetTemperature();
return true;
}
override bool IsSelfAdjustingTemperature()
{
return m_IsBurning || (m_CoolingTimer && m_CoolingTimer.IsRunning()); //FireplaceFireState.NO_FIRE?
}
protected void InitializeTemperatureSources()
{
m_UTSLFireplace = new UniversalTemperatureSourceLambdaFireplace();
m_UTSLFireplace.SetSmallFireplaceTemperatureMax(PARAM_SMALL_FIRE_TEMPERATURE);
m_UTSLFireplace.SetNormalFireplaceTemperatureMax(PARAM_NORMAL_FIRE_TEMPERATURE);
m_UTSource = new UniversalTemperatureSource(this, m_UTSSettings, m_UTSLFireplace);
}
//cooking equipment
ItemBase GetCookingEquipment()
{
return m_CookingEquipment;
}
void SetCookingEquipment(ItemBase equipment)
{
m_CookingEquipment = equipment;
}
void ClearCookingEquipment(ItemBase pItem)
{
if (m_CookingProcess)
{
m_CookingProcess.TerminateCookingSounds(pItem);
}
SetCookingEquipment(null);
}
//! DEPRECATED
void ClearCookingEquipment()
{
SetCookingEquipment(null);
}
bool DirectCookingSlotsInUse()
{
for (int i = 0; i < DIRECT_COOKING_SLOT_COUNT; i++)
{
if (m_DirectCookingSlots[i])
{
return true;
}
}
return false;
}
bool SmokingSlotsInUse()
{
for (int i = 0; i < SMOKING_SLOT_COUNT; i++)
{
if (m_SmokingSlots[i])
{
return true;
}
}
return false;
}
//! [DEPRECATED]
void DestroyFireplace()
{
DeleteSafe();
}
override bool IsPrepareToDelete()
{
return IsEmpty() && !IsBurning() && !HasAshes();
}
override void OnChildItemRemoved(InventoryItem item)
{
super.OnChildItemRemoved(item);
CheckForDestroy();
}
override void CheckForDestroy()
{
if (IsPrepareToDelete())
{
MiscGameplayFunctions.DropAllItemsInInventoryInBounds(this, m_HalfExtents);
super.CheckForDestroy();
}
}
//================================================================
// VISUAL
//================================================================
//Fireplace visual
void RefreshFireplaceVisuals()
{
if (IsHologram())
{
return;
}
// Light
#ifndef SERVER
if (IsBurning())
{
//turn light on
if (!GetLightEntity())
{
SetLightEntity(FireplaceLight.Cast(ScriptedLightBase.CreateLight(FireplaceLight, GetPosition(), 20)));
GetLightEntity().AttachOnMemoryPoint(this, "light");
}
if (GetLightEntity() && GetLightEntity().GetBrightness() > 0)
{
// change brightness based on the distance of player to the fireplace
Object player = g_Game.GetPlayer();
float lightDist = m_LightDistance;
if (IsOven())
{
lightDist = 50;
}
if (player)
{
if (vector.DistanceSq(player.GetPosition(), this.GetPosition()) > lightDist * lightDist)
{
GetLightEntity().FadeBrightnessTo(0, 5);
}
else
{
GetLightEntity().FadeBrightnessTo(FireplaceLight.m_FireplaceBrightness, 5);
}
}
// The following solves an issue with the light point clipping through narrow geometry
if (IsItemTypeAttached(ATTACHMENT_STONES) || IsBarrelWithHoles() || IsFireplaceIndoor())
{
GetLightEntity().SetInteriorMode();
}
else
{
GetLightEntity().SetExteriorMode();
}
}
}
else
{
//turn light off
if (GetLightEntity())
{
GetLightEntity().FadeOut();
}
}
#endif
//set default burn parameters based on fireplace type
if (IsBarrelWithHoles() || IsFireplaceIndoor() || IsIndoorOven() || IsOven())
{
SetFuelBurnRateMP(FUEL_BURN_RATE_OVEN);
}
else if (HasStoneCircle())
{
SetFuelBurnRateMP(FUEL_BURN_RATE_STONES);
}
else
{
SetFuelBurnRateMP(FUEL_BURN_RATE_DEFAULT);
}
// Oven attachments
if (IsOven())
{
//lock attachment slots
if (!m_OvenAttachmentsLockState)
{
LockOvenAttachments(true);
}
}
else
{
//unlock attachment slots
if (m_OvenAttachmentsLockState)
{
LockOvenAttachments(false);
}
}
//VISUAL STATES
//Fuel state
if (g_Game.IsServer())
{
// Sticks state
if (IsItemTypeAttached(ATTACHMENT_STICKS))
SetAnimationPhase(ANIMATION_STICKS, 0);
else
SetAnimationPhase(ANIMATION_STICKS, 1);
// Firewood state
if (IsItemTypeAttached(ATTACHMENT_FIREWOOD))
{
if (IsBurning() && HasAshes())
{
SetAnimationPhase(ANIMATION_BURNT_WOOD, 0); // Because this might not be set under certain circumstances
SetAnimationPhase(ANIMATION_WOOD, 1);
}
else if (IsWet() || !IsBurning())
{
SetAnimationPhase(ANIMATION_BURNT_WOOD, 1);
SetAnimationPhase(ANIMATION_WOOD, 0);
}
else
{
SetAnimationPhase(ANIMATION_BURNT_WOOD, 1);
SetAnimationPhase(ANIMATION_WOOD, 0);
}
}
else
{
SetAnimationPhase(ANIMATION_WOOD, 1);
SetAnimationPhase(ANIMATION_BURNT_WOOD, 1);
}
// Kindling state
if (GetKindlingCount() != 0)
SetAnimationPhase(ANIMATION_KINDLING, 0);
else
SetAnimationPhase(ANIMATION_KINDLING, 1);
// Ashes state
if (HasAshes())
SetAnimationPhase(ANIMATION_ASHES, 0);
else
SetAnimationPhase(ANIMATION_ASHES, 1);
// Stone variations
if (HasStones())
{
if (HasStoneCircle())
{
SetAnimationPhase(ANIMATION_STONES, 0);
SetAnimationPhase(ANIMATION_STONE, 1);
}
else if (IsOven())
{
SetAnimationPhase(ANIMATION_OVEN, 0);
SetAnimationPhase(ANIMATION_STONE, 1);
}
else
{
//! single stone; everything else hidden
SetAnimationPhase(ANIMATION_STONES, 1);
SetAnimationPhase(ANIMATION_OVEN, 1);
SetAnimationPhase(ANIMATION_STONE, 0);
}
}
else
{
SetAnimationPhase(ANIMATION_STONE, 1); //! hide stones stack completely
}
}
//refresh physics (with delay)
g_Game.GetCallQueue(CALL_CATEGORY_GAMEPLAY).Call(RefreshFireplacePhysics);
}
//! DEPRECATED
protected void SetBurntFirewood()
{
if (g_Game.IsServer() && IsInAnimPhase(ANIMATION_WOOD))
{
SetAnimationPhase(ANIMATION_WOOD, 1);
SetAnimationPhase(ANIMATION_BURNT_WOOD, 0);
}
}
//Refresh fireplace object physics
void RefreshFireplacePhysics();
protected void RefreshFireParticlesAndSounds(bool force_refresh)
{
FireplaceFireState fire_state = GetFireState();
if (m_LastFireState != fire_state || force_refresh)
{
switch (fire_state)
{
case FireplaceFireState.START_FIRE:
{
//particles
ParticleFireStartStart();
//sounds
SoundFireStop();
SoundFireLightStart();
break;
}
case FireplaceFireState.SMALL_FIRE:
{
//particles
ParticleFireStartStop();
ParticleSmallFireStart();
if (CanShowSmoke())
ParticleSmallSmokeStart();
else
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleSteamExtinguishingStop();
//sounds
SoundFireStop();
SoundFireLightStart();
break;
}
case FireplaceFireState.NORMAL_FIRE:
{
//particles
ParticleFireStartStop();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStart();
if (CanShowSmoke())
ParticleNormalSmokeStart();
else
ParticleNormalSmokeStop();
ParticleSteamExtinguishingStop();
//sounds
SoundFireStop();
SoundFireHeavyStart();
break;
}
case FireplaceFireState.END_FIRE:
{
//particles
ParticleFireStartStop();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleFireEndStart();
//sounds
SoundFireStop();
SoundFireNoFireStart();
break;
}
case FireplaceFireState.EXTINGUISHING_FIRE: //TODO add steam particles when extinguishing
{
//particles
ParticleFireStartStop();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleFireEndStop();
ParticleSteamExtinguishingStart();
//sounds
SoundFireStop();
SoundFireExtinguishingStart();
break;
}
case FireplaceFireState.EXTINGUISHED_FIRE: //TODO add steam particles when fireplace is extinguished
{
//particles
ParticleFireStartStop();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleFireEndStop();
ParticleSteamExtinguishingStop();
if (CanShowSmoke())
ParticleSteamEndStart();
else
ParticleSteamEndStop();
//sounds
SoundFireStop();
SoundFireExtinguishedStart();
SoundFireNoFireStart();
break;
}
case FireplaceFireState.REIGNITED_FIRE:
{
//particles
ParticleFireStartStart();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleFireEndStop();
ParticleSteamEndStop();
//sounds
SoundFireStop();
break;
}
case FireplaceFireState.NO_FIRE:
{
//particles
ParticleFireStartStop();
ParticleSmallFireStop();
ParticleSmallSmokeStop();
ParticleNormalFireStop();
ParticleNormalSmokeStop();
ParticleFireEndStop();
ParticleSteamEndStop();
ParticleSteamExtinguishingStop();
//sounds
SoundFireStop();
break;
}
}
m_LastFireState = fire_state;
}
}
protected void StopAllParticlesAndSounds()
{
// go through all fireplace particles
if (m_ParticleFireStart)
StopParticle(m_ParticleFireStart);
if (m_ParticleSmallFire)
StopParticle(m_ParticleSmallFire);
if (m_ParticleNormalFire)
StopParticle(m_ParticleNormalFire);
if (m_ParticleSmallSmoke)
StopParticle(m_ParticleSmallSmoke);
if (m_ParticleNormalSmoke)
StopParticle(m_ParticleNormalSmoke);
if (m_ParticleFireEnd)
StopParticle(m_ParticleFireEnd);
if (m_ParticleSteamEnd)
StopParticle(m_ParticleSteamEnd);
if (m_ParticleSteamExtinguishing)
StopParticle(m_ParticleSteamExtinguishing);
// go through all fireplace sounds
if (m_SoundFireLoop)
StopSoundSet(m_SoundFireLoop);
if (m_SoundFire)
StopSoundSet(m_SoundFire);
}
bool CanShowSmoke()
{
return true;
}
//Fireplace fire intensity
void RefreshFireState()
{
float temperature = GetTemperature();
//if it's burning
if (IsBurning())
{
//Start fire
if (temperature <= PARAM_MIN_FIRE_TEMPERATURE)
{
if (GetFireState() != FireplaceFireState.START_FIRE)
{
SetFireState(FireplaceFireState.START_FIRE);
}
}
//Small fire
else if (temperature <= PARAM_SMALL_FIRE_TEMPERATURE)
{
if (GetFireState() != FireplaceFireState.SMALL_FIRE)
{
SetFireState(FireplaceFireState.SMALL_FIRE);
}
}
//Normal fire
else if (temperature > PARAM_SMALL_FIRE_TEMPERATURE)
{
if (GetFireState() != FireplaceFireState.NORMAL_FIRE)
{
SetFireState(FireplaceFireState.NORMAL_FIRE);
}
}
}
}
//================================================================
// PARTICLES & SOUNDS
//================================================================
//returns true if particle started, false if not
protected bool PlayParticle(out Particle particle, int particle_type, vector pos, bool worldpos = false)
{
if (!particle && g_Game && (!g_Game.IsDedicatedServer()))
{
if (!worldpos)
{
particle = ParticleManager.GetInstance().PlayOnObject(particle_type, this, pos);
}
else
{
particle = ParticleManager.GetInstance().PlayInWorld(particle_type, pos);
}
return true;
}
return false;
}
//returns true if particle stopped, false if not
protected bool StopParticle(out Particle particle)
{
if (particle && g_Game && (!g_Game.IsDedicatedServer()))
{
particle.Stop();
particle = NULL;
return true;
}
return false;
}
//start fire
protected void ParticleFireStartStart()
{
if (IsOven())
{
PlayParticle(m_ParticleFireStart, PARTICLE_OVEN_FIRE_START, GetFireEffectPosition());
}
else
{
PlayParticle(m_ParticleFireStart, PARTICLE_FIRE_START, GetFireEffectPosition());
}
}
protected void ParticleFireStartStop()
{
StopParticle(m_ParticleFireStart);
}
//small fire
protected void ParticleSmallFireStart()
{
if (IsOven())
{
PlayParticle(m_ParticleSmallFire, PARTICLE_OVEN_FIRE, GetFireEffectPosition());
}
else
{
PlayParticle(m_ParticleSmallFire, PARTICLE_SMALL_FIRE, GetFireEffectPosition());
}
}
protected void ParticleSmallFireStop()
{
StopParticle(m_ParticleSmallFire);
}
//normal fire
protected void ParticleNormalFireStart()
{
if (IsOven())
{
PlayParticle(m_ParticleNormalFire, PARTICLE_OVEN_FIRE, GetFireEffectPosition());
}
else
{
PlayParticle(m_ParticleNormalFire, PARTICLE_NORMAL_FIRE, GetFireEffectPosition());
}
}
protected void ParticleNormalFireStop()
{
StopParticle(m_ParticleNormalFire);
}
//small smoke
protected void ParticleSmallSmokeStart()
{
PlayParticle(m_ParticleSmallSmoke, PARTICLE_SMALL_SMOKE, GetSmokeEffectPosition());
//calculate air resistance
float actual_height;
if (GetCeilingHeight(actual_height))
{
float air_resistance = GetAirResistanceForSmokeParticles(actual_height);
//alter smoke height
m_ParticleSmallSmoke.SetParameter(-1, EmitorParam.AIR_RESISTANCE, air_resistance);
}
if (IsRoofAbove() || !IsOpen() || IsOven())
{
m_ParticleSmallSmoke.SetParameter(-1, EmitorParam.WIND, false);
}
}
protected void ParticleSmallSmokeStop()
{
StopParticle(m_ParticleSmallSmoke);
}
//normal smoke
protected void ParticleNormalSmokeStart()
{
PlayParticle(m_ParticleNormalSmoke, PARTICLE_NORMAL_SMOKE, GetSmokeEffectPosition());
//calculate air resistance
float actual_height;
if (GetCeilingHeight(actual_height))
{
float air_resistance = GetAirResistanceForSmokeParticles(actual_height);
//alter smoke height
m_ParticleNormalSmoke.SetParameter(-1, EmitorParam.AIR_RESISTANCE, air_resistance);
}
if (IsRoofAbove() || !IsOpen() || IsOven())
{
m_ParticleNormalSmoke.SetParameter(-1, EmitorParam.WIND, false);
}
}
protected void ParticleNormalSmokeStop()
{
StopParticle(m_ParticleNormalSmoke);
}
//fire end
protected void ParticleFireEndStart()
{
if (IsOven())
{
PlayParticle(m_ParticleFireEnd, PARTICLE_OVEN_FIRE_END, GetFireEffectPosition());
}
else
{
PlayParticle(m_ParticleFireEnd, PARTICLE_FIRE_END, GetFireEffectPosition());
}
}
protected void ParticleFireEndStop()
{
StopParticle(m_ParticleFireEnd);
}
protected void ParticleFireWindyNoIgniteStart()
{
if (IsOven())
{
PlayParticle(m_ParticleFireEnd, PARTICLE_OVEN_FIRE_END, GetFireEffectPosition());
}
else
{
PlayParticle(m_ParticleFireEnd, PARTICLE_NO_IGNITE_WIND, GetFireEffectPosition());
}
}
//steam extinguishing
protected void ParticleSteamExtinguishingStart()
{
PlayParticle(m_ParticleSteamExtinguishing, PARTICLE_STEAM_EXTINGUISHING, GetFireEffectPosition());
}
protected void ParticleSteamExtinguishingStop()
{
StopParticle(m_ParticleSteamExtinguishing);
}
//steam end
protected void ParticleSteamEndStart()
{
PlayParticle(m_ParticleSteamEnd, PARTICLE_STEAM_END, GetFireEffectPosition());
}
protected void ParticleSteamEndStop()
{
StopParticle(m_ParticleSteamEnd);
}
protected void ParticleWetNoIgniteStart()
{
PlayParticle(m_ParticleSteamEnd, PARTICLE_STEAM_END, GetFireEffectPosition());
}
protected void ParticleWetNoIgniteStop()
{
StopParticle(m_ParticleSteamEnd);
}
bool GetCeilingHeight(out float actual_height)
{
vector from = this.GetPosition();
vector to = this.GetPosition();
from[1] = from[1] + 1.0;
to[1] = to[1] + MIN_CEILING_HEIGHT;
vector contactPos;
vector contactDir;
int contactComponent;
bool hit = DayZPhysics.RaycastRV(from, to, contactPos, contactDir, contactComponent, NULL, NULL, this);
actual_height = vector.Distance(from, contactPos) + 1.0;
return hit;
}
float GetAirResistanceForSmokeParticles(float actual_height)
{
float air_res;
actual_height = Math.Clamp(actual_height, 0, 36);
air_res = (6 - actual_height) * 0.33;
air_res = Math.Clamp(air_res, 0, 2);
return air_res;
}
//Particle Positions
//Get local fire and smoke effect position
protected vector GetFireEffectPosition()
{
return Vector(0, 0.05, 0);
}
protected vector GetSmokeEffectPosition()
{
return Vector(0, 0.05, 0);
}
//================================================================
// SOUNDS
//================================================================
//Burning
//Start
protected void SoundFireLightStart()
{
PlaySoundSetLoop(m_SoundFireLoop, SOUND_FIRE_LIGHT, 1.0, 1.0);
}
protected void SoundFireHeavyStart()
{
PlaySoundSetLoop(m_SoundFireLoop, SOUND_FIRE_HEAVY, 1.0, 2.0);
}
protected void SoundFireNoFireStart()
{
PlaySoundSetLoop(m_SoundFireLoop, SOUND_FIRE_NO_FIRE, 2.0, 2.0);
}
protected void SoundFireExtinguishedStart()
{
PlaySoundSet(m_SoundFire, SOUND_FIRE_EXTINGUISHED, 0.1, 0.1);
}
protected void SoundFireWetNoIgniteStart()
{
PlaySoundSet(m_SoundFire, SOUND_FIRE_EXTINGUISHED, 0.1, 0.1);
}
protected void SoundFireExtinguishingStart()
{
PlaySoundSetLoop(m_SoundFireLoop, SOUND_FIRE_EXTINGUISHING, 1.0, 0.5);
}
protected void SoundFireWindyNoIgniteStart()
{
PlaySoundSet(m_SoundFire, "ExtinguishByWind_SoundSet", 0, 0);
}
//Stop
protected void SoundFireStop()
{
StopSoundSet(m_SoundFireLoop);
}
//================================================================
// FUEL / KINDLING
//================================================================
//Add to fire consumables
protected void AddToFireConsumables(ItemBase item)
{
m_FireConsumables.Insert(item, new FireConsumable(item, GetFireConsumableTypeEnergy(item)));
CalcAndSetQuantity();
}
protected float GetFireConsumableTypeEnergy(ItemBase item)
{
FireConsumableType fireConsumableType = m_FireConsumableTypes.Get(item.Type());
if (!fireConsumableType)
{
//! support for inherited classes; second pass of lookup inside FireConsumableType
int count = m_FireConsumableTypes.Count();
for (int i = 0; i < count; ++i)
{
if (item.IsInherited(m_FireConsumableTypes.GetKey(i)))
fireConsumableType = m_FireConsumableTypes.GetElement(i);
}
}
if (fireConsumableType)
return fireConsumableType.GetEnergy();
return 0;
}
//Remove from fire consumables
protected void RemoveFromFireConsumables(FireConsumable fire_consumable)
{
if (fire_consumable)
{
m_FireConsumables.Remove(fire_consumable.GetItem());
delete fire_consumable;
}
CalcAndSetQuantity();
}
protected FireConsumable GetFireConsumableByItem(ItemBase item)
{
return m_FireConsumables.Get(item);
}
//Set fuel / kindling to consume
//Sets the item with the lowest energy value as item that will be consumed next
//Returns reference to set fire consumable
protected FireConsumable SetItemToConsume()
{
if (m_FireConsumables.Count() == 0)
{
m_ItemToConsume = null;
}
else
{
for (int i = 0; i < m_FireConsumables.Count(); ++i)
{
ItemBase key = m_FireConsumables.GetKey(i);
FireConsumable fireConsumable = m_FireConsumables.Get(key);
if (i == 0)
{
m_ItemToConsume = fireConsumable;
}
else
{
if (fireConsumable.GetEnergy() < m_ItemToConsume.GetEnergy())
{
m_ItemToConsume = fireConsumable;
}
}
}
}
//refresh visual
RefreshFireplaceVisuals();
return m_ItemToConsume;
}
protected FireConsumable GetItemToConsume()
{
return m_ItemToConsume;
}
//Spend item that is used as consumable for fire (fuel, kindling)
//if 'amount == 0', the whole quantity will be consumed (quantity -= 1)
//debug
//int m_debug_fire_consume_time = 0;
protected void SpendFireConsumable(float amount)
{
//spend item
FireConsumable fireConsumable = GetItemToConsume();
if (!fireConsumable)
{
//Set new item to consume
fireConsumable = SetItemToConsume();
}
if (fireConsumable)
{
ItemBase item = fireConsumable.GetItem();
fireConsumable.SetRemainingEnergy(fireConsumable.GetRemainingEnergy() - amount);
if (fireConsumable.GetRemainingEnergy() <= 0 || amount == 0)
{
//set ashes
if (!HasAshes())
{
SetAshesState(true);
}
if (item.IsAnyInherited({ItemBook, Paper, GiftWrapPaper, EyeMask_ColorBase}))
{
RemoveFromFireConsumables(fireConsumable);
item.Delete();
}
else
{
//if there is nothing left, delete and remove old consumable, set new fire consumable
if (item.GetQuantity() <= 1)
{
//remove item from fuel items
RemoveFromFireConsumables(fireConsumable);
}
else
{
fireConsumable.SetRemainingEnergy(fireConsumable.GetEnergy());
}
item.AddQuantity(-1);
SetItemToConsume();
}
}
}
CalcAndSetQuantity();
}
//! Returns count of all kindling type items (define in 'm_KindlingTypes') attached to fireplace
protected int GetKindlingCount()
{
int attachmentsCount = GetInventory().AttachmentCount();
int kindlingCount = 0;
for (int i = 0; i < attachmentsCount; ++i)
{
ItemBase item = ItemBase.Cast(GetInventory().GetAttachmentFromIndex(i));
if (IsKindling(item))
kindlingCount++;
}
return kindlingCount;
}
//! Returns count of all fuel type items (define in 'm_FuelTypes') attached to fireplace
protected int GetFuelCount()
{
int attachmentsCount = GetInventory().AttachmentCount();
int fuelCount = 0;
for (int i = 0; i < attachmentsCount; ++i)
{
ItemBase item = ItemBase.Cast(GetInventory().GetAttachmentFromIndex(i));
if (IsFuel(item))
fuelCount++;
}
return fuelCount;
}
//! Returns if item attached to fireplace is kindling
protected bool IsKindling(ItemBase item)
{
FireConsumableType fireConsumableType = m_FireConsumableTypes.Get(item.Type());
if (!fireConsumableType)
{
//! support for inherited classes; second pass of lookup inside FireConsumableType
int count = m_FireConsumableTypes.Count();
for (int i = 0; i < count; ++i)
{
if (item.IsInherited(m_FireConsumableTypes.GetKey(i)))
fireConsumableType = m_FireConsumableTypes.GetElement(i);
}
}
return fireConsumableType && fireConsumableType.IsKindling();
}
//! Returns if item attached to fireplace is fuel
protected bool IsFuel(ItemBase item)
{
FireConsumableType fireConsumableType = m_FireConsumableTypes.Get(item.Type());
if (!fireConsumableType)
{
//! support for inherited classes; second pass of lookup inside FireConsumableType
int count = m_FireConsumableTypes.Count();
for (int i = 0; i < count; ++i)
{
if (item.IsInherited(m_FireConsumableTypes.GetKey(i)))
fireConsumableType = m_FireConsumableTypes.GetElement(i);
}
}
return fireConsumableType && !fireConsumableType.IsKindling();
}
//Has attachments of given type
bool IsItemTypeAttached(typename item_type)
{
return GetAttachmentByType(item_type) != null;
}
//Has attachments of required quantity
bool IsItemAttachedQuantity(typename item_type, float quantity)
{
ItemBase item = ItemBase.Cast(GetAttachmentByType(item_type));
return item.GetQuantity() >= quantity;
}
//Has last attached item
bool HasLastAttachment()
{
return GetInventory().AttachmentCount() == 1;
}
//Has last fuel/kindling attached
bool HasLastFuelKindlingAttached()
{
return (GetFuelCount() + GetKindlingCount()) == 1;
}
//================================================================
// FIRE STATE
//================================================================
//Is fireplace burning?
bool IsBurning()
{
return m_IsBurning;
}
//Set burning state
protected void SetBurningState(bool is_burning)
{
m_IsBurning = is_burning;
}
//Are ashes present in fireplace?
bool HasAshes()
{
return m_HasAshes;
}
//Set ashes state
protected void SetAshesState(bool has_ashes)
{
m_HasAshes = has_ashes;
}
//! returns true when FP is heating or cooling
bool IsProcessing()
{
return ((m_HeatingTimer && m_HeatingTimer.IsRunning()) || (m_CoolingTimer && m_CoolingTimer.IsRunning()));
}
//Is in oven state
bool IsOven()
{
return m_IsOven;
}
bool HasStoneCircle()
{
return m_HasStoneCircle;
}
bool HasStones()
{
EntityAI attached_item = GetAttachmentByType(ATTACHMENT_STONES);
return attached_item && attached_item.GetQuantity() > 0;
}
int GetAttachedStonesCount()
{
if (HasStones())
{
return GetAttachmentByType(ATTACHMENT_STONES).GetQuantity();
}
return 0;
}
void SetOvenState(bool is_oven)
{
if (m_IsOven != is_oven)
{
m_IsOven = is_oven;
GetInventory().SetSlotLock(InventorySlots.GetSlotIdFromString("Stones") , is_oven);
Synchronize();
}
}
void SetStoneCircleState(bool has_stonecircle)
{
if (m_HasStoneCircle != has_stonecircle)
{
m_HasStoneCircle = has_stonecircle;
GetInventory().SetSlotLock(InventorySlots.GetSlotIdFromString("Stones") , has_stonecircle);
Synchronize();
}
}
//Fuel burn rate MP
protected float GetFuelBurnRateMP()
{
return m_FuelBurnRateMP;
}
protected void SetFuelBurnRateMP(float value)
{
m_FuelBurnRateMP = value;
}
//Get fire state
protected int GetFireState()
{
return m_FireState;
}
//Sets fire state
protected void SetFireState(FireplaceFireState state)
{
if (m_FireState != state)
{
m_FireState = state;
Synchronize();
}
}
void SetExtinguishingState()
{
SetFireState(FireplaceFireState.EXTINGUISHING_FIRE);
}
//================================================================
// FIRE PROCESS
//================================================================
//Start the fire process
// 1. start heating
// 2. heating
// 3. stop heating
void StartFire(bool force_start = false)
{
//stop cooling process if active
if (m_CoolingTimer)
{
m_CoolingTimer.Stop();
m_CoolingTimer = null;
}
//start fire
if (!IsBurning() || force_start)
{
//roof check
CheckForRoofLimited(0);
SetItemToConsume();
SetBurningState(true);
StartHeating();
//Update navmesh
if (!IsFireplaceIndoor())
{
SetAffectPathgraph(false, true);
g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(g_Game.UpdatePathgraphRegionByObject, 100, false, this);
}
}
Synchronize();
}
//Start heating
protected void StartHeating()
{
//visual
SetObjectMaterial(0, MATERIAL_FIREPLACE_GLOW);
if (IsWindy() && !IsRoofAbove() && IsOpen() && !IsOven() && !IsBarrelWithHoles())
{
StopFire();
return;
}
if (GetWet() > PARAM_BURN_WET_THRESHOLD)
{
StopFire(FireplaceFireState.EXTINGUISHED_FIRE);
return;
}
//create area damage
if (IsBaseFireplace() && !IsOven())
CreateAreaDamage();
m_HeatingTimer = new Timer(CALL_CATEGORY_GAMEPLAY);
m_HeatingTimer.Run(TIMER_HEATING_UPDATE_INTERVAL, this, "Heating", null, true);
m_CookingProcess.SetCookingUpdateTime(TIMER_HEATING_UPDATE_INTERVAL);
//Setup the noise parameters on fire start
m_NoisePar = new NoiseParams();
if (IsRoofAbove() || IsOven() || IsFireplaceIndoor()) //If we have a roof, we are probably inside
m_NoisePar.LoadFromPath("CfgVehicles FireplaceBase NoiseFireplaceSpecial");
else
m_NoisePar.LoadFromPath("CfgVehicles FireplaceBase NoiseFireplaceBase");
}
//Do heating
protected void Heating()
{
float temperature;
float temperatureModifier = 0;
if (IsOpen() && !IsOven())
CheckForRoofLimited(1000 * TIMER_HEATING_UPDATE_INTERVAL * 5);
//check burning conditions
if (GetWet() > PARAM_BURN_WET_THRESHOLD)
{
StopFire(FireplaceFireState.EXTINGUISHED_FIRE);
return;
}
else
{
if (m_FireConsumables.Count() == 0)
{
StopFire();
return;
}
}
//spend actual fire consumable
float amount = PARAM_FIRE_CONSUM_RATE_AMOUNT * GetFuelBurnRateMP() * TIMER_HEATING_UPDATE_INTERVAL;
SpendFireConsumable(amount);
//set wetness and alter temperature modifier (which will lower temperature increase because of soaking)
float rain = g_Game.GetWeather().GetRain().GetActual();
float combinedWindAndSnowfall = MiscGameplayFunctions.GetCombinedSnowfallWindValue();
if ((rain >= RAIN_EFFECT_LIMIT || combinedWindAndSnowfall >= SNOWFALL_EFFECT_LIMIT) && !IsRoofAbove() && IsOpen() && !IsOven())
{
if (rain > combinedWindAndSnowfall)
{
AddWetnessToFireplace(RAIN_WETNESS_INCREASE * rain);
temperatureModifier = PARAM_TEMPERATURE_DECREASE * rain;
}
else
{
AddWetnessToFireplace(SNOWFALL_WETNESS_INCREASE * combinedWindAndSnowfall);
temperatureModifier = PARAM_TEMPERATURE_DECREASE * combinedWindAndSnowfall;
}
}
//subtract wetness when heating and not raining above
else
{
AddWetnessToFireplace(-PARAM_WET_HEATING_DECREASE_COEF);
}
if (m_SurfaceUnderWetnessModifier > 0.0)
AddWetnessToFireplace(m_SurfaceUnderWetnessModifier * WET_SURFACE_INCREMENT);
// FLAT temperature increase
temperature = GetTemperature() + (PARAM_TEMPERATURE_INCREASE * TIMER_HEATING_UPDATE_INTERVAL) - temperatureModifier;
temperature = Math.Clamp(temperature, g_Game.GetMission().GetWorldData().GetBaseEnvTemperatureAtObject(this), m_UTSLFireplace.m_NormalFireplaceTemperatureMax);
SetTemperatureDirect(temperature); //direct heating (non-systematic approach), freezing, overheating, and other stuff inside 'SetTemperatureEx' are therefore UNHANDLED here!
m_UTSLFireplace.SetFuelCount(GetFuelCount()); //legacy reasons
m_UTSLFireplace.SetCurrentTemperature(temperature); //legacy reasons
m_UTSource.Update(m_UTSSettings, m_UTSLFireplace);
//get newly changed temperature
temperature = GetTemperature();
//check fire state
if (GetFireState() != FireplaceFireState.EXTINGUISHING_FIRE)
{
RefreshFireState();
}
//damage cargo items
BurnItemsInFireplace();
//manage cooking equipment (this applies only for case of cooking pot on a tripod)
if (HasCookingStand() && m_CookingEquipment)
{
CookWithEquipment();
}
float cookingItemTemperature;
int i;
// manage cooking on direct cooking slots
if (DirectCookingSlotsInUse())
{
for (i = 0; i < DIRECT_COOKING_SLOT_COUNT; i++)
{
if (m_DirectCookingSlots[i])
{
cookingItemTemperature = m_DirectCookingSlots[i].GetTemperature();
CookOnDirectSlot(m_DirectCookingSlots[i], cookingItemTemperature, temperature);
}
}
}
// manage smoking slots
if (SmokingSlotsInUse())
{
for (i = 0; i < SMOKING_SLOT_COUNT; i++)
{
if (m_SmokingSlots[i])
{
SmokeOnSmokingSlot(m_SmokingSlots[i], cookingItemTemperature, temperature);
}
}
}
//Make noise for AI, only at night
if (g_Game.GetWorld().IsNight() && m_CanNoise)
{
NoiseSystem noise = g_Game.GetNoiseSystem();
if (noise && m_NoisePar)
{
noise.AddNoisePos(this, GetPosition(), m_NoisePar, NoiseAIEvaluate.GetNoiseReduction(g_Game.GetWeather()));
}
m_CanNoise = false;
}
else
m_CanNoise = true;
Synchronize();
}
//Stop the fire process
// 1. start cooling
// 2. cooling
// 3. stop cooling
void StopFire(FireplaceFireState fire_state = FireplaceFireState.END_FIRE)
{
//roof check
if (IsBurning())
CheckForRoofLimited(0);
StopHeating();
SpendFireConsumable(0);
SetBurningState(false);
StartCooling();
SetFireState(fire_state);
//Update navmesh
if (!IsFireplaceIndoor())
{
SetAffectPathgraph(false, false);
g_Game.GetCallQueue(CALL_CATEGORY_SYSTEM).CallLater(g_Game.UpdatePathgraphRegionByObject, 100, false, this);
}
Synchronize();
}
protected void StopHeating()
{
if (!m_HeatingTimer)
return;
m_HeatingTimer.Stop();
m_HeatingTimer = null;
}
protected void StartCooling()
{
if (m_HeatingTimer)
{
m_HeatingTimer.Stop();
m_HeatingTimer = null;
}
//Start cooling
m_CoolingTimer = new Timer(CALL_CATEGORY_GAMEPLAY);
m_CoolingTimer.Run(TIMER_COOLING_UPDATE_INTERVAL, this, "Cooling", null, true);
m_CookingProcess.SetCookingUpdateTime(TIMER_COOLING_UPDATE_INTERVAL);
}
protected void Cooling()
{
float temperature = GetTemperature();
float temperatureModifier = 0;
if (IsOpen() && !IsOven())
CheckForRoofLimited(1000 * TIMER_HEATING_UPDATE_INTERVAL * 5);
//should never be true!
if (IsBurning())
{
StopCooling();
return;
}
float target = Math.Max(g_Game.GetMission().GetWorldData().GetBaseEnvTemperatureAtObject(this),10);
if (temperature > target)
{
//set wetness and alter temperature modifier (which will lower temperature increase because of soaking)
float rain = g_Game.GetWeather().GetRain().GetActual();
float combinedWindAndSnowfall = MiscGameplayFunctions.GetCombinedSnowfallWindValue();
if ((rain >= RAIN_EFFECT_LIMIT || combinedWindAndSnowfall >= SNOWFALL_EFFECT_LIMIT) && !IsRoofAbove() && IsOpen() && !IsOven())
{
if (rain > combinedWindAndSnowfall)
{
AddWetnessToFireplace(RAIN_WETNESS_INCREASE * rain);
temperatureModifier = PARAM_TEMPERATURE_DECREASE * rain;
}
else
{
AddWetnessToFireplace(SNOWFALL_WETNESS_INCREASE * combinedWindAndSnowfall);
temperatureModifier = PARAM_TEMPERATURE_DECREASE * combinedWindAndSnowfall;
}
}
else //subtract wetness
{
AddWetnessToFireplace(-PARAM_WET_COOLING_DECREASE_COEF);
}
//calculate already obtained wetness (e.g. extinguished by water)
float wetness = GetWet();
temperatureModifier = temperatureModifier + (PARAM_TEMPERATURE_DECREASE * wetness);
// COMBINED temperature decrease
target = g_Game.GetMission().GetWorldData().GetBaseEnvTemperatureAtObject(this);
//FLAT for wetness
float flatWetTarget = GetTemperature() - temperatureModifier;
flatWetTarget = Math.Clamp(flatWetTarget,target,GetTemperatureMax());
SetTemperatureDirect(flatWetTarget);
//INTERPOLATED for regular cooling
SetTemperatureEx(new TemperatureDataInterpolated(target,ETemperatureAccessTypes.ACCESS_FIREPLACE,TIMER_COOLING_UPDATE_INTERVAL,GameConstants.TEMP_COEF_FIREPLACE_COOLING));
m_UTSLFireplace.SetFuelCount(GetFuelCount());
m_UTSLFireplace.UpdateFireplaceTemperature(m_UTSSettings);
m_UTSource.Update(m_UTSSettings, m_UTSLFireplace);
//get newly changed temperature
temperature = GetTemperature();
//damage cargo items
BurnItemsInFireplace();
//cook with equipment if temperature of equipment is still high
if (m_CookingEquipment)
{
float cookEquipTemp = m_CookingEquipment.GetTemperature();
if (cookEquipTemp >= PARAM_COOKING_TEMP_THRESHOLD)
{
//cook
CookWithEquipment();
}
}
float cookingItemTemperature;
int i;
// manage cooking on direct cooking slots
if (DirectCookingSlotsInUse())
{
for (i = 0; i < DIRECT_COOKING_SLOT_COUNT; i++)
{
if (m_DirectCookingSlots[i])
CookOnDirectSlot(m_DirectCookingSlots[i], cookingItemTemperature, temperature);
}
}
// manage smoking slots
if (SmokingSlotsInUse())
{
for (i = 0; i < SMOKING_SLOT_COUNT; i++)
{
if (m_SmokingSlots[i])
SmokeOnSmokingSlot(m_SmokingSlots[i], cookingItemTemperature, temperature);
}
}
}
else
{
StopCooling();
}
}
protected void StopCooling()
{
//stop all fire visuals
SetFireState(FireplaceFireState.NO_FIRE);
SetObjectMaterial(0, MATERIAL_FIREPLACE_NOGLOW);
//Stop cooling
m_CoolingTimer.Stop();
m_CoolingTimer = NULL;
//destroy area damage
DestroyAreaDamage();
//remove cookware audio visuals
ItemBase cookware;
if (Class.CastTo(cookware,GetCookingEquipment()) && (cookware.IsCookware() || cookware.IsLiquidContainer())) //also stops boiling effects on bottles
cookware.RemoveAudioVisualsOnClient();
if (DirectCookingSlotsInUse())
{
for (int i = 0; i < DIRECT_COOKING_SLOT_COUNT; i++)
{
if (m_DirectCookingSlots[i])
{
if (Class.CastTo(cookware,m_DirectCookingSlots[i]) && (cookware.IsCookware() || cookware.IsLiquidContainer())) //also stops boiling effects on bottles
cookware.RemoveAudioVisualsOnClient();
Edible_Base itsfood = Edible_Base.Cast(m_DirectCookingSlots[i]);
if (itsfood)
itsfood.MakeSoundsOnClient(false);
GameInventory inv = m_DirectCookingSlots[i].GetInventory();
if (!inv)
return;
CargoBase cargo = inv.GetCargo();
if (!cargo) // cookware
return;
for (int j = 0; j < cargo.GetItemCount(); j++)
{
Edible_Base edible = Edible_Base.Cast(cargo.GetItem(j));
if (edible)
edible.MakeSoundsOnClient(false);
}
}
}
}
}
//================================================================
// COOKING
//================================================================
protected void CookWithEquipment()
{
if (m_CookingProcess == null)
m_CookingProcess = new Cooking();
m_CookingProcess.CookWithEquipment(m_CookingEquipment);
}
Cooking GetCookingProcess()
{
if (m_CookingProcess == null)
m_CookingProcess = new Cooking();
return m_CookingProcess;
}
protected void CookOnDirectSlot(ItemBase slot_item, float temp_equip, float temp_ext)
{
if (m_CookingProcess == null)
m_CookingProcess = new Cooking();
m_CookingProcess.CookWithEquipment(slot_item);
}
protected void SmokeOnSmokingSlot(ItemBase slot_item, float temp_equip, float temp_ext)
{
if (m_CookingProcess == NULL)
m_CookingProcess = new Cooking();
// smoking slots accept only individual meat/fruit/veg items
Edible_Base ingr = Edible_Base.Cast(slot_item);
if (ingr)
m_CookingProcess.SmokeItem(ingr, FireplaceBase.TIMER_HEATING_UPDATE_INTERVAL * FireplaceBase.SMOKING_SPEED);
}
//================================================================
// FIRE VICINITY
//================================================================
//apply damage to all items, raise temperature to only specific items
protected void BurnItemsInFireplace()
{
//! cargo
CargoBase cargo = GetInventory().GetCargo();
for (int i = 0; i < cargo.GetItemCount(); i++)
{
ItemBase item = ItemBase.Cast(cargo.GetItem(i));
//add temperature
AddTemperatureToItemByFire(item);
//set damage
AddDamageToItemByFireEx(item, false, false);
//remove wetness
AddWetnessToItem(item, -PARAM_WET_HEATING_DECREASE_COEF);
}
//! attachments
for (int j = 0; j < GetInventory().AttachmentCount(); ++j)
{
ItemBase attachment = ItemBase.Cast(GetInventory().GetAttachmentFromIndex(j));
//add temperature
AddTemperatureToItemByFire(attachment);
//set damage
AddDamageToItemByFireEx(attachment, false, true);
//remove wetness
AddWetnessToItem(attachment, -PARAM_WET_HEATING_DECREASE_COEF);
}
}
//moves temperature of the item to the fire temperature (can cool down item!)
protected void AddTemperatureToItemByFire(ItemBase item)
{
if (item.CanHaveTemperature())
{
float fireplaceTemp = GetTemperature();
float itemTemp = item.GetTemperature();
float heatPermCoef = item.GetHeatPermeabilityCoef();
float tempCoef;
float deltaTime;
if (m_HeatingTimer && m_HeatingTimer.IsRunning())
{
deltaTime = TIMER_HEATING_UPDATE_INTERVAL;
tempCoef = GameConstants.TEMP_COEF_FIREPLACE_HEATING;
}
else if (m_CoolingTimer && m_CoolingTimer.IsRunning())
{
deltaTime = TIMER_COOLING_UPDATE_INTERVAL;
tempCoef = GameConstants.TEMP_COEF_FIREPLACE_COOLING;
}
else
{
return;
}
item.SetTemperatureEx(new TemperatureDataInterpolated(fireplaceTemp,ETemperatureAccessTypes.ACCESS_FIREPLACE,deltaTime,tempCoef,heatPermCoef));
}
}
//! DEPRECATED
protected void AddTemperatureToFireplace(float amount) {};
//add damage to item by fire
protected void AddDamageToItemByFireEx(ItemBase item, bool can_be_ruined, bool pAttachment)
{
if (!item)
return;
//! fireplace cargo only (recursive)
if (!pAttachment)
{
//! cargo item attachments
GameInventory itemInventory = item.GetInventory();
if (itemInventory)
{
array<EntityAI> entities = new array<EntityAI>();
itemInventory.EnumerateInventory(InventoryTraversalType.INORDER, entities);
foreach (EntityAI ent : entities)
{
if (ent != this)
{
ent.DecreaseHealth(PARAM_BURN_DAMAGE_COEF,!ent.IsKindOf("Grenade_Base"));
}
}
}
}
else //direct attachments
{
float timerCoef = 1.0;
if (m_HeatingTimer && m_HeatingTimer.IsRunning())
timerCoef = TIMER_HEATING_UPDATE_INTERVAL;
else if (m_CoolingTimer && m_CoolingTimer.IsRunning())
timerCoef = TIMER_COOLING_UPDATE_INTERVAL;
switch (item.Type())
{
case ATTACHMENT_TRIPOD:
case ATTACHMENT_COOKINGSTAND:
item.DecreaseHealth(GameConstants.FIRE_ATTACHMENT_DAMAGE_PER_SECOND * timerCoef, false);
break;
}
if (item.IsCookware())
{
item.DecreaseHealth(GameConstants.FIRE_ATTACHMENT_DAMAGE_PER_SECOND * timerCoef, false);
}
//! fuel & kindling
if (IsFuel(item) || IsKindling(item))
{
if (item.GetHealthLevel() < GameConstants.STATE_BADLY_DAMAGED)
item.DecreaseHealth(PARAM_BURN_DAMAGE_COEF, false);
}
}
}
//add wetness on item
protected void AddWetnessToItem(ItemBase item, float amount)
{
float wetness = item.GetWet();
wetness = wetness + amount;
wetness = Math.Clamp(wetness, 0, 1); //wetness <0-1>
item.SetWet(wetness);
}
//add wetness on fireplace
void AddWetnessToFireplace(float amount)
{
//add wetness
float wetness = GetWet();
wetness = wetness + amount;
wetness = Math.Clamp(wetness, 0, 1); //wetness <0-1>
SetWet(wetness);
//decrease temperature
if (amount > 0)
{
float temperature = GetTemperature();
temperature = temperature * (1 - (wetness * 0.5));
temperature = Math.Clamp(temperature, PARAM_MIN_FIRE_TEMPERATURE, PARAM_NORMAL_FIRE_TEMPERATURE);
SetTemperatureDirect(temperature);
m_UTSLFireplace.SetFuelCount(GetFuelCount()); //legacy reasons
m_UTSLFireplace.SetCurrentTemperature(temperature); //legacy reasons
}
}
//! DEPRECATED
protected void TransferHeatToNearPlayers() {}
//Create and Destroy damage radius around the fireplace when starting/stopping burning process
void CreateAreaDamage()
{
//destroy area damage if some already exists
DestroyAreaDamage();
//create new area damage
m_AreaDamage = new AreaDamageLoopedDeferred(this);
m_AreaDamage.SetDamageComponentType(AreaDamageComponentTypes.HITZONE);
m_AreaDamage.SetExtents("-0.30 0 -0.30", "0.30 0.75 0.30");
m_AreaDamage.SetLoopInterval(0.5);
m_AreaDamage.SetDeferDuration(0.5);
m_AreaDamage.SetHitZones({ "Head","Torso","LeftHand","LeftLeg","LeftFoot","RightHand","RightLeg","RightFoot" });
m_AreaDamage.SetAmmoName("FireDamage");
m_AreaDamage.Spawn();
}
void DestroyAreaDamage()
{
if (m_AreaDamage)
{
m_AreaDamage.Destroy();
}
}
//! DEPRECATED
bool IsFacingFireplace(PlayerBase player)
{
ErrorEx("Deprecated method", ErrorExSeverity.INFO);
return false;
}
//================================================================
// ACTIONS
//================================================================
// --- Actions / Action conditions
//Check if there is some kindling attached
bool HasAnyKindling()
{
return GetKindlingCount() > 0;
}
//Check if the weather is too windy
static bool IsWindy()
{
//check wind
float wind_speed = g_Game.GetWeather().GetWindSpeed();
float wind_speed_threshold = g_Game.GetWeather().GetWindMaximumSpeed() * FireplaceBase.IGNITE_WIND_THRESHOLD;
return (wind_speed >= wind_speed_threshold);
}
bool GetIgniteFailure()
{
return m_NoIgnite;
}
void SetIgniteFailure(bool failure)
{
m_NoIgnite = failure;
}
//Check if the fireplace is too wet to be ignited
static bool IsEntityWet(notnull EntityAI entity_ai)
{
return (entity_ai.GetWet() >= FireplaceBase.PARAM_MAX_WET_TO_IGNITE);
}
bool IsWet()
{
return FireplaceBase.IsEntityWet(this);
}
//check if fireplace is opened
override bool IsOpen()
{
return true;
}
//Check if there is enough space for smoke
bool IsCeilingHighEnoughForSmoke()
{
return !MiscGameplayFunctions.IsUnderRoof(this, FireplaceBase.MIN_CEILING_HEIGHT);
}
//Check if it's raining and there is only sky above fireplace
static bool IsRainingAboveEntity(notnull EntityAI entity_ai)
{
return (g_Game && (g_Game.GetWeather().GetRain().GetActual() >= FireplaceBase.PARAM_IGNITE_RAIN_THRESHOLD));
}
bool IsRainingAbove()
{
return FireplaceBase.IsRainingAboveEntity(this);
}
//Check there is water surface bellow fireplace
static bool IsEntityOnWaterSurface(notnull EntityAI entity_ai)
{
vector fireplacePosition = entity_ai.GetPosition();
string surfaceType;
g_Game.SurfaceGetType3D(fireplacePosition[0], fireplacePosition[1] + g_Game.SurfaceGetSeaLevel(), fireplacePosition[2], surfaceType);
if (!surfaceType)
{
float waterLevelDiff = fireplacePosition[1] - g_Game.SurfaceGetSeaLevel();
return waterLevelDiff < 0.5;
}
else if (surfaceType.Contains("water"))
{
return true;
}
return false;
}
protected float GetSurfaceWetnessOnHeatModifier(notnull EntityAI entity)
{
string surfaceType
int liquidType;
g_Game.SurfaceUnderObjectCorrectedLiquid(entity, surfaceType, liquidType);
return Surface.GetParamFloat(surfaceType, "wetnessOnHeatModifier");
}
bool IsOnWaterSurface()
{
return FireplaceBase.IsEntityOnWaterSurface(this);
}
//check if the surface is interior
static bool IsEntityOnInteriorSurface(notnull EntityAI entity_ai)
{
string surfaceType;
vector fireplacePosition = entity_ai.GetPosition();
g_Game.SurfaceGetType3D(fireplacePosition[0], fireplacePosition[1] + 1.0, fireplacePosition[2], surfaceType);
return (g_Game.ConfigGetInt("CfgSurfaces " + surfaceType + " interior") == 1);
}
bool IsOnInteriorSurface()
{
return FireplaceBase.IsEntityOnInteriorSurface(this);
}
//Checks if has not additional items in it
override bool IsEmpty()
{
return (GetInventory().GetCargo().GetItemCount() == 0 && GetInventory().AttachmentCount() == 0);
}
//Checks if it has no items in cargo
bool IsCargoEmpty()
{
return (GetInventory().GetCargo().GetItemCount() == 0);
}
bool HasCookingStand()
{
return GetInventory().FindAttachment(InventorySlots.GetSlotIdFromString("CookingTripod")) != null;
}
//Check if object is in animation phase
bool IsInAnimPhase(string anim_phase)
{
return GetAnimationPhase(anim_phase) == 0;
}
bool IsSpaceFor(vector size)
{
array<Object> objs = {};
if (g_Game.IsBoxCollidingGeometry(GetWorldPosition() + Vector(0, size[1] * 0.5 + 0.1, 0), GetDirection().VectorToAngles(), size, ObjIntersect.View, ObjIntersect.Geom, {this}, objs))
{
foreach (Object obj : objs)
{
if (dBodyGetInteractionLayer(obj) == PhxInteractionLayers.ITEM_LARGE)
return false;
}
}
return true;
}
override bool CanSwapEntities(EntityAI otherItem, InventoryLocation otherDestination, InventoryLocation destination)
{
if (!otherDestination || otherDestination.GetParent() == this)
{
return false;
}
return true;
}
override bool CanPutIntoHands(EntityAI parent)
{
if (!super.CanPutIntoHands(parent))
{
return false;
}
return GetTemperature() <= GameConstants.STATE_HOT_LVL_ONE; //say 'no' to 3rd degree burns!
}
//Action condition for building oven
bool CanBuildOven()
{
return !IsOven() && !IsBurning() && (GetAttachedStonesCount() >= MIN_STONES_TO_BUILD_OVEN) && !FindAttachmentBySlotName("CookingTripod") && !HasStoneCircle() && IsSpaceForOven();
}
bool IsSpaceForOven()
{
const float size = 0.6;
return IsSpaceFor(Vector(size, size, size));
}
//Action condition for stone circle
bool CanBuildStoneCircle()
{
return !HasStoneCircle() && !IsOven() && !IsBurning() && (GetAttachedStonesCount() >= MIN_STONES_TO_BUILD_CIRCLE) && IsSpaceForCircle();
}
bool IsSpaceForCircle()
{
return IsSpaceFor(Vector(0.9, 0.1, 0.9));
}
//Action condition for dismantling oven
bool CanDismantleOven()
{
return (IsOven() && !IsBurning() && !DirectCookingSlotsInUse() && !SmokingSlotsInUse() && GetTemperature() <= MAX_TEMPERATURE_TO_DISMANTLE_OVEN);
}
//Action condition for dismantling stone circle
bool CanDismantleStoneCircle()
{
return (HasStoneCircle() && !IsOven() && !IsBurning());
}
bool CanCookOnStick()
{
return (GetTemperature() >= PARAM_COOKING_TEMP_THRESHOLD);
}
//Can extinguish fire
bool CanExtinguishFire()
{
return IsBurning();
}
FireplaceLight GetLightEntity()
{
return m_Light;
}
void SetLightEntity(FireplaceLight light)
{
m_Light = light;
}
//================================================================
// ADVANCED PLACEMENT
//================================================================
override bool CanBePlaced( Man player, vector position )
{
string surfaceType;
float surfaceHeight = g_Game.SurfaceGetType3D( position[0], position[1], position[2], surfaceType );
if ((position[1] - surfaceHeight) > PLACEMENT_HEIGHT_LIMIT)
return false;
return true;
}
override void OnPlacementComplete(Man player, vector position = "0 0 0", vector orientation = "0 0 0")
{
super.OnPlacementComplete(player, position, orientation);
if (g_Game.IsServer())
{
//remove grass
Object cc_object = g_Game.CreateObjectEx(OBJECT_CLUTTER_CUTTER , position, ECE_PLACE_ON_SURFACE);
cc_object.SetOrientation(orientation);
g_Game.GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(g_Game.ObjectDelete, 1000, false, cc_object);
}
}
override string GetDeploySoundset()
{
return "placeFireplace_SoundSet";
}
//================================================================
// QUANITTY
//================================================================
// calculates and sets total energy based on possible (fuel/kinidling) item attachments
protected void CalcAndSetTotalEnergy()
{
if (g_Game && g_Game.IsServer())
{
m_TotalEnergy = 0;
foreach (FireConsumableType fireConsumableType : m_FireConsumableTypes)
{
string quantityConfigPath = string.Format("CfgVehicles %1 varQuantityMax", fireConsumableType.GetItemType().ToString());
string stackMaxConfigPath = string.Format("CfgSlots Slot_%1 stackMax", fireConsumableType.GetAttSlot());
if (g_Game.ConfigIsExisting(quantityConfigPath))
{
float quantityMax = g_Game.ConfigGetFloat(quantityConfigPath);
}
if (g_Game.ConfigIsExisting(stackMaxConfigPath))
{
float stackMax = g_Game.ConfigGetFloat(stackMaxConfigPath);
}
//debug
//Print(fireConsumableType.GetItemType().ToString() + " quantityMax = " + quantityMax.ToString() + " [" + (quantity_max*fire_consumable_type.GetEnergy()).ToString() + "] | stack_max = " + stack_max.ToString() + " [" + (stack_max*fire_consumable_type.GetEnergy()).ToString() + "]");
if (stackMax > 0)
{
m_TotalEnergy += stackMax * fireConsumableType.GetEnergy();
}
else
{
m_TotalEnergy += quantityMax * fireConsumableType.GetEnergy();
}
}
//debug
//Print("Total energy = " + m_TotalEnergy.ToString());
}
}
// calculates and sets current quantity based on actual (fuel/kinidling) item attachments
protected void CalcAndSetQuantity()
{
if (g_Game && g_Game.IsServer())
{
float remainingEnergy;
foreach (FireConsumable fireConsumable : m_FireConsumables)
{
float quantity = fireConsumable.GetItem().GetQuantity();
if (quantity > 0)
{
remainingEnergy += ((quantity - 1) * fireConsumable.GetEnergy()) + fireConsumable.GetRemainingEnergy();
//Print(fireConsumable.GetItem().GetType() + " remaining energy = " + (((quantity - 1) * fire_consumable.GetEnergy()) + fire_consumable.GetRemainingEnergy()).ToString());
}
else
{
remainingEnergy += fireConsumable.GetRemainingEnergy();
//Print(fireConsumable.GetItem().GetType() + " remaining energy = " + (fireConsumable.GetRemainingEnergy().ToString()));
}
}
SetQuantity(remainingEnergy / m_TotalEnergy * GetQuantityMax());
}
}
override void OnAttachmentQuantityChanged(ItemBase item)
{
super.OnAttachmentQuantityChanged(item);
CalcAndSetQuantity();
}
override bool CanReleaseAttachment(EntityAI attachment)
{
if (!super.CanReleaseAttachment(attachment))
return false;
ItemBase item = ItemBase.Cast(attachment);
if (IsKindling(item) || IsFuel(item))
{
return !IsBurning();
}
return true;
}
void LockOvenAttachments(bool lock)
{
//Print("LockOvenAttachments");
//string path_cooking_equipment = "" + CFG_VEHICLESPATH + " " + GetType() + " GUIInventoryAttachmentsProps CookingEquipment attachmentSlots";
string path_cooking_equipment = string.Format("%1 %2 GUIInventoryAttachmentsProps CookingEquipment attachmentSlots", CFG_VEHICLESPATH, GetType());
//string path_direct_cooking = "" + CFG_VEHICLESPATH + " " + GetType() + " GUIInventoryAttachmentsProps DirectCooking attachmentSlots";
string path_direct_cooking = string.Format("%1 %2 GUIInventoryAttachmentsProps DirectCooking attachmentSlots", CFG_VEHICLESPATH, GetType());
if (g_Game.ConfigIsExisting(path_cooking_equipment) && g_Game.ConfigIsExisting(path_direct_cooking))
{
array<string> arr_cooking_equipment = new array<string>;
array<string> arr_direct_cooking = new array<string>;
g_Game.ConfigGetTextArray(path_cooking_equipment,arr_cooking_equipment);
g_Game.ConfigGetTextArray(path_direct_cooking,arr_direct_cooking);
for (int i = 0; i < arr_cooking_equipment.Count(); i++)
{
if (lock != GetInventory().GetSlotLock(InventorySlots.GetSlotIdFromString(arr_cooking_equipment[i])))
{
GetInventory().SetSlotLock(InventorySlots.GetSlotIdFromString(arr_cooking_equipment[i]),lock);
//Print("attachment lock: " + arr_cooking_equipment[i] + " " + lock);
}
}
for (i = 0; i < arr_direct_cooking.Count(); i++)
{
if (lock == GetInventory().GetSlotLock(InventorySlots.GetSlotIdFromString(arr_direct_cooking[i])))
{
GetInventory().SetSlotLock(InventorySlots.GetSlotIdFromString(arr_direct_cooking[i]),!lock);
//Print("attachment lock: " + arr_direct_cooking[i] + " " + !lock);
}
}
}
m_OvenAttachmentsLockState = lock;
}
override bool DisassembleOnLastDetach()
{
return true;
}
override void OnRPC(PlayerIdentity sender, int rpc_type, ParamsReadContext ctx)
{
super.OnRPC(sender, rpc_type, ctx);
ref Param1<bool> p = new Param1<bool>(false);
if (ctx.Read(p))
{
bool failure = p.param1;
}
switch (rpc_type)
{
case FirePlaceFailure.WIND:
if (failure)
{
ParticleFireWindyNoIgniteStart();
SoundFireStop();
SoundFireWindyNoIgniteStart();
}
break;
case FirePlaceFailure.WET:
if (failure)
{
ParticleWetNoIgniteStart();
SoundFireStop();
SoundFireWetNoIgniteStart();
}
break;
}
}
//================================================================
// DEBUG
//================================================================
//Debug menu Spawn Ground Special
override void OnDebugSpawn()
{
ItemBase firewood = ItemBase.Cast(GetInventory().CreateInInventory("Firewood"));
firewood.SetQuantity(firewood.GetQuantityMax());
ItemBase sticks = ItemBase.Cast(GetInventory().CreateInInventory("WoodenStick"));
sticks.SetQuantity(sticks.GetQuantityMax());
ItemBase stone = ItemBase.Cast(GetInventory().CreateInInventory("Stone"));
stone.SetQuantity(stone.GetQuantityMax());
GetInventory().CreateInInventory("Rag");
SpawnEntityOnGroundPos("PetrolLighter", GetPosition());
}
override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
{
outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.ACTIVATE_ENTITY, "Ignite", FadeColors.LIGHT_GREY));
outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.DEACTIVATE_ENTITY, "Extinguish", FadeColors.LIGHT_GREY));
outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
super.GetDebugActions(outputList);
}
override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
{
if (super.OnAction(action_id, player, ctx))
return true;
if (g_Game.IsServer() || !g_Game.IsMultiplayer())
{
if (action_id == EActions.ACTIVATE_ENTITY)
{
OnIgnitedThis(null);
}
else if (action_id == EActions.DEACTIVATE_ENTITY)
{
StopFire();
}
}
return false;
}
///////////////////////////
//DEPRECATED STUFF BELOW//
/////////////////////////
const float PARAM_ITEM_HEAT_MIN_TEMP = 40;//!DEPRECATED
const float PARAM_ITEM_HEAT_TEMP_INCREASE_COEF = 10; //! value for calculating temperature increase on each heat update interval (degree Celsius)
const float PARAM_ITEM_HEAT_TEMP_HEATING_COEF = 1.0; //! multiplies temperature change on heating
const float PARAM_MAX_ITEM_HEAT_TEMP_INCREASE = 200; //! maximum value of temperature of items in fireplace when heating (degree Celsius)
const float PARAM_HEAT_THROUGH_AIR_COEF = 0.1; //! DEPRECATED value for calculation of heat transfered from fireplace through air to player (environment)
const float TEMPERATURE_LOSS_MP_DEFAULT = 1.0;
const float TEMPERATURE_LOSS_MP_STONES = 0.90; //10% boost
const float TEMPERATURE_LOSS_MP_OVEN = 0.75; //25% boost
const float PARAM_COOKING_EQUIP_MAX_TEMP = 250; //! maximum temperature of attached cooking equipment (degree Celsius)
const float PARAM_COOKING_EQUIP_TEMP_INCREASE = 10; //! how much will temperature increase when attached on burning fireplace (degree Celsius)
const float DIRECT_COOKING_SPEED = 1.5; // per second
const float PARAM_WET_INCREASE_COEF = 0.02; //! value for calculating of wetness that fireplace gain when raining
protected float m_TemperatureLossMP = 1.0;//! determines how fast will the fireplace loose its temperature when cooling (lower is better)
//Temperature loss MP
protected float GetTemperatureLossMP()
{
return m_TemperatureLossMP;
}
protected void SetTemperatureLossMP(float value)
{
m_TemperatureLossMP = value;
}
// DEPRECATED
protected void AddDamageToItemByFire(ItemBase item, bool can_be_ruined)
{
AddDamageToItemByFireEx(item,can_be_ruined,false);
}
/////////////////////////
//////////////////////////
///////////////////////////
}