diff --git a/docs/sdk/definition/properties.xml b/docs/sdk/definition/properties.xml index 2bec17e5f..936440044 100644 --- a/docs/sdk/definition/properties.xml +++ b/docs/sdk/definition/properties.xml @@ -28,7 +28,7 @@ Stand = { Prototype proplist - + Deprecated. Use SetPrototype and GetPrototype. Name diff --git a/docs/sdk/script/Effects.xml b/docs/sdk/script/Effects.xml index e0bb8dfca..5be378ae2 100644 --- a/docs/sdk/script/Effects.xml +++ b/docs/sdk/script/Effects.xml @@ -51,7 +51,8 @@ func Destruction() The magic spell object exists until the spell has ended and then makes the clonk visible again. Also, if the spell object is deleted for other reasons (e.g. a scenario section change), the clonk is made visible in the Destruction callback (if this wasn't so, the clonk would remain invisible for ever). Also there is a Timer (defined in the DefCore) called every second. Notice you couldn't just have a single timer call to mark the end of the spell because timer intervals are marked in the engine beginning with the start of the round and you wouldn't know at what point within an engine timer interval the spell would start. However, there are some problems with this implementation: for example, the magician can not cast a second invisibility spell while he's already invisible - the second spell would have practically no effect, because the end of the first spell would make the clonk visible again. The spell script would have to do some special handling for this case - but not only for multiple invisibility spells, but also for any other spell or script that might affect visibility or coloration of the clonk. Even if this spell would remember the previous value e.g. for coloration it could not handle a situation in which other scripts change the color of their own in the middle of the spell. The same problems occur when multiple scripts modify temporary clonk physcials such as jumping, walking speed, fight strength or visibility range, energy, magic energy etc. Using effects, these conflicts can be avoided. Application - Effects are created using AddEffect and removed with RemoveEffect. If an effect was successfully created, the callback Fx*Start is made (* is replaced with the effect name). Depending on the parameters, there can also be an Fx*Timer call for continuous activity such as casting sparks, adjusting energy etc. Finally, when the effect is deleted, the callback Fx*Stop is made. Now, the invisibility spell implemented using effects: + Effects are created using CreateEffect and removed with RemoveEffect. If an effect was successfully created, the callback Construction is made. Depending on the parameters, there can also be an Timer call for continuous activity such as casting sparks, adjusting energy etc. Finally, when the effect is deleted, the callback Destruction is made. + Now, the invisibility spell implemented using effects: /* Invisibility spell with effect system */ // visibility - previous visibility @@ -59,92 +60,89 @@ func Destruction() func Activate(object caster, object caster2) { - // get caster - if (caster2) caster = caster2; + // get caster + if (caster2) caster = caster2; - // start effect - AddEffect("InvisPSpell", caster, 200, 1111, nil, GetID()); - // done - the spell object is not needed anymore - RemoveObject(); - return true; + // start effect + caster->CreateEffect(InvisPSpell, 200, 1111); + // done - the spell object is not needed anymore + RemoveObject(); + return true; } -func FxInvisPSpellStart(object target, proplist effect) +local InvisPSpell = { - // Save the casters previous visibility - effect.visibility = target.Visibility; - effect.old_mod = target->GetClrModulation(); - // Make the caster invisible - target.Visibility = VIS_Owner | VIS_Allies | VIS_God; - // Semitransparent and slightly blue for owner and allies - target->SetClrModulation(ModulateColor(effect.old_mod, RGBa(127,127,255,127))); - // Fertig - return true; -} - -func FxInvisPSpellStop(object target, proplist effect) -{ - // restore previous values - target->SetClrModulation(effect.old_mod); - target.Visibility = effect.visibility; - // done - return true; + Start = func(object target) + { + // Save the casters previous visibility + this.visibility = target.Visibility; + this.old_mod = target->GetClrModulation(); + // Make the caster invisible + target.Visibility = VIS_Owner | VIS_Allies | VIS_God; + // Semitransparent and slightly blue for owner and allies + target->SetClrModulation(ModulateColor(this.old_mod, RGBa(127,127,255,127))); + }, + Stop = func(object target) + { + // restore previous values + target->SetClrModulation(this.old_mod); + target.Visibility = this.visibility; + } } In this case, the magic spell object only starts the effect, then deletes itself immediately. The engine ensures that there are no conflicts with multiple effects modifying the visibility: effects are stored in a stack which ensures that effects are always removed in the opposite order of their addition. For this, there are a couple of extra Start and Stop calls to be made which are explained in detail later. This effects does not have a timer function. It does, however, define a timer interval of 1111 which will invoke the standard timer function after 1111 frames which will delete the effect. Alternatively, you could define a timer function as such: - func FxInvisPSpellTimer() + Timer = func() { - // return value of -1 means that the effect should be removed - return -1; + // return value of -1 means that the effect should be removed + return -1; } - To store the previous status of the target object, properties of the effect are used. This is necessary because in this case the effect callbacks do not have any object script context. So you cannot access any object local variables in the effect callbacks - remember that the magic spell object which has created the effect is already deleted. If you require an object context in the effect callbacks you can specify one in AddEffect(). In that case, effect callbacks would be in object local context and the effect would automatically be deleted if the target object is destroyed. + To store the previous status of the target object, properties of the effect are used. This way effects are independant of other objects and effects - remember that the magic spell object which has created the effect is already deleted. If you need to call functions in the context of the target object or other objects, use ->. Priorities - When creating an effect you always specify a priority value which determines the effect order. The engine ensures that effects with lower priority are added before effects with a higher priority - even if this means deleting an existing effect of higher priority. So if one effect colors the clonk green and another colors the clonk red, the result will be that of the effect with higher priority. If two effects have the same priority, the order is undefined. However, it is guaranteed that effects added later always notify the Fx*Effect callback of the same priority. - In the case of the red and green color, one effect could also determine the previous coloring and then mix a result using ModulateColor. But priorities also have another function: an effect of higher priority can prevent the addition of other effects of lower priority. This is done through the Fx*Effect callback. If any existing effect reacts to this callback with the return value -1, the new effect is not added (the same applies to the Start callback of the effect itself). Here an example: + When creating an effect you always specify a priority value which determines the effect order. The engine ensures that effects with lower priority are added before effects with a higher priority - even if this means deleting an existing effect of higher priority. So if one effect colors the clonk green and another colors the clonk red, the result will be that of the effect with higher priority. If two effects have the same priority, the order is undefined. However, it is guaranteed that effects added later always notify the Effect callback of the same priority. + In the case of the red and green color, one effect could also determine the previous coloring and then mix a result using ModulateColor. But priorities also have another function: an effect of higher priority can prevent the addition of other effects of lower priority. This is done through the Effect callback. If any existing effect reacts to this callback with the return value -1, the new effect is not added (the same applies to the Start callback of the effect itself). Here an example: /* Spell of immunity against fire */ func Activate(object caster, object caster2) { - // get caster - if (caster2) caster = caster2; + // get caster + if (caster2) caster = caster2; - // start effect - AddEffect("BanBurnPSpell", caster, 180, 1111, nil, GetID()); + // start effect + caster->CreateEffect(BanBurnPSpell, 180, 1111); - // done - the spell object is not needed anymore - RemoveObject(); - return true; + // done - the spell object is not needed anymore + RemoveObject(); + return true; } -func FxBanBurnPSpellStart(object target, proplist effect, bool temporary) -{ - // On start of the effect: extinguish clonk - if (!temporary) target->Extinguish(); - return true; -} - -func FxBanBurnPSpellEffect(string new_name) -{ - // block fire - if (WildcardMatch(new_name, "*Fire*")) return -1; - // everything else is ok - return 0; -} - This effect makes the clonk fire-proof for 30 seconds. The effect is implemented without any Timer or Stop callbacks as the complete functionality is achieved by simply blocking other effects which might have "Fire" as part of their name. This especially applies to the engine internal fire which has exactly the name "Fire". Of course, you could still add a Timer callback for graphic effects so the player can see that his clonk is immune. Also, you could create special visual effects when preventing incineration in FxBanBurnPSpellEffect. For the like: +local BanBurnPSpell = { + Construction = func(object target) + { + // On start of the effect: extinguish clonk + target->Extinguish(); + }, + Effect = func(string new_name) + { + // block fire + if (WildcardMatch(new_name, "*Fire*")) return -1; + // everything else is ok + return 0; + } +}; + This effect makes the clonk fire-proof for 30 seconds. The effect is implemented without any Timer or Stop callbacks as the complete functionality is achieved by simply blocking other effects which might have "Fire" as part of their name. This especially applies to the engine internal fire which has exactly the name "Fire". Of course, you could still add a Timer callback for graphic effects so the player can see that his clonk is immune. Also, you could create special visual effects when preventing incineration in Effect. For the like: [...] - -func FxBanBurnPSpellEffect(string new_name, object target, proplist effect, var1, var2, var3) +Effect = func(string new_name, object target, var1, var2, var3, var4) { - // only handle fire - if (!WildcardMatch(new_name, "*Fire*")) return 0; - // with fire, the three extra parameters have the following meaning: - // var1: caused_by - player that is responsible for the fire - // var2: blasted - bool: if the fire has been created by an explosion - // var3: burning_object - object: incineratable object - // extinguish burning object - if (var3 && GetType(var3) == C4V_C4Object) var3->Extinguish(); - // block fire - return -1; + // only handle fire + if (!WildcardMatch(new_name, "*Fire*")) return 0; + // with fire, the three extra parameters have the following meaning: + // var1: caused_by - player that is responsible for the fire + // var2: blasted - bool: if the fire has been created by an explosion + // var3: burning_object - object: incineratable object + // extinguish burning object + if (var3 && GetType(var3) == C4V_C4Object) var3->Extinguish(); + // block fire + return -1; } This would even delete all burning objects which would otherwise incinerate the target object. The type check for var3 avoids possible conflicts with other "Fire" effects that might have differing parameters. Obviously, conflict situations like this should be avoided at all cost. The following table contains general guidelines for priorities in effects of the original pack: @@ -210,44 +208,46 @@ func FxBanBurnPSpellEffect(string new_name, object target, proplist effect, var1 func Activate(object caster, object caster2) { - // get caster - if (caster2) caster = caster2; + // get caster + if (caster2) caster = caster2; - // start effect - AddEffect("ReincarnationPSpell", caster, 180, 0, nil, GetID()); - - // done - the spell object is not needed anymore - RemoveObject(); - return true; + // start effect + caster->CreateEffect(ReincarnationPSpell, 180, 0); + + // done - the spell object is not needed anymore + RemoveObject(); + return true; } -func FxReincarnationPSpellStart(object target, proplist effect, bool temporary) -{ - // Only at the first start: message - if (!temporary) target->Message("%s gets an extra life", target->GetName()); - return true; -} +local ReincarnationPSpell = { + Construction = func(object target) + { + // Only at the first start: message + target->Message("%s gets an extra life", target->GetName()); + return true; + }, -func FxReincarnationPSpellStop(object target, proplist effect, int reason, bool temporary) -{ - // only when the clonk died - if (reason != 4) return true; - - // the clonk has already been resurrected - if (target->GetAlive()) return -1; - // resurrect clonk - target->SetAlive(true); - // give energy - target->DoEnergy(100); - // message - target->Message("%s has been resurrected.", target->GetName()); + func Stop(object target, int reason, bool temporary) + { + // only when the clonk died + if (reason != 4) return true; + + // the clonk has already been resurrected + if (target->GetAlive()) return -1; + // resurrect clonk + target->SetAlive(true); + // give energy + target->DoEnergy(100); + // message + target->Message("%s has been resurrected.", target->GetName()); - // remove - return true; -} + // remove + return true; + } +}; This effect reanimates the clonk as many times as he has cast the reanimation spell. Global Effects - Global effects are effects that are not bound to any target object. With global effects, too, priorities are observed and temporary Add/Remove calls might be necessary to ensure order. Simply imagine all global effects are attached to an imaginary object. Global effects are accessed whenever you specify nil for the target object. + There are two global effect types: Scenerio effects and global effects. They are bound to the Scenario and Global proplists. With these effects, too, priorities are observed and temporary Add/Remove calls might be necessary to ensure order. This can be used to make changes to gravity, sky color, etc. Here's an example for a spell that temporarily reduces gravity and then resets the original value: /* Gravitation spell */ @@ -488,19 +488,20 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason - Warning: as function names may not be more than 100 characters in length (and you will lose oversight eventually), you should not stuff too much information into the effect name. Effect names are case sensitive. Also, you should avoid using any of these identifiers in your effect names if your effect doesn't have anything to do with them. + Effect names are case sensitive. Also, you should avoid using any of these identifiers in your effect names if your effect doesn't have anything to do with them. Callback Reference - The following callbacks are made by the engine and should be implemented in your script according to necessity. * is to be replaced by your effect name. - Fx*Start - int Fx*Start (object target, proplist effect, int temporary, any var1, any var2, any var3, any var4); - Called at the start of the effect. target is the target object of the effect. effect is the effect itself. effect can be used to manipulate the effect, for example with effect.Interval=newinterval. + The following callbacks are made by the engine and should be implemented in your effect prototype as necessary. + All parameters receive the target as their first or second parameter. This is either an object, the Global or the Scenario proplist. + Start + int Start (object target, int temporary, any var1, any var2, any var3, any var4); + Called at the start of the effect. target is the target object of the effect. this is the effect itself. It can be used to manipulate the effect, for example with this.Interval=newinterval. In normal operation the parameter temporary is 0. It is 1 if the effect is re-added after having been temporarily removed and 2 if the effect was temporarily removed and is now to be deleted (in this case a Remove call will follow). - If temporary is 0, var1 to var4 are the additional parameters passed to AddEffect(). - If temporary is 0 and this callback returns -1 the effect is not created and the corrsponding AddEffect() call returns 0. - Fx*Stop - int Fx*Stop (object target, proplist effect, int reason, bool temporary); - When the effect is temporarily or permanently removed. target again is the target object and effect the effect itself. + If temporary is 0, var1 to var4 are the additional parameters passed to CreateEffect(). + If temporary is 0 and this callback returns -1 the effect is not created and the corrsponding CreateEffect() call returns nil. + Stop + int Stop (object target, int reason, bool temporary); + When the effect is temporarily or permanently removed. target again is the target object and this the effect itself. reason contains the cause of the removal and can be one of the following values: @@ -537,25 +538,33 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason
The effect can prevent removal by returning -1. This will not help, however, in temporary removals or if the target object has been deleted. - Fx*Timer - int Fx*Timer (object target, proplist effect, int time); - Periodic timer call, if a timer interval has been specified at effect creation. target and effect as usual. + Construction + int Construction (object target, any var1, any var2, any var3, any var4); + Called when the effect is first created, before it is started. The parameters var1 to var4 are passed through from CreateEffect. + The return value is ignored. + Destruction + nil Destruction (object target, int reason); + Callback when the effect is removed. reason is the same as in the preceding Stop call, see above. + The return value is ignored. + Timer + int Timer (object target, int time); + Periodic timer call, if a timer interval has been specified at effect creation. time specifies how long the effect has now been active. This might alternatively be determined using effect.Time. If this function is not implemented or returns -1, the effect will be deleted after this call. - Fx*Effect - int Fx*Effect (string new_name, object target, proplist effect, any var1, any var2, any var3, any var4); - A call to all effects of higher priority if a new effect is to be added to the same target object. new_name is the name of the new effect; effect is the effect being called. + Effect + int Effect (string new_name, object target, any var1, any var2, any var3, any var4); + A call to all effects of higher priority if a new effect is to be added to the same target object. new_name is the name of the new effect; this is the effect being called. Warning: the new effect is not yet properly initialized and should not be manipulated in any way. Especially the priority field might not yet have been set. This function can return -1 to reject the new effect. As the new effect might also be rejected by other effects, this callback should not try to add effects or similar (see gravitation spell). Generally you should not try to manipulate any effects during this callback. - Return -2 or -3 to accept the new effect. As long as the new effect is not rejected by any other effect, the Fx*Add call is then made to the accepting effect, the new effect is not actually created, and the calling AddEffect function returns the effect index of the accepting effect. The return value -3 will also temporarily remove all higher prioriy effects just before the Fx*Add callback and re-add them later. - var1 bis var4 are the parameters passed to AddEffect() - Fx*Add - int Fx*Add (object target, proplist effect, string new_name, int new_timer, any var1, any var2, any var3, any var4); - Callback to the accepting effect if that has returned -2 or -3 to a prior Fx*Effect call. effect identifies the accepting effect to which the consequences of the new effect will be added; target is the target object (0 for global effects). + Return -2 or -3 to accept the new effect. As long as the new effect is not rejected by any other effect, the Add call is then made to the accepting effect, the new effect is not actually created, and the calling CreateEffect function returns the accepting effect. The return value -3 will also temporarily remove all higher prioriy effects just before the Add callback and re-add them later. + var1 bis var4 are the parameters passed to CreateEffect() + Add + int Add (object target, string new_name, int new_timer, any var1, any var2, any var3, any var4); + Callback to the accepting effect if that has returned -2 or -3 to a prior Effect call. this identifies the accepting effect to which the consequences of the new effect will be added; target is the target object (0 for global effects). new_timer is the timer interval of the new effect; var1 to var4 are the parameters from AddEffect. Notice: in temporary calls, these parameters are not available - here they will be 0. - If -1 is returned, the accepting effect is deleted also. Logically, the calling AddEffect function will then return -2. - Fx*Damage - int Fx*Damage (object target, proplist effect, int damage, int cause, int by_player); + If -1 is returned, the accepting effect is deleted also. Logically, the calling CreateEffect function will then return nil. + Damage + int Damage (object target, int damage, int cause, int by_player); Every effect receives this callback whenever the energy or damage value of the target object is to change. If the function is defined, it should then return the damage to be done to the target. This callback is made upon life energy changes in living beings and damage value changes in non-livings - but not vice versa. cause contains the value change and reason: @@ -639,7 +648,7 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason There are the following functions for manipulation of effects:
    -
  • AddEffect() - for effect creation
  • +
  • CreateEffect() - for effect creation
  • RemoveEffect() - for effect removal
  • GetEffect() - to search for effects
  • GetEffectCount() - for effect counting
  • diff --git a/docs/sdk/script/fn/AddEffect.xml b/docs/sdk/script/fn/AddEffect.xml index 6c5aa8ef4..aba43a545 100644 --- a/docs/sdk/script/fn/AddEffect.xml +++ b/docs/sdk/script/fn/AddEffect.xml @@ -7,6 +7,7 @@ AddEffect Effects 5.1 OC + proplist @@ -18,7 +19,7 @@ object target - Target object for the effect. If nil, a global effect is created. + Target object for the effect. If nil, Global is used, but the target parameter of the callbacks will get nil. @@ -74,6 +75,7 @@ For examples and more information see the effects documentation. Effects Documentation + CreateEffect CheckEffect GetEffectCount EffectCall diff --git a/docs/sdk/script/fn/CheckEffect.xml b/docs/sdk/script/fn/CheckEffect.xml index 7dbd05451..9ca7f7130 100644 --- a/docs/sdk/script/fn/CheckEffect.xml +++ b/docs/sdk/script/fn/CheckEffect.xml @@ -63,7 +63,7 @@ For examples and more information see the effects documentation. Effects Documentation - AddEffect + CreateEffect GetEffectCount EffectCall GetEffect diff --git a/docs/sdk/script/fn/CreateEffect.xml b/docs/sdk/script/fn/CreateEffect.xml new file mode 100644 index 000000000..01d2719d4 --- /dev/null +++ b/docs/sdk/script/fn/CreateEffect.xml @@ -0,0 +1,69 @@ + + + + + + CreateEffect + Effects + 5.5 OC + + proplist + + + proplist + prototype + A proplist containing the callback functions for the new Effect. The name (GetName) of this proplist becomes the name of the effect. + + + int + priority + Effect priority. Must be greater than zero. + + + int + timer + + Interval for the timer calls. With nil, no timer calls are made and the effect stays on permanently until it is deleted by other calls. + + + any + var1 + + First extra parameter to be passed to Construction, Start and Effect callbacks. + + + any + var2 + + Second extra parameter to be passed to Construction, Start and Effect callbacks. + + + any + var3 + + Third extra parameter to be passed to Construction, Start and Effect callbacks. + + + any + var4 + + Fourth extra parameter to be passed to Construction, Start and Effect callbacks. + + + + Creates an effect. Returns the effect if successful or nil if not (e.g. because the effect was rejected). If the effect was accepted by another effect which is deleting itself within the same call, the return value is probably nil. Effects can be created on objects, Global and Scenario. This is passed as the first parameter to the effect callbacks. + + For examples and more information see the effects documentation. + + Effects Documentation + CheckEffect + GetEffectCount + EffectCall + GetEffect + RemoveEffect + + + Sven22004-03 + Günther2014 + diff --git a/docs/sdk/script/fn/EffectCall.xml b/docs/sdk/script/fn/EffectCall.xml index 5bf4683ad..4da78a910 100644 --- a/docs/sdk/script/fn/EffectCall.xml +++ b/docs/sdk/script/fn/EffectCall.xml @@ -37,7 +37,7 @@ For examples and more information see the effects documentation. Effects Documentation - AddEffect + CreateEffect CheckEffect GetEffectCount GetEffect diff --git a/docs/sdk/script/fn/GetEffect.xml b/docs/sdk/script/fn/GetEffect.xml index 2c80d48be..b70c06e11 100644 --- a/docs/sdk/script/fn/GetEffect.xml +++ b/docs/sdk/script/fn/GetEffect.xml @@ -59,7 +59,7 @@ i = GetEffectCount(); Effects Documentation - AddEffect + CreateEffect CheckEffect GetEffectCount EffectCall diff --git a/docs/sdk/script/fn/GetEffectCount.xml b/docs/sdk/script/fn/GetEffectCount.xml index a02f6ea9f..5c63c0bb9 100644 --- a/docs/sdk/script/fn/GetEffectCount.xml +++ b/docs/sdk/script/fn/GetEffectCount.xml @@ -34,7 +34,7 @@ For an example see GetEffect. Effects Documentation - AddEffect + CreateEffect CheckEffect EffectCall GetEffect diff --git a/docs/sdk/script/fn/GetName.xml b/docs/sdk/script/fn/GetName.xml index a363ee119..41e83b240 100644 --- a/docs/sdk/script/fn/GetName.xml +++ b/docs/sdk/script/fn/GetName.xml @@ -7,8 +7,26 @@ GetName Objects 5.1 OC - string - Returns the name of an object or of an object definition. If the object does not have a name of its own, the definition name is returned anyway. + + string + + + bool + truename + Returns only the constant in which it was defined, ignoring the Name property. + + + + Returns the name of a proplist. This is either the contents of the Name property, or if that doesn't exist or the true name was requested, the name of the constant in which it was defined. + + + +static const Bee = { Buzz = func() {} }; +func Poke(proplist animal) { + if (animal->GetName(true) == "Bee") animal->Buzz(); +} + + - jwk2002-06 + Günther2014 diff --git a/docs/sdk/script/fn/GetPrototype.xml b/docs/sdk/script/fn/GetPrototype.xml new file mode 100644 index 000000000..351a0d306 --- /dev/null +++ b/docs/sdk/script/fn/GetPrototype.xml @@ -0,0 +1,26 @@ + + + + + + GetPrototype + Objects + Properties + 8.0 OC + + nil + + + proplist + obj + The Object whose prototype is returned. Can be nil in local calls. + + + + + When properties of a proplist are read and not set on that proplist, the property is looked up in the proplist's prototype(s). The immediate prototype is returned by this function. + SetPrototype + + Günther2016-04 + diff --git a/docs/sdk/script/fn/RemoveEffect.xml b/docs/sdk/script/fn/RemoveEffect.xml index 6becb7630..38c15a8b1 100644 --- a/docs/sdk/script/fn/RemoveEffect.xml +++ b/docs/sdk/script/fn/RemoveEffect.xml @@ -40,7 +40,7 @@ See GetEffect for an example. Warning: if an effect is meant to delete itself using this function, only use effect, not name! Effects Documentation - AddEffect + CreateEffect CheckEffect GetEffectCount EffectCall diff --git a/docs/sdk/script/fn/SetPrototype.xml b/docs/sdk/script/fn/SetPrototype.xml new file mode 100644 index 000000000..43b5a4bb0 --- /dev/null +++ b/docs/sdk/script/fn/SetPrototype.xml @@ -0,0 +1,34 @@ + + + + + + SetPrototype + Objects + Properties + 8.0 OC + + nil + + + proplist + prototype + The new prototype. + + + + proplist + obj + Object to be changed. Can be nil in local calls. + + + + + This function changes the prototype of a proplist. + When properties of a proplist are read and not set on that proplist, the property is looked up in the proplist's prototype(s). + This can be used for inheritance. + GetPrototype + + Günther2016-04 + diff --git a/planet/System.ocg/Action.c b/planet/System.ocg/Action.c index 6c765de1f..643fe2166 100644 --- a/planet/System.ocg/Action.c +++ b/planet/System.ocg/Action.c @@ -22,6 +22,7 @@ static const DFA_ATTACH = "ATTACH"; static const DFA_CONNECT = "CONNECT"; static const DFA_PULL = "PULL"; static const Action = { + GetName = Global.GetName, Length = 1, Directions = 1, Step = 1, diff --git a/src/game/C4Game.cpp b/src/game/C4Game.cpp index 37409c114..19fca94cf 100644 --- a/src/game/C4Game.cpp +++ b/src/game/C4Game.cpp @@ -579,7 +579,6 @@ void C4Game::Clear() ::Definitions.Clear(); Landscape.Clear(); PXS.Clear(); - if (pGlobalEffects) { delete pGlobalEffects; pGlobalEffects=NULL; } ScriptGuiRoot.reset(); Particles.Clear(); ::MaterialMap.Clear(); @@ -726,8 +725,9 @@ bool C4Game::Execute() // Returns true if the game is over // Game EXEC_S( ExecObjects(); , ExecObjectsStat ) - if (pGlobalEffects) - EXEC_S_DR( pGlobalEffects->Execute(NULL); , GEStats , "GEEx\0"); + EXEC_S_DR( C4Effect::Execute(ScriptEngine.GetPropList(), &ScriptEngine.pGlobalEffects); + C4Effect::Execute(GameScript.GetPropList(), &GameScript.pScenarioEffects); + , GEStats , "GEEx\0"); EXEC_S_DR( PXS.Execute(); , PXSStat , "PXSEx") EXEC_S_DR( MassMover.Execute(); , MassMoverStat , "MMvEx") EXEC_S_DR( Weather.Execute(); , WeatherStat , "WtrEx") @@ -914,8 +914,10 @@ void C4Game::ClearPointers(C4Object * pObj) ::MouseControl.ClearPointers(pObj); ScriptGuiRoot->ClearPointers(pObj); TransferZones.ClearPointers(pObj); - if (pGlobalEffects) - pGlobalEffects->ClearPointers(pObj); + if (::ScriptEngine.pGlobalEffects) + ::ScriptEngine.pGlobalEffects->ClearPointers(pObj); + if (::GameScript.pScenarioEffects) + ::GameScript.pScenarioEffects->ClearPointers(pObj); ::Landscape.ClearPointers(pObj); } @@ -1469,7 +1471,6 @@ void C4Game::Default() pParentGroup=NULL; pScenarioSections=pCurrentScenarioSection=NULL; *CurrentScenarioSection=0; - pGlobalEffects=NULL; fResortAnyObject=false; pNetworkStatistics.reset(); ::Application.MusicSystem.ClearGame(); @@ -1729,40 +1730,7 @@ void C4Game::CompileFunc(StdCompiler *pComp, CompileSettings comp, C4ValueNumber pComp->Value(mkParAdapt(Objects, !comp.fExact, numbers)); - pComp->Name("Script"); - if (!comp.fScenarioSection) - { - pComp->Value(mkParAdapt(ScriptEngine, numbers)); - } - if (comp.fScenarioSection && pComp->isCompiler()) - { - // loading scenario section: Merge effects - // Must keep old effects here even if they're dead, because the LoadScenarioSection call typically came from execution of a global effect - // and otherwise dead pointers would remain on the stack - C4Effect *pOldGlobalEffects, *pNextOldGlobalEffects=pGlobalEffects; - pGlobalEffects = NULL; - try - { - pComp->Value(mkParAdapt(mkNamingPtrAdapt(pGlobalEffects, "Effects"), numbers)); - } - catch (...) - { - delete pNextOldGlobalEffects; - throw; - } - while ((pOldGlobalEffects=pNextOldGlobalEffects)) - { - pNextOldGlobalEffects = pOldGlobalEffects->pNext; - pOldGlobalEffects->Register(NULL, Abs(pOldGlobalEffects->iPriority)); - } - } - else - { - // Otherwise, just compile effects - pComp->Value(mkParAdapt(mkNamingPtrAdapt(pGlobalEffects, "Effects"), numbers)); - } - pComp->Value(mkNamingAdapt(*numbers, "Values")); - pComp->NameEnd(); + pComp->Value(mkNamingAdapt(mkParAdapt(ScriptEngine, comp.fScenarioSection, numbers), "Script")); } bool C4Game::CompileRuntimeData(C4Group &hGroup, bool fLoadSection, bool exact, bool sync, C4ValueNumbers * numbers) @@ -2289,7 +2257,6 @@ bool C4Game::InitGame(C4Group &hGroup, bool fLoadSection, bool fLoadSky, C4Value // Denumerate game data pointers if (!fLoadSection) ScriptEngine.Denumerate(numbers); - if (!fLoadSection && pGlobalEffects) pGlobalEffects->Denumerate(numbers); if (!fLoadSection) GlobalSoundModifier.Denumerate(numbers); numbers->Denumerate(); if (!fLoadSection) ScriptGuiRoot->Denumerate(numbers); @@ -3518,12 +3485,14 @@ bool C4Game::LoadScenarioSection(const char *szSection, DWORD dwFlags) } DeleteObjects(false); // remove global effects - if (pGlobalEffects) if (!(dwFlags & C4S_KEEP_EFFECTS)) - { - pGlobalEffects->ClearAll(NULL, C4FxCall_RemoveClear); - // scenario section call might have been done from a global effect - // rely on dead effect removal for actually removing the effects; do not clear the array here! - } + if (::ScriptEngine.pGlobalEffects && !(dwFlags & C4S_KEEP_EFFECTS)) + { + ::ScriptEngine.pGlobalEffects->ClearAll(NULL, C4FxCall_RemoveClear); + // scenario section call might have been done from a global effect + // rely on dead effect removal for actually removing the effects; do not clear the array here! + } + if (::GameScript.pScenarioEffects && !(dwFlags & C4S_KEEP_EFFECTS)) + ::GameScript.pScenarioEffects->ClearAll(NULL, C4FxCall_RemoveClear); // del particles as well Particles.ClearAllParticles(); // clear transfer zones @@ -3777,12 +3746,16 @@ bool C4Game::ToggleChat() C4Value C4Game::GRBroadcast(const char *szFunction, C4AulParSet *pPars, bool fPassError, bool fRejectTest) { + std::string func{ szFunction }; + if (func[0] != '~') + func.insert(0, 1, '~'); + // call objects first - scenario script might overwrite hostility, etc... - C4Value vResult = ::Objects.GRBroadcast(szFunction, pPars, fPassError, fRejectTest); + C4Value vResult = ::Objects.GRBroadcast(func.c_str(), pPars, fPassError, fRejectTest); // rejection tests abort on first nonzero result if (fRejectTest) if (!!vResult) return vResult; // scenario script call - return ::GameScript.Call(szFunction, pPars, fPassError); + return ::GameScript.Call(func.c_str(), pPars, fPassError); } void C4Game::SetDefaultGamma() diff --git a/src/game/C4Game.h b/src/game/C4Game.h index 6d4b42ee4..29820ab2e 100644 --- a/src/game/C4Game.h +++ b/src/game/C4Game.h @@ -83,7 +83,6 @@ public: C4Extra Extra; class C4ScenarioObjectsScriptHost *pScenarioObjectsScript; C4ScenarioSection *pScenarioSections, *pCurrentScenarioSection; - C4Effect *pGlobalEffects; C4PlayerControlDefs PlayerControlDefs; C4PlayerControlAssignmentSets PlayerControlUserAssignmentSets, PlayerControlDefaultAssignmentSets; C4Scoreboard Scoreboard; diff --git a/src/game/C4GameScript.cpp b/src/game/C4GameScript.cpp index 09ae62d64..4feba7205 100644 --- a/src/game/C4GameScript.cpp +++ b/src/game/C4GameScript.cpp @@ -47,6 +47,22 @@ #include "landscape/C4Sky.h" #include "landscape/C4Particles.h" +C4Effect ** FnGetEffectsFor(C4PropList * pTarget) +{ + if (pTarget) + { + if (pTarget == ScriptEngine.GetPropList()) + return &ScriptEngine.pGlobalEffects; + if (pTarget == GameScript.ScenPrototype.getPropList() || pTarget == GameScript.ScenPropList.getPropList()) + return &GameScript.pScenarioEffects; + C4Object * Obj = pTarget->GetObject(); + if (!Obj) + throw C4AulExecError("Effect target has to be an object"); + return &Obj->pEffects; + } + return &ScriptEngine.pGlobalEffects; +} + // undocumented! static bool FnIncinerateLandscape(C4PropList * _this, long iX, long iY, long caused_by_plr) { @@ -2148,106 +2164,6 @@ static long FnLoadScenarioSection(C4PropList * _this, C4String *pstrSection, lon return Game.LoadScenarioSection(szSection, dwFlags); } -static C4Value FnAddEffect(C4PropList * _this, C4String * szEffect, C4Object * pTarget, - int iPrio, int iTimerInterval, C4Object * pCmdTarget, C4ID idCmdTarget, - const C4Value & Val1, const C4Value & Val2, const C4Value & Val3, const C4Value & Val4) -{ - // safety - if (pTarget && !pTarget->Status) return C4Value(); - if (!szEffect || !*szEffect->GetCStr() || !iPrio) return C4Value(); - // create effect - C4Effect * pEffect = C4Effect::New(pTarget, szEffect, iPrio, iTimerInterval, pCmdTarget, idCmdTarget, Val1, Val2, Val3, Val4); - // return effect - may be 0 if the effect has been denied by another effect - if (!pEffect) return C4Value(); - return C4VPropList(pEffect); -} - -static C4Effect * FnGetEffect(C4PropList * _this, C4String *psEffectName, C4Object *pTarget, int index, int iMaxPriority) -{ - const char *szEffect = FnStringPar(psEffectName); - // get effects - C4Effect *pEffect = pTarget ? pTarget->pEffects : Game.pGlobalEffects; - if (!pEffect) return NULL; - // name/wildcard given: find effect by name and index - if (szEffect && *szEffect) - return pEffect->Get(szEffect, index, iMaxPriority); - return NULL; -} - -static bool FnRemoveEffect(C4PropList * _this, C4String *psEffectName, C4Object *pTarget, C4Effect * pEffect2, bool fDoNoCalls) -{ - // evaluate parameters - const char *szEffect = FnStringPar(psEffectName); - // if the user passed an effect, it can be used straight-away - C4Effect *pEffect = pEffect2; - // otherwise, the correct effect will be searched in the target's effects or in the global ones - if (!pEffect) - { - pEffect = pTarget ? pTarget->pEffects : Game.pGlobalEffects; - // the object has no effects attached, nothing to look for - if (!pEffect) return 0; - // name/wildcard given: find effect by name - if (szEffect && *szEffect) - pEffect = pEffect->Get(szEffect, 0); - } - - // neither passed nor found - nothing to remove! - if (!pEffect) return 0; - - // kill it - if (fDoNoCalls) - pEffect->SetDead(); - else - pEffect->Kill(pTarget); - // done, success - return true; -} - -static C4Value FnCheckEffect(C4PropList * _this, C4String * psEffectName, C4Object * pTarget, - int iPrio, int iTimerInterval, - const C4Value & Val1, const C4Value & Val2, const C4Value & Val3, const C4Value & Val4) -{ - const char *szEffect = FnStringPar(psEffectName); - // safety - if (pTarget && !pTarget->Status) return C4Value(); - if (!szEffect || !*szEffect) return C4Value(); - // get effects - C4Effect *pEffect = pTarget ? pTarget->pEffects : Game.pGlobalEffects; - if (!pEffect) return C4Value(); - // let them check - C4Effect * r = pEffect->Check(pTarget, szEffect, iPrio, iTimerInterval, Val1, Val2, Val3, Val4); - if (r == (C4Effect *)C4Fx_Effect_Deny) return C4VInt(C4Fx_Effect_Deny); - if (r == (C4Effect *)C4Fx_Effect_Annul) return C4VInt(C4Fx_Effect_Annul); - return C4VPropList(r); -} - -static long FnGetEffectCount(C4PropList * _this, C4String *psEffectName, C4Object *pTarget, long iMaxPriority) -{ - // evaluate parameters - const char *szEffect = FnStringPar(psEffectName); - // get effects - C4Effect *pEffect = pTarget ? pTarget->pEffects : Game.pGlobalEffects; - if (!pEffect) return false; - // count effects - if (!*szEffect) szEffect = 0; - return pEffect->GetCount(szEffect, iMaxPriority); -} - -static C4Value FnEffectCall(C4PropList * _this, C4Value * Pars) -{ - // evaluate parameters - C4Object *pTarget = Pars[0].getObj(); - C4Effect * pEffect = Pars[1].getPropList() ? Pars[1].getPropList()->GetEffect() : 0; - C4String *psCallFn = Pars[2].getStr(); - const char *szCallFn = FnStringPar(psCallFn); - // safety - if (pTarget && !pTarget->Status) return C4Value(); - if (!szCallFn || !*szCallFn) return C4Value(); - if (!pEffect) return C4Value(); - // do call - return pEffect->DoCall(pTarget, szCallFn, Pars[3], Pars[4], Pars[5], Pars[6], Pars[7], Pars[8], Pars[9]); -} - static bool FnSetViewOffset(C4PropList * _this, long iPlayer, long iX, long iY) { if (!ValidPlr(iPlayer)) return false; @@ -2911,8 +2827,6 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) F(RemoveUnusedTexMapEntries); F(SimFlight); F(LoadScenarioSection); - F(RemoveEffect); - F(GetEffect); F(SetViewOffset); ::AddFunc(p, "SetPreSend", FnSetPreSend, false); F(GetPlayerID); @@ -2932,7 +2846,6 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) F(AddEvaluationData); F(HideSettlementScoreInEvaluation); F(ExtractMaterialAmount); - F(GetEffectCount); F(CustomMessage); F(GuiOpen); F(GuiUpdateTag); @@ -2966,8 +2879,6 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) F(GetMaterialVal); F(SetPlrExtraData); F(GetPlrExtraData); - F(AddEffect); - F(CheckEffect); F(PV_Linear); F(PV_Random); F(PV_Direction); @@ -2989,33 +2900,6 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine) C4ScriptConstDef C4ScriptGameConstMap[]= { - { "FX_OK" ,C4V_Int, C4Fx_OK }, // generic standard behaviour for all effect callbacks - { "FX_Effect_Deny" ,C4V_Int, C4Fx_Effect_Deny }, // delete effect - { "FX_Effect_Annul" ,C4V_Int, C4Fx_Effect_Annul }, // delete effect, because it has annulled a countereffect - { "FX_Effect_AnnulDoCalls" ,C4V_Int, C4Fx_Effect_AnnulCalls }, // delete effect, because it has annulled a countereffect; temp readd countereffect - { "FX_Execute_Kill" ,C4V_Int, C4Fx_Execute_Kill }, // execute callback: Remove effect now - { "FX_Stop_Deny" ,C4V_Int, C4Fx_Stop_Deny }, // deny effect removal - { "FX_Start_Deny" ,C4V_Int, C4Fx_Start_Deny }, // deny effect start - - { "FX_Call_Normal" ,C4V_Int, C4FxCall_Normal }, // normal call; effect is being added or removed - { "FX_Call_Temp" ,C4V_Int, C4FxCall_Temp }, // temp call; effect is being added or removed in responce to a lower-level effect change - { "FX_Call_TempAddForRemoval" ,C4V_Int, C4FxCall_TempAddForRemoval }, // temp call; effect is being added because it had been temp removed and is now removed forever - { "FX_Call_RemoveClear" ,C4V_Int, C4FxCall_RemoveClear }, // effect is being removed because object is being removed - { "FX_Call_RemoveDeath" ,C4V_Int, C4FxCall_RemoveDeath }, // effect is being removed because object died - return -1 to avoid removal - { "FX_Call_DmgScript" ,C4V_Int, C4FxCall_DmgScript }, // damage through script call - { "FX_Call_DmgBlast" ,C4V_Int, C4FxCall_DmgBlast }, // damage through blast - { "FX_Call_DmgFire" ,C4V_Int, C4FxCall_DmgFire }, // damage through fire - { "FX_Call_DmgChop" ,C4V_Int, C4FxCall_DmgChop }, // damage through chopping - { "FX_Call_Energy" ,C4V_Int, 32 }, // bitmask for generic energy loss - { "FX_Call_EngScript" ,C4V_Int, C4FxCall_EngScript }, // energy loss through script call - { "FX_Call_EngBlast" ,C4V_Int, C4FxCall_EngBlast }, // energy loss through blast - { "FX_Call_EngObjHit" ,C4V_Int, C4FxCall_EngObjHit }, // energy loss through object hitting the living - { "FX_Call_EngFire" ,C4V_Int, C4FxCall_EngFire }, // energy loss through fire - { "FX_Call_EngBaseRefresh" ,C4V_Int, C4FxCall_EngBaseRefresh }, // energy reload in base (also by base object, but that's normally not called) - { "FX_Call_EngAsphyxiation" ,C4V_Int, C4FxCall_EngAsphyxiation }, // energy loss through asphyxiaction - { "FX_Call_EngCorrosion" ,C4V_Int, C4FxCall_EngCorrosion }, // energy loss through corrosion (acid) - { "FX_Call_EngGetPunched" ,C4V_Int, C4FxCall_EngGetPunched }, // energy loss from punch - { "NO_OWNER" ,C4V_Int, NO_OWNER }, // invalid player number // material density @@ -3157,7 +3041,6 @@ C4ScriptFnDef C4ScriptGameFnMap[]= { "PlayerMessage", 1, C4V_Int, { C4V_Int ,C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnPlayerMessage }, { "Message", 1, C4V_Bool, { C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnMessage }, { "AddMessage", 1, C4V_Bool, { C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnAddMessage }, - { "EffectCall", 1, C4V_Any, { C4V_Object ,C4V_PropList,C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnEffectCall }, { "PV_KeyFrames", 1, C4V_Array, { C4V_Int ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnPV_KeyFrames }, { NULL, 0, C4V_Nil, { C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil ,C4V_Nil}, 0 } diff --git a/src/object/C4GameObjects.cpp b/src/object/C4GameObjects.cpp index 77232c21b..997f9488e 100644 --- a/src/object/C4GameObjects.cpp +++ b/src/object/C4GameObjects.cpp @@ -347,8 +347,6 @@ void C4GameObjects::UpdateScriptPointers() // call in sublists C4ObjectList::UpdateScriptPointers(); InactiveObjects.UpdateScriptPointers(); - // adjust global effects - if (Game.pGlobalEffects) Game.pGlobalEffects->ReAssignAllCallbackFunctions(); } C4Value C4GameObjects::GRBroadcast(const char *szFunction, C4AulParSet *pPars, bool fPassError, bool fRejectTest) diff --git a/src/object/C4Object.cpp b/src/object/C4Object.cpp index 2b14a21ee..ee4502d65 100644 --- a/src/object/C4Object.cpp +++ b/src/object/C4Object.cpp @@ -1047,7 +1047,7 @@ void C4Object::Execute() // effects if (pEffects) { - pEffects->Execute(this); + C4Effect::Execute(this, &pEffects); if (!Status) return; } // Life @@ -1189,7 +1189,10 @@ bool C4Object::ChangeDef(C4ID idNew) SetOCF(); // Any effect callbacks to this object might need to reinitialize their target functions // This is ugly, because every effect there is must be updated... - if (Game.pGlobalEffects) Game.pGlobalEffects->OnObjectChangedDef(this); + if (::ScriptEngine.pGlobalEffects) + ::ScriptEngine.pGlobalEffects->OnObjectChangedDef(this); + if (::GameScript.pScenarioEffects) + ::GameScript.pScenarioEffects->OnObjectChangedDef(this); for (C4Object *obj : Objects) if (obj->pEffects) obj->pEffects->OnObjectChangedDef(this); // Containment (no Entrance) diff --git a/src/object/C4ObjectScript.cpp b/src/object/C4ObjectScript.cpp index 379e02f4b..48582dc1c 100644 --- a/src/object/C4ObjectScript.cpp +++ b/src/object/C4ObjectScript.cpp @@ -197,14 +197,6 @@ static long FnGetCon(C4Object *Obj, long iPrec) return iPrec*Obj->GetCon()/FullCon; } -static C4String *FnGetName(C4PropList * _this) -{ - if (!_this) - throw NeedNonGlobalContext("GetName"); - else - return String(_this->GetName()); -} - static bool FnSetName(C4PropList * _this, C4String *pNewName, bool fSetInInfo, bool fMakeValidIfExists) { if (!Object(_this)) @@ -2619,7 +2611,6 @@ void InitObjectFunctionMap(C4AulScriptEngine *pEngine) ::AddFunc(p, "SetContactDensity", FnSetContactDensity, false); F(GetController); F(SetController); - F(GetName); F(SetName); F(GetKiller); F(SetKiller); diff --git a/src/script/C4Aul.cpp b/src/script/C4Aul.cpp index b7e0dab08..3fdd4dd9c 100644 --- a/src/script/C4Aul.cpp +++ b/src/script/C4Aul.cpp @@ -19,9 +19,9 @@ #include "script/C4Aul.h" #include "script/C4AulExec.h" #include "script/C4AulDebug.h" - #include "config/C4Config.h" #include "object/C4Def.h" +#include "script/C4Effect.h" #include "lib/C4Log.h" #include "c4group/C4Components.h" #include "c4group/C4LangStringTable.h" @@ -84,6 +84,7 @@ void C4AulScriptEngine::Clear() RegisterGlobalConstant("Global", C4VPropList(this)); GlobalNamed.Reset(); GlobalNamed.SetNameList(&GlobalNamedNames); + delete pGlobalEffects; pGlobalEffects=NULL; UserFiles.clear(); } @@ -113,17 +114,56 @@ void C4AulScriptEngine::Denumerate(C4ValueNumbers * numbers) { GlobalNamed.Denumerate(numbers); // runtime data only: don't denumerate consts - GameScript.ScenPropList.Denumerate(numbers); + GameScript.Denumerate(numbers); C4PropListStaticMember::Denumerate(numbers); + if (pGlobalEffects) pGlobalEffects->Denumerate(numbers); } -void C4AulScriptEngine::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) +static void GlobalEffectsMergeCompileFunc(StdCompiler *pComp, C4Effect * & pEffects, const char * name, C4ValueNumbers * numbers) { - assert(UserFiles.empty()); // user files must not be kept open - C4ValueMapData GlobalNamedDefault; - GlobalNamedDefault.SetNameList(&GlobalNamedNames); - pComp->Value(mkNamingAdapt(mkParAdapt(GlobalNamed, numbers), "StaticVariables", GlobalNamedDefault)); - pComp->Value(mkNamingAdapt(mkParAdapt(*GameScript.ScenPropList._getPropList(), numbers), "Scenario")); + C4Effect *pOldEffect, *pNextOldEffect=pEffects; + pEffects = NULL; + try + { + pComp->Value(mkParAdapt(mkNamingPtrAdapt(pEffects, name), numbers)); + } + catch (...) + { + delete pNextOldEffect; + throw; + } + while ((pOldEffect=pNextOldEffect)) + { + pNextOldEffect = pOldEffect->pNext; + pOldEffect->Register(&pEffects, Abs(pOldEffect->iPriority)); + } +} + +void C4AulScriptEngine::CompileFunc(StdCompiler *pComp, bool fScenarioSection, C4ValueNumbers * numbers) +{ + if (!fScenarioSection) + { + assert(UserFiles.empty()); // user files must not be kept open + C4ValueMapData GlobalNamedDefault; + GlobalNamedDefault.SetNameList(&GlobalNamedNames); + pComp->Value(mkNamingAdapt(mkParAdapt(GlobalNamed, numbers), "StaticVariables", GlobalNamedDefault)); + pComp->Value(mkNamingAdapt(mkParAdapt(*GameScript.ScenPropList._getPropList(), numbers), "Scenario")); + } + if (fScenarioSection && pComp->isCompiler()) + { + // loading scenario section: Merge effects + // Must keep old effects here even if they're dead, because the LoadScenarioSection call typically came from execution of a global effect + // and otherwise dead pointers would remain on the stack + GlobalEffectsMergeCompileFunc(pComp, pGlobalEffects, "Effects", numbers); + GlobalEffectsMergeCompileFunc(pComp, GameScript.pScenarioEffects, "ScenarioEffects", numbers); + } + else + { + // Otherwise, just compile effects + pComp->Value(mkParAdapt(mkNamingPtrAdapt(pGlobalEffects, "Effects"), numbers)); + pComp->Value(mkParAdapt(mkNamingPtrAdapt(GameScript.pScenarioEffects, "ScenarioEffects"), numbers)); + } + pComp->Value(mkNamingAdapt(*numbers, "Values")); } std::list C4AulScriptEngine::GetFunctionNames(C4PropList * p) diff --git a/src/script/C4Aul.h b/src/script/C4Aul.h index e96556f40..220c6175a 100644 --- a/src/script/C4Aul.h +++ b/src/script/C4Aul.h @@ -46,10 +46,10 @@ class C4AulParseError : public C4AulError { C4AulParseError() = default; public: - C4AulParseError(C4ScriptHost *pScript, const char *pMsg, const char *pIdtf = NULL, bool Warn = false); + C4AulParseError(C4ScriptHost *pScript, const char *pMsg); // constructor + C4AulParseError(class C4AulParse * state, const char *pMsg); // constructor + C4AulParseError(C4AulScriptFunc * Fn, const char *SPos, const char *pMsg); static C4AulParseError FromSPos(const C4ScriptHost *host, const char *SPos, C4AulScriptFunc *Fn, const char *msg, const char *Idtf = nullptr, bool Warn = false); - // constructor - C4AulParseError(class C4AulParse * state, const char *pMsg, const char *pIdtf = NULL, bool Warn = false); // constructor }; // execution error @@ -124,6 +124,8 @@ public: C4ValueMapNames GlobalConstNames; C4ValueMapData GlobalConsts; + C4Effect * pGlobalEffects = NULL; + C4AulScriptEngine(); // constructor ~C4AulScriptEngine(); // destructor void Clear(); // clear data @@ -142,7 +144,7 @@ public: void UnLink(); // called when a script is being reloaded (clears string table) // Compile scenario script data (without strings and constants) - void CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers); + void CompileFunc(StdCompiler *pComp, bool fScenarioSection, C4ValueNumbers * numbers); // Handle user files int32_t CreateUserFile(); // create new file and return handle diff --git a/src/script/C4AulDefFunc.h b/src/script/C4AulDefFunc.h index 0ef61b297..6e3c2f276 100644 --- a/src/script/C4AulDefFunc.h +++ b/src/script/C4AulDefFunc.h @@ -37,6 +37,7 @@ inline C4Object * Object(C4PropList * _this) return _this ? _this->GetObject() : NULL; } StdStrBuf FnStringFormat(C4PropList * _this, C4String *szFormatPar, C4Value * Pars, int ParCount); +C4Effect ** FnGetEffectsFor(C4PropList * pTarget); // Nillable: Allow integer and boolean parameters to be nil // pointer parameters represent nil via plain NULL @@ -49,7 +50,7 @@ class Nillable bool _nil; T _val; public: - inline Nillable(const T &value) : _nil(!value && !C4Value::IsNullableType(C4ValueConv::Type())), _val(value) {} + inline Nillable(const T &value) : _nil(!value && !C4Value::IsNullableType(C4ValueConv::Type)), _val(value) {} inline Nillable() : _nil(true), _val(T()) {} inline Nillable(std::nullptr_t) : _nil(true), _val(T()) {} template inline Nillable(const Nillable & n2) : _nil(n2._nil), _val(n2._val) {} @@ -59,7 +60,7 @@ public: inline Nillable &operator =(const T &val) { _val = val; - _nil = !val && !C4Value::IsNullableType(C4ValueConv::Type()); + _nil = !val && !C4Value::IsNullableType(C4ValueConv::Type); return *this; } inline Nillable &operator =(const Nillable &val) @@ -140,71 +141,71 @@ template struct C4ValueConv > { inline static Nillable _FromC4V(C4Value &v) { if (v.GetType() == C4V_Nil) return C4Void(); else return C4ValueConv::_FromC4V(v); } - inline static C4V_Type Type() { return C4ValueConv::Type(); } + static constexpr C4V_Type Type = C4ValueConv::Type; }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Nil; } + static constexpr C4V_Type Type = C4V_Nil; }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Int; } + static constexpr C4V_Type Type = C4V_Int; inline static int _FromC4V(C4Value &v) { return v._getInt(); } }; template <> struct C4ValueConv: public C4ValueConv { }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Bool; } + static constexpr C4V_Type Type = C4V_Bool; inline static bool _FromC4V(C4Value &v) { return v._getBool(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_PropList; } + static constexpr C4V_Type Type = C4V_PropList; inline static C4ID _FromC4V(C4Value &v) { C4Def * def = v.getDef(); return def ? def->id : C4ID::None; } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Object; } + static constexpr C4V_Type Type = C4V_Object; inline static C4Object *_FromC4V(C4Value &v) { return v._getObj(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_String; } + static constexpr C4V_Type Type = C4V_String; inline static C4String *_FromC4V(C4Value &v) { return v._getStr(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Array; } + static constexpr C4V_Type Type = C4V_Array; inline static C4ValueArray *_FromC4V(C4Value &v) { return v._getArray(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Function; } + static constexpr C4V_Type Type = C4V_Function; inline static C4AulFunc *_FromC4V(C4Value &v) { return v._getFunction(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_PropList; } + static constexpr C4V_Type Type = C4V_PropList; inline static C4PropList *_FromC4V(C4Value &v) { return v._getPropList(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Effect; } + static constexpr C4V_Type Type = C4V_Effect; inline static C4Effect *_FromC4V(C4Value &v) { C4PropList * p = v._getPropList(); return p ? p->GetEffect() : 0; } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Def; } + static constexpr C4V_Type Type = C4V_Def; inline static C4Def *_FromC4V(C4Value &v) { return v._getDef(); } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Any; } + static constexpr C4V_Type Type = C4V_Any; inline static const C4Value &_FromC4V(C4Value &v) { return v; } }; template <> struct C4ValueConv { - inline static C4V_Type Type() { return C4V_Any; } + static constexpr C4V_Type Type = C4V_Any; inline static C4Value _FromC4V(C4Value &v) { return v; } }; @@ -218,7 +219,7 @@ public: C4AulEngineFunc(C4PropListStatic * Parent, const char *pName, Func pFunc, bool Public): C4AulFunc(Parent, pName), - pFunc(pFunc), ParType {C4ValueConv::Type()...}, Public(Public) + pFunc(pFunc), ParType {C4ValueConv::Type...}, Public(Public) { Parent->SetPropertyByS(Name, C4VFunction(this)); for(int i = GetParCount(); i < C4AUL_MAX_Par; ++i) @@ -237,7 +238,7 @@ public: virtual C4V_Type GetRetType() const { - return C4ValueConv::Type(); + return C4ValueConv::Type; } virtual bool GetPublic() const diff --git a/src/script/C4AulLink.cpp b/src/script/C4AulLink.cpp index e7316b2f0..6dffb03ab 100644 --- a/src/script/C4AulLink.cpp +++ b/src/script/C4AulLink.cpp @@ -20,6 +20,7 @@ #include "object/C4Def.h" #include "object/C4DefList.h" +#include "script/C4Effect.h" #include "landscape/C4Material.h" #include "game/C4Game.h" #include "object/C4GameObjects.h" @@ -189,6 +190,10 @@ void C4AulScriptEngine::ReLink(C4DefList *rDefs) // display state LogF("C4AulScriptEngine linked - %d line%s, %d warning%s, %d error%s", lineCnt, (lineCnt != 1 ? "s" : ""), warnCnt, (warnCnt != 1 ? "s" : ""), errCnt, (errCnt != 1 ? "s" : "")); + + // adjust global effects + if (pGlobalEffects) pGlobalEffects->ReAssignAllCallbackFunctions(); + if (GameScript.pScenarioEffects) GameScript.pScenarioEffects->ReAssignAllCallbackFunctions(); } bool C4AulScriptEngine::ReloadScript(const char *szScript, const char *szLanguage) diff --git a/src/script/C4AulParse.cpp b/src/script/C4AulParse.cpp index cb1c76cdf..a32a5264c 100644 --- a/src/script/C4AulParse.cpp +++ b/src/script/C4AulParse.cpp @@ -139,7 +139,7 @@ C4AulParse::C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulS C4AulParse::~C4AulParse() { - while (pLoopStack) PopLoop(); + while (pLoopStack) PopLoop(0); ClearToken(); } @@ -147,11 +147,10 @@ void C4ScriptHost::Warn(const char *pMsg, ...) { va_list args; va_start(args, pMsg); StdStrBuf Buf; - Buf.FormatV(pMsg, args); - - C4AulParseError warning(this, Buf.getData(), 0, true); - // display it - warning.show(); + Buf.Ref("WARNING: "); + Buf.AppendFormatV(pMsg, args); + Buf.AppendFormat(" (%s)", ScriptName.getData()); + DebugLog(Buf.getData()); // count warnings ++Engine->warnCnt; } @@ -160,12 +159,11 @@ void C4AulParse::Warn(const char *pMsg, ...) { va_list args; va_start(args, pMsg); StdStrBuf Buf; - Buf.FormatV(pMsg, args); + Buf.Ref("WARNING: "); + Buf.AppendFormatV(pMsg, args); + AppendPosition(Buf); + DebugLog(Buf.getData()); - C4AulParseError warning(this, Buf.getData(), 0, true); - warning.show(); - if (pOrgScript != Host) - DebugLogF(" (as #appendto/#include to %s)", Host->ScriptName.getData()); // count warnings ++Engine->warnCnt; } @@ -208,49 +206,49 @@ C4AulParseError C4AulParseError::FromSPos(const C4ScriptHost *host, const char * return e; } -C4AulParseError::C4AulParseError(C4AulParse * state, const char *pMsg, const char *pIdtf, bool Warn) +void C4AulParse::AppendPosition(StdStrBuf & Buf) +{ + if (Fn && Fn->GetName()) + { + // Show function name + Buf.AppendFormat(" (in %s", Fn->GetName()); + + // Exact position + if (Fn->pOrgScript && TokenSPos) + Buf.AppendFormat(", %s:%d:%d)", + Fn->pOrgScript->ScriptName.getData(), + SGetLine(Fn->pOrgScript->GetScript(), TokenSPos), + SLineGetCharacters(Fn->pOrgScript->GetScript(), TokenSPos)); + else + Buf.AppendChar(')'); + } + else if (pOrgScript) + { + // Script name + Buf.AppendFormat(" (%s:%d:%d)", + pOrgScript->ScriptName.getData(), + SGetLine(pOrgScript->GetScript(), TokenSPos), + SLineGetCharacters(pOrgScript->GetScript(), TokenSPos)); + } + // show a warning if the error is in a remote script + if (pOrgScript != Host && Host) + Buf.AppendFormat(" (as #appendto/#include to %s)", Host->ScriptName.getData()); +} + +C4AulParseError::C4AulParseError(C4AulParse * state, const char *pMsg) : C4AulError() { // compose error string - sMessage.Format("%s: %s%s", - Warn ? "WARNING" : "ERROR", - pMsg, - pIdtf ? pIdtf : ""); - if (state->Fn && state->Fn->GetName()) - { - // Show function name - sMessage.AppendFormat(" (in %s", state->Fn->GetName()); - - // Exact position - if (state->Fn->pOrgScript && state->TokenSPos) - sMessage.AppendFormat(", %s:%d:%d)", - state->Fn->pOrgScript->ScriptName.getData(), - SGetLine(state->Fn->pOrgScript->GetScript(), state->TokenSPos), - SLineGetCharacters(state->Fn->pOrgScript->GetScript(), state->TokenSPos)); - else - sMessage.AppendChar(')'); - } - else if (state->pOrgScript) - { - // Script name - sMessage.AppendFormat(" (%s:%d:%d)", - state->pOrgScript->ScriptName.getData(), - SGetLine(state->pOrgScript->GetScript(), state->TokenSPos), - SLineGetCharacters(state->pOrgScript->GetScript(), state->TokenSPos)); - } - // show a warning if the error is in a remote script - if (state->pOrgScript != state->Host && state->Host) - sMessage.AppendFormat(" (as #appendto/#include to %s)", state->Host->ScriptName.getData()); - + sMessage.Ref("ERROR: "); + sMessage.Append(pMsg); + state->AppendPosition(sMessage); } -C4AulParseError::C4AulParseError(C4ScriptHost *pScript, const char *pMsg, const char *pIdtf, bool Warn) +C4AulParseError::C4AulParseError(C4ScriptHost *pScript, const char *pMsg) { // compose error string - sMessage.Format("%s: %s%s", - Warn ? "WARNING" : "ERROR", - pMsg, - pIdtf ? pIdtf : ""); + sMessage.Ref("ERROR: "); + sMessage.Append(pMsg); if (pScript) { // Script name @@ -259,6 +257,29 @@ C4AulParseError::C4AulParseError(C4ScriptHost *pScript, const char *pMsg, const } } +C4AulParseError::C4AulParseError(C4AulScriptFunc * Fn, const char *SPos, const char *pMsg) + : C4AulError() +{ + // compose error string + sMessage.Ref("ERROR: "); + sMessage.Append(pMsg); + if (!Fn) return; + sMessage.Append(" ("); + // Show function name + if (Fn->GetName()) + sMessage.AppendFormat("in %s", Fn->GetName()); + if (Fn->GetName() && Fn->pOrgScript && SPos) + sMessage.Append(", "); + // Exact position + if (Fn->pOrgScript && SPos) + sMessage.AppendFormat("%s:%d:%d)", + Fn->pOrgScript->ScriptName.getData(), + SGetLine(Fn->pOrgScript->GetScript(), SPos), + SLineGetCharacters(Fn->pOrgScript->GetScript(), SPos)); + else + sMessage.AppendChar(')'); +} + bool C4AulParse::AdvanceSpaces() { if (!SPos) @@ -990,6 +1011,15 @@ C4V_Type C4AulParse::GetLastRetType(C4V_Type to) case AB_Not: case AB_LessThan: case AB_LessThanEqual: case AB_GreaterThan: case AB_GreaterThanEqual: case AB_Equal: case AB_NotEqual: from = C4V_Bool; break; + case AB_DUP: + { + int pos = Fn->GetLastCode()->Par.i + iStack - 2 + Fn->VarNamed.iSize + Fn->GetParCount(); + if (pos < Fn->GetParCount()) + from = Fn->GetParType()[pos]; + else + from = C4V_Any; + break; + } default: from = C4V_Any; break; } @@ -998,7 +1028,7 @@ C4V_Type C4AulParse::GetLastRetType(C4V_Type to) C4AulBCC C4AulParse::MakeSetter(bool fLeaveValue) { - if(Type != PARSER) { C4AulBCC Dummy; Dummy.bccType = AB_ERR; return Dummy; } + if(Type != PARSER) { return C4AulBCC(AB_ERR, 0); } C4AulBCC Value = *(Fn->GetLastCode()), Setter = Value; // Check type switch (Value.bccType) @@ -1013,11 +1043,9 @@ C4AulBCC C4AulParse::MakeSetter(bool fLeaveValue) case AB_STACK_SET: Setter.bccType = AB_STACK_SET; break; case AB_LOCALN: Setter.bccType = AB_LOCALN_SET; - Setter.Par.s->IncRef(); // so string isn't dropped by RemoveLastBCC, see also C4AulScript::AddBCC break; case AB_PROP: Setter.bccType = AB_PROP_SET; - Setter.Par.s->IncRef(); // so string isn't dropped by RemoveLastBCC, see also C4AulScript::AddBCC break; case AB_GLOBALN: Setter.bccType = AB_GLOBALN_SET; break; default: @@ -1054,6 +1082,11 @@ C4AulBCC C4AulParse::MakeSetter(bool fLeaveValue) return Setter; } +int C4AulParse::AddVarAccess(C4AulBCCType eType, intptr_t varnum) +{ + return AddBCC(eType, 1 + varnum - (iStack + Fn->VarNamed.iSize)); +} + int C4AulParse::JumpHere() { // Set flag so the next generated code chunk won't get joined @@ -1101,9 +1134,15 @@ void C4AulParse::PushLoop() pLoopStack = pNew; } -void C4AulParse::PopLoop() +void C4AulParse::PopLoop(int ContinueJump) { if (Type != PARSER) return; + // Set targets for break/continue + for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next) + if (pCtrl->Break) + SetJumpHere(pCtrl->Pos); + else + SetJump(pCtrl->Pos, ContinueJump); // Delete loop controls Loop *pLoop = pLoopStack; while (pLoop->Controls) @@ -1122,11 +1161,15 @@ void C4AulParse::PopLoop() void C4AulParse::AddLoopControl(bool fBreak) { if (Type != PARSER) return; + // Insert code + if (pLoopStack->StackSize != iStack) + AddBCC(AB_STACK, pLoopStack->StackSize - iStack); Loop::Control *pNew = new Loop::Control(); pNew->Break = fBreak; pNew->Pos = Fn->GetCodePos(); pNew->Next = pLoopStack->Controls; pLoopStack->Controls = pNew; + AddBCC(AB_JUMP); } const char * C4AulParse::GetTokenName(C4AulTokenType TokenType) @@ -1260,7 +1303,7 @@ void C4AulParse::Parse_Script(C4ScriptHost * scripthost) } else // -> unknown directive - throw C4AulParseError(this, "unknown directive: ", Idtf); + Error("unknown directive: %s", Idtf); break; case ATT_IDTF: // need a keyword here to avoid parsing random function contents @@ -1330,7 +1373,7 @@ void C4AulParse::Parse_Function() // check for func declaration if (!SEqual(Idtf, C4AUL_Func)) - throw C4AulParseError(this, "Declaration expected, but found identifier ", Idtf); + Error("Declaration expected, but found identifier: %s", Idtf); Shift(); // get next token, must be func name Check(ATT_IDTF, "function name"); @@ -1343,7 +1386,7 @@ void C4AulParse::Parse_Function() if (is_global || !Host->GetPropList()) { if (Host != pOrgScript) - throw C4AulParseError(this, "global func in appendto/included script: ", Idtf); + Error("global func in appendto/included script: %s", Idtf); if (Engine->GlobalNamedNames.GetItemNr(Idtf) != -1) throw C4AulParseError(this, "function definition: name already in use (global variable)"); if (Engine->GlobalConstNames.GetItemNr(Idtf) != -1) @@ -1601,11 +1644,7 @@ void C4AulParse::Parse_Statement() } else { - // Insert code - if (pLoopStack->StackSize != iStack) - AddBCC(AB_STACK, pLoopStack->StackSize - iStack); AddLoopControl(true); - AddBCC(AB_JUMP); } } } @@ -1621,11 +1660,7 @@ void C4AulParse::Parse_Statement() } else { - // Insert code - if (pLoopStack->StackSize != iStack) - AddBCC(AB_STACK, pLoopStack->StackSize - iStack); AddLoopControl(false); - AddBCC(AB_JUMP); } } } @@ -1668,7 +1703,7 @@ int C4AulParse::Parse_Params(int iMaxCnt, const char * sWarn, C4AulFunc * pFunc) { if (size >= iMaxCnt) break; - AddBCC(AB_DUP, 1 + i - (iStack + Fn->VarNamed.iSize + Fn->GetParCount())); + AddVarAccess(AB_DUP, i - Fn->GetParCount()); ++size; } // Do not allow more parameters even if there is space left @@ -1889,13 +1924,7 @@ void C4AulParse::Parse_DoWhile() // Jump back AddJump(AB_COND, Start); if (Type != PARSER) return; - // Set targets for break/continue - for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next) - if (pCtrl->Break) - SetJumpHere(pCtrl->Pos); - else - SetJump(pCtrl->Pos, BeforeCond); - PopLoop(); + PopLoop(BeforeCond); } void C4AulParse::Parse_While() @@ -1918,13 +1947,7 @@ void C4AulParse::Parse_While() AddJump(AB_JUMP, iStart); // Set target for conditional jump SetJumpHere(iCond); - // Set targets for break/continue - for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next) - if (pCtrl->Break) - SetJumpHere(pCtrl->Pos); - else - SetJump(pCtrl->Pos, iStart); - PopLoop(); + PopLoop(iStart); } void C4AulParse::Parse_If() @@ -2016,13 +2039,7 @@ void C4AulParse::Parse_For() // Set target for condition if (iJumpOut != -1) SetJumpHere(iJumpOut); - // Set targets for break/continue - for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next) - if (pCtrl->Break) - SetJumpHere(pCtrl->Pos); - else - SetJump(pCtrl->Pos, iJumpBack); - PopLoop(); + PopLoop(iJumpBack); } void C4AulParse::Parse_ForEach() @@ -2052,7 +2069,7 @@ void C4AulParse::Parse_ForEach() // push initial position (0) AddBCC(AB_INT); // get array element - int iStart = AddBCC(AB_FOREACH_NEXT, 1 + iVarID - (iStack + Fn->VarNamed.iSize)); + int iStart = AddVarAccess(AB_FOREACH_NEXT, iVarID); // jump out (FOREACH_NEXT will jump over this if // we're not at the end of the array yet) int iCond = AddBCC(AB_JUMP); @@ -2065,13 +2082,7 @@ void C4AulParse::Parse_ForEach() AddJump(AB_JUMP, iStart); // set condition jump target SetJumpHere(iCond); - // set jump targets for break/continue - for (Loop::Control *pCtrl = pLoopStack->Controls; pCtrl; pCtrl = pCtrl->Next) - if (pCtrl->Break) - SetJumpHere(pCtrl->Pos); - else - SetJump(pCtrl->Pos, iStart); - PopLoop(); + PopLoop(iStart); // remove array and counter from stack AddBCC(AB_STACK, -2); } @@ -2096,14 +2107,14 @@ void C4AulParse::Parse_Expression(int iParentPrio) if (Fn->ParNamed.GetItemNr(Idtf) != -1) { // insert variable by id - AddBCC(AB_DUP, 1 + Fn->ParNamed.GetItemNr(Idtf) - (iStack + Fn->VarNamed.iSize + Fn->GetParCount())); + AddVarAccess(AB_DUP, Fn->ParNamed.GetItemNr(Idtf) - Fn->GetParCount()); Shift(); } // check for variable (var) else if (Fn->VarNamed.GetItemNr(Idtf) != -1) { // insert variable by id - AddBCC(AB_DUP, 1 + Fn->VarNamed.GetItemNr(Idtf) - (iStack + Fn->VarNamed.iSize)); + AddVarAccess(AB_DUP, Fn->VarNamed.GetItemNr(Idtf)); Shift(); } else if (ContextToExecIn && (ndx = ContextToExecIn->Func->ParNamed.GetItemNr(Idtf)) != -1) @@ -2275,7 +2286,7 @@ void C4AulParse::Parse_Expression(int iParentPrio) else { // identifier could not be resolved - throw C4AulParseError(this, "unknown identifier: ", Idtf); + Error("unknown identifier: %s", Idtf); } break; case ATT_INT: // constant in cInt @@ -2314,15 +2325,17 @@ void C4AulParse::Parse_Expression(int iParentPrio) Fn->GetLastCode()->Par.i = - Fn->GetLastCode()->Par.i; break; } - // changer? make a setter BCC, leave value for operator - C4AulBCC Changer; - if(op->Changer) - Changer = MakeSetter(true); - // write byte code - AddBCC(op->Code, 0); - // writter setter - if(op->Changer) - AddBCC(Changer.bccType, Changer.Par.X); + { + // changer? make a setter BCC, leave value for operator + C4AulBCC Changer; + if(op->Changer) + Changer = MakeSetter(true); + // write byte code + AddBCC(op->Code, 0); + // writter setter + if(op->Changer) + AddBCC(Changer.bccType, Changer.Par.X); + } break; case ATT_BOPEN: Shift(); @@ -2372,7 +2385,7 @@ void C4AulParse::Parse_Expression(int iParentPrio) // not found? if (!postfixop->Identifier) { - throw C4AulParseError(this, "unexpected prefix operator: ", op->Identifier); + Error("unexpected prefix operator: %s", op->Identifier); } // otherwise use the new-found correct postfix operator op = postfixop; @@ -2529,7 +2542,7 @@ void C4AulParse::Parse_Var() // insert initialization in byte code Shift(); Parse_Expression(); - AddBCC(AB_POP_TO, 1 + iVarID - (iStack + Fn->VarNamed.iSize)); + AddVarAccess(AB_POP_TO, iVarID); } if (TokenType == ATT_SCOLON) return; diff --git a/src/script/C4AulParse.h b/src/script/C4AulParse.h index f24eb3756..9d92eb6a9 100644 --- a/src/script/C4AulParse.h +++ b/src/script/C4AulParse.h @@ -92,12 +92,14 @@ private: void Warn(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O; void Error(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O; + void AppendPosition(StdStrBuf & Buf); bool fJump; int iStack; int GetStackValue(C4AulBCCType eType, intptr_t X = 0); int AddBCC(C4AulBCCType eType, intptr_t X = 0); + int AddVarAccess(C4AulBCCType eType, intptr_t varnum); void DebugChunk(); void RemoveLastBCC(); C4V_Type GetLastRetType(C4V_Type to); // for warning purposes @@ -126,9 +128,9 @@ private: Loop *pLoopStack; void PushLoop(); - void PopLoop(); + void PopLoop(int ContinueJump); void AddLoopControl(bool fBreak); friend class C4AulParseError; }; -#endif \ No newline at end of file +#endif diff --git a/src/script/C4AulScriptFunc.cpp b/src/script/C4AulScriptFunc.cpp index a80a69023..d54655608 100644 --- a/src/script/C4AulScriptFunc.cpp +++ b/src/script/C4AulScriptFunc.cpp @@ -62,38 +62,12 @@ void C4AulScriptFunc::SetOverloaded(C4AulFunc * f) void C4AulScriptFunc::AddBCC(C4AulBCCType eType, intptr_t X, const char * SPos) { // store chunk - C4AulBCC bcc; - bcc.bccType = eType; - bcc.Par.X = X; - Code.push_back(bcc); + Code.emplace_back(eType, X); PosForCode.push_back(SPos); - - switch (eType) - { - case AB_STRING: case AB_CALL: case AB_CALLFS: case AB_LOCALN: case AB_PROP: - /* case AB_LOCALN_SET/AB_PROP_SET: -- expected to already have a reference upon creation, see MakeSetter */ - bcc.Par.s->IncRef(); - break; - case AB_CARRAY: - bcc.Par.a->IncRef(); - break; - default: break; - } } void C4AulScriptFunc::RemoveLastBCC() { - C4AulBCC *pBCC = &Code.back(); - switch (pBCC->bccType) - { - case AB_STRING: case AB_CALL: case AB_CALLFS: case AB_LOCALN: case AB_LOCALN_SET: case AB_PROP: case AB_PROP_SET: - pBCC->Par.s->DecRef(); - break; - case AB_CARRAY: - pBCC->Par.a->DecRef(); - break; - default: break; - } Code.pop_back(); PosForCode.pop_back(); } diff --git a/src/script/C4AulScriptFunc.h b/src/script/C4AulScriptFunc.h index c22aa2d5c..6430b85df 100644 --- a/src/script/C4AulScriptFunc.h +++ b/src/script/C4AulScriptFunc.h @@ -93,18 +93,76 @@ enum C4AulBCCType : int }; // byte code chunk -struct C4AulBCC +class C4AulBCC { +public: C4AulBCCType bccType; // chunk type union { + intptr_t X; int32_t i; C4String * s; C4PropList * p; C4ValueArray * a; C4AulFunc * f; - intptr_t X; } Par; // extra info + C4AulBCC(): bccType(AB_ERR) { } + C4AulBCC(C4AulBCCType bccType, intptr_t X): bccType(bccType), Par{X} + { + IncRef(); + } + C4AulBCC(const C4AulBCC & from): C4AulBCC(from.bccType, from.Par.X) { } + C4AulBCC & operator = (const C4AulBCC & from) + { + DecRef(); + bccType = from.bccType; + Par = from.Par; + IncRef(); + return *this; + } + C4AulBCC(C4AulBCC && from): bccType(from.bccType), Par(from.Par) + { + from.bccType = AB_ERR; + } + C4AulBCC & operator = (C4AulBCC && from) + { + DecRef(); + bccType = from.bccType; + Par = from.Par; + from.bccType = AB_ERR; + return *this; + } + ~C4AulBCC() + { + DecRef(); + } +private: + void IncRef() + { + switch (bccType) + { + case AB_STRING: case AB_CALL: case AB_CALLFS: case AB_LOCALN: case AB_LOCALN_SET: case AB_PROP: case AB_PROP_SET: + Par.s->IncRef(); + break; + case AB_CARRAY: + Par.a->IncRef(); + break; + default: break; + } + } + void DecRef() + { + switch (bccType) + { + case AB_STRING: case AB_CALL: case AB_CALLFS: case AB_LOCALN: case AB_LOCALN_SET: case AB_PROP: case AB_PROP_SET: + Par.s->DecRef(); + break; + case AB_CARRAY: + Par.a->DecRef(); + break; + default: break; + } + } }; // script function class diff --git a/src/script/C4Effect.cpp b/src/script/C4Effect.cpp index 9aa5b6d49..5cccc8b11 100644 --- a/src/script/C4Effect.cpp +++ b/src/script/C4Effect.cpp @@ -21,11 +21,8 @@ #include "C4Include.h" #include "script/C4Effect.h" -#include "object/C4Def.h" -#include "object/C4DefList.h" -#include "object/C4Object.h" -#include "game/C4Game.h" #include "script/C4Aul.h" +#include "game/C4GameScript.h" void C4Effect::AssignCallbackFunctions() { @@ -41,37 +38,37 @@ void C4Effect::AssignCallbackFunctions() C4PropList * C4Effect::GetCallbackScript() { - C4Def *pDef; - if (CommandTarget) - { - // overwrite ID for sync safety in runtime join - idCommandTarget = CommandTarget->id; - return CommandTarget; - } - else if (idCommandTarget && (pDef=::Definitions.ID2Def(idCommandTarget))) - return pDef; - else - return ::ScriptEngine.GetPropList(); + return CommandTarget._getPropList(); } -C4Effect::C4Effect(C4Object *pForObj, C4String *szName, int32_t iPrio, int32_t iTimerInterval, C4Object *pCmdTarget, C4ID idCmdTarget, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4) +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 = pCmdTarget; - idCommandTarget = idCmdTarget; + CommandTarget.SetPropList(pCmdTarget); AcquireNumber(); - Register(pForObj, iPrio); + Register(ppEffectList, iPrio); // Set name and callback functions SetProperty(P_Name, C4VString(szName)); } -void C4Effect::Register(C4Object *pForObj, int32_t iPrio) +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); +} + +void C4Effect::Register(C4Effect **ppEffectList, int32_t iPrio) { // get effect target - C4Effect **ppEffectList = pForObj ? &pForObj->pEffects : &Game.pGlobalEffects; C4Effect *pCheck, *pPrev = *ppEffectList; if (pPrev && Abs(pPrev->iPriority) < iPrio) { @@ -89,18 +86,29 @@ void C4Effect::Register(C4Object *pForObj, int32_t iPrio) } } -C4Effect * C4Effect::New(C4Object * pForObj, C4String * szName, int32_t iPrio, int32_t iTimerInterval, C4Object * pCmdTarget, C4ID idCmdTarget, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4) +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) { - C4Effect * pEffect = new C4Effect(pForObj, szName, iPrio, iTimerInterval, pCmdTarget, idCmdTarget, rVal1, rVal2, rVal3, rVal4); // 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 && pEffect->pNext) + if (fRemoveUpper && pNext) { - C4Effect * pEffect2 = pEffect->pNext->Check(pForObj, szName->GetCStr(), iPrio, iTimerInterval, rVal1, rVal2, rVal3, rVal4); + C4Effect * pEffect2 = pNext->Check(pForObj, GetName(), iPrio, iInterval, rVal1, rVal2, rVal3, rVal4); if (pEffect2) { // effect denied (iResult = -1), added to an effect (iResult = Number of that effect) @@ -116,29 +124,37 @@ C4Effect * C4Effect::New(C4Object * pForObj, C4String * szName, int32_t iPrio, i // because that would cause a wrong initialization order // (hardly ever causing trouble, however...) C4Effect *pLastRemovedEffect=NULL; - if (fRemoveUpper && pEffect->pNext && pEffect->pFnStart) - pEffect->TempRemoveUpperEffects(pForObj, false, &pLastRemovedEffect); + C4AulFunc * pFn; + if (!GetCallbackScript()) + { + Call(P_Construction, &C4AulParSet(pForObj, rVal1, rVal2, rVal3, rVal4)).getInt(); + if (pForObj && !pForObj->Status) return 0; + pFn = GetFunc(P_Start); + } + else + pFn = pFnStart; + if (fRemoveUpper && pNext && pFn) + TempRemoveUpperEffects(pForObj, false, &pLastRemovedEffect); // bad things may happen if (pForObj && !pForObj->Status) return 0; // this will be invalid! - pEffect->iPriority = iPrio; // validate effect now - if (pEffect->pFnStart) - if (pEffect->pFnStart->Exec(pCmdTarget, &C4AulParSet(C4VObj(pForObj), C4VPropList(pEffect), C4VInt(0), rVal1, rVal2, rVal3, rVal4)).getInt() == C4Fx_Start_Deny) - // the effect denied to start: assume it hasn't, and mark it dead - pEffect->SetDead(); - if (fRemoveUpper && pEffect->pNext && pEffect->pFnStart) - pEffect->TempReaddUpperEffects(pForObj, pLastRemovedEffect); + iPriority = iPrio; // validate effect now + if (CallStart(pForObj, 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(pForObj, pLastRemovedEffect); if (pForObj && !pForObj->Status) return 0; // this will be invalid! // Update OnFire cache - if (!pEffect->IsDead() && pForObj && WildcardMatch(C4Fx_AnyFire, szName->GetCStr())) + if (!IsDead() && pForObj && WildcardMatch(C4Fx_AnyFire, GetName())) pForObj->SetOnFire(true); - return pEffect; + return this; } C4Effect::C4Effect() { // defaults iPriority=iTime=iInterval=0; - CommandTarget=NULL; + CommandTarget.Set0(); pNext = NULL; } @@ -161,7 +177,7 @@ void C4Effect::Denumerate(C4ValueNumbers * numbers) do { // command target - pEff->CommandTarget.DenumeratePointers(); + pEff->CommandTarget.Denumerate(numbers); // assign any callback functions pEff->AssignCallbackFunctions(); pEff->C4PropList::Denumerate(numbers); @@ -169,16 +185,16 @@ void C4Effect::Denumerate(C4ValueNumbers * numbers) while ((pEff=pEff->pNext)); } -void C4Effect::ClearPointers(C4Object *pObj) +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 == pObj) + if (pEff->CommandTarget.getPropList() == pObj) { pEff->SetDead(); - pEff->CommandTarget=NULL; + pEff->CommandTarget.Set0(); } while ((pEff=pEff->pNext)); } @@ -222,7 +238,7 @@ int32_t C4Effect::GetCount(const char *szMask, int32_t iMaxPriority) return iCnt; } -C4Effect* C4Effect::Check(C4Object *pForObj, const char *szCheckEffect, int32_t iPrio, int32_t iTimer, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4) +C4Effect* C4Effect::Check(C4PropList *pForObj, 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 0; @@ -231,9 +247,9 @@ C4Effect* C4Effect::Check(C4Object *pForObj, const char *szCheckEffect, int32_t C4Effect *pLastRemovedEffect=NULL; for (C4Effect *pCheck = this; pCheck; pCheck = pCheck->pNext) { - if (!pCheck->IsDead() && pCheck->pFnEffect && pCheck->iPriority >= iPrio) + if (!pCheck->IsDead() && pCheck->iPriority >= iPrio) { - int32_t iResult = pCheck->pFnEffect->Exec(pCheck->CommandTarget, &C4AulParSet(C4VString(szCheckEffect), C4VObj(pForObj), C4VPropList(pCheck), rVal1, rVal2, rVal3, rVal4)).getInt(); + int32_t iResult = pCheck->CallEffect(szCheckEffect, pForObj, rVal1, rVal2, rVal3, rVal4); if (iResult == C4Fx_Effect_Deny) // effect denied return (C4Effect*)C4Fx_Effect_Deny; @@ -270,13 +286,12 @@ C4Effect* C4Effect::Check(C4Object *pForObj, const char *szCheckEffect, int32_t return 0; } -void C4Effect::Execute(C4Object *pObj) +void C4Effect::Execute(C4PropList *pObj, C4Effect **ppEffectList) { // get effect list - C4Effect **ppEffectList = pObj ? &pObj->pEffects : &Game.pGlobalEffects; // execute all effects not marked as dead - C4Effect *pEffect = this, **ppPrevEffect=ppEffectList; - do + C4Effect *pEffect = *ppEffectList, **ppPrevEffect=ppEffectList; + while (pEffect) { // effect dead? if (pEffect->IsDead()) @@ -295,31 +310,24 @@ void C4Effect::Execute(C4Object *pObj) // check timer execution if (pEffect->iInterval && !(pEffect->iTime % pEffect->iInterval)) { - if (pEffect->pFnTimer) + if (pEffect->CallTimer(pObj, pEffect->iTime) == C4Fx_Execute_Kill) { - if (pEffect->pFnTimer->Exec(pEffect->CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(pEffect), C4VInt(pEffect->iTime))).getInt() == C4Fx_Execute_Kill) - { - // safety: this class got deleted! - if (pObj && !pObj->Status) return; - // timer function decided to finish it - pEffect->Kill(pObj); - } // safety: this class got deleted! if (pObj && !pObj->Status) return; - } - else - // no timer function: mark dead after time elapsed + // timer function decided to finish it pEffect->Kill(pObj); + } + // safety: this class got deleted! + if (pObj && !pObj->Status) return; } // next effect ppPrevEffect = &pEffect->pNext; pEffect = pEffect->pNext; } } - while (pEffect); } -void C4Effect::Kill(C4Object *pObj) +void C4Effect::Kill(C4PropList *pObj) { // active? C4Effect *pLastRemovedEffect=NULL; @@ -329,22 +337,23 @@ void C4Effect::Kill(C4Object *pObj) 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 (pFnStart && iPriority!=1) pFnStart->Exec(CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(this), C4VInt(C4FxCall_TempAddForRemoval))); + if (iPriority!=1) CallStart(pObj, C4FxCall_TempAddForRemoval, C4Value(), C4Value(), C4Value(), C4Value()); // remove this effect int32_t iPrevPrio = iPriority; SetDead(); - if (pFnStop) - if (pFnStop->Exec(CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(this), C4VInt(C4FxCall_Normal))).getInt() == C4Fx_Stop_Deny) - // effect denied to be removed: recover - iPriority = iPrevPrio; + if (CallStop(pObj, C4FxCall_Normal, false) == C4Fx_Stop_Deny) + // effect denied to be removed: recover + iPriority = iPrevPrio; // reactivate other effects TempReaddUpperEffects(pObj, pLastRemovedEffect); // Update OnFire cache if (pObj && WildcardMatch(C4Fx_AnyFire, GetName())) if (!Get(C4Fx_AnyFire)) pObj->SetOnFire(false); + if (IsDead() && !GetCallbackScript()) + Call(P_Destruction, &C4AulParSet(pObj, C4FxCall_Normal)); } -void C4Effect::ClearAll(C4Object *pObj, int32_t iClearFlag) +void C4Effect::ClearAll(C4PropList *pObj, 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, @@ -353,45 +362,93 @@ void C4Effect::ClearAll(C4Object *pObj, int32_t iClearFlag) if ((pObj && !pObj->Status) || IsDead()) return; int32_t iPrevPrio = iPriority; SetDead(); - if (pFnStop) - if (pFnStop->Exec(CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(this), C4VInt(iClearFlag))).getInt() == 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 (pObj && !pObj->Status) return; - // effect denied to be removed: recover it - iPriority = iPrevPrio; - } + if (CallStop(pObj, 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 (pObj && !pObj->Status) return; + // effect denied to be removed: recover it + iPriority = iPrevPrio; + } // Update OnFire cache if (pObj && WildcardMatch(C4Fx_AnyFire, GetName()) && IsDead()) if (!Get(C4Fx_AnyFire)) pObj->SetOnFire(false); + if (IsDead() && !GetCallbackScript()) + Call(P_Destruction, &C4AulParSet(pObj, iClearFlag)); } -void C4Effect::DoDamage(C4Object *pObj, int32_t &riDamage, int32_t iDamageType, int32_t iCausePlr) +void C4Effect::DoDamage(C4PropList *pObj, int32_t &riDamage, int32_t iDamageType, int32_t iCausePlr) { // ask all effects for damage adjustments C4Effect *pEff = this; do { - if (!pEff->IsDead() && pEff->pFnDamage) - riDamage = pEff->pFnDamage->Exec(pEff->CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(pEff), C4VInt(riDamage), C4VInt(iDamageType), C4VInt(iCausePlr))).getInt(); + if (!pEff->IsDead()) + pEff->CallDamage(pObj, riDamage, iDamageType, iCausePlr); if (pObj && !pObj->Status) return; } while ((pEff = pEff->pNext) && riDamage); } -C4Value C4Effect::DoCall(C4Object *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) +static C4Object * Obj(C4PropList * p) { return p ? p->GetObject() : NULL; } + +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) { - // def script or global only? C4PropList *p = GetCallbackScript(); + if (!p) return Call(szFn, &C4AulParSet(pObj, rVal1, rVal2, rVal3, rVal4, rVal5, rVal6, rVal7)); + // old variant // compose function name char fn[C4AUL_MAX_Identifier+1]; sprintf(fn, PSF_FxCustom, GetName(), szFn); - return p->Call(fn, &C4AulParSet(pObj, this, rVal1, rVal2, rVal3, rVal4, rVal5, rVal6, rVal7)); + return p->Call(fn, &C4AulParSet(Obj(pObj), this, rVal1, rVal2, rVal3, rVal4, rVal5, rVal6, rVal7)); } -void C4Effect::OnObjectChangedDef(C4Object *pObj) +int C4Effect::CallStart(C4PropList * obj, int temporary, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4) +{ + if (!GetCallbackScript()) + return Call(P_Start, &C4AulParSet(obj, temporary, var1, var2, var3, var4)).getInt(); + if (pFnStart) + return pFnStart->Exec(GetCallbackScript(), &C4AulParSet(Obj(obj), this, temporary, var1, var2, var3, var4)).getInt(); + return C4Fx_OK; +} +int C4Effect::CallStop(C4PropList * obj, int reason, bool temporary) +{ + if (!GetCallbackScript()) + return Call(P_Stop, &C4AulParSet(obj, reason, temporary)).getInt(); + if (pFnStop) + return pFnStop->Exec(GetCallbackScript(), &C4AulParSet(Obj(obj), this, reason, temporary)).getInt(); + return C4Fx_OK; +} +int C4Effect::CallTimer(C4PropList * obj, int time) +{ + if (!GetCallbackScript()) + return Call(P_Timer, &C4AulParSet(obj, time)).getInt(); + if (pFnTimer) + return pFnTimer->Exec(GetCallbackScript(), &C4AulParSet(Obj(obj), this, time)).getInt(); + return C4Fx_Execute_Kill; +} +void C4Effect::CallDamage(C4PropList * obj, int32_t & damage, int damagetype, int plr) +{ + if (!GetCallbackScript()) + { + C4AulFunc *pFn = GetFunc(P_Damage); + if (pFn) + damage = pFn->Exec(this, &C4AulParSet(obj, damage, damagetype, plr)).getInt(); + } + else if (pFnDamage) + damage = pFnDamage->Exec(GetCallbackScript(), &C4AulParSet(Obj(obj), this, damage, damagetype, plr)).getInt(); +} +int C4Effect::CallEffect(const char * effect, C4PropList * obj, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4) +{ + if (!GetCallbackScript()) + return Call(P_Effect, &C4AulParSet(effect, obj, var1, var2, var3, var4)).getInt(); + if (pFnEffect) + return pFnEffect->Exec(GetCallbackScript(), &C4AulParSet(effect, Obj(obj), this, var1, var2, var3, var4)).getInt(); + return C4Fx_OK; +} + +void C4Effect::OnObjectChangedDef(C4PropList *pObj) { // safety if (!pObj) return; @@ -399,13 +456,13 @@ void C4Effect::OnObjectChangedDef(C4Object *pObj) C4Effect *pCheck = this; while (pCheck) { - if (pCheck->CommandTarget == pObj) + if (pCheck->GetCallbackScript() == pObj) pCheck->ReAssignCallbackFunctions(); pCheck = pCheck->pNext; } } -void C4Effect::TempRemoveUpperEffects(C4Object *pObj, bool fTempRemoveThis, C4Effect **ppLastRemovedEffect) +void C4Effect::TempRemoveUpperEffects(C4PropList *pObj, bool fTempRemoveThis, C4Effect **ppLastRemovedEffect) { if (pObj && !pObj->Status) return; // this will be invalid! // priority=1: no callbacks @@ -425,12 +482,12 @@ void C4Effect::TempRemoveUpperEffects(C4Object *pObj, bool fTempRemoveThis, C4Ef if (!Get(C4Fx_AnyFire)) pObj->SetOnFire(false); // temp callbacks only for higher priority effects - if (pFnStop && iPriority!=1) pFnStop->Exec(CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(this), C4VInt(C4FxCall_Temp), C4VBool(true))); + if (iPriority!=1) CallStop(pObj, C4FxCall_Temp, true); if (!*ppLastRemovedEffect) *ppLastRemovedEffect = this; } } -void C4Effect::TempReaddUpperEffects(C4Object *pObj, C4Effect *pLastReaddEffect) +void C4Effect::TempReaddUpperEffects(C4PropList *pObj, C4Effect *pLastReaddEffect) { // nothing to do? - this will also happen if TempRemoveUpperEffects did nothing due to priority==1 if (!pLastReaddEffect) return; @@ -441,7 +498,7 @@ void C4Effect::TempReaddUpperEffects(C4Object *pObj, C4Effect *pLastReaddEffect) if (pEff->IsInactiveAndNotDead()) { pEff->FlipActive(); - if (pEff->pFnStart && pEff->iPriority!=1) pEff->pFnStart->Exec(pEff->CommandTarget, &C4AulParSet(C4VObj(pObj), C4VPropList(pEff), C4VInt(C4FxCall_Temp))); + if (pEff->iPriority!=1) pEff->CallStart(pObj, C4FxCall_Temp, C4Value(), C4Value(), C4Value(), C4Value()); if (pObj && WildcardMatch(C4Fx_AnyFire, pEff->GetName())) pObj->SetOnFire(true); } @@ -460,9 +517,34 @@ void C4Effect::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) pComp->Value(iTime); pComp->Separator(); pComp->Value(iInterval); pComp->Separator(); // read object number - pComp->Value(CommandTarget); pComp->Separator(); + // FIXME: replace with this when savegame compat breaks for other reasons + // pComp->Value(mkParAdapt(CommandTarget, numbers)); + int32_t nptr = 0; + if (!pComp->isCompiler() && CommandTarget.getPropList() && CommandTarget._getPropList()->GetPropListNumbered()) + nptr = CommandTarget._getPropList()->GetPropListNumbered()->Number; + pComp->Value(nptr); + if (pComp->isCompiler()) + CommandTarget.SetObjectEnum(nptr); + pComp->Separator(); // read ID - pComp->Value(idCommandTarget); pComp->Separator(); + if (pComp->isDecompiler()) + { + const C4PropListStatic * p = CommandTarget.getPropList()->IsStatic(); + if (p) + p->RefCompileFunc(pComp, numbers); + else + pComp->String(const_cast("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); // ')' @@ -536,16 +618,7 @@ bool C4Effect::GetPropertyByS(C4String *k, C4Value *pResult) const 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: - if (CommandTarget) - *pResult = C4VObj(CommandTarget); - else if (idCommandTarget) - *pResult = C4VPropList(Definitions.ID2Def(idCommandTarget)); - else - *pResult = C4VNull; - //*pResult = CommandTarget ? C4VObj(CommandTarget) : - // (idCommandTarget ? C4VPropList(Definitions.ID2Def(idCommandTarget)) : C4VNull); - return true; + case P_CommandTarget: *pResult = CommandTarget; return true; case P_Time: *pResult = C4VInt(iTime); return true; } } diff --git a/src/script/C4Effect.h b/src/script/C4Effect.h index c246246d0..2562b2b18 100644 --- a/src/script/C4Effect.h +++ b/src/script/C4Effect.h @@ -24,7 +24,6 @@ #ifndef INC_C4Effects #define INC_C4Effects -#include "object/C4ObjectPtr.h" #include "script/C4PropList.h" // callback return values @@ -70,15 +69,13 @@ class C4Effect: public C4PropListNumbered { public: - C4ObjectPtr CommandTarget; // target object for script callbacks - if deleted, the effect is removed without callbacks - C4ID idCommandTarget; // ID of command target definition - int32_t iPriority; // effect priority for sorting into effect list; -1 indicates a dead effect int32_t iTime, iInterval; // effect time; effect callback intervall C4Effect *pNext; // next effect in linked list protected: + C4Value CommandTarget; // target object for script callbacks - if deleted, the effect is removed without callbacks // presearched callback functions for faster calling C4AulFunc *pFnTimer; // timer function Fx%sTimer C4AulFunc *pFnStart, *pFnStop; // init/deinit-functions Fx%sStart, Fx%sStop @@ -87,17 +84,26 @@ protected: void AssignCallbackFunctions(); // resolve callback function names - C4Effect(C4Object * pForObj, C4String * szName, int32_t iPrio, int32_t iTimerInterval, C4Object * pCmdTarget, C4ID idCmdTarget, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4); + int CallStart(C4PropList * obj, int temporary, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4); + int CallStop(C4PropList * obj, int reason, bool temporary); + int CallTimer(C4PropList * obj, int time); + void CallDamage(C4PropList * obj, int32_t & damage, int damagetype, int plr); + int CallEffect(const char * effect, C4PropList * obj, const C4Value &var1, const C4Value &var2, const C4Value &var3, const C4Value &var4); + + C4Effect(C4Effect **ppEffectList, C4String * szName, int32_t iPrio, int32_t iTimerInterval, C4PropList * pCmdTarget); + C4Effect(C4Effect **ppEffectList, C4PropList * prototype, int32_t iPrio, int32_t iTimerInterval); C4Effect(const C4Effect &); // unimplemented, do not use C4Effect(); // for the StdCompiler + C4Effect * Init(C4PropList *pForObj, int32_t iPrio, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4); friend void CompileNewFunc(C4Effect *&, StdCompiler *, C4ValueNumbers * const &); public: - static C4Effect * New(C4Object * pForObj, C4String * szName, int32_t iPrio, int32_t iTimerInterval, C4Object * pCmdTarget, C4ID idCmdTarget, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4); + static 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); + static 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(); // dtor - deletes all following effects - void Register(C4Object *pForObj, int32_t iPrio); // add into effect list of object or global effect list + void Register(C4Effect **ppEffectList, int32_t iPrio); // add into effect list of object or global effect list void Denumerate(C4ValueNumbers *); // numbers to object pointers - void ClearPointers(C4Object *pObj); // clear all pointers to object - may kill some effects w/o callback, because the callback target is lost + void ClearPointers(C4PropList *pObj); // clear all pointers to object - may kill some effects w/o callback, because the callback target is lost void SetDead() { iPriority=0; } // mark effect to be removed in next execution cycle bool IsDead() { return !iPriority; } // return whether effect is to be removed @@ -107,15 +113,15 @@ public: C4Effect *Get(const char *szName, int32_t iIndex=0, int32_t iMaxPriority=0); // get effect by name int32_t GetCount(const char *szMask, int32_t iMaxPriority=0); // count effects that match the mask - C4Effect *Check(C4Object *pForObj, const char *szCheckEffect, int32_t iPrio, int32_t iTimer, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4); // do some effect callbacks + C4Effect *Check(C4PropList *pForObj, const char *szCheckEffect, int32_t iPrio, int32_t iTimer, const C4Value &rVal1, const C4Value &rVal2, const C4Value &rVal3, const C4Value &rVal4); // do some effect callbacks C4PropList * GetCallbackScript(); // get script context for effect callbacks - void Execute(C4Object *pObj); // execute all effects - void Kill(C4Object *pObj); // mark this effect deleted and do approprioate calls - void ClearAll(C4Object *pObj, int32_t iClearFlag);// kill all effects doing removal calls w/o reagard of inactive effects - void DoDamage(C4Object *pObj, int32_t &riDamage, int32_t iDamageType, int32_t iCausePlr); // ask all effects for damage + static void Execute(C4PropList *pObj, C4Effect **ppEffectList); // execute all effects + void Kill(C4PropList *pObj); // mark this effect deleted and do approprioate calls + void ClearAll(C4PropList *pObj, int32_t iClearFlag);// kill all effects doing removal calls w/o reagard of inactive effects + void DoDamage(C4PropList *pObj, int32_t &riDamage, int32_t iDamageType, int32_t iCausePlr); // ask all effects for damage - C4Value DoCall(C4Object *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); // custom call + C4Value 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); // custom call void ReAssignCallbackFunctions() { AssignCallbackFunctions(); } @@ -124,7 +130,7 @@ public: ReAssignCallbackFunctions(); if (pNext) pNext->ReAssignAllCallbackFunctions(); } - void OnObjectChangedDef(C4Object *pObj); + void OnObjectChangedDef(C4PropList *pObj); void CompileFunc(StdCompiler *pComp, C4ValueNumbers *); virtual C4Effect * GetEffect() { return this; } @@ -134,8 +140,8 @@ public: virtual C4ValueArray * GetProperties() const; protected: - void TempRemoveUpperEffects(C4Object *pObj, bool fTempRemoveThis, C4Effect **ppLastRemovedEffect); // temp remove all effects with higher priority - void TempReaddUpperEffects(C4Object *pObj, C4Effect *pLastReaddEffect); // temp remove all effects with higher priority + void TempRemoveUpperEffects(C4PropList *pObj, bool fTempRemoveThis, C4Effect **ppLastRemovedEffect); // temp remove all effects with higher priority + void TempReaddUpperEffects(C4PropList *pObj, C4Effect *pLastReaddEffect); // temp remove all effects with higher priority }; // fire effect constants diff --git a/src/script/C4PropList.cpp b/src/script/C4PropList.cpp index e717cbc72..22b053edd 100644 --- a/src/script/C4PropList.cpp +++ b/src/script/C4PropList.cpp @@ -266,6 +266,14 @@ StdStrBuf C4PropListStatic::GetDataString() const return r; } +const char *C4PropListStatic::GetName() const +{ + const C4String * s = GetPropertyStr(P_Name); + if (!s) s = ParentKeyName; + if (!s) return ""; + return s->GetCStr(); +} + C4PropList::C4PropList(C4PropList * prototype): FirstRef(NULL), prototype(prototype), constant(false), Status(1) @@ -637,7 +645,17 @@ C4Value C4PropList::Call(const char * s, C4AulParSet *Pars, bool fPassErrors) if (!Status) return C4Value(); assert(s && s[0]); C4AulFunc *pFn = GetFunc(s); - if (!pFn) return C4Value(); + if (!pFn) + { + if (s[0] != '~') + { + C4AulExecError err(FormatString("Undefined function: %s", s).getData()); + if (fPassErrors) + throw err; + err.show(); + } + return C4Value(); + } return pFn->Exec(this, Pars, fPassErrors); } diff --git a/src/script/C4PropList.h b/src/script/C4PropList.h index c79ba9bc8..80548621f 100644 --- a/src/script/C4PropList.h +++ b/src/script/C4PropList.h @@ -66,8 +66,9 @@ class C4PropList { public: void Clear() { constant = false; Properties.Clear(); prototype.Set0(); } - const char *GetName() const; + virtual const char *GetName() const; virtual void SetName (const char *NewName = 0); + virtual void SetOnFire(bool OnFire) { } // These functions return this or a prototype. virtual C4Def const * GetDef() const; @@ -254,8 +255,9 @@ public: virtual C4PropListStatic * IsStatic() { return this; } void RefCompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) const; StdStrBuf GetDataString() const; + virtual const char *GetName() const; const C4PropListStatic * GetParent() { return Parent; } - const C4String * GetParentKeyName() { return ParentKeyName; } + C4String * GetParentKeyName() { return ParentKeyName; } protected: const C4PropListStatic * Parent; C4RefCntPointer ParentKeyName; // property in parent this proplist was created in diff --git a/src/script/C4Script.cpp b/src/script/C4Script.cpp index 483fcf5d3..b3c1cc9a0 100644 --- a/src/script/C4Script.cpp +++ b/src/script/C4Script.cpp @@ -251,6 +251,8 @@ static C4Value FnTrans_Mul(C4PropList * _this, C4Value *pars) #undef MAKE_AND_RETURN_ARRAY +/* PropLists */ + static C4PropList * FnCreatePropList(C4PropList * _this, C4PropList * prototype) { return C4PropList::New(prototype); @@ -298,6 +300,20 @@ static C4ValueArray * FnGetProperties(C4PropList * _this, C4PropList * p) return r; } +static C4PropList * FnGetPrototype(C4PropList * _this, C4PropList * p) +{ + if (!p) p = _this; + if (!p) throw NeedNonGlobalContext("GetPrototype"); + return p->GetPrototype(); +} + +static void FnSetPrototype(C4PropList * _this, C4PropList * prototype, C4PropList * p) +{ + if (!p) p = _this; + if (!p) throw NeedNonGlobalContext("GetPrototype"); + p->SetProperty(P_Prototype, C4Value(prototype)); +} + static C4Value FnCall(C4PropList * _this, C4Value * Pars) { if (!_this) _this = ::ScriptEngine.GetPropList(); @@ -325,6 +341,133 @@ static C4Value FnCall(C4PropList * _this, C4Value * Pars) return fn->Exec(_this, &ParSet, true); } +static C4String *FnGetName(C4PropList * _this, bool truename) +{ + if (!_this) + throw NeedNonGlobalContext("GetName"); + else if(truename) + return _this->IsStatic() ? _this->IsStatic()->GetParentKeyName() : nullptr; + else + return String(_this->GetName()); +} + +/* Effects */ + +static C4Value FnAddEffect(C4PropList * _this, C4String * szEffect, C4PropList * pTarget, + int iPrio, int iTimerInterval, C4PropList * pCmdTarget, C4Def * idCmdTarget, + const C4Value & Val1, const C4Value & Val2, const C4Value & Val3, const C4Value & Val4) +{ + // safety + if (pTarget && !pTarget->Status) return C4Value(); + if (!szEffect || !*szEffect->GetCStr() || !iPrio) return C4Value(); + // create effect + C4PropList * p = pCmdTarget; + if (!p) p = idCmdTarget; + if (!p) p = ::ScriptEngine.GetPropList(); + C4Effect * pEffect = C4Effect::New(pTarget, FnGetEffectsFor(pTarget), + szEffect, iPrio, iTimerInterval, p, Val1, Val2, Val3, Val4); + // return effect - may be 0 if the effect has been denied by another effect + if (!pEffect) return C4Value(); + return C4VPropList(pEffect); +} + +static C4Effect * FnCreateEffect(C4PropList * _this, C4PropList * prototype, int iPrio, int iTimerInterval, + const C4Value & Val1, const C4Value & Val2, const C4Value & Val3, const C4Value & Val4) +{ + if (!prototype || !(prototype->GetName()[0])) throw C4AulExecError("CreateEffect needs a prototype with a name"); + if (!iPrio) throw C4AulExecError("CreateEffect needs a nonzero priority"); + // create effect + C4Effect * pEffect = C4Effect::New(_this, FnGetEffectsFor(_this), prototype, iPrio, iTimerInterval, + Val1, Val2, Val3, Val4); + // return effect - may be 0 if the effect has been denied by another effect + return pEffect; +} + +static C4Effect * FnGetEffect(C4PropList * _this, C4String *psEffectName, C4PropList *pTarget, int index, int iMaxPriority) +{ + const char *szEffect = FnStringPar(psEffectName); + // get effects + C4Effect *pEffect = *FnGetEffectsFor(pTarget); + if (!pEffect) return NULL; + // name/wildcard given: find effect by name and index + if (szEffect && *szEffect) + return pEffect->Get(szEffect, index, iMaxPriority); + return NULL; +} + +static bool FnRemoveEffect(C4PropList * _this, C4String *psEffectName, C4PropList *pTarget, C4Effect * pEffect2, bool fDoNoCalls) +{ + // evaluate parameters + const char *szEffect = FnStringPar(psEffectName); + // if the user passed an effect, it can be used straight-away + C4Effect *pEffect = pEffect2; + // otherwise, the correct effect will be searched in the target's effects or in the global ones + if (!pEffect) + { + pEffect = *FnGetEffectsFor(pTarget); + // the object has no effects attached, nothing to look for + if (!pEffect) return 0; + // name/wildcard given: find effect by name + if (szEffect && *szEffect) + pEffect = pEffect->Get(szEffect, 0); + } + + // neither passed nor found - nothing to remove! + if (!pEffect) return 0; + + // kill it + if (fDoNoCalls) + pEffect->SetDead(); + else + pEffect->Kill(pTarget); + // done, success + return true; +} + +static C4Value FnCheckEffect(C4PropList * _this, C4String * psEffectName, C4PropList * pTarget, + int iPrio, int iTimerInterval, + const C4Value & Val1, const C4Value & Val2, const C4Value & Val3, const C4Value & Val4) +{ + const char *szEffect = FnStringPar(psEffectName); + // safety + if (pTarget && !pTarget->Status) return C4Value(); + if (!szEffect || !*szEffect) return C4Value(); + // get effects + C4Effect *pEffect = *FnGetEffectsFor(pTarget); + if (!pEffect) return C4Value(); + // let them check + C4Effect * r = pEffect->Check(pTarget, szEffect, iPrio, iTimerInterval, Val1, Val2, Val3, Val4); + if (r == (C4Effect *)C4Fx_Effect_Deny) return C4VInt(C4Fx_Effect_Deny); + if (r == (C4Effect *)C4Fx_Effect_Annul) return C4VInt(C4Fx_Effect_Annul); + return C4VPropList(r); +} + +static long FnGetEffectCount(C4PropList * _this, C4String *psEffectName, C4PropList *pTarget, long iMaxPriority) +{ + // evaluate parameters + const char *szEffect = FnStringPar(psEffectName); + // get effects + C4Effect *pEffect = *FnGetEffectsFor(pTarget); + if (!pEffect) return false; + // count effects + if (!*szEffect) szEffect = 0; + return pEffect->GetCount(szEffect, iMaxPriority); +} + +static C4Value FnEffectCall(C4PropList * _this, C4Value * Pars) +{ + // evaluate parameters + C4PropList *pTarget = Pars[0].getPropList(); + C4Effect * pEffect = Pars[1].getPropList() ? Pars[1].getPropList()->GetEffect() : 0; + const char *szCallFn = FnStringPar(Pars[2].getStr()); + // safety + if (pTarget && !pTarget->Status) return C4Value(); + if (!szCallFn || !*szCallFn) return C4Value(); + if (!pEffect) return C4Value(); + // do call + return pEffect->DoCall(pTarget, szCallFn, Pars[3], Pars[4], Pars[5], Pars[6], Pars[7], Pars[8], Pars[9]); +} + static C4Value FnLog(C4PropList * _this, C4Value * Pars) { Log(FnStringFormat(_this, Pars[0].getStr(), &Pars[1], 9).getData()); @@ -733,6 +876,33 @@ static bool FnFileWrite(C4PropList * _this, int32_t file_handle, C4String *data) C4ScriptConstDef C4ScriptConstMap[]= { + { "FX_OK" ,C4V_Int, C4Fx_OK }, // generic standard behaviour for all effect callbacks + { "FX_Effect_Deny" ,C4V_Int, C4Fx_Effect_Deny }, // delete effect + { "FX_Effect_Annul" ,C4V_Int, C4Fx_Effect_Annul }, // delete effect, because it has annulled a countereffect + { "FX_Effect_AnnulDoCalls" ,C4V_Int, C4Fx_Effect_AnnulCalls }, // delete effect, because it has annulled a countereffect; temp readd countereffect + { "FX_Execute_Kill" ,C4V_Int, C4Fx_Execute_Kill }, // execute callback: Remove effect now + { "FX_Stop_Deny" ,C4V_Int, C4Fx_Stop_Deny }, // deny effect removal + { "FX_Start_Deny" ,C4V_Int, C4Fx_Start_Deny }, // deny effect start + + { "FX_Call_Normal" ,C4V_Int, C4FxCall_Normal }, // normal call; effect is being added or removed + { "FX_Call_Temp" ,C4V_Int, C4FxCall_Temp }, // temp call; effect is being added or removed in responce to a lower-level effect change + { "FX_Call_TempAddForRemoval" ,C4V_Int, C4FxCall_TempAddForRemoval }, // temp call; effect is being added because it had been temp removed and is now removed forever + { "FX_Call_RemoveClear" ,C4V_Int, C4FxCall_RemoveClear }, // effect is being removed because object is being removed + { "FX_Call_RemoveDeath" ,C4V_Int, C4FxCall_RemoveDeath }, // effect is being removed because object died - return -1 to avoid removal + { "FX_Call_DmgScript" ,C4V_Int, C4FxCall_DmgScript }, // damage through script call + { "FX_Call_DmgBlast" ,C4V_Int, C4FxCall_DmgBlast }, // damage through blast + { "FX_Call_DmgFire" ,C4V_Int, C4FxCall_DmgFire }, // damage through fire + { "FX_Call_DmgChop" ,C4V_Int, C4FxCall_DmgChop }, // damage through chopping + { "FX_Call_Energy" ,C4V_Int, 32 }, // bitmask for generic energy loss + { "FX_Call_EngScript" ,C4V_Int, C4FxCall_EngScript }, // energy loss through script call + { "FX_Call_EngBlast" ,C4V_Int, C4FxCall_EngBlast }, // energy loss through blast + { "FX_Call_EngObjHit" ,C4V_Int, C4FxCall_EngObjHit }, // energy loss through object hitting the living + { "FX_Call_EngFire" ,C4V_Int, C4FxCall_EngFire }, // energy loss through fire + { "FX_Call_EngBaseRefresh" ,C4V_Int, C4FxCall_EngBaseRefresh }, // energy reload in base (also by base object, but that's normally not called) + { "FX_Call_EngAsphyxiation" ,C4V_Int, C4FxCall_EngAsphyxiation }, // energy loss through asphyxiaction + { "FX_Call_EngCorrosion" ,C4V_Int, C4FxCall_EngCorrosion }, // energy loss through corrosion (acid) + { "FX_Call_EngGetPunched" ,C4V_Int, C4FxCall_EngGetPunched }, // energy loss from punch + { "C4V_Nil", C4V_Int, C4V_Nil}, { "C4V_Int", C4V_Int, C4V_Int}, { "C4V_Bool", C4V_Int, C4V_Bool}, @@ -753,6 +923,7 @@ C4ScriptConstDef C4ScriptConstMap[]= C4ScriptFnDef C4ScriptFnMap[]= { { "Call", 1, C4V_Any, { C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnCall }, + { "EffectCall", 1, C4V_Any, { C4V_Object ,C4V_PropList,C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnEffectCall }, { "Log", 1, C4V_Bool, { C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnLog }, { "DebugLog", 1, C4V_Bool, { C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnDebugLog }, { "Format", 1, C4V_String, { C4V_String ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any ,C4V_Any}, FnFormat }, @@ -792,7 +963,16 @@ void InitCoreFunctionMap(C4AulScriptEngine *pEngine) F(GetProperties); F(GetProperty); F(SetProperty); + F(GetPrototype); + F(SetPrototype); F(ResetProperty); + F(GetName); + F(AddEffect); + F(CreateEffect); + F(CheckEffect); + F(RemoveEffect); + F(GetEffect); + F(GetEffectCount); F(Distance); F(Angle); F(GetChar); diff --git a/src/script/C4ScriptHost.cpp b/src/script/C4ScriptHost.cpp index 6511701d3..3fc4014f2 100644 --- a/src/script/C4ScriptHost.cpp +++ b/src/script/C4ScriptHost.cpp @@ -21,6 +21,7 @@ #include "script/C4ScriptHost.h" #include "object/C4Def.h" +#include "script/C4Effect.h" /*--- C4ScriptHost ---*/ @@ -48,7 +49,7 @@ C4ScriptHost::~C4ScriptHost() void C4ScriptHost::Clear() { - ComponentHost.Clear(); + C4ComponentHost::Clear(); Script.Clear(); LocalNamed.Reset(); LocalValues.Clear(); @@ -97,7 +98,7 @@ bool C4ScriptHost::Load(C4Group &hGroup, const char *szFilename, const char *szLanguage, class C4LangStringTable *pLocalTable) { // Base load - bool fSuccess = ComponentHost.Load(hGroup,szFilename,szLanguage); + bool fSuccess = C4ComponentHost::Load(hGroup,szFilename,szLanguage); // String Table if (stringTable != pLocalTable) { @@ -106,7 +107,7 @@ bool C4ScriptHost::Load(C4Group &hGroup, const char *szFilename, if (stringTable) stringTable->AddRef(); } // set name - ScriptName.Ref(ComponentHost.GetFilePath()); + ScriptName.Ref(GetFilePath()); // preparse script MakeScript(); // Success @@ -145,11 +146,11 @@ void C4ScriptHost::MakeScript() // create script if (stringTable) { - stringTable->ReplaceStrings(ComponentHost.GetDataBuf(), Script); + stringTable->ReplaceStrings(GetDataBuf(), Script); } else { - Script.Ref(ComponentHost.GetDataBuf()); + Script.Ref(GetDataBuf()); } // preparse script @@ -159,7 +160,7 @@ void C4ScriptHost::MakeScript() bool C4ScriptHost::ReloadScript(const char *szPath, const char *szLanguage) { // this? - if (SEqualNoCase(szPath, ComponentHost.GetFilePath()) || (stringTable && SEqualNoCase(szPath, stringTable->GetFilePath()))) + if (SEqualNoCase(szPath, GetFilePath()) || (stringTable && SEqualNoCase(szPath, stringTable->GetFilePath()))) { // try reload char szParentPath[_MAX_PATH + 1]; C4Group ParentGrp; @@ -300,6 +301,7 @@ void C4GameScriptHost::Clear() C4ScriptHost::Clear(); ScenPropList.Set0(); ScenPrototype.Set0(); + delete pScenarioEffects; pScenarioEffects=NULL; } C4PropListStatic * C4GameScriptHost::GetPropList() @@ -308,6 +310,12 @@ C4PropListStatic * C4GameScriptHost::GetPropList() return p ? p->IsStatic() : 0; } +void C4GameScriptHost::Denumerate(C4ValueNumbers * numbers) +{ + ScenPropList.Denumerate(numbers); + if (pScenarioEffects) pScenarioEffects->Denumerate(numbers); +} + C4Value C4GameScriptHost::Call(const char *szFunction, C4AulParSet *Pars, bool fPassError) { return ScenPropList._getPropList()->Call(szFunction, Pars, fPassError); diff --git a/src/script/C4ScriptHost.h b/src/script/C4ScriptHost.h index b4be7cf03..301c054a5 100644 --- a/src/script/C4ScriptHost.h +++ b/src/script/C4ScriptHost.h @@ -33,7 +33,7 @@ enum C4AulScriptState }; // generic script host for objects -class C4ScriptHost +class C4ScriptHost: public C4ComponentHost { public: virtual ~C4ScriptHost(); @@ -57,7 +57,6 @@ protected: void Unreg(); // remove from list void MakeScript(); virtual bool ReloadScript(const char *szPath, const char *szLanguage); - C4ComponentHost ComponentHost; bool Preparse(); // preparse script; return if successfull virtual bool Parse(); // parse preparsed script; return if successfull @@ -132,9 +131,11 @@ public: virtual bool LoadData(const char *, const char *, C4LangStringTable *); void Clear(); virtual C4PropListStatic * GetPropList(); + void Denumerate(C4ValueNumbers * numbers); C4Value Call(const char *szFunction, C4AulParSet *pPars=0, bool fPassError=false); C4Value ScenPropList; C4Value ScenPrototype; + C4Effect * pScenarioEffects = NULL; }; extern C4GameScriptHost GameScript; diff --git a/src/script/C4ScriptStandaloneStubs.cpp b/src/script/C4ScriptStandaloneStubs.cpp index cf405a860..c35998757 100644 --- a/src/script/C4ScriptStandaloneStubs.cpp +++ b/src/script/C4ScriptStandaloneStubs.cpp @@ -34,6 +34,17 @@ int32_t C4PropListNumbered::EnumerationIndex = 0; C4StringTable Strings; C4AulScriptEngine ScriptEngine; +/* Avoid a C4Object dependency */ +C4Effect ** FnGetEffectsFor(C4PropList * pTarget) +{ + if (pTarget == ScriptEngine.GetPropList()) + return &ScriptEngine.pGlobalEffects; + if (pTarget == GameScript.ScenPrototype.getPropList() || pTarget == GameScript.ScenPropList.getPropList()) + return &GameScript.pScenarioEffects; + if (pTarget) throw C4AulExecError("Only global and scenario effects are supported"); + return &ScriptEngine.pGlobalEffects; +} + /* Stubs */ C4Config Config; C4Config::C4Config() {} diff --git a/src/script/C4StringTable.cpp b/src/script/C4StringTable.cpp index f9f4ec2ba..981c00930 100644 --- a/src/script/C4StringTable.cpp +++ b/src/script/C4StringTable.cpp @@ -87,6 +87,13 @@ C4StringTable::C4StringTable() P[P_Interval] = "Interval"; P[P_CommandTarget] = "CommandTarget"; P[P_Time] = "Time"; + P[P_Construction] = "Construction"; + P[P_Destruction] = "Destruction"; + P[P_Start] = "Start"; + P[P_Stop] = "Stop"; + P[P_Timer] = "Timer"; + P[P_Effect] = "Effect"; + P[P_Damage] = "Damage"; P[P_Collectible] = "Collectible"; P[P_Touchable] = "Touchable"; P[P_ActMap] = "ActMap"; diff --git a/src/script/C4StringTable.h b/src/script/C4StringTable.h index d27447d3f..3fce599d8 100644 --- a/src/script/C4StringTable.h +++ b/src/script/C4StringTable.h @@ -294,6 +294,13 @@ enum C4PropertyName P_Interval, P_CommandTarget, P_Time, + P_Construction, + P_Destruction, + P_Start, + P_Stop, + P_Timer, + P_Effect, + P_Damage, P_Collectible, P_Touchable, P_ActMap, diff --git a/src/script/C4Value.h b/src/script/C4Value.h index d582cbe4c..6f37dc546 100644 --- a/src/script/C4Value.h +++ b/src/script/C4Value.h @@ -130,6 +130,7 @@ public: void SetArray(C4ValueArray * Array) { C4V_Data d; d.Array = Array; Set(d, C4V_Array); } void SetFunction(C4AulFunc * Fn) { C4V_Data d; d.Fn = Fn; Set(d, C4V_Function); } void SetPropList(C4PropList * PropList) { C4V_Data d; d.PropList = PropList; Set(d, C4V_PropList); } + void SetObjectEnum(int i) { C4V_Data d; d.Int = i; Set(d, C4V_C4ObjectEnum); } void Set0(); bool operator == (const C4Value& Value2) const; diff --git a/tests/aul/AulPredefinedFunctionTest.cpp b/tests/aul/AulPredefinedFunctionTest.cpp index cccf8bcd1..2109aad63 100644 --- a/tests/aul/AulPredefinedFunctionTest.cpp +++ b/tests/aul/AulPredefinedFunctionTest.cpp @@ -141,6 +141,12 @@ TEST_F(AulPredefFunctionTest, Abs) EXPECT_EQ(C4VINT_MAX, RunExpr("Abs(2147483647)")); } +TEST_F(AulPredefFunctionTest, CreateEffect) +{ + EXPECT_EQ(C4VInt(3), RunCode("local A = { Start=func() { this.Magicnumber = 3; } } func Main() { return CreateEffect(A, 1).Magicnumber; }", false)); + EXPECT_EQ(C4VInt(3), RunCode("local A = { Construction=func() { this.Magicnumber = 3; } } func Main() { return CreateEffect(A, 1).Magicnumber; }", false)); +} + TEST_F(AulPredefFunctionTest, Trivial) { EXPECT_EQ(C4VInt(100), RunExpr("Sin(900,100,10)")); diff --git a/tests/aul/AulTest.cpp b/tests/aul/AulTest.cpp index eb63e4740..dd66200a6 100644 --- a/tests/aul/AulTest.cpp +++ b/tests/aul/AulTest.cpp @@ -22,6 +22,7 @@ #include "script/C4ScriptHost.h" #include "lib/C4Random.h" #include "object/C4DefList.h" +#include "TestLog.h" C4Value AulTest::RunCode(const char *code, bool wrap) { @@ -203,3 +204,19 @@ TEST_F(AulTest, Conditionals) EXPECT_EQ(C4VInt(1), RunCode("if (true) return 1; else return 2;")); EXPECT_EQ(C4VInt(2), RunCode("if (false) return 1; else return 2;")); } + +TEST_F(AulTest, Warnings) +{ + LogMock log; + EXPECT_CALL(log, DebugLog(testing::StartsWith("WARNING:"))).Times(3); + EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(s); }", false)); + EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(o); }", false)); + EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { Sin(a); }", false)); +} + +TEST_F(AulTest, NoWarnings) +{ + LogMock log; + EXPECT_CALL(log, DebugLog(testing::StartsWith("WARNING:"))).Times(0); + EXPECT_EQ(C4Value(), RunCode("func Main(string s, object o, array a) { var x; Sin(x); }", false)); +}