class Edible_Base : ItemBase
{
	const string DIRECT_COOKING_SLOT_NAME 	= "DirectCooking";

	const string SOUND_BAKING_START 		= "Baking_SoundSet";
	const string SOUND_BAKING_DONE 			= "Baking_Done_SoundSet";
	const string SOUND_BOILING_START 		= "Boiling_SoundSet";
	const string SOUND_BOILING_DONE 		= "Boiling_Done_SoundSet";
	const string SOUND_DRYING_START 		= "Drying_SoundSet";
	const string SOUND_DRYING_DONE 			= "Drying_Done_SoundSet";
	const string SOUND_BURNING_DONE 		= "Food_Burning_SoundSet";

	protected bool m_MakeCookingSounds;
	protected SoundOnVehicle m_SoundCooking; //! DEPRECATED
	protected EffectSound m_SoundEffectCooking;
	protected string m_SoundPlaying;
	ref FoodStage m_FoodStage;
	protected float m_DecayTimer;
	protected float m_DecayDelta = 0.0;
	protected FoodStageType m_LastDecayStage = FoodStageType.NONE;
	protected ParticleSource 	m_HotVaporParticle;
	
	private CookingMethodType m_CookedByMethod;
		
	void Edible_Base()
	{
		if (HasFoodStage())
		{
			m_FoodStage = new FoodStage(this);
			
			RegisterNetSyncVariableInt("m_FoodStage.m_FoodStageType",  FoodStageType.NONE, FoodStageType.COUNT);
			RegisterNetSyncVariableFloat("m_FoodStage.m_CookingTime",  0, 600, 0);

			m_SoundPlaying = "";
			m_CookedByMethod = CookingMethodType.NONE;
			RegisterNetSyncVariableInt("m_CookedByMethod", CookingMethodType.NONE, CookingMethodType.COUNT);
			RegisterNetSyncVariableBool("m_MakeCookingSounds");
		}
	}
	
	override void EEInit()
	{
		super.EEInit();
		
		UpdateVisualsEx(true); //forced init visuals, mostly for debugs and SP
	}
	
	override void EEDelete(EntityAI parent)
	{
		super.EEDelete(parent);
		
		RemoveAudio();
		
		if (m_HotVaporParticle)
			m_HotVaporParticle.Stop();
	}
	
	override void EEItemLocationChanged(notnull InventoryLocation oldLoc, notnull InventoryLocation newLoc)
	{
		super.EEItemLocationChanged(oldLoc, newLoc);

		//! disable sounds (from cooking)		
		if (oldLoc.GetType() == InventoryLocationType.ATTACHMENT || oldLoc.GetType() == InventoryLocationType.CARGO)
		{
			switch (oldLoc.GetParent().GetType())
			{
				case "FryingPan":
				case "Pot":
				case "Cauldron":
				case "SharpWoodenStick":
					MakeSoundsOnClient(false);
				break;
			}
			
			//! check for DirectCooking slot name
 			if (oldLoc.GetSlot() > -1 && InventorySlots.GetSlotName(oldLoc.GetSlot()).Contains(DIRECT_COOKING_SLOT_NAME))
			{
				MakeSoundsOnClient(false);
			}
		}
		
		if (oldLoc.IsValid())
			ResetCookingTime();
		
		if (CanHaveTemperature())
			UpdateVaporParticle();
	}

	void UpdateVisualsEx(bool forced = false)
	{
		if (GetFoodStage())
			GetFoodStage().UpdateVisualsEx(forced);
	}
	
	bool Consume(float amount, PlayerBase consumer)
	{
		AddQuantity(-amount, false, false);
		OnConsume(amount, consumer);

		return true;
	}
	
	void OnConsume(float amount, PlayerBase consumer)
	{
		if (GetTemperature() > PlayerConstants.CONSUMPTION_DAMAGE_TEMP_THRESHOLD)
		{
			consumer.ProcessDirectDamage(DamageType.CUSTOM, this, "", "EnviroDmg", "0 0 0", PlayerConstants.CONSUMPTION_DAMAGE_PER_BITE);
		}
	}
	
	//! Filter agents from the item (override on higher implementations)
	int FilterAgents(int agentsIn)
	{
		int foodStageType;

		FoodStage foodStage = GetFoodStage();
		if (foodStage)
			foodStageType = foodStage.GetFoodStageType();

		//! if no per FoodStage/Nutrition override is set, remove possible Food Poisoning to prevent double punishment from Bloody Hands
		NutritionalProfile nutritionalProfile = GetNutritionalProfile(this, ClassName(), foodStageType);
		if ((agentsIn & eAgents.SALMONELLA == eAgents.SALMONELLA) && (nutritionalProfile.m_Agents == 0 || nutritionalProfile.m_AgentsPerDigest == 0))
			agentsIn &= ~eAgents.FOOD_POISON;

		return agentsIn;
	}
	
	//food staging
	override bool CanBeCooked()
	{
		return false;
	}
	
	override bool CanBeCookedOnStick()
	{
		return false;
	}
	
	override float GetTemperatureFreezeTime()
	{
		if (GetFoodStage())
		{
			switch (m_FoodStage.GetFoodStageType())
			{
				case FoodStageType.DRIED:
					return super.GetTemperatureFreezeTime() * GameConstants.TEMPERATURE_FREEZE_TIME_COEF_DRIED;
				
				case FoodStageType.BURNED:
					return super.GetTemperatureFreezeTime() * GameConstants.TEMPERATURE_FREEZE_TIME_COEF_BURNED;
				
				default:
					return super.GetTemperatureFreezeTime();
			}
		}
		
		return super.GetTemperatureFreezeTime();
	}
	
	override float GetTemperatureThawTime()
	{
		if (GetFoodStage())
		{
			switch (m_FoodStage.GetFoodStageType())
			{
				case FoodStageType.DRIED:
					return super.GetTemperatureThawTime() * GameConstants.TEMPERATURE_THAW_TIME_COEF_DRIED;
				
				case FoodStageType.BURNED:
					return super.GetTemperatureThawTime() * GameConstants.TEMPERATURE_THAW_TIME_COEF_BURNED;
				
				default:
					return super.GetTemperatureThawTime();
			}
		}
		
		return super.GetTemperatureThawTime();
	}
	
	override bool CanItemOverheat()
	{
		return super.CanItemOverheat() && (!GetFoodStage() || (IsFoodBurned() || !GetFoodStage().CanTransitionToFoodStageType(FoodStageType.BURNED))); //for foodstaged items - either is burned, or it can't get burned
	}
	
	//================================================================
	// SYNCHRONIZATION
	//================================================================	
	void Synchronize()
	{
		SetSynchDirty();
	}
	
	override void OnVariablesSynchronized()
	{
		super.OnVariablesSynchronized();
		
		//UpdateVisualsEx(); //performed on client only
		
		//update audio
		if (m_MakeCookingSounds)
		{
			RefreshAudio();
		}
		else
		{
			RemoveAudio();
		}
		
		if (CanHaveTemperature())
			UpdateVaporParticle();
	}

	//================================================================
	// AUDIO EFFECTS (WHEN ON DCS)
	//================================================================	
	void MakeSoundsOnClient(bool soundstate, CookingMethodType cookingMethod = CookingMethodType.NONE)
	{
		m_MakeCookingSounds = soundstate;
		m_CookedByMethod 	= cookingMethod;

		Synchronize();
	}

	protected void RefreshAudio()
	{
		string soundName = "";
		
		FoodStageType currentFoodStage = GetFoodStageType();
		FoodStageType nextFoodStage = GetNextFoodStageType(m_CookedByMethod);

		if (currentFoodStage == FoodStageType.BURNED)
		{
			soundName = SOUND_BURNING_DONE;
		}
		else
		{
			switch (m_CookedByMethod)
			{
				case CookingMethodType.BAKING:
				{
					if (nextFoodStage == FoodStageType.BAKED)
						soundName = SOUND_BAKING_START;
					else if (currentFoodStage == FoodStageType.BAKED)
						soundName = SOUND_BAKING_DONE;
					else
						soundName = "";
					break;
				}
				
				case CookingMethodType.BOILING:
				{
					if (nextFoodStage == FoodStageType.BOILED)
						soundName = SOUND_BOILING_START;
					else if (currentFoodStage == FoodStageType.BOILED)
						soundName = SOUND_BOILING_DONE;
					else
						soundName = "";
					break;
				}
				
				case CookingMethodType.DRYING:
				{
					if (nextFoodStage == FoodStageType.DRIED)
						soundName = SOUND_DRYING_START;
					else if (currentFoodStage == FoodStageType.DRIED)
						soundName = SOUND_DRYING_DONE;
					else
						soundName = "";
					break;
				}
				
				default:
					soundName = "";
					break;
			}
			
			if (nextFoodStage == FoodStageType.BURNED)
			{
				if (soundName == "") //on 'bad' transitions only, indicates bad outcome
				{
					soundName = SOUND_BURNING_DONE;
				}
				else // pre-emptive burning sounds replace regular ones
				{
					array<float> nextStageProperties = new array<float>();
					nextStageProperties = FoodStage.GetAllCookingPropertiesForStage(nextFoodStage, null, GetType());
					float nextStageTime = nextStageProperties.Get(eCookingPropertyIndices.COOK_TIME);
					float progress01 = GetCookingTime() / nextStageTime;
					if (progress01 > Cooking.BURNING_WARNING_THRESHOLD)
					{
						soundName = SOUND_BURNING_DONE;
					}
				}
			}
		}
		
		SoundCookingStart(soundName);
	}

	protected void RemoveAudio()
	{
		m_MakeCookingSounds = false;
		SoundCookingStop();
	}

	//================================================================
	// SERIALIZATION
	//================================================================	
	override void OnStoreSave(ParamsWriteContext ctx)
	{   
		super.OnStoreSave(ctx);

		if (GetFoodStage())
		{
			GetFoodStage().OnStoreSave(ctx);
		}
		
		// food decay
		ctx.Write(m_DecayTimer);
		ctx.Write(m_LastDecayStage);
	}
	
	override bool OnStoreLoad(ParamsReadContext ctx, int version)
	{
		if (!super.OnStoreLoad(ctx, version))
			return false;

		if (GetFoodStage())
		{
			if (!GetFoodStage().OnStoreLoad(ctx, version))
				return false;
		}
		
		if (version >= 115)
		{
			if (!ctx.Read(m_DecayTimer))
			{
				m_DecayTimer = 0.0;
				return false;
			}
			if (!ctx.Read(m_LastDecayStage))
			{
				m_LastDecayStage = FoodStageType.NONE;
				return false;
			}
		}
		
		UpdateVisualsEx(true); //forced init visuals
		Synchronize();
		
		return true;
	}
	
	override void AfterStoreLoad()
	{	
		super.AfterStoreLoad();
		
		Synchronize();
	}	

	//get food stage
	override FoodStage GetFoodStage()
	{
		return m_FoodStage;
	}
	
	//food types
	override bool IsMeat()
	{
		return false;
	}
	
	override bool IsCorpse()
	{
		return false;
	}
	
	override bool IsFruit()
	{
		return false;
	}
	
	override bool IsMushroom()
	{
		return false;
	}	
	
	//================================================================
	// NUTRITIONAL VALUES
	//================================================================	
	//food properties
	static float GetFoodTotalVolume(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			 return FoodStage.GetFullnessIndex(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetFullnessIndex(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetFloat( class_path + " fullnessIndex" );

	}
	
	static float GetFoodEnergy(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			 return FoodStage.GetEnergy(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetEnergy(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetFloat( class_path + " energy" );			
	}
	
	static float GetFoodWater(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			return FoodStage.GetWater(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetWater(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetFloat( class_path + " water" );			
	}
	
	static float GetFoodNutritionalIndex(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			return FoodStage.GetNutritionalIndex(food_item.GetFoodStage());	
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetNutritionalIndex(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetFloat( class_path + " nutritionalIndex" );		
		
	}
	
	static float GetFoodToxicity(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			return FoodStage.GetToxicity(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetToxicity(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetFloat( class_path + " toxicity" );			
	}
	
	static int GetFoodAgents(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			return FoodStage.GetAgents(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetAgents(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetInt( class_path + " agents" );
	}
	
	static float GetFoodDigestibility(ItemBase item, string classname = "", int food_stage = 0)
	{
		Edible_Base food_item = Edible_Base.Cast(item);
		if (food_item && food_item.GetFoodStage())
		{
			return FoodStage.GetDigestibility(food_item.GetFoodStage());
		}
		else if (classname != "" && food_stage)
		{
			return FoodStage.GetDigestibility(null, food_stage, classname);
		}
		string class_path = string.Format("cfgVehicles %1 Nutrition", classname);
		return GetGame().ConfigGetInt( class_path + " digestibility" );
	}
	
	static float GetAgentsPerDigest(ItemBase item, string className = "", int foodStage = 0)
	{
		Edible_Base foodItem = Edible_Base.Cast(item);
		if (foodItem && foodItem.GetFoodStage())
		{
			return FoodStage.GetAgentsPerDigest(foodItem.GetFoodStage());
		}
		else if (className != "" && foodStage)
		{
			return FoodStage.GetAgentsPerDigest(null, foodStage, className);
		}
		string classPath = string.Format("cfgVehicles %1 Nutrition", className);
		return GetGame().ConfigGetInt(classPath + " agentsPerDigest");
	}
	
	static NutritionalProfile GetNutritionalProfile(ItemBase item, string classname = "", int food_stage = 0)
	{
		NutritionalProfile profile = new NutritionalProfile();
		profile.m_Energy = GetFoodEnergy(item, classname, food_stage);
		profile.m_WaterContent = GetFoodWater(item, classname, food_stage);
		profile.m_NutritionalIndex = GetFoodNutritionalIndex(item, classname, food_stage);
		profile.m_FullnessIndex = GetFoodTotalVolume(item, classname, food_stage);
		profile.m_Toxicity = GetFoodToxicity(item, classname, food_stage);
		profile.m_Agents = GetFoodAgents(item, classname, food_stage);
		profile.m_Digestibility = GetFoodDigestibility(item, classname, food_stage);
		profile.m_AgentsPerDigest = GetAgentsPerDigest(item, classname, food_stage);
		
		return profile;
	}
	
	//================================================================
	// FOOD STAGING
	//================================================================
	FoodStageType GetFoodStageType()
	{
		return GetFoodStage().GetFoodStageType();
	}
	
	//food stage states
	bool IsFoodRaw()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodRaw();
		}
		
		return false;
	}

	bool IsFoodBaked()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodBaked();
		}
		
		return false;
	}
	
	bool IsFoodBoiled()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodBoiled();
		}
		
		return false;
	}
	
	bool IsFoodDried()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodDried();
		}
		
		return false;
	}
	
	bool IsFoodBurned()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodBurned();
		}
		
		return false;
	}
	
	bool IsFoodRotten()
	{
		if ( GetFoodStage() ) 
		{
			return GetFoodStage().IsFoodRotten();
		}
		
		return false;
	}
	
	//food stage change
	void ChangeFoodStage( FoodStageType new_food_stage_type )
	{
		GetFoodStage().ChangeFoodStage( new_food_stage_type );
	}
	
	FoodStageType GetNextFoodStageType( CookingMethodType cooking_method )
	{
		return GetFoodStage().GetNextFoodStageType( cooking_method );
	}
	
	string GetFoodStageName( FoodStageType food_stage_type )
	{
		return GetFoodStage().GetFoodStageName( food_stage_type );
	}
	
	bool CanChangeToNewStage( CookingMethodType cooking_method )
	{
		return GetFoodStage().CanChangeToNewStage( cooking_method );
	}
	
	//Use this to receive food stage from another Edible_Base
	void TransferFoodStage( notnull Edible_Base source )
	{
		if ( !source.GetFoodStage())
			return;
		m_LastDecayStage = source.GetLastDecayStage();
		ChangeFoodStage(source.GetFoodStage().GetFoodStageType());
		m_DecayTimer = source.GetDecayTimer();
		m_DecayDelta = source.GetDecayDelta();
	}
	
	//! called on server 
	void OnFoodStageChange(FoodStageType stageOld, FoodStageType stageNew)
	{
		HandleFoodStageChangeAgents(stageOld,stageNew);
		UpdateVisualsEx(); //performed on server only
	}
	
	//! removes select agents on foodstage transitions
	void HandleFoodStageChangeAgents(FoodStageType stageOld, FoodStageType stageNew)
	{
		switch (stageNew)
		{
			case FoodStageType.BAKED:
			case FoodStageType.BOILED:
			case FoodStageType.DRIED:
				RemoveAllAgentsExcept(eAgents.BRAIN|eAgents.HEAVYMETAL);
			break;
			
			case FoodStageType.BURNED:
				RemoveAllAgentsExcept(eAgents.BRAIN|eAgents.HEAVYMETAL);
			break;
		}
	}
	
	//================================================================
	// COOKING
	//================================================================
	//cooking time
	float GetCookingTime()
	{
		return GetFoodStage().GetCookingTime();
	}
	
	void SetCookingTime( float time )
	{
		GetFoodStage().SetCookingTime( time );
		
		//synchronize when calling on server
		Synchronize();
	}
	
	void ResetCookingTime()
	{
		if (GetFoodStage())
		{
			GetFoodStage().SetCookingTime(0);
			Synchronize();
		}
	}
	
	//replace edible with new item (opening cans)
	void ReplaceEdibleWithNew( string typeName )
	{
		PlayerBase player = PlayerBase.Cast(GetHierarchyRootPlayer());
		if (player)
		{
			ReplaceEdibleWithNewLambda lambda = new ReplaceEdibleWithNewLambda(this, typeName, player);
			player.ServerReplaceItemInHandsWithNew(lambda);
		}
		else
			Error("ReplaceEdibleWithNew - cannot use edible without player");
	}
	
	int GetConsumptionPenaltyContext()
	{
		return EConsumptionPenaltyContext.DRINK|EConsumptionPenaltyContext.EAT;
	}

	override void SetActions()
	{
		super.SetActions();

		AddAction(ActionAttach);
		AddAction(ActionDetach);
	}

	protected void SoundCookingStart(string sound_name)
	{
		#ifndef SERVER
		if (m_SoundPlaying != sound_name)
		{
			SoundCookingStop();

			m_SoundEffectCooking = SEffectManager.PlaySound(sound_name, GetPosition(), 0, 0, true);
			m_SoundPlaying = sound_name;
		}
		#endif
	}

	protected void SoundCookingStop()
	{
		#ifndef SERVER
		if (m_SoundEffectCooking)
		{
			m_SoundEffectCooking.Stop();
			m_SoundEffectCooking = null;
			m_SoundPlaying = "";
		}
		#endif
	}
	
	override bool CanDecay()
	{
		return false;
	}
	
	override bool CanProcessDecay()
	{
		return !GetIsFrozen() && ( GetFoodStageType() != FoodStageType.ROTTEN );
	}
	
	override void ProcessDecay( float delta, bool hasRootAsPlayer )
	{
		m_DecayDelta = 0.0;
		
		delta *= DayZGame.Cast(GetGame()).GetFoodDecayModifier();
		m_DecayDelta += ( 1 + ( 1 - GetHealth01( "", "" ) ) );
		if ( hasRootAsPlayer )
			m_DecayDelta += GameConstants.DECAY_RATE_ON_PLAYER;
		
		/*Print( "-------------------------" );
		Print( this );
		Print( m_DecayTimer );
		Print( m_DecayDelta );
		Print( m_LastDecayStage );*/
		
		if ( IsFruit() || IsMushroom() )
		{
			// fruit, vegetables and mushrooms
			if ( m_LastDecayStage != GetFoodStageType() )
			{
				switch ( GetFoodStageType() )
				{
					case FoodStageType.RAW:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_RAW_FRVG + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_RAW_FRVG * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.RAW;
						break;
					
					case FoodStageType.BOILED:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_BOILED_FRVG + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_BOILED_FRVG * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.BOILED;
						break;
					
					case FoodStageType.BAKED:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_BAKED_FRVG + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_BAKED_FRVG * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.BAKED;
						break;
					
					case FoodStageType.DRIED:
					case FoodStageType.BURNED:
					case FoodStageType.ROTTEN:
					default:
						m_DecayTimer = -1;
						m_LastDecayStage = FoodStageType.NONE;
						return;
				}
				
				//m_DecayTimer = m_DecayTimer / 1000.0;
			}
			
			m_DecayTimer -= ( delta * m_DecayDelta );
						
			if ( m_DecayTimer <= 0 ) 
			{
				if ( m_LastDecayStage != FoodStageType.NONE )
				{
					// switch to decayed stage
					if ( ( m_LastDecayStage == FoodStageType.BOILED ) || ( m_LastDecayStage == FoodStageType.BAKED ) )
					{
						ChangeFoodStage( FoodStageType.ROTTEN );
					} 
					if ( m_LastDecayStage == FoodStageType.RAW )
					{
						int rng = Math.RandomIntInclusive( 0, 100 );
						if ( rng > GameConstants.DECAY_FOOD_FRVG_DRIED_CHANCE )
						{
							ChangeFoodStage( FoodStageType.ROTTEN );
						}
						else
						{
							if ( CanChangeToNewStage( FoodStageType.DRIED ) )
							{
								ChangeFoodStage( FoodStageType.DRIED );
							}
							else
							{
								ChangeFoodStage( FoodStageType.ROTTEN );
							}
						}
					}
				}
			}

		}
		else if ( IsMeat() )
		{
			// meat
			if ( m_LastDecayStage != GetFoodStageType() )
			{
				switch ( GetFoodStageType() )
				{
					case FoodStageType.RAW:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_RAW_MEAT + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_RAW_MEAT * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.RAW;
						break;
					
					case FoodStageType.BOILED:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_BOILED_MEAT + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_BOILED_MEAT * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.BOILED;
						break;
					
					case FoodStageType.BAKED:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_BAKED_MEAT + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_BAKED_MEAT * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.BAKED;
						break;
					
					case FoodStageType.DRIED:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_DRIED_MEAT + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_DRIED_MEAT * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.DRIED;
						break;

					case FoodStageType.BURNED:
					case FoodStageType.ROTTEN:
					default:
						m_DecayTimer = -1;
						m_LastDecayStage = FoodStageType.NONE;
						return;
				}
			}
			
			m_DecayTimer -= ( delta * m_DecayDelta );
			
			if ( m_DecayTimer <= 0 ) 
			{
				if ( m_LastDecayStage != FoodStageType.NONE )
				{
					// switch to decayed stage
					if ( ( m_LastDecayStage == FoodStageType.DRIED ) || ( m_LastDecayStage == FoodStageType.RAW ) || ( m_LastDecayStage == FoodStageType.BOILED ) || ( m_LastDecayStage == FoodStageType.BAKED ) )
					{
						ChangeFoodStage( FoodStageType.ROTTEN );
					}
				}
			}
		}
		else if ( IsCorpse() )
		{
			// corpse
			if ( m_LastDecayStage != GetFoodStageType() )
			{
				switch ( GetFoodStageType() )
				{
					case FoodStageType.RAW:
						m_DecayTimer = ( GameConstants.DECAY_FOOD_RAW_CORPSE + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_RAW_CORPSE * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
						m_LastDecayStage = FoodStageType.RAW;
						break;

					case FoodStageType.BURNED:
					case FoodStageType.ROTTEN:
					default:
						m_DecayTimer = -1;
						m_LastDecayStage = FoodStageType.NONE;
						return;
				}
			}
			
			m_DecayTimer -= ( delta * m_DecayDelta );
			
			if ( m_DecayTimer <= 0 ) 
			{
				if ( m_LastDecayStage != FoodStageType.NONE )
				{
					// switch to decayed stage
					if ( ( m_LastDecayStage == FoodStageType.DRIED ) || ( m_LastDecayStage == FoodStageType.RAW ) || ( m_LastDecayStage == FoodStageType.BOILED ) || ( m_LastDecayStage == FoodStageType.BAKED ) )
					{
						ChangeFoodStage( FoodStageType.ROTTEN );
					}
				}
			}
		}
		else
		{
			// opened cans
			m_DecayTimer -= ( delta * m_DecayDelta );

			if ( ( m_DecayTimer <= 0 ) && ( m_LastDecayStage == FoodStageType.NONE ) )
			{
				m_DecayTimer = ( GameConstants.DECAY_FOOD_CAN_OPEN + ( Math.RandomFloat01() * ( GameConstants.DECAY_FOOD_DRIED_MEAT * ( GameConstants.DECAY_TIMER_RANDOM_PERCENTAGE / 100.0 ) ) ) );
				m_LastDecayStage = FoodStageType.RAW;
				//m_DecayTimer = m_DecayTimer / 1000.0;
			}
			else
			{
				if ( m_DecayTimer <= 0 )
				{
					InsertAgent(eAgents.FOOD_POISON, 1);
					m_DecayTimer = -1;
				}
			}
		}
	}
	
	protected void UpdateVaporParticle()
	{
		if (GetGame().IsDedicatedServer())
			return;
		
		if (m_VarTemperature >= GameConstants.STATE_HOT_LVL_TWO && !m_HotVaporParticle)
		{
			InventoryLocation invLoc = new InventoryLocation();
			GetInventory().GetCurrentInventoryLocation(invLoc);
			if (invLoc && (invLoc.GetType() == InventoryLocationType.GROUND || invLoc.GetType() == InventoryLocationType.HANDS || invLoc.GetType() == InventoryLocationType.ATTACHMENT))
			{
				ParticleManager ptcMgr = ParticleManager.GetInstance();
				if (ptcMgr)
				{
					m_HotVaporParticle = ParticleManager.GetInstance().PlayOnObject(ParticleList.ITEM_HOT_VAPOR, this);
					m_HotVaporParticle.SetParticleParam(EmitorParam.SIZE, 0.3);
					m_HotVaporParticle.SetParticleParam(EmitorParam.BIRTH_RATE, 10);
					m_HotVaporParticle.SetParticleAutoDestroyFlags(ParticleAutoDestroyFlags.ON_STOP);
				}
			}
		}	
		else if (m_HotVaporParticle)
		{
			if (m_VarTemperature <= GameConstants.STATE_HOT_LVL_TWO)
			{
				m_HotVaporParticle.Stop();
				m_HotVaporParticle = null;
				return;
			}
			
			InventoryLocation inventoryLoc = new InventoryLocation();
			GetInventory().GetCurrentInventoryLocation(inventoryLoc);
			if (invLoc && (invLoc.GetType() != InventoryLocationType.GROUND && invLoc.GetType() != InventoryLocationType.HANDS))
			{
				m_HotVaporParticle.Stop();
				m_HotVaporParticle = null;
			}
		}
	}
	
	override void GetDebugActions(out TSelectableActionInfoArrayEx outputList)
	{
		if (GetFoodStage())
		{
			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FOOD_NUTRITIONS_DATA, "Food Nutritions Data", FadeColors.WHITE));
			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FOOD_STAGE_PREV, "Food Stage Prev", FadeColors.WHITE));
			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.FOOD_STAGE_NEXT, "Food Stage Next", FadeColors.WHITE));
			outputList.Insert(new TSelectableActionInfoWithColor(SAT_DEBUG_ACTION, EActions.SEPARATOR, "___________________________", FadeColors.RED));
		}

		super.GetDebugActions(outputList);
	}
	
	override bool OnAction(int action_id, Man player, ParamsReadContext ctx)
	{
		super.OnAction(action_id, player, ctx);
		
		if ( GetGame().IsServer() )
		{
			if ( action_id == EActions.FOOD_STAGE_PREV )
			{
				int food_stage_prev = GetFoodStageType() - 1;
				if (food_stage_prev <= 0)
				{
					food_stage_prev = FoodStageType.COUNT - 1;
				}
				ChangeFoodStage(food_stage_prev);
				return true;
			}
			else if ( action_id == EActions.FOOD_STAGE_NEXT )
			{
				int food_stage_next = GetFoodStageType() + 1;
				if (food_stage_next >= FoodStageType.COUNT )
				{
					food_stage_next = FoodStageType.RAW;
				}
				ChangeFoodStage(food_stage_next);
				return true;
			}
			
		}
		
		#ifdef DIAG_DEVELOPER
		if (action_id == EActions.FOOD_NUTRITIONS_DATA)
		{
			PrintNutritionsData();
			return true;
		}
		#endif

		return false;
	}
	
	override string GetDebugText()
	{
		string debug_output;

		debug_output = super.GetDebugText();
		
		debug_output+="m_CookedByMethod:"+m_CookedByMethod+"\n";
		debug_output+="m_MakeCookingSounds:"+m_MakeCookingSounds+"\n";

		return debug_output;
	}

	//================================================================
	// GENERAL GETTERS
	//================================================================
	
	override float GetBaitEffectivity()
	{
		float ret = super.GetBaitEffectivity();
		
		if (IsFoodRotten())
		{
			ret *= 0.5;
		}
		
		return ret;
	}
	
	float GetDecayTimer()
	{
		return m_DecayTimer;
	}
	
	float GetDecayDelta()
	{
		return m_DecayDelta;
	}
	
	FoodStageType GetLastDecayStage()
	{
		return m_LastDecayStage;
	}
	
	#ifdef DIAG_DEVELOPER
	private void PrintNutritionsData()
	{
		string nutritionsData 	= "";
		
		FoodStage stage 		= GetFoodStage();
		FoodStageType stageType = stage.GetFoodStageType();

		NutritionalProfile profile = GetNutritionalProfile(this, ClassName(), stageType);
		if (profile)
		{
			nutritionsData = string.Format("Item: %1\n\n", this);
			nutritionsData += string.Format("Stage name: %1\n", GetFoodStageName(stageType));
			nutritionsData += string.Format("Energy: %1\n", profile.m_Energy);
			nutritionsData += string.Format("Water content: %1\n", profile.m_WaterContent);
			nutritionsData += string.Format("Nutritional index: %1\n", profile.m_NutritionalIndex);
			nutritionsData += string.Format("Fullness index: %1\n", profile.m_FullnessIndex);
			nutritionsData += string.Format("Toxicity (obsolete): %1\n", profile.m_Toxicity);
			nutritionsData += string.Format("Digestibility: %1\n", profile.m_Digestibility);

			if (profile.IsLiquid())
				nutritionsData += string.Format("Liquid type: %1\n", profile.m_LiquidClassname);
			
			nutritionsData += string.Format("Agents: %1\n", profile.m_Agents);
			nutritionsData += string.Format("Agents per consume: %1\n", profile.m_AgentsPerDigest);
		}
		
		nutritionsData += "-----\n";
		
		Debug.Log(nutritionsData);
	}
	#endif
	
	//////////////////////////////////////////
	//DEPRECATED
	//////////////////////////////////////////
	void UpdateVisuals()
	{
		UpdateVisualsEx();
	}
}

class ReplaceEdibleWithNewLambda : TurnItemIntoItemLambda
{
	void ReplaceEdibleWithNewLambda(EntityAI old_item, string new_item_type, PlayerBase player) { }
};
