/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 1998-2000, Matthes Bender
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
 *
 * Distributed under the terms of the ISC license; see accompanying file
 * "COPYING" for details.
 *
 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
 * See accompanying file "TRADEMARK" for details.
 *
 * To redistribute this file separately, substitute the full license texts
 * for the above references.
 */

// C4AulFun-based effects assigned to an object
/* Also contains some helper functions for various landscape effects */

#include "C4Include.h"
#include "script/C4Effect.h"

#include "game/C4GameScript.h"
#include "script/C4Aul.h"

void C4Effect::AssignCallbackFunctions()
{
	C4PropList *p = GetCallbackScript();
	if (!p) return;
	// compose function names and search them
	char fn[C4AUL_MAX_Identifier+1];
	sprintf(fn, PSF_FxStart,  GetName()); pFnStart  = p->GetFunc(fn);
	sprintf(fn, PSF_FxStop,   GetName()); pFnStop   = p->GetFunc(fn);
	sprintf(fn, PSF_FxTimer,  GetName()); pFnTimer  = p->GetFunc(fn);
	sprintf(fn, PSF_FxEffect, GetName()); pFnEffect = p->GetFunc(fn);
	sprintf(fn, PSF_FxDamage, GetName()); pFnDamage = p->GetFunc(fn);
}

C4PropList * C4Effect::GetCallbackScript()
{
	return CommandTarget._getPropList();
}

C4Effect::C4Effect(C4Effect **ppEffectList, C4String *szName, int32_t iPrio, int32_t iTimerInterval, C4PropList *pCmdTarget)
{
	// assign values
	iPriority = 0; // effect is not yet valid; some callbacks to other effects are done before
	iInterval = iTimerInterval;
	iTime = 0;
	CommandTarget.SetPropList(pCmdTarget);
	AcquireNumber();
	Register(ppEffectList, iPrio);
	// Set name and callback functions
	SetProperty(P_Name, C4VString(szName));
}

C4Effect::C4Effect(C4Effect **ppEffectList, C4PropList * prototype, int32_t iPrio, int32_t iTimerInterval):
		C4PropListNumbered(prototype)
{
	// assign values
	iPriority = 0; // effect is not yet valid; some callbacks to other effects are done before
	iInterval = iTimerInterval;
	iTime = 0;
	CommandTarget.Set0();
	AcquireNumber();
	Register(ppEffectList, iPrio);
	SetProperty(P_Name, C4VString(prototype->GetName()));
}

void C4Effect::Register(C4Effect **ppEffectList, int32_t iPrio)
{
	// get effect target
	C4Effect *pCheck, *pPrev = *ppEffectList;
	if (pPrev && Abs(pPrev->iPriority) < iPrio)
	{
		while ((pCheck = pPrev->pNext))
				if (Abs(pCheck->iPriority) >= iPrio) break; else pPrev = pCheck;
		// insert after previous
		pNext = pPrev->pNext;
		pPrev->pNext = this;
	}
	else
	{
		// insert as first effect
		pNext = *ppEffectList;
		*ppEffectList = this;
	}
}

C4Effect * C4Effect::New(C4PropList *pForObj, C4Effect **ppEffectList, C4String * szName, int32_t iPrio, int32_t iTimerInterval, C4PropList * pCmdTarget, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4)
{
	C4Effect * pEffect = new C4Effect(ppEffectList, szName, iPrio, iTimerInterval, pCmdTarget);
	return pEffect->Init(pForObj, iPrio, rVal1, rVal2, rVal3, rVal4);
}

C4Effect * C4Effect::New(C4PropList *pForObj, C4Effect **ppEffectList, C4PropList * prototype, int32_t iPrio, int32_t iTimerInterval, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4)
{
	C4Effect * pEffect = new C4Effect(ppEffectList, prototype, iPrio, iTimerInterval);
	return pEffect->Init(pForObj, iPrio, rVal1, rVal2, rVal3, rVal4);
}

C4Effect * C4Effect::Init(C4PropList *pForObj, int32_t iPrio, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4)
{
	Target = pForObj;
	// ask all effects with higher priority first - except for prio 1 effects, which are considered out of the priority call chain (as per doc)
	bool fRemoveUpper = (iPrio != 1);
	// note that apart from denying the creation of this effect, higher priority effects may also remove themselves
	// or do other things with the effect list
	// (which does not quite make sense, because the effect might be denied by another effect)
	// so the priority is assigned after this call, marking this effect dead before it's definitely valid
	if (fRemoveUpper && pNext)
	{
		C4Effect * pEffect2 = pNext->Check(GetName(), iPrio, iInterval, rVal1, rVal2, rVal3, rVal4);
		if (pEffect2)
		{
			// effect denied (iResult = -1), added to an effect (iResult = Number of that effect)
			// or added to an effect that destroyed itself (iResult = -2)
			if (pEffect2 != (C4Effect*)C4Fx_Effect_Deny && pEffect2 != (C4Effect*)C4Fx_Effect_Annul) return pEffect2;
			// effect is still marked dead
			return nullptr;
		}
	}
	// init effect
	// higher-priority effects must be deactivated temporarily, and then reactivated regarding the new effect
	// higher-level effects should not be inserted during the process of removing or adding a lower-level effect
	// because that would cause a wrong initialization order
	// (hardly ever causing trouble, however...)
	C4Effect *pLastRemovedEffect=nullptr;
	C4AulFunc * pFn;
	if (!GetCallbackScript())
	{
		Call(P_Construction, &C4AulParSet(rVal1, rVal2, rVal3, rVal4)).getInt();
		if (pForObj && !pForObj->Status) return nullptr;
		pFn = GetFunc(P_Start);
	}
	else
		pFn = pFnStart;
	if (fRemoveUpper && pNext && pFn)
		TempRemoveUpperEffects(false, &pLastRemovedEffect);
	// bad things may happen
	if (pForObj && !pForObj->Status) return nullptr; // this will be invalid!
	iPriority = iPrio; // validate effect now
	if (CallStart(0, rVal1, rVal2, rVal3, rVal4) == C4Fx_Start_Deny)
		// the effect denied to start: assume it hasn't, and mark it dead
		SetDead();
	if (fRemoveUpper && pNext && pFn)
		TempReaddUpperEffects(pLastRemovedEffect);
	if (pForObj && !pForObj->Status) return nullptr; // this will be invalid!
	// Update OnFire cache
	if (!IsDead() && pForObj && WildcardMatch(C4Fx_AnyFire, GetName()))
		pForObj->SetOnFire(true);
	return this;
}

C4Effect::C4Effect()
{
	// defaults
	iPriority=iTime=iInterval=0;
	CommandTarget.Set0();
	pNext = nullptr;
}

C4Effect::~C4Effect()
{
	// del following effects (not recursively)
	C4Effect *pEffect;
	while ((pEffect = pNext))
	{
		pNext = pEffect->pNext;
		pEffect->pNext = nullptr;
		delete pEffect;
	}
}

void C4Effect::Denumerate(C4ValueNumbers * numbers)
{
	// denum in all effects
	C4Effect *pEff = this;
	do
	{
		// command target
		pEff->CommandTarget.Denumerate(numbers);
		// assign any callback functions
		pEff->AssignCallbackFunctions();
		pEff->C4PropList::Denumerate(numbers);
	}
	while ((pEff=pEff->pNext));
}

void C4Effect::ClearPointers(C4PropList *pObj)
{
	// clear pointers in all effects
	C4Effect *pEff = this;
	do
		// command target lost: effect dead w/o callback
		if (pEff->CommandTarget.getPropList() == pObj)
		{
			pEff->SetDead();
			pEff->CommandTarget.Set0();
		}
	while ((pEff=pEff->pNext));
}

void C4Effect::SetDead()
{
	iPriority = 0;
}

C4Effect *C4Effect::Get(const char *szName, int32_t iIndex, int32_t iMaxPriority)
{
	// safety
	if (!szName) return nullptr;
	// check all effects
	C4Effect *pEff = this;
	do
	{
		// skip dead
		if (pEff->IsDead()) continue;
		// skip effects with too high priority
		if (iMaxPriority && pEff->iPriority > iMaxPriority) continue;
		// wildcard compare name
		const char *szEffectName = pEff->GetName();
		if (!SWildcardMatchEx(szEffectName, szName)) continue;
		// effect name matches
		// check index
		if (iIndex--) continue;
		// effect found
		return pEff;
	}
	while ((pEff=pEff->pNext));
	// nothing found
	return nullptr;
}

int32_t C4Effect::GetCount(const char *szMask, int32_t iMaxPriority)
{
	// count all matching effects
	int32_t iCnt=0; C4Effect *pEff = this;
	do if (!pEff->IsDead())
			if (!szMask || SWildcardMatchEx(pEff->GetName(), szMask))
				if (!iMaxPriority || pEff->iPriority <= iMaxPriority)
					++iCnt;
	while ((pEff = pEff->pNext));
	// return count
	return iCnt;
}

C4Effect* C4Effect::Check(const char *szCheckEffect, int32_t iPrio, int32_t iTimer, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4)
{
	// priority=1: always OK; no callbacks
	if (iPrio == 1) return nullptr;
	// check this and other effects
	C4Effect *pAddToEffect = nullptr; bool fDoTempCallsForAdd = false;
	C4Effect *pLastRemovedEffect=nullptr;
	for (C4Effect *pCheck = this; pCheck; pCheck = pCheck->pNext)
	{
		if (!pCheck->IsDead() && pCheck->iPriority >= iPrio)
		{
			int32_t iResult = pCheck->CallEffect(szCheckEffect, rVal1, rVal2, rVal3, rVal4);
			if (iResult == C4Fx_Effect_Deny)
				// effect denied
				return (C4Effect*)C4Fx_Effect_Deny;
			// add to other effect
			if (iResult == C4Fx_Effect_Annul || iResult == C4Fx_Effect_AnnulCalls)
			{
				pAddToEffect = pCheck;
				fDoTempCallsForAdd = (iResult == C4Fx_Effect_AnnulCalls);
			}
		}
	}
	// adding to other effect?
	if (pAddToEffect)
	{
		// do temp remove calls if desired
		if (pAddToEffect->pNext && fDoTempCallsForAdd)
			pAddToEffect->TempRemoveUpperEffects(false, &pLastRemovedEffect);
		C4Value Par1 = C4VString(szCheckEffect), Par2 = C4VInt(iTimer), Par8;
		int32_t iResult = pAddToEffect->DoCall(Target, PSFS_FxAdd, Par1, Par2, rVal1, rVal2, rVal3, rVal4, Par8).getInt();
		// do temp readd calls if desired
		if (pAddToEffect->pNext && fDoTempCallsForAdd)
			pAddToEffect->TempReaddUpperEffects(pLastRemovedEffect);
		// effect removed by this call?
		if (iResult == C4Fx_Start_Deny)
		{
			pAddToEffect->Kill();
			return (C4Effect*)C4Fx_Effect_Annul;
		}
		else
			// other effect is the target effect number
			return pAddToEffect;
	}
	// added to no effect and not denied
	return nullptr;
}

void C4Effect::Execute(C4Effect **ppEffectList)
{
	// advance all effect timers first; then do execution
	// this prevents a possible endless loop if timers register into the same effect list with interval 1 while it is being executed
	for (C4Effect *pEffect = *ppEffectList; pEffect; pEffect = pEffect->pNext)
	{
		// ignore dead status; adjusting their time doesn't hurt
		++pEffect->iTime;
	}
	// get effect list
	// execute all effects not marked as dead
	C4Effect *pEffect = *ppEffectList, **ppPrevEffect=ppEffectList;
	while (pEffect)
	{
		// effect dead?
		if (pEffect->IsDead())
		{
			// delete it, then
			C4Effect *pNextEffect = pEffect->pNext;
			pEffect->pNext = nullptr;
			delete pEffect;
			// next effect
			*ppPrevEffect = pEffect = pNextEffect;
		}
		else
		{
			// check timer execution
			if (pEffect->iInterval && !(pEffect->iTime % pEffect->iInterval) && pEffect->iTime)
			{
				if (pEffect->CallTimer(pEffect->iTime) == C4Fx_Execute_Kill)
				{
					// safety: this class got deleted!
					if (pEffect->Target && !pEffect->Target->Status) return;
					// timer function decided to finish it
					pEffect->Kill();
				}
				// safety: this class got deleted!
				if (pEffect->Target && !pEffect->Target->Status) return;
			}
			// next effect
			ppPrevEffect = &pEffect->pNext;
			pEffect = pEffect->pNext;
		}
	}
}

void C4Effect::Kill()
{
	// active?
	C4Effect *pLastRemovedEffect=nullptr;
	if (IsActive())
		// then temp remove all higher priority effects
		TempRemoveUpperEffects(false, &pLastRemovedEffect);
	else
		// otherwise: temp reactivate before real removal
		// this happens only if a lower priority effect removes an upper priority effect in its add- or removal-call
		if (iPriority!=1) CallStart(C4FxCall_TempAddForRemoval, C4Value(), C4Value(), C4Value(), C4Value());
	// remove this effect
	int32_t iPrevPrio = iPriority; SetDead();
	if (CallStop(C4FxCall_Normal, false) == C4Fx_Stop_Deny)
		// effect denied to be removed: recover
		iPriority = iPrevPrio;
	// reactivate other effects
	TempReaddUpperEffects(pLastRemovedEffect);
	// Update OnFire cache
	if (Target && WildcardMatch(C4Fx_AnyFire, GetName()))
		if (!Get(C4Fx_AnyFire))
			Target->SetOnFire(false);
	if (IsDead() && !GetCallbackScript())
		Call(P_Destruction, &C4AulParSet(C4FxCall_Normal));
}

void C4Effect::ClearAll(int32_t iClearFlag)
{
	// simply remove access all effects recursively, and do removal calls
	// this does not regard lower-level effects being added in the removal calls,
	// because this could hang the engine with poorly coded effects
	if (pNext) pNext->ClearAll(iClearFlag);
	if ((Target && !Target->Status) || IsDead()) return;
	int32_t iPrevPrio = iPriority;
	SetDead();
	if (CallStop(iClearFlag, false) == C4Fx_Stop_Deny)
	{
		// this stop-callback might have deleted the object and then denied its own removal
		// must not modify self in this case...
		if (Target && !Target->Status) return;
		// effect denied to be removed: recover it
		iPriority = iPrevPrio;
	}
	// Update OnFire cache
	if (Target && WildcardMatch(C4Fx_AnyFire, GetName()) && IsDead())
		if (!Get(C4Fx_AnyFire))
			Target->SetOnFire(false);
	if (IsDead() && !GetCallbackScript())
		Call(P_Destruction, &C4AulParSet(iClearFlag));
}

void C4Effect::DoDamage(int32_t &riDamage, int32_t iDamageType, int32_t iCausePlr)
{
	// ask all effects for damage adjustments
	C4Effect *pEff = this;
	do
	{
		if (!pEff->IsDead())
			pEff->CallDamage(riDamage, iDamageType, iCausePlr);
		if (Target && !Target->Status) return;
	}
	while ((pEff = pEff->pNext) && riDamage);
}

static C4Object * Obj(C4PropList * p) { return p ? p->GetObject() : nullptr; }

C4Value C4Effect::DoCall(C4PropList *pObj, const char *szFn, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4, const C4Value &rVal5, const C4Value &rVal6, const C4Value &rVal7)
{
	C4PropList * p = GetCallbackScript();
	if (!p)
	{
		C4AulFunc * fn = GetFunc(szFn);
		if (fn) return fn->Exec(this, &C4AulParSet(rVal1, rVal2, rVal3, rVal4, rVal5, rVal6, rVal7));
	}
	else
	{
		// old variant
		// compose function name
		C4AulFunc * fn = p->GetFunc(FormatString(PSF_FxCustom, GetName(), szFn).getData());
		if (fn) return fn->Exec(p, &C4AulParSet(Obj(pObj), this, rVal1, rVal2, rVal3, rVal4, rVal5, rVal6, rVal7));
	}
	return C4Value();
}

int C4Effect::CallStart(int temporary, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4)
{
	if (!GetCallbackScript())
		return Call(P_Start, &C4AulParSet(temporary, var1, var2, var3, var4)).getInt();
	if (pFnStart)
		return pFnStart->Exec(GetCallbackScript(), &C4AulParSet(Obj(Target), this, temporary, var1, var2, var3, var4)).getInt();
	return C4Fx_OK;
}
int C4Effect::CallStop(int reason, bool temporary)
{
	if (!GetCallbackScript())
		return Call(P_Stop, &C4AulParSet(reason, temporary)).getInt();
	if (pFnStop)
		return pFnStop->Exec(GetCallbackScript(), &C4AulParSet(Obj(Target), this, reason, temporary)).getInt();
	return C4Fx_OK;
}
int C4Effect::CallTimer(int time)
{
	if (!GetCallbackScript())
		return Call(P_Timer, &C4AulParSet(time)).getInt();
	if (pFnTimer)
		return pFnTimer->Exec(GetCallbackScript(), &C4AulParSet(Obj(Target), this, time)).getInt();
	return C4Fx_Execute_Kill;
}
void C4Effect::CallDamage(int32_t & damage, int damagetype, int plr)
{
	if (!GetCallbackScript())
	{
		C4AulFunc *pFn = GetFunc(P_Damage);
		if (pFn)
			damage = pFn->Exec(this, &C4AulParSet(damage, damagetype, plr)).getInt();
	}
	else if (pFnDamage)
		damage = pFnDamage->Exec(GetCallbackScript(), &C4AulParSet(Obj(Target), this, damage, damagetype, plr)).getInt();
}
int C4Effect::CallEffect(const char * effect, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4)
{
	if (!GetCallbackScript())
		return Call(P_Effect, &C4AulParSet(effect, var1, var2, var3, var4)).getInt();
	if (pFnEffect)
		return pFnEffect->Exec(GetCallbackScript(), &C4AulParSet(effect, Obj(Target), this, var1, var2, var3, var4)).getInt();
	return C4Fx_OK;
}

void C4Effect::OnObjectChangedDef(C4PropList *pObj)
{
	// safety
	if (!pObj) return;
	// check all effects for reassignment
	C4Effect *pCheck = this;
	while (pCheck)
	{
		if (pCheck->GetCallbackScript() == pObj)
			pCheck->ReAssignCallbackFunctions();
		pCheck = pCheck->pNext;
	}
}

void C4Effect::TempRemoveUpperEffects(bool fTempRemoveThis, C4Effect **ppLastRemovedEffect)
{
	if (Target && !Target->Status) return; // this will be invalid!
	// priority=1: no callbacks
	if (iPriority == 1) return;
	// remove from high to low priority
	// recursive implementation...
	C4Effect *pEff = pNext;
		while (pEff) if (pEff->IsActive()) break; else pEff = pEff->pNext;
	// temp remove active effects with higher priority
	if (pEff) pEff->TempRemoveUpperEffects(true, ppLastRemovedEffect);
	// temp remove this
	if (fTempRemoveThis)
	{
		FlipActive();
		// Update OnFire cache
		if (Target && WildcardMatch(C4Fx_AnyFire, GetName()))
			if (!Get(C4Fx_AnyFire))
				Target->SetOnFire(false);
		// temp callbacks only for higher priority effects
		if (iPriority!=1) CallStop(C4FxCall_Temp, true);
		if (!*ppLastRemovedEffect) *ppLastRemovedEffect = this;
	}
}

void C4Effect::TempReaddUpperEffects(C4Effect *pLastReaddEffect)
{
	// nothing to do? - this will also happen if TempRemoveUpperEffects did nothing due to priority==1
	if (!pLastReaddEffect) return;
	if (Target && !Target->Status) return; // this will be invalid!
	// simply activate all following, inactive effects
	for (C4Effect *pEff = pNext; pEff; pEff = pEff->pNext)
	{
		if (pEff->IsInactiveAndNotDead())
		{
			pEff->FlipActive();
			if (pEff->iPriority!=1) pEff->CallStart(C4FxCall_Temp, C4Value(), C4Value(), C4Value(), C4Value());
			if (Target && WildcardMatch(C4Fx_AnyFire, pEff->GetName()))
				Target->SetOnFire(true);
		}
		// done?
		if (pEff == pLastReaddEffect) break;
	}
}

void C4Effect::CompileFunc(StdCompiler *pComp, C4PropList * Owner, C4ValueNumbers * numbers)
{
	if (pComp->isDeserializer()) Target = Owner;
	// read name
	pComp->Separator(StdCompiler::SEP_START); // '('
	// read priority
	pComp->Value(iPriority); pComp->Separator();
	// read time and intervall
	pComp->Value(iTime); pComp->Separator();
	pComp->Value(iInterval); pComp->Separator();
	// read object number
	// FIXME: replace with this when savegame compat breaks for other reasons
	// pComp->Value(mkParAdapt(CommandTarget, numbers));
	int32_t nptr = 0;
	if (!pComp->isDeserializer() && CommandTarget.getPropList() && CommandTarget._getPropList()->GetPropListNumbered())
		nptr = CommandTarget._getPropList()->GetPropListNumbered()->Number;
	pComp->Value(nptr);
	if (pComp->isDeserializer())
		CommandTarget.SetObjectEnum(nptr);
	pComp->Separator();
	// read ID
	if (pComp->isSerializer())
	{
		const C4PropListStatic * p = CommandTarget.getPropList() ? CommandTarget._getPropList()->IsStatic() : nullptr;
		if (p)
			p->RefCompileFunc(pComp, numbers);
		else
			pComp->String(const_cast<char*>("None"), 5, StdCompiler::RCT_ID);
	}
	else
	{
		StdStrBuf s;
		pComp->Value(mkParAdapt(s, StdCompiler::RCT_ID));
		// An Object trumps a definition as command target
		if (!nptr)
			if (!::ScriptEngine.GetGlobalConstant(s.getData(), &CommandTarget))
				CommandTarget.Set0();
	}
	pComp->Separator();
	// proplist
	C4PropListNumbered::CompileFunc(pComp, numbers);
	pComp->Separator(StdCompiler::SEP_END); // ')'
	// is there a next effect?
	bool fNext = !! pNext;
	if (pComp->hasNaming())
	{
		if (fNext || pComp->isDeserializer())
			fNext = pComp->Separator();
	}
	else
		pComp->Value(fNext);
	if (!fNext) return;
	// read next
	pComp->Value(mkParAdapt(mkPtrAdaptNoNull(pNext), Owner, numbers));
	// denumeration and callback assignment will be done later
}

void C4Effect::SetPropertyByS(C4String * k, const C4Value & to)
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Name:
				if (!to.getStr() || !*to.getStr()->GetCStr())
					throw C4AulExecError("effect: Name has to be a nonempty string");
				C4PropListNumbered::SetPropertyByS(k, to);
				ReAssignCallbackFunctions();
				return;
			case P_Priority:
				throw C4AulExecError("effect: Priority is readonly");
			case P_Interval: iInterval = to.getInt(); return;
			case P_CommandTarget:
				throw C4AulExecError("effect: CommandTarget is readonly");
			case P_Target:
				throw C4AulExecError("effect: Target is readonly");
			case P_Time: iTime = to.getInt(); return;
			case P_Prototype:
				throw new C4AulExecError("effect: Prototype is readonly");
		}
	}
	C4PropListNumbered::SetPropertyByS(k, to);
}

void C4Effect::ResetProperty(C4String * k)
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Name:
				throw C4AulExecError("effect: Name has to be a nonempty string");
			case P_Priority:
				throw C4AulExecError("effect: Priority is readonly");
			case P_Interval: iInterval = 0; return;
			case P_CommandTarget:
				throw C4AulExecError("effect: CommandTarget is readonly");
			case P_Target:
				throw C4AulExecError("effect: Target is readonly");
			case P_Time: iTime = 0; return;
			case P_Prototype:
				throw new C4AulExecError("effect: Prototype is readonly");
		}
	}
	C4PropListNumbered::ResetProperty(k);
}

bool C4Effect::GetPropertyByS(const C4String *k, C4Value *pResult) const
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Name: return C4PropListNumbered::GetPropertyByS(k, pResult);
			case P_Priority: *pResult = C4VInt(Abs(iPriority)); return true;
			case P_Interval: *pResult = C4VInt(iInterval); return true;
			case P_CommandTarget: *pResult = CommandTarget; return true;
			case P_Target: *pResult = C4Value(Target); return true;
			case P_Time: *pResult = C4VInt(iTime); return true;
		}
	}
	return C4PropListNumbered::GetPropertyByS(k, pResult);
}

C4ValueArray * C4Effect::GetProperties() const
{
	C4ValueArray * a = C4PropList::GetProperties();
	int i;
	i = a->GetSize();
	a->SetSize(i + 5);
	(*a)[i++] = C4VString(&::Strings.P[P_Name]);
	(*a)[i++] = C4VString(&::Strings.P[P_Priority]);
	(*a)[i++] = C4VString(&::Strings.P[P_Interval]);
	(*a)[i++] = C4VString(&::Strings.P[P_CommandTarget]);
	(*a)[i++] = C4VString(&::Strings.P[P_Target]);
	(*a)[i++] = C4VString(&::Strings.P[P_Time]);
	return a;
}
