Merge branch script

liquid_container
Günther Brammer 2016-04-28 03:00:04 +02:00
commit b00b8554ab
40 changed files with 1062 additions and 624 deletions

View File

@ -28,7 +28,7 @@ Stand = {
<row>
<col><code>Prototype</code></col>
<col>proplist</col>
<col></col>
<col>Deprecated. Use <funclink>SetPrototype</funclink> and <funclink>GetPrototype</funclink>.</col>
</row>
<row>
<col><code>Name</code></col>

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

@ -7,8 +7,26 @@
<title>GetName</title>
<category>Objects</category>
<version>5.1 OC</version>
<syntax><rtype>string</rtype></syntax>
<desc>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.</desc>
<syntax>
<rtype>string</rtype>
<params>
<param>
<type>bool</type>
<name>truename</name>
<desc>Returns only the constant in which it was defined, ignoring the <code>Name</code> property.</desc>
</param>
</params>
</syntax>
<desc>Returns the name of a proplist. This is either the contents of the <code>Name</code> property, or if that doesn't exist or the true name was requested, the name of the constant in which it was defined.</desc>
<examples>
<example>
<code>
static const Bee = { Buzz = func() {} };
func Poke(proplist animal) {
if (animal->GetName(true) == "Bee") animal->Buzz();
}</code>
</example>
</examples>
</func>
<author>jwk</author><date>2002-06</date>
<author>Günther</author><date>2014</date>
</funcs>

View File

@ -0,0 +1,26 @@
<?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>GetPrototype</title>
<category>Objects</category>
<subcat>Properties</subcat>
<version>8.0 OC</version>
<syntax>
<rtype>nil</rtype>
<params>
<param>
<type>proplist</type>
<name>obj</name>
<desc>The Object whose prototype is returned. Can be <code>nil</code> in local calls.</desc>
<optional />
</param>
</params>
</syntax>
<desc>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.</desc>
<related><funclink>SetPrototype</funclink></related>
</func>
<author>Günther</author><date>2016-04</date>
</funcs>

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

@ -0,0 +1,34 @@
<?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>SetPrototype</title>
<category>Objects</category>
<subcat>Properties</subcat>
<version>8.0 OC</version>
<syntax>
<rtype>nil</rtype>
<params>
<param>
<type>proplist</type>
<name>prototype</name>
<desc>The new prototype.</desc>
<optional />
</param>
<param>
<type>proplist</type>
<name>obj</name>
<desc>Object to be changed. Can be <code>nil</code> in local calls.</desc>
<optional />
</param>
</params>
</syntax>
<desc>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.</desc>
<related><funclink>GetPrototype</funclink></related>
</func>
<author>Günther</author><date>2016-04</date>
</funcs>

View File

@ -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,

View File

@ -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()

View File

@ -83,7 +83,6 @@ public:
C4Extra Extra;
class C4ScenarioObjectsScriptHost *pScenarioObjectsScript;
C4ScenarioSection *pScenarioSections, *pCurrentScenarioSection;
C4Effect *pGlobalEffects;
C4PlayerControlDefs PlayerControlDefs;
C4PlayerControlAssignmentSets PlayerControlUserAssignmentSets, PlayerControlDefaultAssignmentSets;
C4Scoreboard Scoreboard;

View File

@ -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 }

View File

@ -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)

View File

@ -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)

View File

@ -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);

View File

@ -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<const char*> C4AulScriptEngine::GetFunctionNames(C4PropList * p)

View File

@ -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

View File

@ -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<T>::Type())), _val(value) {}
inline Nillable(const T &value) : _nil(!value && !C4Value::IsNullableType(C4ValueConv<T>::Type)), _val(value) {}
inline Nillable() : _nil(true), _val(T()) {}
inline Nillable(std::nullptr_t) : _nil(true), _val(T()) {}
template <typename T2> inline Nillable(const Nillable<T2> & n2) : _nil(n2._nil), _val(n2._val) {}
@ -59,7 +60,7 @@ public:
inline Nillable<T> &operator =(const T &val)
{
_val = val;
_nil = !val && !C4Value::IsNullableType(C4ValueConv<T>::Type());
_nil = !val && !C4Value::IsNullableType(C4ValueConv<T>::Type);
return *this;
}
inline Nillable<T> &operator =(const Nillable<T> &val)
@ -140,71 +141,71 @@ template <typename T>
struct C4ValueConv<Nillable<T> >
{
inline static Nillable<T> _FromC4V(C4Value &v) { if (v.GetType() == C4V_Nil) return C4Void(); else return C4ValueConv<T>::_FromC4V(v); }
inline static C4V_Type Type() { return C4ValueConv<T>::Type(); }
static constexpr C4V_Type Type = C4ValueConv<T>::Type;
};
template <> struct C4ValueConv<void>
{
inline static C4V_Type Type() { return C4V_Nil; }
static constexpr C4V_Type Type = C4V_Nil;
};
template <> struct C4ValueConv<int>
{
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<long>: public C4ValueConv<int> { };
template <> struct C4ValueConv<bool>
{
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<C4ID>
{
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<C4Object *>
{
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<C4String *>
{
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<C4ValueArray *>
{
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<C4AulFunc *>
{
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<C4PropList *>
{
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<C4Effect *>
{
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<C4Def *>
{
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<const C4Value &>
{
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<C4Value>
{
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<ParTypes>::Type()...}, Public(Public)
pFunc(pFunc), ParType {C4ValueConv<ParTypes>::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<RType>::Type();
return C4ValueConv<RType>::Type;
}
virtual bool GetPublic() const

View File

@ -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)

View File

@ -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;

View File

@ -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
#endif

View File

@ -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();
}

View File

@ -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

View File

@ -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<char*>("None"), 5, StdCompiler::RCT_ID);
}
else
{
StdStrBuf s;
pComp->Value(mkParAdapt(s, StdCompiler::RCT_ID));
// An Object trumps a definition as command target
if (!nptr)
if (!::ScriptEngine.GetGlobalConstant(s.getData(), &CommandTarget))
CommandTarget.Set0();
}
pComp->Separator();
// proplist
C4PropListNumbered::CompileFunc(pComp, numbers);
pComp->Separator(StdCompiler::SEP_END); // ')'
@ -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;
}
}

View File

@ -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, C4ValueNumbers *>(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

View File

@ -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);
}

View File

@ -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<C4String> ParentKeyName; // property in parent this proplist was created in

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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() {}

View File

@ -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";

View File

@ -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,

View File

@ -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;

View File

@ -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)"));

View File

@ -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));
}