Merge 'master' into liquid_container

liquid_container
Mark 2016-05-01 08:43:05 +02:00
commit 853af2c155
72 changed files with 1699 additions and 1011 deletions

View File

@ -1066,6 +1066,7 @@ src/script/C4AulFunc.h
src/script/C4Aul.h
src/script/C4AulLink.cpp
src/script/C4AulParse.cpp
src/script/C4AulParse.h
src/script/C4AulScriptFunc.cpp
src/script/C4AulScriptFunc.h
src/script/C4Effect.cpp

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

@ -11,6 +11,6 @@ VertexX=-2,+2,0
VertexY=5,5,-5
VertexFriction=90,90,90
Value=6
Mass=8
Mass=6
Rotate=1
Components=Coal=1;Firestone=1

View File

@ -11,6 +11,6 @@ VertexX=0,3,-3
VertexY=-2,2,2
VertexFriction=20,20,20
Value=20
Mass=10
Mass=8
Components=Metal=1;Firestone=1;
Rotate=1

View File

@ -1,31 +1,30 @@
/*
/**
IronBomb
Author: Ringwaul, Clonkonaut
Explodes after a short fuse. Explodes on contact if shot by the grenade launcher
Explodes after a short fuse. Explodes on contact if shot by the grenade launcher.
@author Ringwaul, Clonkonaut
*/
local armed; // If true, explodes on contact
// If true, explodes on contact.
local armed;
public func ControlUse(object clonk, int x, int y)
{
// if already activated, nothing (so, throw)
// If already activated, nothing (so, throw).
if (GetEffect("FuseBurn", this))
{
clonk->ControlThrow(this, x, y);
return true;
}
else
{
Fuse();
return true;
}
Fuse();
return true;
}
func Fuse(bool explode_on_hit)
public func Fuse(bool explode_on_hit)
{
armed = explode_on_hit;
AddEffect("FuseBurn", this, 1,1, this);
AddEffect("FuseBurn", this, 1, 1, this);
return;
}
public func FuseTime() { return 90; }
@ -34,49 +33,68 @@ public func IsFusing() { return !!GetEffect("FuseBurn", this); }
public func OnCannonShot(object cannon)
{
Fuse(true);
return Fuse(true);
}
func FxFuseBurnTimer(object bomb, proplist effect, int timer)
public func FxFuseBurnStart(object target, effect fx, int temp)
{
if (temp)
return FX_OK;
Sound("Fire::FuseLoop", {loop_count = +1});
return FX_OK;
}
public func FxFuseBurnTimer(object target, effect fx, int time)
{
// Emit some smoke from the fuse hole.
var i = 3;
var x = +Sin(GetR(), i);
var y = -Cos(GetR(), i);
CreateParticle("Smoke", x, y, x, y, PV_Random(18, 36), Particles_Smoke(), 2);
if(timer == 1) Sound("Fire::FuseLoop",nil,nil,nil,+1);
if(timer >= FuseTime())
// Explode if time is up.
if (time >= FuseTime())
{
Sound("Fire::FuseLoop",nil,nil,nil,-1);
DoExplode();
return -1;
return FX_Execute_Kill;
}
return FX_OK;
}
func DoExplode()
public func FxFuseBurnStop(object target, effect fx, int reason, bool temp)
{
var i = 23;
while(i != 0)
if (temp)
return FX_OK;
Sound("Fire::FuseLoop", {loop_count = -1});
return FX_OK;
}
public func DoExplode()
{
// Cast lots of shrapnel.
var shrapnel_count = 20;
for (var cnt = 0; cnt < shrapnel_count; cnt++)
{
var shrapnel = CreateObjectAbove(Shrapnel);
shrapnel->SetVelocity(Random(359), RandomX(100,140));
shrapnel->SetRDir(-30+ Random(61));
shrapnel->SetVelocity(Random(359), RandomX(100, 140));
shrapnel->SetRDir(-30 + Random(61));
shrapnel->Launch(GetController());
CreateObjectAbove(BulletTrail)->Set(2,30,shrapnel);
i--;
CreateObjectAbove(BulletTrail)->Set(2, 30, shrapnel);
}
if(GBackLiquid())
if (GBackLiquid())
Sound("Fire::BlastLiquid2");
else
Sound("Fire::BlastMetal");
CreateParticle("Smoke", PV_Random(-30, 30), PV_Random(-30, 30), 0, 0, PV_Random(40, 60), Particles_Smoke(), 60);
Explode(30);
Explode(28);
return;
}
protected func Hit(int x, int y)
{
if (armed) return DoExplode();
StonyObjectHit(x,y);
if (armed)
return DoExplode();
return StonyObjectHit(x,y);
}
protected func Incineration(int caused_by)
@ -84,11 +102,7 @@ protected func Incineration(int caused_by)
Extinguish();
Fuse();
SetController(caused_by);
}
protected func RejectEntrance()
{
return GetAction() == "Fuse" || GetAction() == "Ready";
return;
}
// Drop fusing bomb on death to prevent explosion directly after respawn
@ -103,8 +117,10 @@ public func IsWeapon() { return true; }
public func IsArmoryProduct() { return true; }
public func IsGrenadeLauncherAmmo() { return true; }
/*-- Properties --*/
local Name = "$Name$";
local Description = "$Description$";
local Collectible = 1;
local Collectible = true;
local BlastIncinerate = 1;
local ContactIncinerate = 1;

View File

@ -1,4 +1,9 @@
/* shrapnel */
/**
Shrapnel
Fragment of the iron bomb.
@author Ringwaul
*/
public func ProjectileDamage() { return 3; }
public func TumbleStrength() { return 100; }
@ -13,22 +18,23 @@ protected func Initialize()
public func Launch(int shooter)
{
SetController(shooter);
AddEffect("HitCheck", this, 1,1, nil, nil);
AddEffect("HitCheck", this, 1, 1, nil, nil);
}
protected func FxFadeTimer(object target, proplist effect, int timer)
{
if(timer > FlightTime()) RemoveObject();
if (timer > FlightTime())
RemoveObject();
return FX_OK;
}
protected func Hit()
{
ShakeFree(6);
RemoveEffect("HitCheck",this);
RemoveEffect("HitCheck", this);
Sound("Objects::Weapons::Musket::BulletHitGround?");
CreateParticle("StarSpark", 0, 0, PV_Random(-20, 20), PV_Random(-20, 20), PV_Random(10, 20), Particles_Glimmer(), 3);
RemoveObject();
return RemoveObject();
}
public func HitObject(object obj)
@ -40,15 +46,13 @@ public func HitObject(object obj)
obj->~OnProjectileHit(this);
WeaponDamage(obj, this->ProjectileDamage(), FX_Call_EngObjHit);
WeaponTumble(obj, this->TumbleStrength());
if (!this) return;
}
RemoveObject();
return RemoveObject();
}
public func TrailColor(int time)
{
return RGBa(100,100,100,240*Max(0,FlightTime()-time)/FlightTime());
return RGBa(100, 100, 100, 240 * Max(0, FlightTime() - time) / FlightTime());
}
local ActMap = {
@ -59,5 +63,5 @@ local ActMap = {
NextAction = "Fly",
Delay = 1,
Length = 1,
},
}
};

View File

@ -18,7 +18,7 @@ func Construction()
{
if(this.inventory == nil)
this.inventory = {};
this.inventory.last_slot = 0;
this.inventory.last_slot = 1;
return _inherited(...);
}
@ -405,15 +405,17 @@ private func PickUpAll()
}
}
// used in Inventory.ocd
// Used in Inventory.ocd
public func SetHandItemPos(int hand, int inv)
{
// save current slot
if(hand == 0)
// Save the current slot as the last slot only for the first hand
// and if the inventory slot actually changes.
if (hand == 0 && this->GetHandItemPos(0) != inv)
this.inventory.last_slot = this->GetHandItemPos(0);
return _inherited(hand, inv, ...);
}
/* Backpack control */
func Selected(object mnu, object mnu_item)
{
@ -424,4 +426,4 @@ func Selected(object mnu, object mnu_item)
mnu_item->SetSymbol(show_new_item);
// swap index with backpack index
this->Switch2Items(hands_index, backpack_index);
}
}

View File

@ -34,7 +34,7 @@ static const BASEMATERIAL_ProductionRate = 2160;
global func GetBaseMaterial(int plr, id def, int index, int category)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)
@ -43,7 +43,7 @@ global func GetBaseMaterial(int plr, id def, int index, int category)
global func SetBaseMaterial(int plr, id def, int cnt)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)
@ -52,7 +52,7 @@ global func SetBaseMaterial(int plr, id def, int cnt)
global func DoBaseMaterial(int plr, id def, int change)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)
@ -61,7 +61,7 @@ global func DoBaseMaterial(int plr, id def, int change)
global func GetBaseProduction(int plr, id def, int index, int category)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)
@ -70,7 +70,7 @@ global func GetBaseProduction(int plr, id def, int index, int category)
global func SetBaseProduction(int plr, id def, int cnt)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)
@ -79,7 +79,7 @@ global func SetBaseProduction(int plr, id def, int cnt)
global func DoBaseProduction(int plr, id def, int change)
{
var base = FindObject(Find_ID(BaseMaterial), Find_Owner(plr));
var base = FindObject(Find_ID(BaseMaterial), Find_AnyLayer(), Find_Owner(plr));
if (!base)
base = CreateObjectAbove(BaseMaterial, AbsX(10), AbsY(10), plr);
if (base)

View File

@ -209,7 +209,7 @@ func GetBuyMenuEntry(int index, id item, int amount, int value)
// ----- buying
public func GetBuyMenuEntries(object clonk)
{
{
// We need to know when exactly we should refresh the menu to prevent unecessary refreshs.
var lowest_greyed_out_price = nil;
@ -255,9 +255,8 @@ public func GetBuyMenuEntries(object clonk)
var fx = AddEffect("UpdateWealthDisplay", this, 1, 5, nil, GetID());
fx.lowest_greyed_out_price = lowest_greyed_out_price;
fx.last_wealth = wealth;
fx.plr = wealth_player;
fx.wealth_player = wealth_player;
PushBack(menu_entries, {symbol = nil, extra_data = nil, custom = entry, fx = fx});
return menu_entries;
}
@ -298,7 +297,7 @@ private func EjectContents(object contents)
private func FxUpdateWealthDisplayTimer(object target, effect fx, int time)
{
if (!fx.menu_target) return -1;
if (!fx.menu_target) return FX_Execute_Kill;
if (fx.last_wealth == GetWealth(fx.wealth_player)) return FX_OK;
fx.last_wealth = GetWealth(fx.wealth_player);
// Do we need a full refresh? New objects might have become available.

View File

@ -41,7 +41,7 @@ public func NoConstructionFlip() { return true; }
public func IsContainer() { return true; }
// Allow buying only if the rule is active
public func AllowBuyMenuEntries(){ return ObjectCount(Find_ID(Rule_BuyAtFlagpole));}
public func AllowBuyMenuEntries(){ return ObjectCount(Find_ID(Rule_BuyAtFlagpole), Find_AnyLayer());}
public func RejectCollect(id def, object obj)
{

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

@ -197,7 +197,7 @@ global func ExplosionEffect(...)
/*-- Blast objects & shockwaves --*/
// Damage and hurl objects away.
global func BlastObjects(int x, int y, int level, object container, int cause_plr, int damage_level, object layer, object no_blast)
global func BlastObjects(int x, int y, int level, object container, int cause_plr, int damage_level, object layer, object prev_container)
{
var obj;
@ -221,7 +221,7 @@ global func BlastObjects(int x, int y, int level, object container, int cause_pl
container->BlastObject(damage_level, cause_plr);
if (!container)
return true; // Container could be removed in the meanwhile.
for (obj in FindObjects(Find_Container(container), Find_Layer(layer), Find_Exclude(no_blast)))
for (obj in FindObjects(Find_Container(container), Find_Layer(layer), Find_Exclude(prev_container)))
if (obj)
obj->BlastObject(damage_level, cause_plr);
}
@ -231,14 +231,24 @@ global func BlastObjects(int x, int y, int level, object container, int cause_pl
// Object is outside.
var at_rect = Find_AtRect(l_x - 5, l_y - 5, 10, 10);
// Damage objects at point of explosion.
for (var obj in FindObjects(at_rect, Find_NoContainer(), Find_Layer(layer), Find_Exclude(no_blast)))
for (var obj in FindObjects(at_rect, Find_NoContainer(), Find_Layer(layer), Find_Exclude(prev_container)))
if (obj) obj->BlastObject(damage_level, cause_plr);
// Damage objects in radius.
for (var obj in FindObjects(Find_Distance(level, l_x, l_y), Find_Not(at_rect), Find_NoContainer(), Find_Layer(layer), Find_Exclude(no_blast)))
for (var obj in FindObjects(Find_Distance(level, l_x, l_y), Find_Not(at_rect), Find_NoContainer(), Find_Layer(layer), Find_Exclude(prev_container)))
if (obj) obj->BlastObject(damage_level / 2, cause_plr);
DoShockwave(x, y, level, cause_plr, layer);
// Perform the shockwave at the location where the top level container previously was.
// This ensures reliable flint jumps for explosives that explode inside a crew member.
var off_x = 0, off_y = 0;
if (prev_container)
{
var max_offset = 300;
off_x = BoundBy(-prev_container->GetXDir(100), -max_offset, max_offset);
off_y = BoundBy(-prev_container->GetYDir(100), -max_offset, max_offset);
}
DoShockwave(x, y, level, cause_plr, layer, off_x, off_y);
}
// Done.
return true;
@ -262,7 +272,7 @@ global func BlastObject(int level, int caused_by)
return;
}
global func DoShockwave(int x, int y, int level, int cause_plr, object layer)
global func DoShockwave(int x, int y, int level, int cause_plr, object layer, int off_x, int off_y)
{
// Zero-size shockwave
if (level <= 0) return;
@ -282,9 +292,9 @@ global func DoShockwave(int x, int y, int level, int cause_plr, object layer)
if (cnt)
{
// The hurl energy is distributed over the objects.
//Log("Shockwave objs %v (%d)", shockwave_objs, cnt);
var shock_speed = Sqrt(2 * level * level / BoundBy(cnt, 2, 12));
for (var obj in shockwave_objs)
{
if (obj) // Test obj, cause OnShockwaveHit could have removed objects.
{
var cat = obj->GetCategory();
@ -302,8 +312,9 @@ global func DoShockwave(int x, int y, int level, int cause_plr, object layer)
mass_mul = 80;
}
mass_fact = BoundBy(obj->GetMass() * mass_mul / 1000, 4, mass_fact);
var dx = 100 * (obj->GetX() - x) + Random(51) - 25;
var dy = 100 * (obj->GetY() - y) + Random(51) - 25;
// Determine difference between object and explosion center, take into account the offset.
var dx = 100 * (obj->GetX() - x) - off_x + Random(51) - 25;
var dy = 100 * (obj->GetY() - y) - off_y + Random(51) - 25;
var vx, vy;
if (dx)
vx = Abs(dx) / dx * (100 * level - Abs(dx)) * shock_speed / level / mass_fact;
@ -317,10 +328,11 @@ global func DoShockwave(int x, int y, int level, int cause_plr, object layer)
if (ovy * vy > 0)
vy = (Sqrt(vy * vy + ovy * ovy) - Abs(vy)) * Abs(vy) / vy;
}
//Log("%v v(%v %v) d(%v %v) m=%v l=%v s=%v", obj, vx,vy, dx,dy, mass_fact, level, shock_speed);
obj->Fling(vx, vy, 100, true);
}
}
}
return;
}
global func DoShockwaveCheck(int x, int y, int cause_plr)

View File

@ -20,7 +20,7 @@ global func PlayerControl(int plr, int ctrl, id spec_id, int x, int y, int stren
//Log("%d, %s, %i, %d, %d, %d, %v, %v", plr, GetPlayerControlName(ctrl), spec_id, x,y,strength, repeat, status);
if (status == CONS_Moved) return false;
// Control handled by definition? Forward
if (spec_id) return spec_id->PlayerControl(plr, ctrl, x, y, strength, repeat, release);
if (spec_id) return spec_id->ForwardedPlayerControl(plr, ctrl, x, y, strength, repeat, release);
// Forward control to player
if (Control2Player(plr,ctrl, x, y, strength, repeat, release)) return true;

View File

@ -34,7 +34,7 @@ DWORD GenerateRandomPlayerColor(int32_t iTry) // generate a random player color
{
// generate a random one biased towards max channel luminance
// (for greater color difference and less gray-ish colors)
return C4RGB(std::min<int>(SafeRandom(302), 256), std::min<int>(SafeRandom(302), 256), std::min<int>(SafeRandom(302), 256));
return C4RGB(std::min<int>(UnsyncedRandom(302), 256), std::min<int>(UnsyncedRandom(302), 256), std::min<int>(UnsyncedRandom(302), 256));
}
bool IsColorConflict(DWORD dwClr1, DWORD dwClr2) // return whether dwClr1 and dwClr2 are closely together

View File

@ -446,7 +446,7 @@ C4Team *C4TeamList::GetRandomSmallestTeam() const
iLowestTeamCount = 1;
}
else if (pLowestTeam->GetPlayerCount() == (*ppCheck)->GetPlayerCount())
if (!SafeRandom(++iLowestTeamCount))
if (!UnsyncedRandom(++iLowestTeamCount))
pLowestTeam = *ppCheck;
}
return pLowestTeam;
@ -894,7 +894,7 @@ StdStrBuf C4TeamList::GetScriptPlayerName() const
if (!Game.PlayerInfos.GetActivePlayerInfoByName(sOut.getData()))
return sOut;
// none are available: Return a random name
sScriptPlayerNames.GetSection(SafeRandom(iNameIdx-1), &sOut, '|');
sScriptPlayerNames.GetSection(UnsyncedRandom(iNameIdx-1), &sOut, '|');
return sOut;
}

View File

@ -181,7 +181,7 @@ public:
// assign a team ID to player info; recheck if any user-set team infos are valid within the current team options
// creates a new team for melee; assigns the least-used team for teamwork melee
// host/single-call only; using SafeRandom!
// host/single-call only; using UnsyncedRandom!
bool RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost);
// compiler

View File

@ -76,6 +76,7 @@
#include "landscape/C4MapScript.h"
#include "landscape/C4SolidMask.h"
#include "landscape/fow/C4FoW.h"
#include "landscape/C4Particles.h"
#include <unordered_map>
@ -578,7 +579,6 @@ void C4Game::Clear()
::Definitions.Clear();
Landscape.Clear();
PXS.Clear();
if (pGlobalEffects) { delete pGlobalEffects; pGlobalEffects=NULL; }
ScriptGuiRoot.reset();
Particles.Clear();
::MaterialMap.Clear();
@ -725,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")
@ -913,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);
}
@ -1468,7 +1471,6 @@ void C4Game::Default()
pParentGroup=NULL;
pScenarioSections=pCurrentScenarioSection=NULL;
*CurrentScenarioSection=0;
pGlobalEffects=NULL;
fResortAnyObject=false;
pNetworkStatistics.reset();
::Application.MusicSystem.ClearGame();
@ -1728,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)
@ -2288,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);
@ -3517,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
@ -3776,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

@ -45,6 +45,23 @@
#include "landscape/fow/C4FoW.h"
#include "landscape/C4Landscape.h"
#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)
@ -2147,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;
@ -2910,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);
@ -2931,7 +2846,6 @@ void InitGameFunctionMap(C4AulScriptEngine *pEngine)
F(AddEvaluationData);
F(HideSettlementScoreInEvaluation);
F(ExtractMaterialAmount);
F(GetEffectCount);
F(CustomMessage);
F(GuiOpen);
F(GuiUpdateTag);
@ -2965,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);
@ -2988,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
@ -3156,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

@ -40,6 +40,7 @@
#include "object/C4GameObjects.h"
#include "network/C4Network2.h"
#include "landscape/fow/C4FoWRegion.h"
#include "landscape/C4Particles.h"
void C4Viewport::DropFile(const char* fileName, float x, float y)
{

View File

@ -123,7 +123,7 @@ int C4LoaderScreen::SeekLoaderScreens(C4Group &rFromGrp, const char *szWildcard,
{
// loader found; choose it, if Daniel wants it that way
++iLocalLoaders;
if (!SafeRandom(++iLoaderCount))
if (!UnsyncedRandom(++iLoaderCount))
{
// copy group and path
*ppDestGrp=&rFromGrp;

View File

@ -1326,7 +1326,7 @@ C4StartupPlrPropertiesDlg::C4StartupPlrPropertiesDlg(C4StartupPlrSelDlg::PlayerL
C4P.Default(&::DefaultRanks);
// Set name, color, comment
SCopy(LoadResStr("IDS_PLR_NEWCOMMENT"), C4P.Comment, C4MaxComment);
C4P.PrefColor = SafeRandom(8);
C4P.PrefColor = UnsyncedRandom(8);
C4P.PrefColorDw = C4P.GetPrefColorValue(C4P.PrefColor);
C4P.OldPrefControlStyle = 1;
C4P.OldPrefAutoContextMenu = 1;

View File

@ -218,6 +218,7 @@ C4ParticleValueProvider & C4ParticleValueProvider::operator= (const C4ParticleVa
valueFunction = other.valueFunction;
isConstant = other.isConstant;
keyFrameCount = other.keyFrameCount;
rng = other.rng;
if (keyFrameCount > 0)
{
@ -419,7 +420,9 @@ float C4ParticleValueProvider::Random(C4Particle *forParticle)
// particles lie on the heap we just use the address here.
const std::uintptr_t ourAddress = reinterpret_cast<std::uintptr_t>(forParticle);
rng.set_stream(ourAddress);
std::uniform_real_distribution<float> distribution(startValue, endValue);
// We need to advance the RNG a bit to make streams with the same seed diverge.
rng.advance(5);
std::uniform_real_distribution<float> distribution(std::min(startValue, endValue), std::max(startValue, endValue));
currentValue = distribution(rng);
}
return currentValue;
@ -590,7 +593,7 @@ void C4ParticleValueProvider::Set(const C4ValueArray &fromArray)
}
else
{
rng.seed(SafeRandom());
rng.seed(UnsyncedRandom());
}
alreadyRolled = 0;
}

View File

@ -18,6 +18,7 @@
#include "platform/StdScheduler.h"
#include <pcg/pcg_random.hpp>
#ifndef INC_C4Particles
#define INC_C4Particles

View File

@ -571,9 +571,9 @@ void C4TextureMap::StoreMapPalette(CStdPalette *Palette, C4MaterialMap &rMateria
if (j >= i) break;
// change randomly
Palette->Colors[i] = C4RGB(
SafeRandom(2) ? GetRedValue(Palette->Colors[i]) + 3 : GetRedValue(Palette->Colors[i]) - 3,
SafeRandom(2) ? GetGreenValue(Palette->Colors[i]) + 3 : GetGreenValue(Palette->Colors[i]) - 3,
SafeRandom(2) ? GetBlueValue(Palette->Colors[i]) + 3 : GetBlueValue(Palette->Colors[i]) - 3);
UnsyncedRandom(2) ? GetRedValue(Palette->Colors[i]) + 3 : GetRedValue(Palette->Colors[i]) - 3,
UnsyncedRandom(2) ? GetGreenValue(Palette->Colors[i]) + 3 : GetGreenValue(Palette->Colors[i]) - 3,
UnsyncedRandom(2) ? GetBlueValue(Palette->Colors[i]) + 3 : GetBlueValue(Palette->Colors[i]) - 3);
}
}

View File

@ -21,14 +21,14 @@
#include "lib/C4Random.h"
#include "control/C4Record.h"
#include <pcg/pcg_random.hpp>
int RandomCount = 0;
static pcg32 RandomRng;
pcg32 SafeRandom;
static pcg32 RandomRng, UnsyncedRandomRng;
void FixedRandom(uint64_t seed)
{
// for SafeRandom
SafeRandom.seed(seed);
UnsyncedRandomRng.seed(seed);
RandomRng.seed(seed);
RandomCount = 0;
}
@ -61,3 +61,22 @@ uint32_t Random(uint32_t iRange)
RecordRandom(iRange, result);
return result;
}
uint32_t UnsyncedRandom()
{
return UnsyncedRandomRng();
}
uint32_t UnsyncedRandom(uint32_t iRange)
{
if (!iRange) return 0u;
return UnsyncedRandomRng(iRange);
}
uint32_t SeededRandom(uint64_t iSeed, uint32_t iRange)
{
if (!iRange) return 0;
pcg32 rng(iSeed);
return rng(iRange);
}

View File

@ -20,20 +20,18 @@
#ifndef INC_C4Random
#define INC_C4Random
#include <pcg/pcg_random.hpp>
#include <inttypes.h>
extern int RandomCount;
extern pcg32 SafeRandom;
// Seeds both the synchronized and the unsynchronized RNGs.
void FixedRandom(uint64_t dwSeed);
// Synchronized RNG.
uint32_t Random(uint32_t iRange);
inline uint32_t SeededRandom(uint64_t iSeed, uint32_t iRange)
{
if (!iRange) return 0;
pcg32 rng(iSeed);
return rng(iRange);
}
// Unsynchronized RNG.
uint32_t UnsyncedRandom();
uint32_t UnsyncedRandom(uint32_t range);
// Generates a single random value from a seed.
uint32_t SeededRandom(uint64_t iSeed, uint32_t iRange);
#endif // INC_C4Random

View File

@ -1888,7 +1888,7 @@ bool C4NetIOUDP::InitBroadcast(addr_t *pBroadcastAddr)
{
// create new - random - address
MCAddr.sin_addr.s_addr =
0x000000ef | (SafeRandom(0x1000000) << 8);
0x000000ef | (UnsyncedRandom(0x1000000) << 8);
// init broadcast
if (!C4NetIOSimpleUDP::InitBroadcast(&MCAddr))
return false;
@ -3017,7 +3017,7 @@ bool C4NetIOUDP::SendDirect(C4NetIOPacket &&rPacket) // (mt-safe)
#ifdef C4NETIO_SIMULATE_PACKETLOSS
if ((rPacket.getStatus() & 0x7F) != IPID_Test)
if (SafeRandom(100) < C4NETIO_SIMULATE_PACKETLOSS) return true;
if (UnsyncedRandom(100) < C4NETIO_SIMULATE_PACKETLOSS) return true;
#endif
// send it
@ -3032,7 +3032,7 @@ bool C4NetIOUDP::DoLoopbackTest()
if (!C4NetIOSimpleUDP::getMCLoopback()) return false;
// send test packet
const PacketHdr TestPacket = { uint8_t(IPID_Test | 0x80), SafeRandom(UINT32_MAX) };
const PacketHdr TestPacket = { uint8_t(IPID_Test | 0x80), UnsyncedRandom(UINT32_MAX) };
if (!C4NetIOSimpleUDP::Broadcast(C4NetIOPacket(&TestPacket, sizeof(TestPacket))))
return false;

View File

@ -268,7 +268,7 @@ int32_t C4Network2ResChunkData::GetChunkToRetrieve(const C4Network2ResChunkData
// invert to get everything that should be retrieved
C4Network2ResChunkData ChData2; ChData.GetNegative(ChData2);
// select chunk (random)
int32_t iRetrieveChunk = SafeRandom(ChData2.getPresentChunkCnt());
int32_t iRetrieveChunk = UnsyncedRandom(ChData2.getPresentChunkCnt());
// return
return ChData2.getPresentChunk(iRetrieveChunk);
}
@ -1029,7 +1029,7 @@ void C4Network2Res::StartNewLoads()
for (pChunks = pCChunks, i = 0; i < iCChunkCnt; i++, pChunks = pChunks->Next)
{
// determine position
int32_t iPos = SafeRandom(iCChunkCnt - i);
int32_t iPos = UnsyncedRandom(iCChunkCnt - i);
// find & set
for (int32_t j = 0; ; j++)
if (!pC[j] && !iPos--)

View File

@ -30,6 +30,7 @@
#include "player/C4RankSystem.h"
#include "platform/C4SoundSystem.h"
#include "landscape/C4SolidMask.h"
#include "landscape/C4Particles.h"
#include "graphics/CSurface8.h"
#include "lib/StdColors.h"

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

@ -48,6 +48,7 @@
#include "control/C4Record.h"
#include "object/C4MeshAnimation.h"
#include "landscape/fow/C4FoW.h"
#include "landscape/C4Particles.h"
namespace
{
@ -1046,7 +1047,7 @@ void C4Object::Execute()
// effects
if (pEffects)
{
pEffects->Execute(this);
C4Effect::Execute(this, &pEffects);
if (!Status) return;
}
// Life
@ -1188,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

@ -22,7 +22,6 @@
#include "game/C4GameScript.h"
#include "graphics/C4Facet.h"
#include "landscape/C4Particles.h"
#include "lib/StdMesh.h"
#include "object/C4Id.h"
#include "object/C4ObjectPtr.h"
@ -175,7 +174,7 @@ public:
StdMeshInstance* pMeshInstance; // Instance for mesh-type objects
C4Effect *pEffects; // linked list of effects
// particle lists that are bound to this object (either in front of behind it)
C4ParticleList *FrontParticles, *BackParticles;
class C4ParticleList *FrontParticles, *BackParticles;
void ClearParticleLists();
uint32_t ColorMod; // color by which the object-drawing is modulated

View File

@ -25,6 +25,7 @@
#include "gui/C4GameMessage.h"
#include "graphics/C4GraphicsResource.h"
#include "landscape/C4Material.h"
#include "landscape/C4Particles.h"
#include "object/C4MeshAnimation.h"
#include "object/C4ObjectCom.h"
#include "object/C4ObjectInfo.h"
@ -91,7 +92,7 @@ static void FnDeathAnnounce(C4Object *Obj)
}
else
{
char idDeathMsg[128+1]; sprintf(idDeathMsg, "IDS_OBJ_DEATH%d", 1 + SafeRandom(MaxDeathMsg));
char idDeathMsg[128+1]; sprintf(idDeathMsg, "IDS_OBJ_DEATH%d", 1 + UnsyncedRandom(MaxDeathMsg));
GameMsgObject(FormatString(LoadResStr(idDeathMsg), Obj->GetName()).getData(), Obj);
}
}
@ -196,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))
@ -2618,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

@ -481,7 +481,7 @@ bool C4MusicSystem::Play(const char *szSongname, bool fLoop, int fadetime_ms, do
else if (check_file_playability == new_file_playability)
{
// Found a fit in the same playability category: Roll for it
if (!SafeRandom(++new_file_num_rolls)) NewFile = check_file;
if (!UnsyncedRandom(++new_file_num_rolls)) NewFile = check_file;
}
else
{
@ -596,11 +596,11 @@ bool C4MusicSystem::ScheduleWaitTime()
{
// Roll for scheduling a break after the next piece.
if (SCounter < 3) return false; // But not right away.
if (SafeRandom(100) >= music_break_chance) return false;
if (UnsyncedRandom(100) >= music_break_chance) return false;
if (music_break_max > 0)
{
int32_t music_break = music_break_min;
if (music_break_max > music_break_min) music_break += SafeRandom(music_break_max - music_break_min); // note that SafeRandom has limited range
if (music_break_max > music_break_min) music_break += UnsyncedRandom(music_break_max - music_break_min); // note that UnsyncedRandom has limited range
if (music_break > 0)
{
is_waiting = true;

View File

@ -116,7 +116,7 @@ C4SoundEffect* C4SoundSystem::GetEffect(const char *szSndName)
// Nothing found? Abort
if(iNumber == 0)
return NULL;
iNumber=SafeRandom(iNumber)+1;
iNumber=UnsyncedRandom(iNumber)+1;
}
// Find requested sound effect in bank
C4SoundEffect *pSfx;

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

@ -44,9 +44,12 @@ public:
// parse error
class C4AulParseError : public C4AulError
{
C4AulParseError() = default;
public:
C4AulParseError(C4ScriptHost *pScript, const char *pMsg, const char *pIdtf = NULL, bool Warn = false); // constructor
C4AulParseError(class C4AulParse * state, const char *pMsg, const char *pIdtf = NULL, bool Warn = false); // constructor
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);
};
// execution error
@ -121,6 +124,8 @@ public:
C4ValueMapNames GlobalConstNames;
C4ValueMapData GlobalConsts;
C4Effect * pGlobalEffects = NULL;
C4AulScriptEngine(); // constructor
~C4AulScriptEngine(); // destructor
void Clear(); // clear data
@ -139,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
@ -150,6 +155,7 @@ public:
friend class C4AulProfiler;
friend class C4ScriptHost;
friend class C4AulParse;
friend class C4CodeGen;
friend class C4AulDebug;
};

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

@ -245,7 +245,10 @@ C4Value C4AulExec::Exec(C4AulBCC *pCPos)
throw C4AulExecError("internal error: function didn't return");
case AB_ERR:
throw C4AulExecError("syntax error: see above for details");
if (pCPos->Par.s)
throw C4AulExecError((std::string("syntax error: ") + pCPos->Par.s->GetCStr()).c_str());
else
throw C4AulExecError("syntax error: see above for details");
case AB_DUP_CONTEXT:
PushValue(AulExec.GetContext(AulExec.GetContextDepth()-2)->Pars[pCPos->Par.i]);

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)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
#ifndef INC_C4AulParse
#define INC_C4AulParse
#include "script/C4Aul.h"
#include "script/C4AulScriptFunc.h"
enum C4AulBCCType : int;
enum C4AulTokenType : int;
class C4CodeGen
{
public:
C4AulScriptFunc *Fn;
bool fJump = false;
int iStack = 0;
int GetStackValue(C4AulBCCType eType, intptr_t X = 0);
int AddBCC(const char * SPos, C4AulBCCType eType, intptr_t X = 0);
void ErrorOut(const char * SPos, C4AulError & e);
void RemoveLastBCC();
C4V_Type GetLastRetType(C4AulScriptEngine * Engine, C4V_Type to); // for warning purposes
C4AulBCC MakeSetter(const char * TokenSPos, bool fLeaveValue = false); // Prepares to generate a setter for the last value that was generated
int JumpHere(); // Get position for a later jump to next instruction added
void SetJumpHere(int iJumpOp); // Use the next inserted instruction as jump target for the given jump operation
void SetJump(int iJumpOp, int iWhere);
void AddJump(const char * SPos, C4AulBCCType eType, int iWhere);
// Keep track of loops and break/continue usages
struct Loop
{
struct Control
{
bool Break;
int Pos;
Control *Next;
};
Control *Controls;
int StackSize;
Loop *Next;
};
Loop *pLoopStack = NULL;
void PushLoop();
void PopLoop(int ContinueJump);
void AddLoopControl(const char * SPos, bool fBreak);
~C4CodeGen()
{ while (pLoopStack) PopLoop(0); }
};
struct C4ScriptOpDef
{
unsigned short Priority;
const char* Identifier;
C4AulBCCType Code;
bool Postfix;
bool Changer; // changes first operand to result, rewrite to "a = a (op) b"
bool NoSecondStatement; // no second statement expected (++/-- postfix)
C4V_Type RetType; // type returned. ignored by C4V
C4V_Type Type1;
C4V_Type Type2;
};
extern const C4ScriptOpDef C4ScriptOpMap[];
class C4AulParse
{
public:
enum Type { PARSER, PREPARSER };
C4AulParse(C4ScriptHost * a, enum Type Type);
C4AulParse(C4AulScriptFunc * Fn, C4AulScriptContext* context, C4AulScriptEngine *Engine);
~C4AulParse();
void Parse_DirectExec();
void Parse_Script(C4ScriptHost *);
private:
C4AulScriptFunc *Fn; C4ScriptHost * Host; C4ScriptHost * pOrgScript;
C4AulScriptEngine *Engine;
const char *SPos; // current position in the script
const char *TokenSPos; // start of the current token in the script
char Idtf[C4AUL_MAX_Identifier]; // current identifier
C4AulTokenType TokenType; // current token type
int32_t cInt; // current int constant
C4String * cStr; // current string constant
enum Type Type; // emitting bytecode?
C4AulScriptContext* ContextToExecIn;
void Parse_Function();
void Parse_FuncBody();
void Parse_Statement();
void Parse_Block();
int Parse_Params(int iMaxCnt, const char * sWarn, C4AulFunc * pFunc = 0);
void Parse_Array();
void Parse_PropList();
void Parse_DoWhile();
void Parse_While();
void Parse_If();
void Parse_For();
void Parse_ForEach();
void Parse_Expression(int iParentPrio = -1);
void Parse_Var();
void Parse_Local();
void Parse_Static();
void Parse_Const();
C4Value Parse_ConstExpression(C4PropListStatic * parent, C4String * Name);
C4Value Parse_ConstPropList(C4PropListStatic * parent, C4String * Name);
void Store_Const(C4PropListStatic * parent, C4String * Name, const C4Value & v);
bool AdvanceSpaces(); // skip whitespaces; return whether script ended
int GetOperator(const char* pScript);
void ClearToken(); // clear any data held with the current token
C4AulTokenType GetNextToken(); // get next token of SPos
void Shift();
void Match(C4AulTokenType TokenType, const char * Expected = NULL);
void Check(C4AulTokenType TokenType, const char * Expected = NULL);
NORETURN void UnexpectedToken(const char * Expected);
static const char * GetTokenName(C4AulTokenType TokenType);
void Warn(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O;
void Error(const char *pMsg, ...) GNUC_FORMAT_ATTRIBUTE_O;
void AppendPosition(StdStrBuf & Buf);
int AddVarAccess(C4AulBCCType eType, intptr_t varnum);
void DebugChunk();
C4CodeGen codegen;
int AddBCC(C4AulBCCType eType, intptr_t X = 0)
{ if (Type == PARSER) return codegen.AddBCC(TokenSPos, eType, X); else return -1; }
C4V_Type GetLastRetType(C4V_Type to)
{ return codegen.GetLastRetType(Engine, to); }
C4AulBCC MakeSetter(bool fLeaveValue = false)
{ return Type == PARSER ? codegen.MakeSetter(TokenSPos, fLeaveValue) : C4AulBCC(AB_ERR, 0); }
void SetJumpHere(int iJumpOp)
{ if (Type == PARSER) codegen.SetJumpHere(iJumpOp); }
void AddJump(C4AulBCCType eType, int iWhere)
{ if (Type == PARSER) codegen.AddJump(TokenSPos, eType, iWhere); }
void PushLoop()
{ if (Type == PARSER) codegen.PushLoop(); }
void PopLoop(int Jump)
{ if (Type == PARSER) codegen.PopLoop(Jump); }
friend class C4AulParseError;
};
#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

@ -21,7 +21,7 @@
// byte code chunk type
// some special script functions defined hard-coded to reduce the exec context
enum C4AulBCCType
enum C4AulBCCType : int
{
AB_ARRAYA, // array or proplist access
AB_ARRAYA_SET,
@ -93,18 +93,80 @@ enum C4AulBCCType
};
// 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_EOFN) { }
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_EOFN;
}
C4AulBCC & operator = (C4AulBCC && from)
{
DecRef();
bccType = from.bccType;
Par = from.Par;
from.bccType = AB_EOFN;
return *this;
}
~C4AulBCC()
{
DecRef();
}
private:
void IncRef()
{
switch (bccType)
{
case AB_ERR:
if (Par.s)
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_ERR:
if (Par.s)
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
@ -121,6 +183,7 @@ protected:
int GetCodePos() const { return Code.size(); }
C4AulBCC *GetCodeByPos(int iPos) { return &Code[iPos]; }
C4AulBCC *GetLastCode() { return Code.empty() ? NULL : &Code.back(); }
void DumpByteCode();
std::vector<C4AulBCC> Code;
std::vector<const char *> PosForCode;
int ParCount;
@ -156,6 +219,7 @@ public:
uint32_t tProfileTime; // internally set by profiler
friend class C4CodeGen;
friend class C4AulParse;
friend class C4ScriptHost;
};

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() ? CommandTarget._getPropList()->IsStatic() : NULL;
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());
@ -503,11 +646,6 @@ static long FnRandom(C4PropList * _this, long iRange)
return Random(iRange);
}
static long FnAsyncRandom(C4PropList * _this, long iRange)
{
return SafeRandom(iRange);
}
static int FnGetType(C4PropList * _this, const C4Value & Value)
{
// dynamic types
@ -738,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},
@ -758,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 },
@ -791,14 +957,22 @@ void InitCoreFunctionMap(C4AulScriptEngine *pEngine)
F(BoundBy);
F(Inside);
F(Random);
F(AsyncRandom);
F(CreateArray);
F(CreatePropList);
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 ---*/
@ -33,7 +34,6 @@ C4ScriptHost::C4ScriptHost():
Script = NULL;
stringTable = 0;
SourceScripts.push_back(this);
LocalNamed.Reset();
// prepare include list
IncludesResolved = false;
Resolving=false;
@ -48,9 +48,8 @@ C4ScriptHost::~C4ScriptHost()
void C4ScriptHost::Clear()
{
ComponentHost.Clear();
C4ComponentHost::Clear();
Script.Clear();
LocalNamed.Reset();
LocalValues.Clear();
SourceScripts.clear();
SourceScripts.push_back(this);
@ -97,7 +96,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 +105,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 +144,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 +158,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 +299,7 @@ void C4GameScriptHost::Clear()
C4ScriptHost::Clear();
ScenPropList.Set0();
ScenPrototype.Set0();
delete pScenarioEffects; pScenarioEffects=NULL;
}
C4PropListStatic * C4GameScriptHost::GetPropList()
@ -308,6 +308,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();
@ -45,6 +45,7 @@ public:
virtual bool LoadData(const char *szFilename, const char *szData, class C4LangStringTable *pLocalTable);
void Reg2List(C4AulScriptEngine *pEngine); // reg to linked list
virtual C4PropListStatic * GetPropList() { return 0; }
const C4PropListStatic *GetPropList() const { return const_cast<C4ScriptHost*>(this)->GetPropList(); }
const char *GetScript() const { return Script.getData(); }
bool IsReady() { return State == ASS_PARSED; } // whether script calls may be done
// Translate a string using the script's lang table
@ -56,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
@ -79,7 +79,6 @@ protected:
StdStrBuf Script; // script
C4LangStringTable *stringTable;
C4ValueMapNames LocalNamed;
C4Set<C4Property> LocalValues;
C4AulScriptState State; // script state
friend class C4AulParse;
@ -131,9 +130,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

@ -466,7 +466,7 @@ int32_t C4ValueMapNames::AddName(const char *pnName)
return iSize-1;
}
int32_t C4ValueMapNames::GetItemNr(const char *strName)
int32_t C4ValueMapNames::GetItemNr(const char *strName) const
{
for (int32_t i = 0; i < iSize; i++)
if (SEqual(pNames[i], strName))

View File

@ -118,7 +118,7 @@ public:
// returns the nr of the given name
// (= nr of value in "child" data lists)
// returns -1 if no item with given name exists
int32_t GetItemNr(const char *strName);
int32_t GetItemNr(const char *strName) const;
// get name by index; awway bounds not checked
const char *GetItemUnsafe(int32_t idx) const { return pNames[idx]; }

View File

@ -31,7 +31,7 @@ using ::testing::_;
TEST_F(AulPredefFunctionTest, Translate)
{
// Expect the engine to warn when it can't find a translation
LogMock log;
::testing::NiceMock<LogMock> log;
EXPECT_CALL(log, DebugLogF(testing::StrEq(R"(WARNING: Translate: no translation for string "%s")"), _));
EXPECT_CALL(log, DebugLog(StartsWith(" by: "))).Times(AnyNumber()); // ignore stack trace
@ -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)
{
@ -107,6 +108,34 @@ TEST_F(AulTest, Loops)
EXPECT_EQ(C4Value(), RunCode("var a = [], sum; for(var i in a) sum += i; return sum;"));
EXPECT_EQ(C4VInt(1), RunCode("var a = [1], sum; for(var i in a) sum += i; return sum;"));
EXPECT_EQ(C4VInt(6), RunCode("var a = [1,2,3], sum; for(var i in a) sum += i; return sum;"));
EXPECT_EQ(C4VInt(-6), RunCode(R"(
var a = [-3, -2, -1, 0, 1, 2, 3], b;
for (var i in a) {
if (i > 0) break;
b += i;
}
return b;
)"));
EXPECT_EQ(C4VInt(0), RunCode(R"(
var a = [-3, -2, -1, 0, 1, 2, 3], b;
for (var i in a) {
if (i < -1) continue;
if (i > 1) break;
b += i;
}
return b;
)"));
// Test nested loops
EXPECT_EQ(C4VInt(-6), RunCode(R"(
var a = [[-3, -2], [-1, 0], [1, 2, 3]], b;
for (var i in a) {
for (var j in i) {
if (j > 0) break;
b += j;
}
}
return b;
)"));
}
TEST_F(AulTest, Locals)
@ -131,3 +160,63 @@ TEST_F(AulTest, Vars)
EXPECT_EQ(C4VInt(42), RunCode("var i = 21; i = i + i; return i;"));
EXPECT_EQ(C4VInt(42), RunCode("var i = -42; i = Abs(i); return i;"));
}
TEST_F(AulTest, ParameterPassing)
{
EXPECT_EQ(C4VArray(
C4VInt(1), C4VInt(2), C4VInt(3), C4VInt(4), C4VInt(5),
C4VInt(6), C4VInt(7), C4VInt(8), C4VInt(9), C4VInt(10)),
RunCode(R"(
func f(...)
{
return [Par(0), Par(1), Par(2), Par(3), Par(4), Par(5), Par(6), Par(7), Par(8), Par(9)];
}
func Main()
{
return f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
)", false));
EXPECT_EQ(C4VArray(
C4VInt(1), C4VInt(2), C4VInt(3), C4VInt(4), C4VInt(5),
C4VInt(6), C4VInt(7), C4VInt(8), C4VInt(9), C4VInt(10)),
RunCode(R"(
func f(a, b, ...)
{
return g(b, a, ...);
}
func g(...)
{
return [Par(0), Par(1), Par(2), Par(3), Par(4), Par(5), Par(6), Par(7), Par(8), Par(9)];
}
func Main()
{
return f(2, 1, 3, 4, 5, 6, 7, 8, 9, 10);
}
)", false));
}
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));
}

View File

@ -50,20 +50,21 @@ for the above references.
bccType==AB_PROP_SET ||
bccType==AB_LOCALN ||
bccType==AB_LOCALN_SET ||
bccType==AB_GLOBALN ||
bccType==AB_GLOBALN_SET ||
bccType==AB_CALL ||
bccType==AB_CALLFS
">{bccType,en} {Par.s,na}</DisplayString>
<!-- opcodes with int parameter -->
">{bccType,en} {Par.s,na} [{stack}]</DisplayString>
<DisplayString Condition="
bccType==AB_GLOBALN ||
bccType==AB_GLOBALN_SET
">
{bccType,en} {::ScriptEngine.GlobalNamed.pNames->pNames[Par.i],na} [{stack}]</DisplayString>
<!-- opcodes with int parameter -->
<DisplayString Condition="
bccType==AB_INT ||
bccType==AB_BOOL ||
bccType==AB_DUP ||
bccType==AB_STACK_SET ||
bccType==AB_POP_TO ||
bccType==AB_GLOBALN ||
bccType==AB_GLOBALN_SET ||
bccType==AB_NEW_ARRAY ||
bccType==AB_NEW_PROPLIST ||
bccType==AB_STACK ||
@ -74,20 +75,20 @@ for the above references.
bccType==AB_CONDN ||
bccType==AB_COND ||
bccType==AB_FOREACH_NEXT
">{bccType,en} {Par.i}</DisplayString>
">{bccType,en} {Par.i} [{stack}]</DisplayString>
<!-- opcodes with function parameter -->
<DisplayString Condition="
bccType==AB_CFUNCTION ||
bccType==AB_FUNC
">{bccType,en} {Par.f}</DisplayString>
">{bccType,en} {Par.f,na} [{stack}]</DisplayString>
<!-- opcodes with proplist parameter -->
<DisplayString Condition="
bccType==AB_CPROPLIST
">{bccType,en} {Par.p}</DisplayString>
">{bccType,en} {Par.p} [{stack}]</DisplayString>
<!-- opcodes with array parameter -->
<DisplayString Condition="
bccType==AB_CARRAY
">{bccType,en} {Par.a}</DisplayString>
">{bccType,en} {Par.a} [{stack}]</DisplayString>
<!-- opcodes without parameters -->
<DisplayString Condition="
bccType==AB_NIL ||
@ -97,6 +98,7 @@ for the above references.
bccType==AB_Not ||
bccType==AB_Neg ||
bccType==AB_Inc ||
bccType==AB_Dec ||
bccType==AB_Pow ||
bccType==AB_Div ||
bccType==AB_Mul ||
@ -122,9 +124,56 @@ for the above references.
bccType==AB_PAR ||
bccType==AB_THIS ||
bccType==AB_DEBUG
">{bccType,en}</DisplayString>
">{bccType,en} [{stack}]</DisplayString>
<!-- unknown opcodes -->
<DisplayString>{bccType,en} {Par,na}</DisplayString>
<DisplayString>{bccType,en} {Par,na} [{stack}]</DisplayString>
<!-- Expansions -->
<Expand>
<Item Name="[opcode]">bccType</Item>
<Item Condition="
bccType==AB_STRING ||
bccType==AB_PROP ||
bccType==AB_PROP_SET ||
bccType==AB_LOCALN ||
bccType==AB_LOCALN_SET ||
bccType==AB_CALL ||
bccType==AB_CALLFS"
Name="[par]">Par.s</Item>
<Item Condition="
bccType==AB_INT ||
bccType==AB_BOOL ||
bccType==AB_DUP ||
bccType==AB_STACK_SET ||
bccType==AB_POP_TO ||
bccType==AB_GLOBALN ||
bccType==AB_GLOBALN_SET ||
bccType==AB_NEW_ARRAY ||
bccType==AB_NEW_PROPLIST ||
bccType==AB_STACK ||
bccType==AB_JUMP ||
bccType==AB_JUMPAND ||
bccType==AB_JUMPOR ||
bccType==AB_JUMPNNIL ||
bccType==AB_CONDN ||
bccType==AB_COND ||
bccType==AB_FOREACH_NEXT
" Name="[par]">Par.i</Item>
<Item Condition="
bccType==AB_JUMP ||
bccType==AB_JUMPAND ||
bccType==AB_JUMPOR ||
bccType==AB_JUMPNNIL ||
bccType==AB_CONDN ||
bccType==AB_COND
" Name="[jump]">this[Par.i]</Item>
<Synthetic Name="[code]" Optional="true">
<DisplayString>{code,na}</DisplayString>
</Synthetic>
</Expand>
</Type>
<Type Name="C4Value">
@ -166,7 +215,7 @@ for the above references.
<Variable Name="slot" InitialValue="0"/>
<Size>Size</Size>
<Loop>
<If Condition="Table[slot].Key != nullptr">
<If Condition="Table[slot].Key != 0">
<Item Name="[{slot}]">Table[slot]</Item>
</If>
<Exec>++slot</Exec>
@ -177,7 +226,9 @@ for the above references.
</Type>
<Type Name="C4AulScriptFunc">
<DisplayString>{*Parent,view(name)}::{Name,sb}</DisplayString>
<DisplayString Condition="((C4DefScriptHost*)pOrgScript)->Def == Parent">{*Parent,view(name)}::{Name,sb}</DisplayString>
<DisplayString Condition="&amp;ScriptEngine == Parent">Global::{Name,sb}</DisplayString>
<DisplayString>{*Parent,view(name)}-&gt;{*pOrgScript,view(name)}::{Name,sb}</DisplayString>
<Expand>
<Synthetic Name="[parameters]">
<!--<DisplayString>{ParType,[ParCount]}</DisplayString>-->
@ -191,10 +242,23 @@ for the above references.
</Expand>
</Type>
<Type Name="C4AulScriptEngine">
<DisplayString IncludeView="name">Global</DisplayString>
<DisplayString>ScriptEngine</DisplayString>
</Type>
<Type Name="C4AulFunc">
<DisplayString>{Name,sb}</DisplayString>
</Type>
<Type Name="C4DefScriptHost">
<DisplayString IncludeView="name">{*Def,view(name)}</DisplayString>
<DisplayString>{*Def}</DisplayString>
</Type>
<Type Name="C4ExtraScriptHost">
<DisplayString IncludeView="name">{{{ComponentHost.Filename,sb}}}</DisplayString>
<DisplayString>{ScriptName}</DisplayString>
</Type>
<Type Name="C4GameScriptHost">
<DisplayString IncludeView="name">{{scenario}}</DisplayString>
<DisplayString>{ScriptName}</DisplayString>