/***********************************************************************/
/** Copyright © 2012-2015
/** Author : Rafal Jarczewski, Tomek Kozera
/***********************************************************************/

// Class added to each actor to manage actor's buffs/debuffs/effects
class W3EffectManager
{
	private var owner : CActor;											//this won't get saved if we use the 'saved' keyword
	private saved var effects : array< CBaseGameplayEffect >;			//list of applied (working) effects
	private saved var statDeltas : array<float>;						//array of cached stat updates to perform (regenerations)
	private saved var cachedDamages : array<SEffectCachedDamage>;		//array of cached damages to deal
	private var isReady : bool;											//set to true when object is initialized
	private saved var currentlyAnimatedCS : CBaseGameplayEffect;		//currently animated critical state on target
	private var currentlyPlayedFX : array<SCurrentBuffFX>;				//list of currently played FXs to avoid displaying the same effect more than once or stopping from one source while others need it
	private saved var pausedEffects : array<STemporarilyPausedEffect>;	//array of temporarily paused buffs
	private saved var pausedNotAppliedAutoBuffs : array<SPausedAutoEffect>;	//array of autobuffs to pause once they get added to character
	private var ownerIsWitcher : bool;									//set to true if owner is witcher
	private var isInitializingAutobuffs : bool;							//set for duration of initialization of autobuffs
	private var hasCriticalStateSaveLock : bool;						//true if there's currently a SaveLock in effect
	private var criticalStateSaveLockId : int;							//SaveLock id from critical states if any
	
	private var vitalityAutoRegenOn 		: bool;		default vitalityAutoRegenOn 		= false;
	private var essenceAutoRegenOn 			: bool;		default essenceAutoRegenOn 			= false;
	private var staminaAutoRegenOn 			: bool;		default staminaAutoRegenOn 			= false;
	private var moraleAutoRegenOn 			: bool;		default moraleAutoRegenOn 			= false;
	private var panicAutoRegenOn			: bool;		default panicAutoRegenOn			= false;
	private var airAutoRegenOn				: bool;		default airAutoRegenOn				= false;
	private var swimmingStaminaAutoRegenOn	: bool;		default swimmingStaminaAutoRegenOn	= false;
	private var adrenalineAutoRegenOn		: bool;		default adrenalineAutoRegenOn		= false;
	
		default isReady = false;
		default isInitializingAutobuffs = false;
		
	
	//Applies autobuffs to owner and initializes this object	
	public final function Initialize( actor : CActor )
	{
		var i : int;
		var effect : CBaseGameplayEffect;
		var overridenEffectsIdxs : array<int>;
		var autoEffects : array<name>;
		var type : EEffectType;
		var tmpName : name;
		var npc : CNewNPC;

		owner = actor;
		ownerIsWitcher = (W3PlayerWitcher)owner;
		hasCriticalStateSaveLock = false;
		statDeltas.Grow(EnumGetMax('EBaseCharacterStats')+1);
		for(i=0; i<statDeltas.Size(); i+=1)
			statDeltas[i] = 0;
		
		//get autobuffs
		npc = (CNewNPC)actor;
		if( npc )
		{
			if( npc.GetNPCType() != ENGT_Commoner )
			{
				autoEffects = PrepareAutoBuffs();
			}
		}
		else if ( (CR4Player)actor )
			autoEffects = PrepareAutoBuffs();
		
		FilterOutExactly( autoEffects, EET_AutoVitalityRegen );
		FilterOutExactly( autoEffects, EET_AutoEssenceRegen );
		FilterOutExactly( autoEffects, EET_AutoStaminaRegen );
		FilterOutExactly( autoEffects, EET_AutoMoraleRegen );
		FilterOutExactly( autoEffects, EET_AutoPanicRegen );
		FilterOutExactly( autoEffects, EET_AutoAirRegen );
		FilterOutExactly( autoEffects, EET_AutoSwimmingStaminaRegen );
		FilterOutExactly( autoEffects, EET_AdrenalineDrain );
		
		//HACK: sometimes actor OnSpawn event is called before Game's OnGameStarting() and thus we have no GameEffectManager!
		if(autoEffects.Size() > 0 && !theGame.IsEffectManagerInitialized())
			theGame.InitializeEffectManager();
		
		isInitializingAutobuffs = true;
		for(i=0; i<autoEffects.Size(); i+=1)
		{
			EffectNameToType(autoEffects[i], type, tmpName);
			InternalAddEffect(type,owner,'autobuff');			
		}
		isInitializingAutobuffs = false;
				
		isReady = true;
	}
	
	// Updates all effects
	public final function PerformUpdate(deltaTime : float)
	{
		var i, size : int;
		var delta : float;
		var action : W3DamageAction;
		var wasPaused : bool;
		var carrier, pausedBuff : CBaseGameplayEffect;
		var cachedDeltaRemoved : bool;
			
		//update time and remove if timed-out
		size = effects.Size();		
		for(i=size-1; i>=0; i -= 1 )
		{
			//check pause flags status
			if( effects[ i ].IsPausedDuringDialogAndCutscene() )
			{
				if( theGame.IsDialogOrCutscenePlaying() && !effects[i].IsPaused('dialogOrCutscene'))
				{
					if(IsCriticalEffect(effects[i]))
						RemoveEffect(effects[i], true);
					else
						effects[i].Pause( 'dialogOrCutscene', true );
				}
				else if( !theGame.IsDialogOrCutscenePlaying() && effects[i].IsPaused('dialogOrCutscene'))
				{
					effects[i].Resume( 'dialogOrCutscene' );
				}
			}
		
			//skip paused effects
			if( effects[i].IsPaused() )
			{
				continue;
			}
				
			//update or finish
			if(effects[i].IsActive())
			{
				effects[i].OnTimeUpdated(deltaTime);		
			}
			else
			{
				RemoveEffectOnIndex( i );
			}
		}

		//Now perform cached stat updates. Stat changes are cached when processing effects, not applied on the fly. This is to prevent situations where
		//you would have 2 hp left, and one buff that restores 10 hp and another that drains 5 hp. If the latter would be processed first then you would
		//die although the delta hp update is +5 so you should not die.
		//
		//We cannot simply process positive buffs before negative ones because a buff might give two effects at the same time e.g. -hp per sec and +stamina per sec 
		//-> a positive buff that additionally drains stat what in this case could kill the owner
		
		//update stats (gains and drains)
		size = statDeltas.Size();
		cachedDeltaRemoved = false;
		for(i=0; i<size; i+=1)
		{
			delta = statDeltas[i];
			if(delta == 0)
			{
				continue;
			}
			else
			{
				UpdateStatValueChange(i, delta);				
				statDeltas[i] = 0;	//clear
				cachedDeltaRemoved = true;
				
				if(i == BCS_Vitality || i == BCS_Essence)
					owner.ShowFloatingValue(EFVT_Heal, delta, true);
			}
		}
				
		//update damages
		size = cachedDamages.Size();
		if( size > 0 )
		{
			action = new W3DamageAction in theGame.damageMgr;
			
			for(i=0; i<size; i+=1)
			{
				action.Initialize( (CGameplayEntity)EntityHandleGet(cachedDamages[i].attacker), owner, cachedDamages[i].carrier, cachedDamages[i].sourceName, EHRT_None, cachedDamages[i].powerStatType, false, false, false, cachedDamages[i].isEnvironment);
				action.AddDamage(cachedDamages[i].dmgType, cachedDamages[i].dmgVal);
				action.SetHitAnimationPlayType(EAHA_ForceNo);
				if(cachedDamages[i].dt > 0)
				{
					action.SetPointResistIgnored(true);
					action.SetIsDoTDamage(cachedDamages[i].dt);
				}
				
				if(cachedDamages[i].dontShowHitParticle)
				{
					action.SetCanPlayHitParticle(false);
				}
				
				theGame.damageMgr.ProcessAction(action);

				//custom - shut down bleeding particle if no damage dealt
				carrier = cachedDamages[i].carrier;
				if(carrier && carrier.GetEffectType() == EET_Bleeding)
				{
					((W3Effect_Bleeding)carrier).OnDamageDealt( action.DealsAnyDamage() );	//inform the buff (carrier) that damage was not dealt
				}
			}

			delete action;
			cachedDamages.Clear();
			owner.SetEffectsUpdateTicking( false ); // conditions checked internally
		}
		
		//process existing paused effects
		for(i=pausedEffects.Size()-1; i>=0; i-=1)
		{
			if(pausedEffects[i].timeLeft > -1)
			{
				pausedEffects[i].timeLeft -= deltaTime;
				if(pausedEffects[i].timeLeft <= 0)
				{
					pausedEffects[i].buff.Resume(pausedEffects[i].source);
					pausedEffects.Erase(i);
				}
			}
		}
		
		//process non-existing autobuff pauses
		for(i=pausedNotAppliedAutoBuffs.Size()-1; i>=0; i-=1)
		{
			if(pausedNotAppliedAutoBuffs[i].timeLeft > -1)
			{
				pausedNotAppliedAutoBuffs[i].timeLeft -= deltaTime;
				if(pausedNotAppliedAutoBuffs[i].timeLeft <= 0)
				{
					pausedNotAppliedAutoBuffs.Erase(i);
				}
			}
		}
		
		//check if can be removed - this internally checks if all conditions for removing are fullfiled.
		owner.SetEffectsUpdateTicking( false );
	}
	
	//prepares array of autobuffs to add on owner
	private final function PrepareAutoBuffs() : array<name>
	{
		var autoEffects : array<name>;
		
		//autobuffs from template
		owner.GetAutoEffects(autoEffects);
		
		//all npcs get all auto-regens
		//also check if its value is set in XML - remove if not
		FilterAutoBuff(autoEffects, CRS_Vitality, EET_AutoVitalityRegen);
		FilterAutoBuff(autoEffects, CRS_Essence, EET_AutoEssenceRegen);
		FilterAutoBuff(autoEffects, CRS_Stamina, EET_AutoStaminaRegen);
		FilterAutoBuff(autoEffects, CRS_Morale, EET_AutoMoraleRegen);
		
		return autoEffects;
	}
	
	//If given autobuff is not in array it gets added if it's regen value is >0.
	//If autobuff is in array but it's regen value is == 0 it is removed from array
	private final function FilterAutoBuff(out autoEffects : array<name>, regenStat : ECharacterRegenStats, effectType : EEffectType)
	{
		var autoName : name;
		var effectValue, null : SAbilityAttributeValue;
		
		effectValue = owner.GetAttributeValue( RegenStatEnumToName(regenStat) );	
		autoName = EffectTypeToName(effectType);		
		
		if(!autoEffects.Contains(autoName) && effectValue != null)
		{
			autoEffects.PushBack(autoName);
		}
		else if(autoEffects.Contains(autoName) && effectValue == null)
		{
			autoEffects.Remove(autoName);
		}
	}
	
	private final function FilterOutAllApart( out autoEffects : array< name >, effectType : EEffectType )
	{
		var i			: int;
		var autoName 	: name;

		autoName = EffectTypeToName(effectType);
		for( i = autoEffects.Size()-1; i >= 0; i -= 1 )
		{
			if( autoEffects[ i ] != autoName )
			{
				autoEffects.Erase( i );
			}
		}
	}
	
	private final function FilterOutExactly( out autoEffects : array< name >, effectType : EEffectType )
	{
		var i			: int;
		var autoName 	: name;

		autoName = EffectTypeToName( effectType );
		for( i = autoEffects.Size()-1; i >= 0; i -= 1 )
		{
			if( autoEffects[ i ] == autoName )
			{
				autoEffects.Erase( i );
			}
		}
	}
	
	public final function StartVitalityRegen() : bool
	{
		if( vitalityAutoRegenOn )
		{
			return false;
		}
		
		vitalityAutoRegenOn = StartRegenInternal( CRS_Vitality, EET_AutoVitalityRegen );
		return vitalityAutoRegenOn;
	}
	
	public final function StopVitalityRegen()
	{
		if( !vitalityAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoVitalityRegen );
		vitalityAutoRegenOn = false;
	}
	
	public final function StartEssenceRegen() : bool
	{
		if( essenceAutoRegenOn )
		{
			return false;
		}
		
		essenceAutoRegenOn = StartRegenInternal( CRS_Essence, EET_AutoEssenceRegen );
		return essenceAutoRegenOn;
	}
	
	public final function StopEssenceRegen()
	{
		if( !essenceAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoEssenceRegen );
		essenceAutoRegenOn = false;
	}
	
	public final function StartStaminaRegen() : bool
	{
		if( staminaAutoRegenOn )
		{
			return false;
		}
		
		staminaAutoRegenOn = StartRegenInternal( CRS_Stamina, EET_AutoStaminaRegen );
		return staminaAutoRegenOn;
	}
	
	public final function StopStaminaRegen()
	{
		if( !staminaAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoStaminaRegen );
		staminaAutoRegenOn = false;
	}
	
	public final function StartMoraleRegen() : bool
	{
		if( moraleAutoRegenOn )
		{
			return false;
		}
		
		moraleAutoRegenOn = StartRegenInternal( CRS_Morale, EET_AutoMoraleRegen );
		return moraleAutoRegenOn;
	}
	
	public final function StopMoraleRegen()
	{
		if( !moraleAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoMoraleRegen );
		moraleAutoRegenOn = false;
	}
	
	public final function StartPanicRegen() : bool
	{
		if( panicAutoRegenOn )
		{
			return false;
		}
		
		panicAutoRegenOn = StartRegenInternal( CRS_Panic, EET_AutoPanicRegen );
		return panicAutoRegenOn;
	}
	
	public final function StopPanicRegen()
	{
		if( !panicAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoPanicRegen );
		panicAutoRegenOn = false;
	}
	
	public final function StartAirRegen() : bool
	{
		if( airAutoRegenOn )
		{
			return false;
		}
		
		airAutoRegenOn = StartRegenInternal( CRS_Air, EET_AutoAirRegen );
		return airAutoRegenOn;
	}
	
	public final function StopAirRegen()
	{
		if( !airAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoAirRegen );
		airAutoRegenOn = false;
	}
	
	public final function StartSwimmingStaminaRegen() : bool
	{
		if( swimmingStaminaAutoRegenOn )
		{
			return false;
		}
		
		swimmingStaminaAutoRegenOn = StartRegenInternal( CRS_SwimmingStamina, EET_AutoSwimmingStaminaRegen );
		return airAutoRegenOn;
	}
	
	public final function StopSwimmingStaminaRegen()
	{
		if( !airAutoRegenOn )
		{
			return;
		}
		
		StopRegenInternal( EET_AutoSwimmingStaminaRegen );
		airAutoRegenOn = false;
	}
	
	private final function StopRegenInternal( effectType : EEffectType )
	{
		var effect : CBaseGameplayEffect;
		effect = GetEffect( effectType );
		RemoveEffect( effect, true );
	}
	
	private final function StartRegenInternal( regenStat : ECharacterRegenStats, effectType : EEffectType ) : bool
	{
		var autoEffects 	: array<name>;
		var npc 			: CNewNPC;
		var i 				: int;
		var addResult		: EEffectInteract;
		
		npc = (CNewNPC)owner;
		if( ( npc && ( npc.GetNPCType() == ENGT_Commoner ) ) || ( !npc && !( (CR4Player )owner ) ) )
		{
			return false;
		}
		
		owner.GetAutoEffects( autoEffects );
		FilterOutAllApart( autoEffects, effectType );
		
		if( regenStat != CRS_UNUSED )
		{
			FilterAutoBuff( autoEffects, regenStat, effectType );
		}
		
		if( autoEffects.Size() > 0 )
		{
			addResult = InternalAddEffect( effectType, owner, 'autobuff' );
			
			if(addResult == EI_Pass)
			{
				return true;		
			}
			else if(addResult == EI_Cumulate)
			{
				owner.SetEffectsUpdateTicking(true);
				return true;
			}				
		}
		
		return false;
	}
	
	// Called on game load
	public final function OnLoad(own : CActor)
	{
		var i : int;
		
		owner = own;
		hasCriticalStateSaveLock = false;
		ownerIsWitcher = (W3PlayerWitcher)owner;
		for(i=0; i<effects.Size(); i+=1)
			effects[i].OnLoad(owner, this);
			
		//save hack - these should be false!
		staminaAutoRegenOn = false;		
		essenceAutoRegenOn = false;		
		staminaAutoRegenOn = false;		
		moraleAutoRegenOn = false;		
		panicAutoRegenOn = false;		
		airAutoRegenOn = false;		
		swimmingStaminaAutoRegenOn = false;		
		adrenalineAutoRegenOn = false;		
				
		//call timer - if there are no buffs it will turn off after 1 tick. It's the safest way to make sure effect manager restores in weird cases (e.g. having pauses for non-existing buffs etc)
		owner.SetEffectsUpdateTicking(true);
		isReady = true;
	}
	
	public final function IsReady() : bool {return isReady;}		
	public final function GetCurrentlyAnimatedCS() : CBaseGameplayEffect			{return currentlyAnimatedCS;}
	public final function SetCurrentlyAnimatedCS(buff : CBaseGameplayEffect)
	{
		if(owner == thePlayer)
			LogCriticalPlayer("** EffectManager.SetCurrentlyAnimatedCS() - current is now <<" + buff + ">>");
			
		if(!buff)
			Log("");
			
		currentlyAnimatedCS = buff;
	}
	
	//If partialSourceNameSearch is set then instead of matching exact sourceName, it checks if provided sourcename is a substring of buff's sourceName
	public final function GetCurrentEffects(optional type : EEffectType, optional sourceName : string, optional partialSourceNameSearch : bool) : array< CBaseGameplayEffect >
	{
		var i : int;
		var ret : array< CBaseGameplayEffect >;
		var buffOk, sourceNameSet : bool;
		
		sourceNameSet = (sourceName != "" && sourceName != "None" && sourceName != "none");
		
		if(type == EET_Undefined && !sourceNameSet)
			return effects;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			//if not NULL
			if(effects[i])
			{
				buffOk = true;
				
				//if type passed then check type first
				if(type != EET_Undefined)
				{
					if(type != effects[i].GetEffectType())
						buffOk = false;
				}
				
				if(buffOk)
				{
					if(partialSourceNameSearch)
					{
						buffOk = (StrFindFirst(effects[i].GetSourceName(), sourceName) != -1);
					}
					else
					{
						if(!sourceNameSet)
						{
							buffOk = true;
						}	
						else
						{
							buffOk = (effects[i].GetSourceName() == sourceName);
						} 
					}
					
					if(buffOk)
						ret.PushBack(effects[i]);
				}
			}
		}
		
		return ret;
	}
			
	/*
		Applies given effect on actor		
		
		effect - effect to apply
		overridenEffectsIdxs - array of indexes of effects which are overriden by this effect (those effect will be removed) (provide if applicable)
		cumulateIdx - index of the effect with which this effect should cumulate (if applicable)
		
		@returns - effect interaction type to know what actually happened (if the effect was added, denied, cumulated etc.)
	*/
	private final function ApplyEffect( effect : CBaseGameplayEffect, overridenEffectsIdxs : array<int>, cumulateIdx : int, customParams : W3BuffCustomParams) : EEffectInteract
	{
		var i, size : int;		
		var effectType : EEffectType;
		
		//remove overriden effects
		size = overridenEffectsIdxs.Size();
		for(i=size-1; i>=0; i-=1)
		{
			RemoveEffectOnIndex( overridenEffectsIdxs[ i ], true );
		}
		
		//cumulate with old effect, if anything to cumulate with
		if(cumulateIdx >= 0)
		{
			effects[cumulateIdx].CumulateWith(effect);
			delete effect;
			return EI_Cumulate;
		}
		//otherwise add new effect
		else
		{
			effect.OnEffectAdded(customParams);
						
			//check if effect is added properly
			if(!effect.IsActive())
			{
				LogAssert(false, "W3EffectManager.ApplyEffect: effect <<" + effect + ">> did not add properly (is inactive just after added) to <<" + owner + ">> and is removed!");
				effect.OnEffectRemoved();
				return EI_Undefined;
			}
			
			//pause if autobuff and it's supposed to be paused
			effectType = effect.GetEffectType();
			if(pausedNotAppliedAutoBuffs.Size() > 0 && IsBuffAutoBuff(effectType))
			{			
				for(i=pausedNotAppliedAutoBuffs.Size()-1; i>=0; i-=1)
				{
					if(pausedNotAppliedAutoBuffs[i].effectType == effectType)
					{
						PauseEffect(effect, pausedNotAppliedAutoBuffs[i].sourceName, pausedNotAppliedAutoBuffs[i].singleLock, pausedNotAppliedAutoBuffs[i].duration, pausedNotAppliedAutoBuffs[i].useMaxDuration);
						pausedNotAppliedAutoBuffs.EraseFast(i);
					}
				}
			}
			
			effects.PushBack( effect );
			owner.SetEffectsUpdateTicking( true, isInitializingAutobuffs );			
			OnBuffAdded(effect);
			
			effect.OnEffectAddedPost();
			
			//if here then either we added the effect or overriden some effect
			if(size > 0)
				return EI_Override;
			else
				return EI_Pass;
		}
	}
	
	//called after buff was removed
	private final function OnBuffRemoved()
	{
		if(hasCriticalStateSaveLock && GetCriticalBuffsCount() == 0)
		{
			theGame.ReleaseNoSaveLock(criticalStateSaveLockId);
			hasCriticalStateSaveLock = false;
		}
	}
	
	//called after buff was successfully added
	private final function OnBuffAdded(effect : CBaseGameplayEffect)
	{
		var signEffects : array < CBaseGameplayEffect >;
		var npcOwner : CNewNPC;
		var mutagen : W3Mutagen13_Effect;
		var i : int;
		var effectType : EEffectType;
		
		effectType = effect.GetEffectType();
		
		if(!hasCriticalStateSaveLock && owner == thePlayer && IsCriticalEffectType(effectType) )
		{
			 theGame.CreateNoSaveLock("critical_state", criticalStateSaveLockId);
			 hasCriticalStateSaveLock = true;
		}
		
		//achievement for bleeding, poison and burning
		npcOwner = (CNewNPC)owner;
		if(npcOwner && (effectType == EET_Burning || effectType == EET_Bleeding || effectType == EET_Poison || effectType == EET_PoisonCritical) && !npcOwner.WasBurnedBleedingPoisoned())
		{
			if( owner.HasBuff(EET_Burning) && owner.HasBuff(EET_Bleeding) && (owner.HasBuff(EET_Poison) || owner.HasBuff(EET_PoisonCritical)) )
			{
				theGame.GetGamerProfile().IncStat(ES_BleedingBurnedPoisoned);
				npcOwner.SetBleedBurnPoison();
			}
		}
		
		//achievement for all minor places of power
		if(owner == thePlayer && IsBuffShrine(effectType) && HasAllShrineBuffs())
		{
			theGame.GetGamerProfile().AddAchievement(EA_PowerOverwhelming);
		}
		
		//mutagen 13 - decrease control impairing effects duration
		mutagen = (W3Mutagen13_Effect)owner.GetBuff(EET_Mutagen13);
		if(mutagen && mutagen.IsEffectTypeAffected(effectType))
		{					
			effect.SetTimeLeft(mutagen.GetForcedDuration());
		}		
	}
	
	//runs through all effects from provided array and removes the ones that don't exist anymore
	public final function UpdateLocalBuffsArray(out localArray : array<CBaseGameplayEffect>)
	{
		var i : int;
		
		for(i=localArray.Size()-1; i>=0; i-=1)
			if(!effects.Contains(localArray[i]))
				localArray.Erase(i);
	}
			
	public final function AddEffectCustom(params : SCustomEffectParams) : EEffectInteract
	{
		return InternalAddEffect(
			params.effectType,
			params.creator,
			params.sourceName,
			params.duration,
			params.effectValue,
			params.customAbilityName,
			params.customFXName,
			params.isSignEffect,
			params.customPowerStatValue,
			params.buffSpecificParams,
			params.vibratePadLowFreq,
			params.vibratePadHighFreq
		);
	}
	
	/*
		Adds effect with default parameters
		
		effectType - effect to add
		creat - entity that creted the effect
		srcName - name of the source (needed for cumulation/overrides)
		
		@returns - interaction result
	*/
	public final function AddEffectDefault(effectType : EEffectType, creat : CGameplayEntity, optional srcName : string, optional signEffect : bool) : EEffectInteract
	{
		var none : SAbilityAttributeValue;
		var noneParams : W3BuffCustomParams;
		
		return InternalAddEffect(effectType, creat, srcName, 0, none, '', '', signEffect, none, noneParams);
	}
	
	// Internal function that adds effect to the owner. Is encapsulated by AddEffect* functions to provide proper parameters.
	private final function InternalAddEffect(effectType : EEffectType, creat : CGameplayEntity, srcName : string, optional inDuration : float, optional customVal : SAbilityAttributeValue, optional customAbilityName : name, optional customFXName : name, optional signEffect : bool, optional powerStatValue : SAbilityAttributeValue, optional customParams : W3BuffCustomParams, optional vibratePadLowFreq : float, optional vibratePadHighFreq : float) : EEffectInteract
	{
		var effect : CBaseGameplayEffect;
		var overridenEffectsIdxs : array<int>;
		var cumulateIdx, i : int;
		var npc : CNewNPC;
		var actorCreator : CActor;
		var action : W3DamageAction;
		var hasQuen : bool;
		var damages : array<SRawDamage>;
		
		if(effectType == EET_Undefined)
		{
			LogAssert(false, "EffectManager.AddEffectByType: trying to add effect of undefined type!");
			return EI_Undefined;
		}
		
		//if burning then don't do it if target is underwater
		if(effectType == EET_Burning)
		{
			if( ((CMovingPhysicalAgentComponent)owner.GetMovingAgentComponent()).GetSubmergeDepth() <= -1)
			{
				LogEffects("EffectManager.InternalAddEffect: unit <<" + owner + ">> will not get burning effect since it's underwater!");
				return EI_Deny;
			}
		}		
		//if freeze and flying enemy - disable
		else if(effectType == EET_Frozen)
		{
			npc = (CNewNPC)owner;
			if(npc && npc.IsFlying())
			{
				LogEffects("EffectManager.InternalAddEffect: unit <<" + owner + ">> will not get frozen effect since it's currently flying!");
				return EI_Deny;
			}
		}
		
		//if stagger and has quen then break quen and don't stagger		
		if( ((W3PlayerWitcher)owner) && GetWitcherPlayer().IsAnyQuenActive())
		{
			hasQuen = true;
			
			if(effectType == EET_Stagger || effectType == EET_LongStagger || effectType == EET_CounterStrikeHit)
			{
				GetWitcherPlayer().FinishQuen(false);
				LogEffects("EffectManager.InternalAddEffect: Geralt has active quen so it breaks and we don't stagger.");
				return EI_Deny;
			}
		}
		
		//default srouce name if not provided
		if(srcName == "" && creat)
			srcName = creat.GetName();
		
		//knockdown HACK
		//if target is knocked down and we knock him down, instead we apply ragdoll
		//this is to avoid badly looking animation blends
		// -turned off as for now we don't want to be able to knockdown knocked-down opponents
		/*
		if(effectType == EET_Knockdown || effectType == EET_HeavyKnockdown || effectType == EET_KnockdownTypeApplicator)
		{
			if(owner.HasBuff(EET_Knockdown) || owner.HasBuff(EET_HeavyKnockdown) || owner.HasBuff(EET_KnockdownTypeApplicator) || owner.HasBuff(EET_Ragdoll))
			{
				effectType = EET_Ragdoll;
			}
		}
		*/
		
		// Discard effect before creating it if possible
		//------------------------------------------------------------------------------------------------------------
		
		//applying on dead target
		if(!owner.IsAlive() && !effect.CanBeAppliedOnDeadTarget())
			return EI_Deny;
			
		actorCreator = (CActor)creat;
		
		//buff immunity
		if(owner.IsImmuneToBuff(effectType) && (!actorCreator.HasAbility('ForceCriticalEffects') || IsCriticalEffectType(effectType)) )
		{
			LogEffects("EffectManager.InternalAddEffect: unit <<" + owner + ">> is immune to effect of this type (" + effectType + ")");
			return EI_Deny;
		}		
		
		//friendlies don't give negative buffs (mind the hacks over here)
		if( actorCreator && GetAttitudeBetween( actorCreator, owner ) == AIA_Friendly && creat != owner && IsNegativeEffectType( effectType ) && effectType != EET_Confusion && effectType != EET_AxiiGuardMe )
		{
			LogAssert(false, "EffectManager.InternalAddEffect: unit <<" + owner + ">> is friendly to buff creator: <<" + creat + ">> negative buff cannot be added");
			return EI_Deny;
		}
		
		// Create the effect
		effect = theGame.effectMgr.MakeNewEffect(effectType, creat, owner, this, inDuration, srcName, powerStatValue, customVal, customAbilityName, customFXName, signEffect, vibratePadLowFreq, vibratePadHighFreq);
		
		if(effect)
		{
			//quen absorbs some damage and denies DoT
			if( (hasQuen || (((W3PlayerWitcher)owner) && FactsQuerySum("player_had_quen") > 0)) && IsDoTEffect(effect))
			{
				FactsRemove("player_had_quen");
				
				if((W3DamageOverTimeEffect)effect)
					damages = ((W3DamageOverTimeEffect)effect).GetDamages();
				else if((W3CriticalDOTEffect)effect)
					damages = ((W3CriticalDOTEffect)effect).GetDamages();
				
				action = new W3DamageAction in theGame;
				action.Initialize(creat, owner, effect, srcName, EHRT_None, CPS_Undefined, false, false, false, true);
				action.SetHitAnimationPlayType(EAHA_ForceNo);
				
				for(i=0; i<damages.Size(); i+=1)
				{
					//#Quen hack
					action.AddDamage(damages[i].dmgType, damages[i].dmgVal);
				}
				
				action.SetPointResistIgnored(true);
				action.SetIsDoTDamage(0.1f);
				
				theGame.damageMgr.ProcessAction(action);
				delete action;
				
				LogEffects("EffectManager.InternalAddEffect: applying DoT when having quen: dealing 0.1s of damage and aborting");
				return EI_Deny;
			}
			
			//buff custom fail on add or duration resistance reduced duration to 0
			if(effect.GetDurationLeft() == 0)
			{
				LogEffects("EffectManager.InternalAddEffect: unit <<" + owner + ">>: effect <<" + effectType + ">> cannot be added as its final duration is 0.");
				LogEffects("EffectManager.InternalAddEffect: this can be due to high unit's resist, which is " + NoTrailZeros(effect.GetBuffResist()*100) + "%.");
				return EI_Deny;
			}

			//if effect of Axii from player and player has S_Magic_s17 at level 3 and buff duration is less than stagger then deny - this will force stagger later through S_Magic_s17 skill							
			if(signEffect && (effectType == EET_Confusion || effectType == EET_Hypnotized) && creat == thePlayer && thePlayer.CanUseSkill(S_Magic_s17) && thePlayer.GetSkillLevel(S_Magic_s17) == 3 && effect.GetDurationLeft() < CalculateAttributeValue(thePlayer.GetSkillAttributeValue(S_Magic_s17, 'duration_to_force_stagger', false, true)) )
			{				
				LogEffects("EffectManager.InternalAddEffect: Axii effect is blocked, will be stagger from S_Magic_s17 skill");
				return EI_Deny;
			}
		
			//check if any current buffs don't allow this buff to be applied
			if( theGame.effectMgr.CheckInteractionWith(this, effect, effects, overridenEffectsIdxs, cumulateIdx) )
				return ApplyEffect(effect, overridenEffectsIdxs, cumulateIdx, customParams);
			else
				return EI_Deny;
		}
		
		return EI_Undefined;
	}
	
	public final function GetDrunkMutagens() : array<CBaseGameplayEffect>
	{
		var i : int;
		var ret : array<CBaseGameplayEffect>;
		var mutagen : W3Mutagen_Effect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			mutagen = (W3Mutagen_Effect)effects[i];
			if(mutagen)
				ret.PushBack(mutagen);
		}
		
		return ret;
	}
	
	public final function GetPotionBuffs() : array<CBaseGameplayEffect>
	{
		var i : int;
		var ret : array<CBaseGameplayEffect>;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			if(effects[i].IsPotionEffect())
				ret.PushBack(effects[i]);
		}
		
		return ret;
	}
	
	public final function GetPotionBuffsCount() : int
	{
		var i, cnt : int;
		
		cnt = 0;
		for(i=0; i<effects.Size(); i+=1)
		{
			if(effects[i].IsPotionEffect())
				cnt += 1;
		}
		
		return cnt;
	}	
	
	// Returns first found buff object of given type
	public final function GetEffect(effectType : EEffectType, optional sourceName : string) : CBaseGameplayEffect
	{
		var i,size : int;
	
		size = effects.Size();
		for(i=0; i<size; i+=1)
		{
			if(effects[i].GetEffectType() == effectType)
			{
				if(StrLen(sourceName) == 0 || sourceName == effects[i].GetSourceName())
					return effects[i];
			}
		}
		
		return NULL;
	}
	
	//function to remove given effect
	public final function RemoveEffect(effect : CBaseGameplayEffect, optional csForcedRemove : bool)
	{
		var witcher : W3PlayerWitcher;
		var i : int;
		var autoBuffPause : SPausedAutoEffect;
		var isCritical : bool;
		
		if(!effect || !effects.Contains(effect))
			return;
			
		isCritical = IsCriticalEffect(effect);
		
		if(owner == thePlayer && isCritical)
			LogCriticalPlayer("EffectManager.RemoveEffect() | " + effect + " - forced: " + csForcedRemove);
			
		if(!csForcedRemove && isCritical && currentlyAnimatedCS == effect)
		{				
			if(effect.IsActive())
			{
				//request anim stop
				effect.SetTimeLeft(0);
				return;
			}
		}
		
		//removed effect is a CS that was about to be animated on owner
		if(isCritical && effect == owner.GetNewRequestedCS())
			owner.SetNewRequestedCS(NULL);
		
		effects.Remove(effect);
		
		if( effects.Size() == 0 )
		{
			owner.SetEffectsUpdateTicking( false );
		}
		
		effect.OnEffectRemoved();
		OnBuffRemoved();
		
		if(ownerIsWitcher)		
		{
			witcher = GetWitcherPlayer();
			if(witcher.GetSkillBonusPotionEffect() == effect)
				witcher.ClearSkillBonusPotionEffect();
		}		

		if(isCritical && currentlyAnimatedCS == effect)
		{
			owner.RaiseEvent( 'CriticalStateEnded' );
//			owner.SetBehaviorVariable( 'CriticalStateType', (int)ECST_None );
			SetCurrentlyAnimatedCS(NULL);
		}	
		
		//remove pauses
		for(i=pausedEffects.Size()-1; i>=0; i-=1)
		{
			if(pausedEffects[i].buff != effect)
				continue;
				
			if(IsBuffAutoBuff(pausedEffects[i].buff.GetEffectType()))
			{
				//add lock to autobuff paused array so that buff would be paused again when added for the second time
				autoBuffPause.effectType = effect.GetEffectType();
				autoBuffPause.duration = pausedEffects[i].duration;
				autoBuffPause.timeLeft = pausedEffects[i].timeLeft;
				autoBuffPause.sourceName = pausedEffects[i].source;
				autoBuffPause.singleLock = pausedEffects[i].singleLock;
				autoBuffPause.useMaxDuration = pausedEffects[i].useMaxDuration;
				
				pausedNotAppliedAutoBuffs.PushBack(autoBuffPause);
			}
			
			//remove effect from currently paused effects array
			pausedEffects.EraseFast(i);
			break;
		}
		
		delete effect;
	}
	
	private final function RemoveEffectOnIndex( index : int , optional csForcedRemove : bool)
	{
		RemoveEffect(effects[index], csForcedRemove);
	}
		
	// Removes all potion effects except provided effect
	public final function RemoveAllPotionEffects(optional skip : array<CBaseGameplayEffect>)
	{
		var size,i : int;
	
		size = effects.Size();
		for(i=size-1; i>=0; i-=1)
		{
			if(effects[i].IsPotionEffect() && (skip.Size() == 0 || !skip.Contains(effects[i])) )
			{
				RemoveEffectOnIndex( i );
			}
		}
	}
	
	//removes all buffs of given type
	public final function RemoveAllEffectsOfType(type : EEffectType, optional forced : bool)
	{
		var i : int;
		
		for(i=effects.Size()-1; i>=0; i-=1)
		{
			if( effects[i].GetEffectType() == type )
			{
				RemoveEffectOnIndex( i, forced );
			}
		}
	}
	
	//removes all effects EXCEPT autobuffs
	public final function RemoveAllNonAutoEffects()
	{
		var autoEffects : array<name>;
		var i : int;
		var type : EEffectType;
		var tmpName : name;
		var autos : array<EEffectType>;
				
		//get autobuffs
		owner.GetAutoEffects(autoEffects);
		for(i=0; i<autoEffects.Size(); i+=1)		
		{
			EffectNameToType(autoEffects[i], type, tmpName);
			autos.PushBack(type);
		}
		
		//added by default to all
		if(!autos.Contains(EET_AutoVitalityRegen))
			autos.PushBack(EET_AutoVitalityRegen);
		if(!autos.Contains(EET_AutoStaminaRegen))
			autos.PushBack(EET_AutoStaminaRegen);
		if(!autos.Contains(EET_AutoEssenceRegen))
			autos.PushBack(EET_AutoEssenceRegen);
		if(!autos.Contains(EET_AutoMoraleRegen))
			autos.PushBack(EET_AutoMoraleRegen);
		
		//remove
		for(i=effects.Size()-1; i>=0; i-=1)
		{
			type = effects[i].GetEffectType();
			if(!autos.Contains(type))
			{
				RemoveEffectOnIndex( i, true );
			}
		}
	}
	
	// Called when the owner has died
	public final function OwnerHasDied()
	{
		var i : int;
		
		for(i=effects.Size()-1; i>=0; i-=1)
			effects[i].OnTargetDeath();
	}
	
	public final function OwnerHasEnteredUnconscious()
	{
		var i : int;
		
		for(i=effects.Size()-1; i>=0; i-=1)
			effects[i].OnTargetUnconscious();
	}
	
	public final function OnOwnerRevived()
	{
		var i : int;
	
		RemoveAllNonAutoEffects();		
		cachedDamages.Clear();
		SetCurrentlyAnimatedCS(NULL);
		
		for(i=0; i<statDeltas.Size(); i+=1)
			statDeltas[i] = 0;
		
		ResumeAllBuffsForced();
		
		//HANDS ON HACKFIX
		vitalityAutoRegenOn = false;
		essenceAutoRegenOn 			= false;
		staminaAutoRegenOn 			= false;
		moraleAutoRegenOn 			= false;
		panicAutoRegenOn			= false;
		airAutoRegenOn				= false;
		swimmingStaminaAutoRegenOn	= false;
		adrenalineAutoRegenOn		= false;
	
		StartStaminaRegen();
		StartVitalityRegen();
		StartEssenceRegen();
		StartMoraleRegen();
		StartPanicRegen();
		StartAirRegen();
		StartSwimmingStaminaRegen();
	}
	
	// Called when the owner has finished playing its death animation
	public final function OwnerHasFinishedDeathAnim()
	{
		var i : int;
		
		for(i=effects.Size()-1; i>=0; i-=1)
			effects[i].OnTargetDeathAnimFinished();
	}
		
	private final function UpdateStatValueChange(stat : EBaseCharacterStats, val : float)
	{
		var playerOwner : CR4Player;
	
		if(val > 0)
		{
			owner.GainStat(stat, val);
			return;
		}
		else if (val == 0)
		{
			return;
		}
		
		val = -val;
		playerOwner = (CR4Player)owner;
		
		switch(stat)
		{
			case BCS_Stamina :
				owner.DrainStamina(ESAT_FixedValue, val, 1);
				break;
			case BCS_Toxicity :			
				if(playerOwner)
					playerOwner.DrainToxicity(val);
				else
					LogAssert(false, "W3EffectManager.UpdateStatValueChange: trying to drain Toxicity points on non-player!");
				break;			
			case BCS_Focus :
				if(playerOwner)
					playerOwner.DrainFocus(val);
				else
					LogAssert(false, "W3EffectManager.UpdateStatValueChange: trying to drain Focus points on non-player!");
				break;
			case BCS_Morale :
				owner.DrainMorale(val);
				break;
			case BCS_Panic :
				owner.AddPanic(val);
				break;
			case BCS_Air :
				owner.DrainAir(val);
				break;
			case BCS_SwimmingStamina :
				owner.DrainSwimmingStamina(val);
				break;
			default:
				LogAssert(false, "W3EffectManager.UpdateStatValueChange: trying to drain invalid stat <<" + stat + ">>!");
				break;
		}
	}

	public final function HasEffect(effectType : EEffectType) : bool
	{
		var i,size : int;
		
		if(effectType != EET_Undefined)
		{
			size = effects.Size();
			for( i = 0; i < size; i += 1 )
			{
				if(effects[i] && effects[i].GetEffectType() == effectType)
					return true;
			}
		}
		
		return false;
	}

	public final function GetEffectTimePercentageByType(effectType : EEffectType) : int
	{
		var i, size : int;	
		
		if ( effectType != EET_Undefined )
		{
			size = effects.Size();
			for( i = 0; i < size; i += 1 )
				if( effects[i].GetEffectType() == effectType )
					return GetEffectTimePercentage(effects[i]);					
		}		
		return 0;
	}
	
	public final function GetEffectTimePercentage(buff : CBaseGameplayEffect) : int
	{
		var maxDur : float;
		
		if (buff)
		{
			maxDur = buff.GetInitialDuration();
			if(maxDur > 0)
				return RoundMath( 100.0f * buff.GetDurationLeft() / maxDur );
		}
		return 0;
	}

	// Adds buffs form given DamageAction. Returns true if there was at least one valid effect (might not got applied but it was valid)
	public final function AddEffectsFromAction( action : W3DamageAction ) : bool
	{
		var i, size : int;
		var effectInfos : array< SEffectInfo >;
		var ret : EEffectInteract;
		var signProjectile : W3SignProjectile;
		var attackerPowerStatValue : SAbilityAttributeValue;
		var retB, applyBuff : bool;
		var signEntity : W3SignEntity;
		var canLog : bool;
		
		canLog = theGame.CanLog();
		size = action.GetEffects( effectInfos );
		signProjectile = (W3SignProjectile)action.causer;
		attackerPowerStatValue = action.GetPowerStatValue();
		retB = true;
		
		//get signEntity if valid
		signEntity = (W3SignEntity)action.causer;
		if(!signEntity && signProjectile)
			signEntity = signProjectile.GetSignEntity();
			
		for( i = 0; i < size; i += 1 )
		{		
			if ( canLog )
			{
				LogDMHits("Trying to add buff <<" + effectInfos[i].effectType + ">> on target...", action);
			}
			
			//chance to apply buff
			if(signEntity)
			{
				applyBuff = GetSignApplyBuffTest(signEntity.GetSignType(), effectInfos[i].effectType, attackerPowerStatValue, signEntity.IsAlternateCast(), (CActor)action.attacker);
			}
			else
			{
				applyBuff = GetNonSignApplyBuffTest(effectInfos[i].applyChance);
			}
			
			if(applyBuff)
			{
				//apply buff
				ret = InternalAddEffect(effectInfos[i].effectType, action.attacker, action.GetBuffSourceName(), effectInfos[i].effectDuration, effectInfos[i].effectCustomValue, effectInfos[i].effectAbilityName, effectInfos[i].customFXName, signEntity, attackerPowerStatValue, effectInfos[i].effectCustomParam );
			}
			else
			{
				//from sign and failed chance test
				if(signEntity.GetSignType() == ST_Aard)
				{
					//aard always gives stagger at least
					ret = InternalAddEffect(EET_Stagger, action.attacker, action.GetBuffSourceName(), effectInfos[i].effectDuration, effectInfos[i].effectCustomValue, effectInfos[i].customFXName, effectInfos[i].effectAbilityName, signEntity, attackerPowerStatValue, effectInfos[i].effectCustomParam );
				}
			}
			
			if ( theGame.CanLog() )
			{
				if(ret == EI_Undefined)
				{
					retB = false;
					LogDMHits("... not valid effect!", action);
				}
				else if(!applyBuff)
					LogDMHits("... failed randomization test.", action);
				else if(ret == EI_Deny)
					LogDMHits("... denied.", action);
				else if(ret == EI_Override)
					LogDMHits("... overriden by other effect already on target.", action);
				else if(ret == EI_Pass)
					LogDMHits("... added.", action);
				else if(ret == EI_Cumulate)
					LogDMHits("... cumulated with existing effect on target.", action);			
			}
			else
			{
				if ( ret == EI_Undefined )
				{
					retB = false;
				}
			}
			
		}
		
		return retB;
	}
	
	//Does a test whether non-sign source will apply the buff or not
	private final function GetNonSignApplyBuffTest(applyChance : float) : bool
	{
		return RandF() < applyChance;
	}
	
	//Does a test whether sign will apply the buff or not
	private final function GetSignApplyBuffTest(signType : ESignType, effectType : EEffectType, powerStatValue : SAbilityAttributeValue, isAlternate : bool, caster : CActor) : bool
	{
		var sp, res, chance, tempF : float;
		var chanceBonus : SAbilityAttributeValue;
		var witcher : W3PlayerWitcher;

		//level 3 petri philtre gives 100% chance
		witcher = (W3PlayerWitcher)caster;
		if(witcher && witcher.GetPotionBuffLevel(EET_PetriPhiltre) == 3)
			return true;
	
		//calculate chance to apply buff		
		sp = powerStatValue.valueMultiplicative;
		owner.GetResistValue(theGame.effectMgr.GetBuffResistStat(effectType), tempF, res);
		chance = sp / theGame.params.MAX_SPELLPOWER_ASSUMED - res;
		
		if(signType == ST_Yrden || signType == ST_Axii )
		{
			chance = 1;
		}
		else if(signType == ST_Igni)
		{
			if(witcher)
			{
				if(witcher.CanUseSkill(S_Magic_s09))
				{
					chanceBonus = witcher.GetSkillAttributeValue(S_Magic_s09, 'chance_bonus', false, true);
					chance += chance * chanceBonus.valueMultiplicative * witcher.GetSkillLevel(S_Magic_s09) + chanceBonus.valueAdditive * witcher.GetSkillLevel(S_Magic_s09);
				}			
				if(witcher.CanUseSkill(S_Perk_03))
					chance += CalculateAttributeValue(witcher.GetSkillAttributeValue(S_Perk_03, 'burning_chance', false, true));
			}
		}
		else if(signType == ST_Quen && effectType == EET_KnockdownTypeApplicator)
		{
			witcher = (W3PlayerWitcher)caster;
			if(witcher)
			{
				chanceBonus = witcher.GetSkillAttributeValue(S_Magic_s13, 'chance_multiplier', false, true);
				chance *= CalculateAttributeValue(chanceBonus);
			}
			if( owner.HasAbility('WeakToAard') )
			{
				chance = 1;
			}
		}
		else if( signType == ST_Aard && owner.HasAbility('WeakToAard') )
		{
			chance = 1;
		}
				
		chance = ClampF(chance, 0, 1);
			
		LogEffects("Buff <<" + effectType + ">> is from sign, chance = " + NoTrailZeros(100*chance) + "%, spell_power = " + NoTrailZeros(sp) + ", resist=" + NoTrailZeros(res));		
		if(RandF() >= chance)
		{
			if ( theGame.CanLog() )
			{
				LogEffects("Sign buff chance failed - no effect applied");
			}
			return false;
		}
		else
		{
			LogEffects("Sign buff chance succeeded!");
		}
		
		return true;
	}
	
	// Processes OnHit Applicator buffs (called from Damage Manager)
	public final function ProcessOnHitEffects(victim : CActor, silverSword : bool, steelSword : bool, sign : bool)
	{
		var i : int;
		var applicator : W3Effect_ApplicatorOnHit;
	
		for(i=0; i<effects.Size(); i+=1)
		{
			applicator = (W3Effect_ApplicatorOnHit)effects[i];
			if(applicator)
			{
				applicator.ProcessOnHit(victim, silverSword, steelSword, sign);
			}
		}
	}
	
	// Pauses all effects of given type.
	// singleLock - lock can be either on or off, there is no counter
	// useMaxDuration - if set then reapplying the lock will set the duration to the greater of current duration and provided duration
	public final function PauseEffects(effectType : EEffectType, sourceName : name, optional singleLock : bool, optional duration : float, optional useMaxDuration : bool)
	{
		var i : int;
		var pausedAnyBuff : bool;
		var pause : SPausedAutoEffect;
	
		//inf duration by default
		if(duration == 0)
			duration = -1;
			
		//buffs we already have on character
		for(i=0; i<effects.Size(); i+=1)
		{
			if(effects[i].GetEffectType() == effectType)
			{	
				PauseEffect(effects[i], sourceName, singleLock, duration,useMaxDuration);
				pausedAnyBuff = true;
			}
		}
		
		//we might also have to pause an autobuff that is not yet applied on character
		if(!pausedAnyBuff && IsBuffAutoBuff(effectType))
		{
			pause.effectType = effectType;
			pause.sourceName = sourceName;
			pause.singleLock = singleLock;
			pause.duration = duration;
			pause.useMaxDuration = useMaxDuration;
			pause.timeLeft = duration;
			
			pausedNotAppliedAutoBuffs.PushBack(pause);
		}
	}
	
	// singleLock - lock can be either on or off, there is no counter
	// useMaxDuration - if set then reapplying the lock will set the duration to the greater of current duration and provided duration
	private final function PauseEffect(buff : CBaseGameplayEffect, sourceName : name, optional singleLock : bool, optional duration : float, optional useMaxDuration : bool)
	{
		var tpe : STemporarilyPausedEffect;
		var j : int;
		var processed : bool;
		
		processed = false;
		
		//check if buff is already paused - if so then update pause counter
		for(j=0; j<pausedEffects.Size(); j+=1)
		{
			if(pausedEffects[j].buff == buff && pausedEffects[j].source == sourceName)
			{
				//if finite duration
				if(duration > 0)
				{
					//if has duration lock and now gets new one then refresh the value
					if(useMaxDuration)
						pausedEffects[j].timeLeft = MaxF(pausedEffects[j].timeLeft, duration);
					else //else override with new value
						pausedEffects[j].timeLeft = duration;
				}
				else if(pausedEffects[j].timeLeft >= 0)
				{
					//if has duration lock and now gets infinite duration lock
					pausedEffects[j].timeLeft = -1;
				}
				
				processed = true;
				buff = pausedEffects[j].buff;
				break;
			}
		}
		
		//if buff is not already paused then add it to paused array
		if(!processed)
		{
			tpe.buff = buff;	
			tpe.timeLeft = duration;
			tpe.duration = duration;
			tpe.source = sourceName;
			tpe.singleLock = singleLock;
			tpe.useMaxDuration = useMaxDuration;
		
			pausedEffects.PushBack(tpe);			
		}
		
		buff.Pause(sourceName, singleLock);
	}

	public final function PauseAllRegenEffects(sourceName : name, optional singleLock : bool, optional duration : float, optional useMaxDuration : bool)
	{
		var i : int;
		var regenEffect : W3RegenEffect;
	
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
				PauseEffects(regenEffect.GetEffectType(), sourceName, singleLock, duration, useMaxDuration);
		}
	}
	
	public final function ResumeAllRegenEffects(sourceName : name)
	{
		var i : int;
		var regenEffect : W3RegenEffect;
	
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
				ResumeEffects(regenEffect.GetEffectType(), sourceName);
		}
	}
	
	//Forcefully resumes all buffs REGARDLESS of source locks, lock counters and lock durations. Conscious use only.
	private final function ResumeAllBuffsForced()
	{
		var i : int;
	
		for(i=0; i<effects.Size(); i+=1)
		{
			ResumeEffectsInternal(effects[i].GetEffectType(), '', true);
		}
	}
	
	public final function PauseHPRegenEffects(sourceName : name, optional duration : float)
	{		
		var i : int;
		var regenEffect : W3RegenEffect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
			{
				if(regenEffect.GetRegenStat() == CRS_Vitality || regenEffect.GetRegenStat() == CRS_Essence)
				{
					PauseEffects(effects[i].GetEffectType(), sourceName, true, duration);
				}
			}
		}
	}
	
	public final function PauseStaminaRegen(sourceName : name, optional duration : float)
	{
		var i : int;
		var regenEffect : W3RegenEffect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
			{
				if(regenEffect.GetRegenStat() == CRS_Stamina)
				{
					PauseEffects(effects[i].GetEffectType(), sourceName, true, duration);
				}
			}
		}
	}
	
	public final function ResumeStaminaRegen(sourceName : name)
	{
		var i : int;
		var regenEffect : W3RegenEffect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
			{
				if(regenEffect.GetRegenStat() == CRS_Stamina)
				{
					ResumeEffects(effects[i].GetEffectType(), sourceName);
				}
			}
		}
	}
	
	public final function ResumeHPRegenEffects(sourceName : name)
	{		
		var i : int;
		var regenEffect : W3RegenEffect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			regenEffect = (W3RegenEffect)effects[i];
			if(regenEffect)
			{
				if(regenEffect.GetRegenStat() == CRS_Vitality || regenEffect.GetRegenStat() == CRS_Essence)
				{
					ResumeEffects(effects[i].GetEffectType(), sourceName);
				}
			}
		}
	}
	
	// Resumes given effect
	public final function ResumeEffects(effectType : EEffectType, sourceName : name)
	{
		ResumeEffectsInternal(effectType, sourceName);
	}
	
	// Resumes given effect
	private final function ResumeEffectsInternal(effectType : EEffectType, optional sourceName : name, optional forced : bool)
	{
		var i, j : int;
		var removedOneLock : bool;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			if(effects[i].GetEffectType() == effectType && (forced || effects[i].IsPaused(sourceName)) )
			{
				if(forced)
					effects[i].ResumeForced();
				else
					effects[i].Resume(sourceName);
				
				//remove from paused buffs 
				for(j=0; j<pausedEffects.Size(); j+=1)
				{
					if(pausedEffects[j].buff == effects[i] && (forced || sourceName == pausedEffects[j].source) )
					{
						pausedEffects.EraseFast(j);
					}						
				}					
			}
		}
				
		//process non-existing autobuff pauses
		removedOneLock = false;
		for(i=pausedNotAppliedAutoBuffs.Size()-1; i>=0; i-=1)
		{
			if(pausedNotAppliedAutoBuffs[i].effectType == effectType && (forced || pausedNotAppliedAutoBuffs[i].sourceName == sourceName) )
			{
				//if single lock then remove all
				if(pausedNotAppliedAutoBuffs[i].singleLock)
				{
					pausedNotAppliedAutoBuffs.Erase(i);
					continue;
				}
				//if not single lock then remove only one
				else if(!removedOneLock)
				{
					pausedNotAppliedAutoBuffs.Erase(i);
					removedOneLock = true;
					continue;
				}
			}
		}
	}
		
	// Returns the amount of critical effects applied on owner
	public final function GetCriticalBuffsCount() : int
	{
		var i, cnt : int;
	
		cnt = 0;
		for(i=0; i<effects.Size(); i+=1)
			if(IsCriticalEffect(effects[i]))
				cnt += 1;
		
		return cnt;
	}
	
	public final function GetCriticalBuffs() : array<CBaseGameplayEffect>
	{
		var i : int;
		var ret : array<CBaseGameplayEffect>;
	
		for(i=0; i<effects.Size(); i+=1)
			if(IsCriticalEffect(effects[i]))
				ret.PushBack(effects[i]);
		
		return ret;
	}
	
	public final function HasPotionBuff() : bool
	{
		var i : int;
	
		for(i=0; i<effects.Size(); i+=1)
			if(effects[i].IsPotionEffect() && effects[i].IsActive())
				return true;
				
		return false;
	}
	
	// Caches stat update (value) of given stat
	public final function CacheStatUpdate(stat : EBaseCharacterStats, value : float)
	{
		if(value == 0)
		{
			LogAssert(false, "EffectManager.CacheStatUpdate: value is 0 for <<" + owner + ">> and stat <<" + stat + ">> !!!");
			return;
		}
		
		statDeltas[stat] += value;
		owner.SetEffectsUpdateTicking( true );
	}
	
	/*
		Caches damage to be dealt in the next update call
		
		damageTypeName - damage type
		val - damage value
		attacker - attacker entity
		carrier - carrier entity (buff)
		isDoTDamage - set to true if this is a Damage over Time type of damage
		dontShowHitParticle - if set then hit particle will not be shown
		pwrStatType - type of power stat to use when dealing damage
	*/
	public final function CacheDamage(damageTypeName : name, val : float, attacker : CGameplayEntity, carrier : CBaseGameplayEffect, DoTdt : float, dontShowHitParticle : bool, pwrStatType : ECharacterPowerStats, isEnvironment : bool)
	{
		var dmg : SEffectCachedDamage;
		var eh : EntityHandle;
	
		if(val <= 0)
		{
			LogAssert(false, "EffectManager.CacheDamage: value is <= 0!");
			return;
		}
		
		EntityHandleSet(eh, attacker);
		
		dmg.dmgType = damageTypeName;
		dmg.dmgVal = val;
		dmg.attacker = eh;
		dmg.carrier = carrier;
		dmg.dt = DoTdt;
		dmg.dontShowHitParticle = dontShowHitParticle;
		dmg.powerStatType = pwrStatType;
		dmg.isEnvironment = isEnvironment;
		
		if(carrier)
			dmg.sourceName = carrier.GetSourceName();
			
		cachedDamages.PushBack(dmg);
		owner.SetEffectsUpdateTicking( true );
	}
	
	// Called when resistances change to update buff durations
	public final function RecalcEffectDurations()
	{
		var i : int;
		
		for(i=0; i<effects.Size(); i+=1)
			effects[i].RecalcDuration();
	}
	
	// Updates aplicator type buffs
	public final function UpdateApplicatorBuffs()
	{
		var i : int;
		var applicator : W3ApplicatorEffect;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			applicator = (W3ApplicatorEffect)effects[i];
			if(applicator)
				applicator.UpdateParams();
		}
	}
	
	//returns potion buff's level or 0 if no such potion active
	public final function GetPotionBuffLevel(effectType : EEffectType) : int
	{
		var buff : CBaseGameplayEffect;
		
		buff = GetEffect(effectType);
		
		if(buff && buff.IsPotionEffect())
			return buff.GetBuffLevel();
			
		return 0;
	}
	
	public final function CanBeRemoved() : bool
	{
		var i : int;
		
		if( effects.Size() > 0 )
		{
			return false; // some effects left
		}
	
		if( cachedDamages.Size() > 0 )
		{
			return false; // some cached damages left
		}
	
		for( i = 0; i < statDeltas.Size(); i += 1 )
		{
			if( statDeltas[ i ] != 0 )
			{
				return false; // some deltas left
			}
		}
		
		//some non-infinite pauses ticking
		for(i=0; i<pausedEffects.Size(); i+=1)
		{
			if(pausedEffects[i].duration != -1)
				return false;
		}
		
		//some non-infinite autobuff pauses ticking
		for(i=0; i<pausedNotAppliedAutoBuffs.Size(); i+=1)
		{
			if(pausedNotAppliedAutoBuffs[i].duration != -1)
				return false;
		}
		
		return true;
	}
	
	public final function ShouldStopFx(fx : name) : bool
	{
		var i : int;
		
		for(i=0; i<currentlyPlayedFX.Size(); i+=1)
		{
			if(currentlyPlayedFX[i].fx == fx)
				return currentlyPlayedFX[i].sources.Size() == 1;	//only one source is currently playing fx - can stop
		}
		
		//fx not played
		return false;
	}
	
	public final function IsPlayingFX(fx : name) : bool
	{
		var i : int;
		
		for(i=0; i<currentlyPlayedFX.Size(); i+=1)
		{
			if(currentlyPlayedFX[i].fx == fx)
				return true;
		}
		
		return false;
	}
	
	public final function AddPlayedFX(fx : name, sourceName : string)
	{
		var i : int;
		var f : SCurrentBuffFX;
		
		for(i=0; i<currentlyPlayedFX.Size(); i+=1)
		{
			if(currentlyPlayedFX[i].fx == fx)
			{
				if(!currentlyPlayedFX[i].sources.Contains(sourceName))
					currentlyPlayedFX[i].sources.PushBack(sourceName);
					
				return;
			}
		}

		//not in array
		f.fx = fx;
		f.sources.PushBack(sourceName);
		
		currentlyPlayedFX.PushBack(f);
	}
	
	public final function RemovePlayedFX(fx : name, sourceName : string)
	{
		var i : int;
		
		for(i=0; i<currentlyPlayedFX.Size(); i+=1)
		{
			if(currentlyPlayedFX[i].fx == fx)
			{
				currentlyPlayedFX[i].sources.Remove(sourceName);
				
				if(currentlyPlayedFX[i].sources.Size() == 0)
					currentlyPlayedFX.EraseFast(i);
					
				return;
			}
		}
	}
	
	//Right now we don't simulate time - just remove everything that's not infinite
	public final function SimulateBuffTimePassing(simulatedTime : float)
	{
		var i : int;
		
		for(i=effects.Size()-1; i>=0; i-=1)
		{
			//repair buffs don't expire when we have runeword 5 active
			if(owner == GetWitcherPlayer() && (W3RepairObjectEnhancement)effects[i] && GetWitcherPlayer().HasRunewordActive('Runeword 5 _Stats'))
			{
				effects[i].OnTimeUpdated(simulatedTime);
				continue;
			}
				
			if(effects[i].GetInitialDuration() != -1)
				RemoveEffectOnIndex(i, true);
		}
	}
	
	public final function Debug_ReleaseCriticalStateSaveLocks()
	{
		if(hasCriticalStateSaveLock)
			theGame.ReleaseNoSaveLock(criticalStateSaveLockId);
	}
	
	private final function HasAllShrineBuffs() : bool
	{
		var aard, axii, igni, quen, yrden : bool;
		var i : int;
		var type : EEffectType;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			type = effects[i].GetEffectType();
			
			switch(type)
			{
				case EET_ShrineAard:
					aard = true;
					break;
				case EET_ShrineAxii:
					axii = true;
					break;
				case EET_ShrineIgni:
					igni = true;
					break;
				case EET_ShrineQuen:
					quen = true;
					break;
				case EET_ShrineYrden:
					yrden = true;
					break;					
				default:
					break;
			}			
		}

		return aard && axii && yrden && quen && igni;
	}
	
	public final function HasAnyMutagen23ShrineBuff() : bool
	{
		var i : int;
		var shrineBuff : W3Effect_Shrine;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			shrineBuff = (W3Effect_Shrine) effects[i];
			if(shrineBuff && shrineBuff.IsFromMutagen23())
				return true;
		}
		
		return false;
	}
	
	public final function HasAnyShrineBuff() : bool
	{
		var i : int;
		var shrineBuff : W3Effect_Shrine;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			shrineBuff = (W3Effect_Shrine) effects[i];
			if(shrineBuff)
				return true;
		}
		
		return false;
	}
	
	public final function GetShrineBuffs() : array<CBaseGameplayEffect>
	{
		var i : int;
		var shrineBuff : W3Effect_Shrine;
		var ret : array<CBaseGameplayEffect>;
		
		for(i=0; i<effects.Size(); i+=1)
		{
			shrineBuff = (W3Effect_Shrine) effects[i];
			if(shrineBuff)
				ret.PushBack(effects[i]);
		}
		
		return ret;
	}
}
