Script: CreateEffect starts effects that receive callbacks themselves

liquid_container
Günther Brammer 2015-12-23 00:40:16 +01:00
parent c167e990a5
commit 50378ffda0
16 changed files with 289 additions and 140 deletions

View File

@ -51,7 +51,8 @@ func Destruction()
<text>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.</text>
<text>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.</text>
<h id="Usage">Application</h>
<text>Effects are created using <funclink>AddEffect</funclink> and removed with <funclink>RemoveEffect</funclink>. 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:</text>
<text>Effects are created using <funclink>CreateEffect</funclink> and removed with <funclink>RemoveEffect</funclink>. If an effect was successfully created, the callback <emlink href="script/Effects.html#Construction">Construction</emlink> is made. Depending on the parameters, there can also be an <emlink href="script/Effects.html#TimerCallback">Timer</emlink> call for continuous activity such as casting sparks, adjusting energy etc. Finally, when the effect is deleted, the callback <emlink href="script/Effects.html#Destruction">Destruction</emlink> is made.</text>
<text>Now, the invisibility spell implemented using effects:</text>
<code>/* 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
<funclink>AddEffect</funclink>(&quot;InvisPSpell&quot;, caster, 200, 1111, nil, GetID());
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
// start effect
caster-><funclink>CreateEffect</funclink>(InvisPSpell, 200, 1111);
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
}
func FxInvisPSpellStart(object target, proplist effect)
local InvisPSpell =
{
// Save the casters previous visibility
effect.visibility = target.Visibility;
effect.old_mod = target-&gt;<funclink>GetClrModulation</funclink>();
// Make the caster invisible
target.Visibility = <funclink>VIS_Owner</funclink> | <funclink>VIS_Allies</funclink> | <funclink>VIS_God</funclink>;
// Semitransparent and slightly blue for owner and allies
target-&gt;SetClrModulation(<funclink>ModulateColor</funclink>(effect.old_mod, RGBa(127,127,255,127)));
// Fertig
return true;
}
func FxInvisPSpellStop(object target, proplist effect)
{
// restore previous values
target-&gt;<funclink>SetClrModulation</funclink>(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-&gt;<funclink>GetClrModulation</funclink>();
// Make the caster invisible
target.Visibility = <funclink>VIS_Owner</funclink> | <funclink>VIS_Allies</funclink> | <funclink>VIS_God</funclink>;
// Semitransparent and slightly blue for owner and allies
target-&gt;SetClrModulation(<funclink>ModulateColor</funclink>(this.old_mod, RGBa(127,127,255,127)));
},
Stop = func(object target)
{
// restore previous values
target-&gt;<funclink>SetClrModulation</funclink>(this.old_mod);
target.Visibility = this.visibility;
}
}</code>
<text>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.</text>
<text>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:</text>
<code>func FxInvisPSpellTimer()
<code>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;
}</code>
<text>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 <funclink>AddEffect</funclink>(). In that case, effect callbacks would be in object local context and the effect would automatically be deleted if the target object is destroyed.</text>
<text>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 <code>-></code>.</text>
<h id="Priorities">Priorities</h>
<text>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.</text>
<text>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:</text>
<text>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 <code>Effect</code> callback of the same priority.</text>
<text>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 <code>Effect</code> 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:</text>
<code>/* 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
<funclink>AddEffect</funclink>(&quot;BanBurnPSpell&quot;, caster, 180, 1111, nil, GetID());
// start effect
caster-><funclink>CreateEffect</funclink>(BanBurnPSpell, 180, 1111);
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
}
func FxBanBurnPSpellStart(object target, proplist effect, bool temporary)
{
// On start of the effect: extinguish clonk
if (!temporary) target-&gt;<funclink>Extinguish</funclink>();
return true;
}
func FxBanBurnPSpellEffect(string new_name)
{
// block fire
if (<funclink>WildcardMatch</funclink>(new_name, &quot;*Fire*&quot;)) return -1;
// everything else is ok
return 0;
}</code>
<text>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:</text>
local BanBurnPSpell = {
Construction = func(object target)
{
// On start of the effect: extinguish clonk
target-&gt;<funclink>Extinguish</funclink>();
},
Effect = func(string new_name)
{
// block fire
if (<funclink>WildcardMatch</funclink>(new_name, &quot;*Fire*&quot;)) return -1;
// everything else is ok
return 0;
}
};</code>
<text>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 <code>Effect</code>. For the like:</text>
<code>[...]
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 (!<funclink>WildcardMatch</funclink>(new_name, &quot;*Fire*&quot;)) 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 &amp;&amp; <funclink>GetType</funclink>(var3) == <funclink>C4V_C4Object</funclink>) var3-&gt;<funclink>Extinguish</funclink>();
// block fire
return -1;
// only handle fire
if (!<funclink>WildcardMatch</funclink>(new_name, &quot;*Fire*&quot;)) 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 &amp;&amp; <funclink>GetType</funclink>(var3) == <funclink>C4V_C4Object</funclink>) var3-&gt;<funclink>Extinguish</funclink>();
// block fire
return -1;
}</code>
<text>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.</text>
<text>The following table contains general guidelines for priorities in effects of the original pack:</text>
@ -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
<funclink>AddEffect</funclink>(&quot;ReincarnationPSpell&quot;, caster, 180, 0, nil, GetID());
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
// start effect
caster-><funclink>CreateEffect</funclink>(ReincarnationPSpell, 180, 0);
// done - the spell object is not needed anymore
<funclink>RemoveObject</funclink>();
return true;
}
func FxReincarnationPSpellStart(object target, proplist effect, bool temporary)
{
// Only at the first start: message
if (!temporary) target-&gt;<funclink>Message</funclink>(&quot;%s gets an extra life&quot;, target-&gt;<funclink>GetName</funclink>());
return true;
}
local ReincarnationPSpell = {
Construction = func(object target)
{
// Only at the first start: message
target-&gt;<funclink>Message</funclink>(&quot;%s gets an extra life&quot;, target-&gt;<funclink>GetName</funclink>());
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-&gt;<funclink>GetAlive</funclink>()) return -1;
// resurrect clonk
target-&gt;<funclink>SetAlive</funclink>(true);
// give energy
target-&gt;<funclink>DoEnergy</funclink>(100);
// message
target-&gt;<funclink>Message</funclink>(&quot;%s has been resurrected.&quot;, target-&gt;<funclink>GetName</funclink>());
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-&gt;<funclink>GetAlive</funclink>()) return -1;
// resurrect clonk
target-&gt;<funclink>SetAlive</funclink>(true);
// give energy
target-&gt;<funclink>DoEnergy</funclink>(100);
// message
target-&gt;<funclink>Message</funclink>(&quot;%s has been resurrected.&quot;, target-&gt;<funclink>GetName</funclink>());
// remove
return true;
}</code>
// remove
return true;
}
};</code>
<text>This effect reanimates the clonk as many times as he has cast the reanimation spell.</text>
<h id="GlobalEffects">Global Effects</h>
<text>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 <code>nil</code> for the target object.</text>
<text>There are two global effect types: Scenerio effects and global effects. They are bound to the <code>Scenario</code> and <code>Global</code> proplists. With these effects, too, priorities are observed and temporary Add/Remove calls might be necessary to ensure order.</text>
<text>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:</text>
<code>/* Gravitation spell */
@ -488,19 +488,20 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason
</row>
</table>
</text>
<text>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.</text>
<text>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.</text>
<h id="CBRef">Callback Reference</h>
<part>
<text>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.</text>
<h>Fx*Start</h>
<text><code>int Fx*Start (object target, proplist effect, int temporary, any var1, any var2, any var3, any var4);</code></text>
<text>Called at the start of the effect. target is the target object of the effect. <code>effect</code> is the effect itself. <code>effect</code> can be used to manipulate the effect, for example with <code>effect.Interval=newinterval</code>.</text>
<text>The following callbacks are made by the engine and should be implemented in your effect prototype as necessary.</text>
<text>All parameters receive the target as their first or second parameter. This is either an object, the <code>Global</code> or the <code>Scenario</code> proplist.</text>
<h>Start</h>
<text><code>int Start (object target, int temporary, any var1, any var2, any var3, any var4);</code></text>
<text>Called at the start of the effect. <code>target</code> is the target object of the effect. <code>this</code> is the effect itself. It can be used to manipulate the effect, for example with <code>this.Interval=newinterval</code>.</text>
<text>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).</text>
<text>If temporary is 0, var1 to var4 are the additional parameters passed to <funclink>AddEffect</funclink>().</text>
<text>If temporary is 0 and this callback returns -1 the effect is not created and the corrsponding <funclink>AddEffect</funclink>() call returns 0.</text>
<h>Fx*Stop</h>
<text><code>int Fx*Stop (object target, proplist effect, int reason, bool temporary);</code></text>
<text>When the effect is temporarily or permanently removed. target again is the target object and <code>effect</code> the effect itself.</text>
<text>If temporary is 0, var1 to var4 are the additional parameters passed to <funclink>CreateEffect</funclink>().</text>
<text>If temporary is 0 and this callback returns -1 the effect is not created and the corrsponding <funclink>CreateEffect</funclink>() call returns <code>nil</code>.</text>
<h>Stop</h>
<text><code>int Stop (object target, int reason, bool temporary);</code></text>
<text>When the effect is temporarily or permanently removed. target again is the target object and <code>this</code> the effect itself.</text>
<text>reason contains the cause of the removal and can be one of the following values:</text>
<text>
<table>
@ -537,25 +538,33 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason
</table>
</text>
<text>The effect can prevent removal by returning -1. This will not help, however, in temporary removals or if the target object has been deleted.</text>
<h id="TimerCallback">Fx*Timer</h>
<text><code>int Fx*Timer (object target, proplist effect, int time);</code></text>
<text>Periodic timer call, if a timer interval has been specified at effect creation. target and <code>effect</code> as usual.</text>
<h id="FxConstructionCallback">Construction</h>
<text><code>int Construction (object target, any var1, any var2, any var3, any var4);</code></text>
<text>Called when the effect is first created, before it is started. The parameters <code>var1</code> to <code>var4</code> are passed through from <funclink>CreateEffect</funclink>.</text>
<text>The return value is ignored.</text>
<h id="FxDestructionCallback">Destruction</h>
<text><code>nil Destruction (object target, int reason);</code></text>
<text>Callback when the effect is removed. <code>reason</code> is the same as in the preceding <code>Stop</code> call, see above.</text>
<text>The return value is ignored.</text>
<h id="TimerCallback">Timer</h>
<text><code>int Timer (object target, int time);</code></text>
<text>Periodic timer call, if a timer interval has been specified at effect creation.</text>
<text>time specifies how long the effect has now been active. This might alternatively be determined using effect.Time.</text>
<text>If this function is not implemented or returns -1, the effect will be deleted after this call.</text>
<h>Fx*Effect</h>
<text><code>int Fx*Effect (string new_name, object target, proplist effect, any var1, any var2, any var3, any var4);</code></text>
<text>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; <code>effect</code> is the effect being called.</text>
<h>Effect</h>
<text><code>int Effect (string new_name, object target, any var1, any var2, any var3, any var4);</code></text>
<text>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; <code>this</code> is the effect being called.</text>
<text>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.</text>
<text>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.</text>
<text>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.</text>
<text>var1 bis var4 are the parameters passed to <funclink>AddEffect</funclink>()</text>
<h>Fx*Add</h>
<text><code>int Fx*Add (object target, proplist effect, string new_name, int new_timer, any var1, any var2, any var3, any var4);</code></text>
<text>Callback to the accepting effect if that has returned -2 or -3 to a prior Fx*Effect call. <code>effect</code> identifies the accepting effect to which the consequences of the new effect will be added; target is the target object (0 for global effects).</text>
<text>Return -2 or -3 to accept the new effect. As long as the new effect is not rejected by any other effect, the <code>Add</code> call is then made to the accepting effect, the new effect is not actually created, and the calling <funclink>CreateEffect</funclink> function returns the accepting effect. The return value -3 will also temporarily remove all higher prioriy effects just before the <code>Add</code> callback and re-add them later.</text>
<text>var1 bis var4 are the parameters passed to <funclink>CreateEffect</funclink>()</text>
<h>Add</h>
<text><code>int Add (object target, string new_name, int new_timer, any var1, any var2, any var3, any var4);</code></text>
<text>Callback to the accepting effect if that has returned -2 or -3 to a prior <code>Effect</code> call. <code>this</code> identifies the accepting effect to which the consequences of the new effect will be added; <code>target</code> is the target object (0 for global effects).</text>
<text>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.</text>
<text>If -1 is returned, the accepting effect is deleted also. Logically, the calling AddEffect function will then return -2.</text>
<h>Fx*Damage</h>
<text><code>int Fx*Damage (object target, proplist effect, int damage, int cause, int by_player);</code></text>
<text>If -1 is returned, the accepting effect is deleted also. Logically, the calling <funclink>CreateEffect</funclink> function will then return <code>nil</code>.</text>
<h>Damage</h>
<text><code>int Damage (object target, int damage, int cause, int by_player);</code></text>
<text>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.</text>
<text id="damagecause">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:</text>
<text>
@ -639,7 +648,7 @@ global func FxExplodeOnDeathCurseStop(object target, proplist effect, int reason
<text>There are the following functions for manipulation of effects:</text>
<text>
<ul>
<li><funclink>AddEffect</funclink>() - for effect creation</li>
<li><funclink>CreateEffect</funclink>() - for effect creation</li>
<li><funclink>RemoveEffect</funclink>() - for effect removal</li>
<li><funclink>GetEffect</funclink>() - to search for effects</li>
<li><funclink>GetEffectCount</funclink>() - for effect counting</li>

View File

@ -7,6 +7,7 @@
<title>AddEffect</title>
<category>Effects</category>
<version>5.1 OC</version>
<deprecated />
<syntax>
<rtype>proplist</rtype>
<params>
@ -18,7 +19,7 @@
<param>
<type>object</type>
<name>target</name>
<desc>Target object for the effect. If <code>nil</code>, a global effect is created.</desc>
<desc>Target object for the effect. If <code>nil</code>, <code>Global</code> is used, but the target parameter of the callbacks will get <code>nil</code>.</desc>
<optional />
</param>
<param>
@ -74,6 +75,7 @@
<remark>For examples and more information see the <emlink href="script/Effects.html">effects documentation</emlink>.</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>CreateEffect</funclink>
<funclink>CheckEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>EffectCall</funclink>

View File

@ -63,7 +63,7 @@
<remark>For examples and more information see the <emlink href="script/Effects.html">effects documentation</emlink>.</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>AddEffect</funclink>
<funclink>CreateEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>EffectCall</funclink>
<funclink>GetEffect</funclink>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE funcs
SYSTEM '../../../clonk.dtd'>
<?xml-stylesheet type="text/xsl" href="../../../clonk.xsl"?>
<funcs>
<func>
<title>CreateEffect</title>
<category>Effects</category>
<version>5.5 OC</version>
<syntax>
<rtype>proplist</rtype>
<params>
<param>
<type>proplist</type>
<name>prototype</name>
<desc>A proplist containing the callback functions for the new Effect. The name (<funclink>GetName</funclink>) of this proplist becomes the name of the effect.</desc>
</param>
<param>
<type>int</type>
<name>priority</name>
<desc>Effect priority. Must be greater than zero.</desc>
</param>
<param>
<type>int</type>
<name>timer</name>
<optional />
<desc>Interval for the timer calls. With <code>nil</code>, no timer calls are made and the effect stays on permanently until it is deleted by other calls.</desc>
</param>
<param>
<type>any</type>
<name>var1</name>
<optional />
<desc>First extra parameter to be passed to <code>Construction</code>, <code>Start</code> and <code>Effect</code> callbacks.</desc>
</param>
<param>
<type>any</type>
<name>var2</name>
<optional />
<desc>Second extra parameter to be passed to <code>Construction</code>, <code>Start</code> and <code>Effect</code> callbacks.</desc>
</param>
<param>
<type>any</type>
<name>var3</name>
<optional />
<desc>Third extra parameter to be passed to <code>Construction</code>, <code>Start</code> and <code>Effect</code> callbacks.</desc>
</param>
<param>
<type>any</type>
<name>var4</name>
<optional />
<desc>Fourth extra parameter to be passed to <code>Construction</code>, <code>Start</code> and <code>Effect</code> callbacks.</desc>
</param>
</params>
</syntax>
<desc>Creates an effect. Returns the effect if successful or <code>nil</code> 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 <code>nil</code>. Effects can be created on objects, <code>Global</code> and <code>Scenario</code>. This is passed as the first parameter to the effect callbacks.
</desc>
<remark>For examples and more information see the <emlink href="script/Effects.html">effects documentation</emlink>.</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>CheckEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>EffectCall</funclink>
<funclink>GetEffect</funclink>
<funclink>RemoveEffect</funclink>
</related>
</func>
<author>Sven2</author><date>2004-03</date>
<author>Günther</author><date>2014</date>
</funcs>

View File

@ -37,7 +37,7 @@
<remark>For examples and more information see the <emlink href="script/Effects.html">effects documentation</emlink>.</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>AddEffect</funclink>
<funclink>CreateEffect</funclink>
<funclink>CheckEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>GetEffect</funclink>

View File

@ -59,7 +59,7 @@ i = <funclink>GetEffectCount</funclink>();
</examples>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>AddEffect</funclink>
<funclink>CreateEffect</funclink>
<funclink>CheckEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>EffectCall</funclink>

View File

@ -34,7 +34,7 @@
<remark>For an example see <funclink>GetEffect</funclink>.</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>AddEffect</funclink>
<funclink>CreateEffect</funclink>
<funclink>CheckEffect</funclink>
<funclink>EffectCall</funclink>
<funclink>GetEffect</funclink>

View File

@ -40,7 +40,7 @@
<remark>See <funclink>GetEffect</funclink> for an example. Warning: if an effect is meant to delete itself using this function, only use effect, not name!</remark>
<related>
<emlink href="script/Effects.html">Effects Documentation</emlink>
<funclink>AddEffect</funclink>
<funclink>CreateEffect</funclink>
<funclink>CheckEffect</funclink>
<funclink>GetEffectCount</funclink>
<funclink>EffectCall</funclink>

View File

@ -720,7 +720,7 @@ bool C4Game::Execute() // Returns true if the game is over
// Game
EXEC_S( ExecObjects(); , ExecObjectsStat )
EXEC_S_DR( C4Effect::Execute(NULL, &ScriptEngine.pGlobalEffects);
EXEC_S_DR( C4Effect::Execute(ScriptEngine.GetPropList(), &ScriptEngine.pGlobalEffects);
, GEStats , "GEEx\0");
EXEC_S_DR( PXS.Execute(); , PXSStat , "PXSEx")
EXEC_S_DR( MassMover.Execute(); , MassMoverStat , "MMvEx")

View File

@ -47,6 +47,8 @@ C4Effect ** FnGetEffectsFor(C4PropList * pTarget)
{
if (pTarget)
{
if (pTarget == ScriptEngine.GetPropList())
return &ScriptEngine.pGlobalEffects;
C4Object * Obj = pTarget->GetObject();
if (!Obj)
throw C4AulExecError("Effect target has to be an object");

View File

@ -54,6 +54,18 @@ C4Effect::C4Effect(C4Effect **ppEffectList, C4String *szName, int32_t iPrio, int
SetProperty(P_Name, C4VString(szName));
}
C4Effect::C4Effect(C4Effect **ppEffectList, C4PropList * prototype, int32_t iPrio, int32_t iTimerInterval):
C4PropListNumbered(prototype)
{
// assign values
iPriority = 0; // effect is not yet valid; some callbacks to other effects are done before
iInterval = iTimerInterval;
iTime = 0;
CommandTarget.Set0();
AcquireNumber();
Register(ppEffectList, iPrio);
}
void C4Effect::Register(C4Effect **ppEffectList, int32_t iPrio)
{
// get effect target
@ -80,6 +92,12 @@ C4Effect * C4Effect::New(C4PropList *pForObj, C4Effect **ppEffectList, C4String
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)
{
// ask all effects with higher priority first - except for prio 1 effects, which are considered out of the priority call chain (as per doc)
@ -106,7 +124,12 @@ C4Effect * C4Effect::Init(C4PropList *pForObj, int32_t iPrio, const C4Value &rVa
// because that would cause a wrong initialization order
// (hardly ever causing trouble, however...)
C4Effect *pLastRemovedEffect=NULL;
if (fRemoveUpper && pNext && pFnStart)
C4AulFunc * pFn;
if (!GetCallbackScript())
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!
@ -114,7 +137,7 @@ C4Effect * C4Effect::Init(C4PropList *pForObj, int32_t iPrio, const C4Value &rVa
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 && pFnStart)
if (fRemoveUpper && pNext && pFn)
TempReaddUpperEffects(pForObj, pLastRemovedEffect);
if (pForObj && !pForObj->Status) return 0; // this will be invalid!
// Update OnFire cache
@ -360,43 +383,60 @@ void C4Effect::DoDamage(C4PropList *pObj, int32_t &riDamage, int32_t iDamageType
while ((pEff = pEff->pNext) && riDamage);
}
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));
}
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, this, temporary, var1, var2, var3, var4)).getInt();
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, this, reason, temporary)).getInt();
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, this, time)).getInt();
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 (pFnDamage)
damage = pFnDamage->Exec(GetCallbackScript(), &C4AulParSet(obj, this, damage, damagetype, plr)).getInt();
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, this, var1, var2, var3, var4)).getInt();
return pFnEffect->Exec(GetCallbackScript(), &C4AulParSet(effect, Obj(obj), this, var1, var2, var3, var4)).getInt();
return C4Fx_OK;
}

View File

@ -91,12 +91,14 @@ protected:
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, C4ValueNumbers *>(C4Effect *&, StdCompiler *, C4ValueNumbers * const &);
public:
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(C4Effect **ppEffectList, int32_t iPrio); // add into effect list of object or global effect list

View File

@ -357,6 +357,18 @@ static C4Value FnAddEffect(C4PropList * _this, C4String * szEffect, C4PropList *
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);
@ -946,6 +958,7 @@ void InitCoreFunctionMap(C4AulScriptEngine *pEngine)
F(ResetProperty);
F(GetName);
F(AddEffect);
F(CreateEffect);
F(CheckEffect);
F(RemoveEffect);
F(GetEffect);

View File

@ -37,6 +37,8 @@ C4AulScriptEngine ScriptEngine;
/* Avoid a C4Object dependency */
C4Effect ** FnGetEffectsFor(C4PropList * pTarget)
{
if (pTarget == ScriptEngine.GetPropList())
return &ScriptEngine.pGlobalEffects;
if (pTarget) throw C4AulExecError("Only global effects are supported");
return &ScriptEngine.pGlobalEffects;
}

View File

@ -87,6 +87,11 @@ C4StringTable::C4StringTable()
P[P_Interval] = "Interval";
P[P_CommandTarget] = "CommandTarget";
P[P_Time] = "Time";
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";

View File

@ -294,6 +294,11 @@ enum C4PropertyName
P_Interval,
P_CommandTarget,
P_Time,
P_Start,
P_Stop,
P_Timer,
P_Effect,
P_Damage,
P_Collectible,
P_Touchable,
P_ActMap,