1 /* User action execution handler */
2 // Handles actions set in editor e.g. for dialogues, switches, etc.
3 // An object is sometimes needed to show a menu or start a timer, so this definition is created whenever a user action is run
4 
5 local Name = "UserAction";
6 local Plane=0;
7 
8 /* UserAction definition */
9 
10 // Base classes for EditorProps using actions
11 local Evaluator;
12 
13 // EditorProps for generic user action callbacks
14 local Prop, PropProgressMode, PropParallel;
15 
16 // Base props for action execution conditions
17 local ActionEvaluation ;
18 
19 // Proplist containing callback function. Indexed by option names.
20 local EvaluatorCallbacks;
21 
22 // Proplist containing option definitions. Indexed by option names.
23 local EvaluatorDefs;
24 
25 // Call this definition early (but after EditorBase) to allow EditorProp initialization
26 local DefinitionPriority=99;
27 
28 // Localized group names
29 local GroupNames = { Structure="$Structure$", Game="$Game$", Ambience="$Ambience$", Disasters="$Disasters$" };
30 
31 // Storage for global user variables
32 static g_UserAction_global_vars;
33 
34 // Localized evaluator names
35 local EvaluatorTypeNames = {
36 	Action = "$UserAction$",
37 	Color = "$UserColor$",
38 	Object = "$UserObject$",
39 	ObjectList = "$UserObjectList$",
40 	Definition = "$UserDefinition$",
41 	Player = "$UserPlayer$",
42 	PlayerList = "$UserPlayerList$",
43 	Boolean = "$UserBoolean$",
44 	Integer = "$UserInteger$",
45 	String = "$UserString$",
46 	Position = "$UserPosition$",
47 	Offset = "$UserOffset$"
48 };
49 
50 // All evaluator types (unfortunately, EvaluatorReturnTypes->GetProperties() does not work)
51 local EvaluatorTypes = ["Action", "Object", "ObjectList", "Definition", "Player", "PlayerList", "Boolean", "Integer", "Color", "String", "Position", "Offset", "Any"];
52 
53 // Evaluator return types
54 local EvaluatorReturnTypes = {
55 	Action = C4V_Nil,
56 	Object = C4V_C4Object,
57 	ObjectList = [C4V_C4Object],
58 	Definition = C4V_Def,
59 	Player = C4V_Int,
60 	PlayerList = [C4V_Int],
61 	Boolean = C4V_Bool,
62 	Integer = C4V_Int,
63 	String = C4V_String,
64 	Position = [C4V_Int, 2],
65 	Offset = [C4V_Int, 2],
66 	Any = C4V_Nil
67 };
68 
69 // Array of specialized object evaluators
70 local object_evaluators;
71 
Definition(def)72 func Definition(def)
73 {
74 	// Typed evaluator base definitions
75 	Evaluator = {};
76 	Evaluator.Action = { Name="$UserAction$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
77 	Evaluator.Object = { Name="$UserObject$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
78 	Evaluator.ObjectList = { Name="$UserObjectList$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
79 	Evaluator.Definition = { Name="$UserDefinition$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
80 	Evaluator.Player = { Name="$UserPlayer$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Noone$", Priority=100 } ] };
81 	Evaluator.PlayerList = { Name="$UserPlayerList$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Noone$", Priority=100 } ] };
82 	Evaluator.Boolean = { Name="$UserBoolean$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
83 	Evaluator.Integer = { Name="$UserInteger$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="0", Priority=100 } ] };
84 	Evaluator.Color = { Name="$UserColor$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="$Default$", Priority=100 } ] };
85 	Evaluator.String = { Name="$UserString$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="($EmptyString$)", Priority=100 } ] };
86 	Evaluator.Position = { Name="$UserPosition$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Here$", Priority=100 } ] };
87 	Evaluator.Offset = { Name="$UserOffset$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
88 	Evaluator.Any = { Name="$UserAny$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
89 	EvaluatorCallbacks = {};
90 	EvaluatorDefs = {};
91 	// Object constant evaluator may be needed early be evaluators referencing filtered objects
92 	AddEvaluator("Object", nil, ["$ConstantObject$", ""], "$ConstantObjectHelp$", "object_constant", [def, def.EvalConstant], { Value=nil }, { Type="object", Name="$Value$" });
93 		// Action evaluators
94 	AddEvaluator("Action", "$Sequence$", "$Sequence$", "$SequenceHelp$", "sequence", [def, def.EvalAct_Sequence], { Actions=[] }, { Type="proplist", DescendPath="Actions", HideFullName=true, Display="{{Actions}}", EditorProps = {
95 		Actions = { Name="$Actions$", Type="array", Elements=Evaluator.Action },
96 		} } );
97 	AddEvaluator("Action", "$Sequence$", "$Goto$", "$GotoHelp$", "goto", [def, def.EvalAct_Goto], { Index={Function="int_constant", Value=0} }, new Evaluator.Integer { Name="$Index$" }, "Index");
98 	AddEvaluator("Action", "$Sequence$", "$StopSequence$", "$StopSequenceHelp$", "stop_sequence", [def, def.EvalAct_StopSequence]);
99 	AddEvaluator("Action", "$Sequence$", "$SuspendSequence$", "$SuspendSequenceHelp$", "suspend_sequence", [def, def.EvalAct_SuspendSequence]);
100 	AddEvaluator("Action", "$Sequence$", "$Wait$", "$WaitHelp$", "wait", [def, def.EvalAct_Wait], { Time=60 }, { Type="proplist", Display="{{Time}}", EditorProps = {
101 		Time = { Name="$Time$", Type="int", Min=1 }
102 		} } );
103 	AddEvaluator("Action", "$Sequence$", "$WaitForcondition$", "$WaitForConditionHelp$", "wait_condition", [def, def.EvalAct_WaitCondition], { Interval=20 }, { Type="proplist", Display="{{Condition}}", EditorProps = {
104 		Interval = { Name="$CheckInterval$", Type="int", Min=1 },
105 		Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$WaitConditionHelp$", Priority=60 }
106 		} } );
107 	AddEvaluator("Action", "$Ambience$", "$Sound$", "$SoundHelp$", "sound", [def, def.EvalAct_Sound], { Pitch={Function="int_constant", Value=0}, Volume={Function="int_constant", Value=100}, TargetPlayers={Function="all_players"} }, { Type="proplist", Display="{{Sound}}", EditorProps = {
108 		Sound = { Name="$SoundName$", EditorHelp="$SoundNameHelp$", Type="sound", AllowEditing=true, Priority=100 },
109 		Pitch = new Evaluator.Integer { Name="$SoundPitch$", EditorHelp="$SoundPitchHelp$" },
110 		Volume = new Evaluator.Integer { Name="$SoundVolume$", EditorHelp="$SoundVolumeHelp$" },
111 		Loop = { Name="$SoundLoop$", EditorHelp="$SoundLoopHelp$", Type="enum", Options=[
112 			{ Name="$SoundLoopNone$" },
113 			{ Name="$SoundLoopOn$", Value=+1 },
114 			{ Name="$SoundLoopOff$", Value=-1 }
115 			] },
116 		TargetPlayers = new Evaluator.PlayerList { EditorHelp="$SoundTargetPlayersHelp$" },
117 		SourceObject = new Evaluator.Object { Name="$SoundSourceObject$", EditorHelp="$SoundSourceObjectHelp$", EmptyName="$Global$" }
118 		} } );
119 	AddEvaluator("Action", "$Object$", "$CreateObject$", "$CreateObjectHelp$", "create_object", [def, def.EvalAct_CreateObject], { SpeedX={Function="int_constant", Value=0},SpeedY={Function="int_constant", Value=0},SpeedR={Function="int_constant", Value=0},Rotation={Function="int_constant", Value=0} }, { Type="proplist", Display="{{ID}}", EditorProps = {
120 		ID = new Evaluator.Definition { EditorHelp="$CreateObjectDefinitionHelp$", Priority=100 },
121 		Position = new Evaluator.Position { EditorHelp="$CreateObjectPositionHelp$" },
122 		CreateAbove = { Name="$CreateObjectCreationOffset$", EditorHelp="$CreateObjectCreationOffsetHelp$", Type="enum", Options=[
123 			{ Name="$Center$" },
124 			{ Name="$Bottom$", Value=true }
125 			]},
126 		Owner = new Evaluator.Player { Name="$Owner$", EditorHelp="$CreateObjectOwnerHelp$" },
127 		Container = new Evaluator.Object { Name="$Container$", EditorHelp="$CreateObjectContainerHelp$" },
128 		SpeedX = new Evaluator.Integer { Name="$SpeedX$", EditorHelp="$CreateObjectSpeedXHelp$" },
129 		SpeedY = new Evaluator.Integer { Name="$SpeedY$", EditorHelp="$CreateObjectSpeedYHelp$" },
130 		Rotation = new Evaluator.Integer { Name="$Rotation$", EditorHelp="$CreateObjectRotationHelp$" },
131 		SpeedR = new Evaluator.Integer { Name="$SpeedR$", EditorHelp="$CreateObjectSpeedRHelp$" }
132 		} } );
133 	AddEvaluator("Action", "$Object$", "$CastObjects$", "$CastObjectsHelp$", "cast_objects", [def, def.EvalAct_CastObjects], { Amount={Function="int_constant", Value=8},Speed={Function="int_constant", Value=20},AngleDeviation={Function="int_constant", Value=360} }, { Type="proplist", Display="{{Amount}}x{{ID}}", EditorProps = {
134 		ID = new Evaluator.Definition { EditorHelp="$CastObjectsDefinitionHelp$", Priority=100 },
135 		Position = new Evaluator.Position { EditorHelp="$CastObjectsPositionHelp$" },
136 		Amount = new Evaluator.Integer { Name="$Amount$", EditorHelp="$CastObjectsAmountHelp$" },
137 		Speed = new Evaluator.Integer { Name="$Speed$", EditorHelp="$CastObjectsSpeedHelp$" },
138 		MeanAngle = new Evaluator.Integer { Name="$MeanAngle$", EditorHelp="$CastObjectsMeanAngleHelp$" },
139 		AngleDeviation = new Evaluator.Integer { Name="$AngleDeviation$", EditorHelp="$CastObjectsAngleDeviationHelp$" },
140 		Owner = new Evaluator.Player { Name="$Owner$", EditorHelp="$CastObjectsOwnerHelp$" }
141 		} } );
142 	AddEvaluator("Action", "$Object$", "$RemoveObject$", "$RemoveObjectHelp$", "remove_object", [def, def.EvalAct_RemoveObject], { }, { Type="proplist", Display="{{Object}}", EditorProps = {
143 		Object = new Evaluator.Object { EditorHelp="$RemoveObjectObject$", Priority=100 },
144 		EjectContents = { Name="$EjectContents$", EditorHelp="$EjectContentsHelp$", Type="enum", Options=[
145 			{ Name="$EjectContentsNo$" },
146 			{ Name="$EjectContentsYes$", Value=true }
147 			] },
148 		} } );
149 	AddEvaluator("Action", "$Object$", "$SetPosition$", "$SetPositionHelp$", "set_position", [def, def.EvalAct_SetPosition], { Object={ Function="triggering_clonk" }, Position={ Function="position_constant_rel" } }, { Type="proplist", Display="({{Object}}->{{Position}})", EditorProps = {
150 		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$SetPositionObjectHelp$" },
151 		Position = new Evaluator.Position { Name="$Position$", EditorHelp="$SetPositionPositionHelp$" }
152 		} } );
153 	AddEvaluator("Action", "$Object$", "$Fling$", "$FlingHelp$", "fling", [def, def.EvalAct_Fling], { Object={ Function="triggering_clonk" }, SpeedX={ Function="int_constant", Value=0 }, SpeedY={ Function="int_constant", Value=-20 }, AddSpeed={ Function="bool_constant", Value=false } }, { Type="proplist", Display="({{Object}}, {{SpeedX}}, {{SpeedY}})", EditorProps = {
154 		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$FlingObjectHelp$" },
155 		SpeedX = new Evaluator.Integer { Name="$SpeedX$", EditorHelp="$FlingSpeedXHelp$" },
156 		SpeedY = new Evaluator.Integer { Name="$SpeedY$", EditorHelp="$FlingSpeedYHelp$" },
157 		AddSpeed = new Evaluator.Boolean { Name="$AddSpeedY$", EditorHelp="$FlingAddSpeedHelp$" },
158 		} } );
159 	AddEvaluator("Action", "$Object$", "$EnterObject$", "$EnterObjectHelp$", "enter_object", [def, def.EvalAct_EnterObject], { }, { Type="proplist", Display="{{Object}} -> {{Container}}", EditorProps = {
160 		Object = new Evaluator.Object { EditorHelp="$EnterObjectObjectHelp$", Priority=90 },
161 		Container = new Evaluator.Object { Name="$Container$", EditorHelp="$EnterObjectContainerHelp$", Priority=80 },
162 		CollectionCheck = { Name="$CollectionCheck$", EditorHelp="$CollectionCheckHelp$", Type="enum", Options=[
163 			{ Name="$CollectionCheckIgnore$" },
164 			{ Name="$CollectionCheckCheck$", Value="check" },
165 			{ Name="$CollectionCheckExit$", Value="exit" }
166 			] }
167 		} } );
168 	AddEvaluator("Action", "$Object$", "$ExitObject$", "$ExitObjectHelp$", "exit_object", [def, def.EvalAct_ExitObject], { }, new Evaluator.Object { }, "Object");
169 	AddEvaluator("Action", "$Object$", "$SetVisibility$", "$SetVisibilityHelp$", "set_visibility", [def, def.EvalAct_SetVisibility], { Object={ Function="triggering_clonk" }, Visibility=VIS_All }, { Type="proplist", Display="({{Object}}, {{Visibility}})", EditorProps = {
170 		Object = new Evaluator.Object { Name="$Object$" },
171 		Visibility = { Name="$Visibility$", Type="enum", Options = [{ Name="$Visible$", Value=VIS_All }, { Name="$Invisible$", Value=VIS_None }] }
172 		} } );
173 	AddEvaluator("Action", "$Object$", "$SetVincibility$", "$SetVincibilityHelp$", "set_vincibility", [def, def.EvalAct_SetVincibility], { Object={ Function="triggering_clonk" }, Vincibility=false }, { Type="proplist", Display="({{Object}}, {{Vincibility}})", EditorProps = {
174 		Object = new Evaluator.Object { Name="$Object$" },
175 		Vincibility = { Name="$Vincibility$", Type="enum", Options = [{ Name="$Invincible$", Value=false }, { Name="$Vincible$", Value=true }] }
176 		} } );
177 	AddEvaluator("Action", "Clonk", "$DoEnergy$", "$DoEnergyHelp$", "do_energy", [def, def.EvalAct_ObjectCallInt, Global.DoEnergy], { Object={ Function="triggering_clonk" } }, { Type="proplist", Display="({{Object}}, {{Value}})", EditorProps = {
178 		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$DoEnergyObjectHelp$" },
179 		Value = new Evaluator.Integer { Name="$ValueChange$", EditorHelp="$DoEnergyValueChangeHelp$" }
180 		} } );
181 	AddEvaluator("Action", "Clonk", "$SetDirection$", "$SetDirectionHelp$", "set_direction", [def, def.EvalAct_SetDirection], { Object={ Function="triggering_clonk" }, Direction=DIR_Left }, { Type="proplist", Display="({{Object}}, {{Direction}})", EditorProps = {
182 		Object = GetObjectEvaluator("IsClonk", "$Clonk$"),
183 		Direction = { Name="$Direction$", Type="enum", Options=[{ Name="$Left$", Value=DIR_Left }, { Name="$Right$", Value=DIR_Right }] }
184 		} } );
185 	AddEvaluator("Action", "Ambience", "$CastParticles$", "$CastParticlesHelp$", "cast_particles", [def, def.EvalAct_CastParticles], {
186 			Name="StarFlash",
187 			Amount={Function="int_constant", Value=8},
188 			Speed={Function="int_constant", Value=20},
189 			Lifetime={Function="int_constant", Value=100},
190 			Size={Function="int_constant", Value=10},
191 			SizeEnd={Function="int_constant", Value=1},
192 			Color={Function="color_constant", Value=0xffff},
193 			BlitMode=0,
194 			Gravity={Function="int_constant", Value=100},
195 			FadeOut=true,
196 			CollisionFunc="bounce"
197 		}, { Type="proplist", Display="{{Amount}}x{{Name}}", EditorProps = {
198 		Name = { Name="$ParticleName$", EditorHelp="$ParticleNameHelp$", Type="enum", Priority=50, Sorted=true, Options = [
199 			{ Name="$Dust$", Value="Dust" },
200 			{ Name="$Flash$", Value="Flash" },
201 			{ Name="$Magic$", Value="Magic" },
202 			{ Name="$Smoke$", Value="Smoke" },
203 			{ Name="$Sphere$", Value="Sphere" },
204 			{ Name="$StarFlash$", Value="StarFlash" },
205 			{ Name="$StarSpark$", Value="StarSpark" },
206 			{ Name="$Fire$", Value="MagicFire" },
207 			{ Name="$Ring$", Value="MagicRing" }
208 			] },
209 		Position = new Evaluator.Position { EditorHelp="$CastObjectsPositionHelp$" },
210 		Amount = new Evaluator.Integer { Name="$Amount$", EditorHelp="$CastParticlesAmountHelp$" },
211 		Speed = new Evaluator.Integer { Name="$Speed$", EditorHelp="$CastParticlesSpeedHelp$" },
212 		Lifetime = new Evaluator.Integer { Name="$Lifetime$", EditorHelp="$CastParticlesLifetimeHelp$" },
213 		Size = new Evaluator.Integer { Name="$Size$", EditorHelp="$CastParticlesSizeHelp$" },
214 		SizeEnd = new Evaluator.Integer { Name="$SizeEnd$", EditorHelp="$CastParticlesSizeEndHelp$" },
215 		Color = new Evaluator.Color { Name="$Color$", EditorHelp="$CastParticlesColorHelp$" },
216 		BlitMode = { Name="$BlitMode$", EditorHelp="$ParticleBlitModeHelp$", Type="enum", Options = [
217 			{ Name="$Normal$", Value=0 },
218 			{ Name="$Additive$", Value=GFX_BLIT_Additive },
219 			{ Name="$Mod2$", Value=GFX_BLIT_Mod2 }
220 			] },
221 		Gravity = new Evaluator.Integer { Name="$Gravity$", EditorHelp="$ParticleGravityHelp$" },
222 		FadeOut = { Name="$FadeOut$", EditorHelp="$ParticleFadeOutHelp$", Type="bool" },
223 		CollisionFunc = { Name="$CollisionFunc$", EditorHelp="$ParticleCollisionFuncHelp$", Type="enum", Options = [
224 			{ Value="pass", Name="$Pass$" },
225 			{ Value="stop", Name="$Stop$" },
226 			{ Value="bounce", Name="$Bounce$" },
227 			{ Value="die", Name="$Die$" }
228 			] }
229 		} } );
230 	AddEvaluator("Action", "$Player$", "$DoWealth$", "$DoWealthHelp$", "do_wealth", [def, def.EvalAct_DoWealth], { Player={ Function="triggering_player" }, DoSound={ Function="bool_constant", Value=true } }, { Type="proplist", Display="({{Player}}, {{Change}})", EditorProps = {
231 		Player = Evaluator.Player,
232 		Change = new Evaluator.Integer { Name="$Change$", EditorHelp="$DoWealthChangeHelp$" },
233 		DoSound = new Evaluator.Boolean { Name="$Sound$", EditorHelp="$DoWealthSoundHelp$", Priority=-1 }
234 		} } );
235 	AddEvaluator("Action", "$Player$", "$PlrKnowledge$", "$PlrKnowledgeHelp$", "plr_knowledge", [def, def.EvalAct_PlrKnowledge], { Players={ Function="triggering_player_list" }, ID={ Function="def_constant" } }, { Type="proplist", Display="({{Players}}, {{ID}})", EditorProps = {
236 		Players = Evaluator.PlayerList,
237 		ID = Evaluator.Definition
238 		} } );
239 	AddEvaluator("Action", "$Player$", "$SetPlrView$", "$SetPlrViewHelp$", "plr_view", [def, def.EvalAct_PlrView], { Players={ Function="triggering_player_list" }, Target={ Function="action_object" } }, { Type="proplist", Display="({{Players}}, {{Target}})", EditorProps = {
240 		Players = Evaluator.PlayerList,
241 		Target = new Evaluator.Object { Name="$Target$", EditorHelp="$PlrViewTargetHelp$" },
242 		Immediate = { Name="$ScrollMode$", EditorHelp="$SetPlrViewScrollModeHelp$", Type="enum", Priority=-10, Options = [
243 			{ Name="$Smooth$" },
244 			{ Value=true, Name="$Immediate$" }
245 			] }
246 		} } );
247 	AddEvaluator("Action", "$Script$", "$ConditionalAction$", "$ConditionalActionHelp$", "if", [def, def.EvalAct_If], { }, { Type="proplist", Display="if({{Condition}}) {{TrueEvaluator}} else {{FalseEvaluator}}", EditorProps = {
248 		Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$IfConditionHelp$", Priority=60 },
249 		TrueEvaluator = new Evaluator.Action { Name="$TrueEvaluator$", EditorHelp="$TrueEvaluatorHelp$", Priority=50 },
250 		FalseEvaluator = new Evaluator.Action { Name="$FalseEvaluator$", EditorHelp="$FalseEvaluatorHelp$", Priority=30 }
251 		} } );
252 	AddEvaluator("Action", "$Script$", "$SetVariable$", "$SetVariableHelp$", "set_variable", [def, def.EvalAct_SetVariable], { VariableName={ Function="string_constant", Value="" } }, { Type="proplist", Display="{{Context}}::{{VariableName}}={{Value}}", EditorProps = {
253 		Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
254 		VariableName = new Evaluator.String { Name="$VariableName$", EditorHelp="$VariableNameHelp$" },
255 		Value = new Evaluator.Any { Name="$Value$", EditorHelp="$SetVariableValueHelp$" }
256 		} } );
257 	AddEvaluator("Action", "$Script$", "$ForInteger$", "$ForIntegerHelp$", "for_int", [def, def.EvalAct_For, def.EvalAct_For_IntRange], { Start={ Function="int_constant", Value=1}, End={ Function="int_constant", Value=10}, Step={ Function="int_constant", Value=1} }, { Type="proplist", HideFullName=true, Display="for({{Start}}:{{Step}}:{{End}}) {{Action}}", EditorProps = {
258 		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
259 		Start = new Evaluator.Integer { Name="$Start$", EditorHelp="$ForStartHelp$", Priority=90 },
260 		End = new Evaluator.Integer { Name="$End$", EditorHelp="$ForEndHelp$", Priority=80 },
261 		Step = new Evaluator.Integer { Name="$Step$", EditorHelp="$ForStepHelp$", Priority=70 }
262 		} } );
263 	AddEvaluator("Action", "$Script$", "$ForPlayer$", "$ForPlayerHelp$", "for_player", [def, def.EvalAct_For, def.EvalAct_For_PlayerList], { Players={ Function="all_players" } }, { Type="proplist", HideFullName=true, Display="for({{Players}}) {{Action}}", EditorProps = {
264 		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
265 		Players = new Evaluator.PlayerList { EditorHelp="$ForPlayersHelp$" }
266 		} } );
267 	AddEvaluator("Action", "$Script$", "$ForObject$", "$ForObjectHelp$", "for_object", [def, def.EvalAct_For, def.EvalAct_For_ObjectList], { }, { Type="proplist", HideFullName=true, Display="for({{Objects}}) {{Action}}", EditorProps = {
268 		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
269 		Objects = new Evaluator.ObjectList { EditorHelp="$ForObjectsHelp$" }
270 		} } );
271 	AddEvaluator("Action", "$Script$", "$Log$", "$LogHelp$", "log", [def, def.EvalAct_Log], { }, new Evaluator.String { Name="$LogMessage$", EditorHelp="$LogMessageHelp$" }, "Message");
272 	AddEvaluator("Action", "$Script$", "$Comment$", "$CommentHelp$", "comment", [def, def.EvalAct_Nop], { Comment="" }, { Name="$Comment$", EditorHelp="$CommentHelp$", Type="string" }, "Comment");
273 	AddEvaluator("Action", "Game", "$GameOver$", "$GameOverHelp$", "game_over", [def, def.EvalAct_GameOver]);
274 	// Object evaluators
275 	AddEvaluator("Object", nil, "$ActionObject$", "$ActionObjectHelp$", "action_object", [def, def.EvalContextValue, "action_object"]);
276 	AddEvaluator("Object", nil, "$TriggerClonk$", "$TriggerClonkHelp$", "triggering_clonk", [def, def.EvalContextValue, "triggering_clonk"]);
277 	AddEvaluator("Object", nil, "$TriggerObject$", "$TriggerObjectHelp$", "triggering_object", [def, def.EvalContextValue, "triggering_object"]);
278 	AddEvaluator("Object", nil, "$IteratedObject$", "$IteratedObjectHelp$", "iterated_object", [def, def.EvalContextValue, "for_object"]);
279 	AddEvaluator("Object", nil, "$LastCreatedObject$", "$LastCreatedObjectHelp$", "last_created_object", [def, def.EvalContextValue, "last_created_object"]);
280 	var find_object_in_area_delegate = { Type="proplist", Display="{{ID}}", EditorProps = {
281 		ID = new Evaluator.Definition { Name="$ID$", EditorHelp="$FindObjectsIDHelp$", EmptyName="$Any$", Priority=51 },
282 		Area = { Name="$SearchArea$", EditorHelp="$SearchAreaHelp$", Type="enum", OptionKey="Function", Priority=41, Options=[
283 			{ Name="$SearchAreaWholeMap$", EditorHelp="$SearchAreaWholeMapHelp$" },
284 			{ Name="$SearchAreaInRect$", EditorHelp="$SearchAreaInRectHelp$", Value={ Function="InRect" }, DefaultValueFunction=def.GetDefaultRect, ValueKey="Area", Delegate={ Type="rect", Name="$Rectangle$", Relative=false, Color=0xffff00 } },
285 			{ Name="$SearchAreaAtRect$", EditorHelp="$SearchAreaAtRectHelp$", Value={ Function="AtRect" }, DefaultValueFunction=def.GetDefaultRect, ValueKey="Area", Delegate={ Type="rect", Name="$Rectangle$", Relative=false, Color=0xffff80 } },
286 			{ Name="$SearchAreaCircle$", EditorHelp="$SearchAreaCircleHelp$", Value={ Function="Circle" }, DefaultValueFunction=def.GetDefaultCircle, ValueKey="Area", Delegate={ Type="circle", Name="$Circle$", Relative=false, CanMoveCenter=true, Color=0xff00ff } },
287 			{ Name="$SearchAreaNearPosition$", EditorHelp="$SearchAreaNearPositionHelp$", Value={ Function="NearPosition", Parameters={Radius=25} }, ValueKey="Parameters", Delegate={ Type="proplist", Display="({{Position}}, {{Radius}})", EditorProps = {
288 				Position = new Evaluator.Position { EditorHelp="$SearchAreaNearPositionPositionHelp$"},
289 				Radius = { Type="circle", Relative=true, Name="$Radius$", Color=0xff80ff }
290 				} } }
291 			] },
292 		AllowContained = { Name="$AllowContained$", EditorHelp="$AllowContainedHelp$", Type="bool", Priority=31 }
293 		} };
294 	var find_object_in_container_delegate = { Type="proplist", Display="{{ID}} in {{Container}}", EditorProps = {
295 		ID = new Evaluator.Definition { Name="$ID$", EditorHelp="$FindObjectsIDHelp$", EmptyName="$Any$" },
296 		Container = new Evaluator.Object { Name="$Container$", EditorHelp="FindObjectsContainerHelp" }
297 		} };
298 	AddEvaluator("Object", nil, "$FindObjectInArea$", "$FindObjectInAreaHelp$", "find_object_in_area", [def, def.EvalObjList_FindObjectsInArea, true], {}, find_object_in_area_delegate);
299 	AddEvaluator("Object", nil, "$FindObjectInContainer$", "$FindObjectInContainerHelp$", "find_object_in_container", [def, def.EvalObjList_FindObjectInContainer], {}, find_object_in_container_delegate);
300 	// Object list evaluators
301 	AddEvaluator("ObjectList", nil, "$FindObjectsInArea$", "$FindObjectsInAreaHelp$", "find_objects_in_area", [def, def.EvalObjList_FindObjectsInArea], {}, find_object_in_area_delegate);
302 	AddEvaluator("ObjectList", nil, "$FindObjectsInContainer$", "$FindObjectsInContainerHelp$", "find_objects_in_container", [def, def.EvalObjList_FindObjectsInContainer], {}, find_object_in_container_delegate);
303 	// Definition evaluators
304 	AddEvaluator("Definition", nil, ["$Constant$", ""], "$ConstantHelp$", "def_constant", [def, def.EvalConstant], { Value=nil }, { Type="def", Name="$Value$" });
305 	AddEvaluator("Definition", nil, "$TypeOfObject$", "$TypeOfObjectHelp$", "type_of_object", [def, def.EvalObjProp, Global.GetID], { }, new Evaluator.Object { }, "Object");
306 	// Player evaluators
307 	AddEvaluator("Player", nil, "$TriggeringPlayer$", "$TriggeringPlayerHelp$", "triggering_player", [def, def.EvalContextValue, "triggering_player"]);
308 	AddEvaluator("Player", nil, "$OwnerOfObject$", "$OwnerOfObjectHelp$", "owner", [def, def.EvalObjProp, Global.GetOwner], { }, new Evaluator.Object { }, "Object");
309 	AddEvaluator("Player", nil, "$ControllerOfObject$", "$ControllerOfObjectHelp$", "owner", [def, def.EvalObjProp, Global.GetController], { }, new Evaluator.Object { }, "Object");
310 	AddEvaluator("Player", nil, "$IteratedPlayer$", "$IteratedPlayerHelp$", "iterated_player", [def, def.EvalContextValue, "for_player"]);
311 	// Player list evaluators
312 	AddEvaluator("PlayerList", nil, "$TriggeringPlayer$", "$TriggeringPlayerHelp$", "triggering_player_list", [def, def.EvalPlrList_Single, def.EvalPlr_Trigger]);
313 	AddEvaluator("PlayerList", nil, "$AllPlayers$", "$AllPlayersHelp$", "all_players", [def, def.EvalPlrList_All]);
314 	// Boolean (condition) evaluators
315 	AddEvaluator("Boolean", nil, ["$Constant$", ""], "$ConstantHelp$", "bool_constant", [def, def.EvalConstant], { Value=true }, { Type="bool", Name="$Value$" });
316 	AddEvaluator("Boolean", "$Comparison$", "$CompareInteger$", "$ComparisonHelp$", "compare_int", [def, def.EvalComparison, "Integer"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
317 		LeftOperand = new Evaluator.Integer { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
318 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
319 			{ Name="==", EditorHelp="$EqualHelp$" },
320 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
321 			{ Name="<", EditorHelp="$LessThanHelp$", Value="lt" },
322 			{ Name=">", EditorHelp="$GreaterThanHelp$", Value="gt" },
323 			{ Name="<=", EditorHelp="$LessOrEqualHelp$", Value="le" },
324 			{ Name=">=", EditorHelp="$GreaterOrEqualHelp$", Value="ge" }
325 			] },
326 		RightOperand = new Evaluator.Integer { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
327 		} } );
328 	AddEvaluator("Boolean", "$Comparison$", "$CompareBoolean$", "$ComparisonHelp$", "compare_bool", [def, def.EvalComparison, "Boolean"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
329 		LeftOperand = new Evaluator.Object { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
330 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
331 			{ Name="==", EditorHelp="$EqualHelp$" },
332 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
333 			] },
334 		RightOperand = new Evaluator.Object { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
335 		} } );
336 	AddEvaluator("Boolean", "$Comparison$", "$CompareObject$", "$ComparisonHelp$", "compare_object", [def, def.EvalComparison, "Object"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
337 		LeftOperand = new Evaluator.Object { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
338 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
339 			{ Name="==", EditorHelp="$EqualHelp$" },
340 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
341 			] },
342 		RightOperand = new Evaluator.Object { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
343 		} } );
344 	AddEvaluator("Boolean", "$Comparison$", "$CompareString$", "$ComparisonHelp$", "compare_string", [def, def.EvalComparison, "String"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
345 		LeftOperand = new Evaluator.String { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
346 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
347 			{ Name="==", EditorHelp="$EqualHelp$" },
348 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
349 			] },
350 		RightOperand = new Evaluator.String { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
351 		} } );
352 	AddEvaluator("Boolean", "$Comparison$", "$CompareDefinition$", "$ComparisonHelp$", "compare_definition", [def, def.EvalComparison, "Definition"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
353 		LeftOperand = new Evaluator.Definition { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
354 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
355 			{ Name="==", EditorHelp="$EqualHelp$" },
356 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
357 			] },
358 		RightOperand = new Evaluator.Definition { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
359 		} } );
360 	AddEvaluator("Boolean", "$Comparison$", "$ComparePlayer$", "$ComparisonHelp$", "compare_player", [def, def.EvalComparison, "Player"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
361 		LeftOperand = new Evaluator.Player { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
362 		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
363 			{ Name="==", EditorHelp="$EqualHelp$" },
364 			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
365 			] },
366 		RightOperand = new Evaluator.Player { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
367 		} } );
368 	AddEvaluator("Boolean", "$Logic$", "$Not$", "$NotHelp$", "not", [def, def.EvalBool_Not], { }, new Evaluator.Boolean { }, "Operand");
369 	AddEvaluator("Boolean", "$Logic$", "$And$", "$AndHelp$", "and", [def, def.EvalBool_And], { Operands=[] }, { Type="proplist", DescendPath="Operands", Display="{{Operands}}", EditorProps = {
370 		Operands = { Name="$Operands$", Type="array", Elements=Evaluator.Boolean }
371 		} } );
372 	AddEvaluator("Boolean", "$Logic$", "$Or$", "$OrHelp$", "or", [def, def.EvalBool_Or], { Operands=[] }, { Type="proplist", DescendPath="Operands", Display="{{Operands}}", EditorProps = {
373 		Operands = { Name="$Operands$", Type="array", Elements=Evaluator.Boolean }
374 		} } );
375 	AddEvaluator("Boolean", nil, "$ObjectExists$", "$ObjectExistsHelp$", "object_exists", [def, def.EvalBool_ObjectExists], { }, new Evaluator.Object { }, "Object");
376 	AddEvaluator("Boolean", nil, "$ObjectAlive$", "$ObjectAliveHelp$", "object_alive", [def, def.EvalBool_ObjectAlive], { }, new Evaluator.Object { }, "Object");
377 	// Integer evaluators
378 	AddEvaluator("Integer", nil, ["$Constant$", ""], "$ConstantHelp$", "int_constant", [def, def.EvalConstant], { Value=0 }, { Type="int", Name="$Value$" });
379 	var arithmetic_delegate = { Type="proplist", HideFullName=true, EditorProps = {
380 		LeftOperand = new Evaluator.Integer { Name="$LeftOperand$", EditorHelp="$LeftArithmeticOperandHelp$", Priority=44 },
381 		RightOperand = new Evaluator.Integer { Name="$RightOperand$", EditorHelp="$RightArithmeticOperandHelp$", Priority=42 }
382 		} };
383 	AddEvaluator("Integer", "$Arithmetic$", "$Sum$ (+)", "$SumHelp$", "add", [def, def.EvalInt_Add], { }, new arithmetic_delegate { Display="{{LeftOperand}}+{{RightOperand}}" });
384 	AddEvaluator("Integer", "$Arithmetic$", "$Sub$ (-)", "$SumHelp$", "subtract", [def, def.EvalInt_Sub], { }, new arithmetic_delegate { Display="{{LeftOperand}}-{{RightOperand}}" });
385 	AddEvaluator("Integer", "$Arithmetic$", "$Mul$ (*)", "$MulHelp$", "multiply", [def, def.EvalInt_Mul], { }, new arithmetic_delegate { Display="{{LeftOperand}}*{{RightOperand}}" });
386 	AddEvaluator("Integer", "$Arithmetic$", "$Div$ (/)", "$DivHelp$", "divide", [def, def.EvalInt_Div], { }, new arithmetic_delegate { Display="{{LeftOperand}}/{{RightOperand}}" });
387 	AddEvaluator("Integer", "$Arithmetic$", "$Mod$ (%)", "$ModHelp$", "modulo", [def, def.EvalInt_Mod], { }, new arithmetic_delegate { Display="{{LeftOperand}}%{{RightOperand}}" });
388 	AddEvaluator("Integer", nil, "$Random$", "$RandomIntHelp$", "int_random", [def, def.EvalIntRandom], { Min={Function="int_constant", Value=0}, Max={Function="int_constant", Value=99} }, { Type="proplist", HideFullName=true, Display="Rnd({{Min}}..{{Max}})", EditorProps = {
389 		Min = new Evaluator.Integer { Name="$Min$", EditorHelp="$RandomMinHelp$", Priority=51 },
390 		Max = new Evaluator.Integer { Name="$Max$", EditorHelp="$RandomMaxHelp$", Priority=21 }
391 		} } );
392 	AddEvaluator("Integer", nil, "$Distance$", "$DistanceHelp$", "distance", [def, def.EvalInt_Distance], { }, { Type="proplist", HideFullName=true, Display="d({{PositionA}}..{{PositionB}})", EditorProps = {
393 		PositionA = new Evaluator.Position { Name="$PositionA$", EditorHelp="$PositionAHelp$" },
394 		PositionB = new Evaluator.Position { Name="$PositionB$", EditorHelp="$PositionBHelp$" }
395 		} } );
396 	AddEvaluator("Integer", nil, "$NumberOfObjects$", "$NumberOfObjectsHelp$", "object_count", [def, def.EvalCount, "ObjectList"], { }, new Evaluator.ObjectList { }, "Array");
397 	AddEvaluator("Integer", nil, "$NumberOfPlayers$", "$NumberOfPlayersHelp$", "player_count", [def, def.EvalCount, "PlayerList"], { }, new Evaluator.PlayerList { }, "Array");
398 	AddEvaluator("Integer", nil, "$PlayerWealth$", "$PlayerWealthHelp$", "player_wealth", [def, def.EvalInt_Wealth], { }, new Evaluator.Player { }, "Player");
399 	AddEvaluator("Integer", nil, "$ClonkEnergy$", "$ClonkEnergyHelp$", "clonk_energy", [def, def.EvalObjProp, Global.GetEnergy], { }, GetObjectEvaluator("IsClonk", "$Clonk$"), "Object");
400 	AddEvaluator("Integer", nil, "$ObjectMass$", "$ObjectMassHelp$", "object_mass", [def, def.EvalObjProp, Global.GetMass], { }, new Evaluator.Object { }, "Object");
401 	AddEvaluator("Integer", nil, "$ObjectSpeed$", "$ObjectSpeedHelp$", "object_speed", [def, def.EvalObjProp, Global.GetSpeed], { }, new Evaluator.Object { }, "Object");
402 	AddEvaluator("Integer", nil, "$PositionX$", "$PositionXHelp$", "position_x", [def, def.EvalInt_PosCoord, 0], { }, new Evaluator.Position { }, "Position");
403 	AddEvaluator("Integer", nil, "$PositionY$", "$PositionYHelp$", "position_y", [def, def.EvalInt_PosCoord, 1], { }, new Evaluator.Position { }, "Position");
404 	AddEvaluator("Integer", nil, "$IteratedInteger$", "$IteratedIntegerHelp$", "iterated_int", [def, def.EvalContextValue, "for_int"]);
405 	// String evaluators
406 	AddEvaluator("String", nil, ["$Constant$", ""], "$ConstantHelp$", "string_constant", [def, def.EvalStringConstant], { Value="" }, { Type="string", Name="$Value$", Translatable=true });
407 	AddEvaluator("String", nil, ["$ValueToString$", ""], "$ValueToStringHelp$", "value_to_string", [def, def.EvalStr_ValueToString], { }, new Evaluator.Any { });
408 	AddEvaluator("String", nil, "$Concat$", "$ConcatHelp$", "string_concat", [def, def.EvalStr_Concat], { Substrings=[] }, { Type="proplist", HideFullName=true, DescendPath="Substrings", Display="{{Substrings}}", EditorProps = {
409 		Substrings = { Name="$Substrings$", Type="array", Elements=Evaluator.String }
410 		} } );
411 	// Color evaluators
412 	AddEvaluator("Color", nil, ["$Constant$", ""], "$ConstantHelp$", "color_constant", [def, def.EvalConstant], { Value=0xffffff }, { Type="color", Name="$Value$" });
413 	AddEvaluator("Color", nil, "$RandomColor$", "$RandomColorHelp$", "random_color", [def, def.EvalClr_Random], { ColorA={ Function="color_constant", Value=0 }, ColorB={ Function="color_constant", Value=0xffffff } }, { Type="proplist", Display="({{ColorA}}..{{ColorB}})", EditorProps = {
414 		ColorA = new Evaluator.Color { Name="$ColorA$" },
415 		ColorB = new Evaluator.Color { Name="$ColorB$" }
416 		} } );
417 	AddEvaluator("Color", nil, "$PlayerColor$", "$PlayerColorHelp$", "player_color", [def, def.EvalClr_PlayerColor], { Player={ Function="triggering_player" } }, new Evaluator.Player { }, "Player");
418 	AddEvaluator("Color", nil, "$RGB$", "$RGBHelp$", "rgb_color", [def, def.EvalClr_RGB], { R={ Function="int_constant", Value=255 }, G={ Function="int_constant", Value=255 }, B={ Function="int_constant", Value=255 } }, { Type="proplist", Display="({{R}}, {{G}}, {{B}})", EditorProps = {
419 		R = new Evaluator.Integer { Name="$Red$", Priority=51 },
420 		G = new Evaluator.Integer { Name="$Green$", Priority=41 },
421 		B = new Evaluator.Integer { Name="$Blue$", Priority=31 }
422 		} } );
423 	// Position evaluators
424 	AddEvaluator("Position", nil, ["$ConstantPositionAbsolute$", ""], "$ConstantPositionAbsoluteHelp$", "position_constant", [def, def.EvalConstant], def.GetDefaultPosition, { Type="point", Name="$Position$", Relative=false, Color=0xff2000 });
425 	AddEvaluator("Position", nil, ["$ConstantPositionRelative$", "+"], "$ConstantPositionRelativeHelp$", "position_constant_rel", [def, def.EvalPositionRelative], { Value=[0,0] }, { Type="point", Name="$Position$", Relative=true, Color=0xff0050 });
426 	AddEvaluator("Position", nil, "$Coordinates$", "$CoordinatesHelp$", "position_coordinates", [def, def.EvalCoordinates], def.GetDefaultCoordinates, { Type="proplist", HideFullName=true, Display="({{X}},{{Y}})", EditorProps = {
427 		X = new Evaluator.Integer { Name="X", EditorHelp="$PosXHelp$" },
428 		Y = new Evaluator.Integer { Name="Y", EditorHelp="$PosYHelp$" }
429 		} } );
430 	AddEvaluator("Position", nil, "$PositionOffset$", "$PositionOffsetHelp$", "position_offset", [def, def.EvalPositionOffset], { }, { Type="proplist", HideFullName=true, Display="{{Position}}+{{Offset}}", EditorProps = {
431 		Position = new Evaluator.Position { EditorHelp="$PositionOffsetPositionHelp$", Priority=51 },
432 		Offset = new Evaluator.Offset { EditorHelp="$PositionOffsetOffsetHelp$", Priority=21 }
433 		} } );
434 	AddEvaluator("Position", nil, "$ObjectPosition$", "$ObjectPositionHelp$", "object_position", [def, def.EvalPositionObject], { Object={Function="triggering_object"} }, new Evaluator.Object { EditorHelp="$ObjectPositionObjectHelp$" }, "Object");
435 	AddEvaluator("Position", nil, "$LastUsePosition$", "$LastUsePositionHelp$", "use_position", [def, def.EvalPos_LastUse]);
436 	AddEvaluator("Position", "$RandomPosition$", "$RandomRectAbs$", "$RandomRectAbsHelp$", "random_pos_rect_abs", [def, def.EvalPos_RandomRect, false], def.GetDefaultRect, { Type="rect", Name="$Rectangle$", Relative=false, Color=0xffff00 }, "Area");
437 	AddEvaluator("Position", "$RandomPosition$", "$RandomRectRel$", "$RandomRectRelHelp$", "random_pos_rect_rel", [def, def.EvalPos_RandomRect, true], { Area=[-30,-30,60,60] }, { Type="rect", Name="$Rectangle$", Relative=true, Color=0x00ffff }, "Area");
438 	AddEvaluator("Position", "$RandomPosition$", "$RandomCircleAbs$", "$RandomCircleAbsHelp$", "random_pos_circle_abs", [def, def.EvalPos_RandomCircle, false], def.GetDefaultCircle, { Type="circle", Name="$Circle$", Relative=false, CanMoveCenter=true, Color=0xff00ff }, "Area");
439 	AddEvaluator("Position", "$RandomPosition$", "$RandomCircleRel$", "$RandomCircleRelHelp$", "random_pos_circle_rel", [def, def.EvalPos_RandomCircle, true], { Area=[50,0,0] }, { Type="circle", Name="$Circle$", Relative=true, CanMoveCenter=true, Color=0xa000a0 }, "Area");
440 	// Offset evaluators
441 	AddEvaluator("Offset", nil, ["$ConstantOffsetRelative$", ""], "$ConstantOffsetRelativeHelp$", "offset_constant", [def, def.EvalConstant], { Value=[0,0] }, { Type="point", Name="$Position$", Relative=true, Color=0xff30ff });
442 	AddEvaluator("Offset", nil, ["$Coordinates$", ""], "$CoordinatesHelp$", "offset_coordinates", [def, def.EvalCoordinates], { Value={X=0,Y=0} }, { Type="proplist", HideFullName=true, Display="({{X}},{{Y}})", EditorProps = {
443 		X = new Evaluator.Integer { Name="X", EditorHelp="$OffXHelp$" },
444 		Y = new Evaluator.Integer { Name="Y", EditorHelp="$OffYHelp$" },
445 		} } );
446 	AddEvaluator("Offset", nil, "$AddOffsets$", "$AddOffsetsHelp$", "add_offsets", [def, def.EvalOffsetAdd], { }, { Type="proplist", HideFullName=true, Display="{{Offset1}}+{{Offset2}}", EditorProps = {
447 		Offset1 = new Evaluator.Offset { EditorHelp="$AddOffsetOffsetHelp$" },
448 		Offset2 = new Evaluator.Offset { EditorHelp="$AddOffsetOffsetHelp$" }
449 		} } );
450 	AddEvaluator("Offset", nil, "$DiffPositions$", "$DiffPositionsHelp$", "diff_positions", [def, def.EvalOffsetDiff], { }, { Type="proplist", HideFullName=true, Display="{{PositionB}}-{{PositionA}}", EditorProps = {
451 		PositionA = new Evaluator.Position { Name="$PositionA$", EditorHelp="$PositionAHelp$" },
452 		PositionB = new Evaluator.Position { Name="$PositionB$", EditorHelp="$PositionBHelp$" }
453 		} } );
454 	AddEvaluator("Offset", nil, "$RandomOffRectRel$", "$RandomRectRelHelp$", "random_off_rect_rel", [def, def.EvalPos_RandomRect, "rect", false], { Area=[-30,-30,60,60] }, { Type="rect", Name="$Rectangle$", Relative=true, Color=0x00ffff }, "Area");
455 	AddEvaluator("Offset", nil, "$RandomOffCircleRel$", "$RandomCircleRelHelp$", "random_off_circle_rel", [def, def.EvalPos_RandomCircle, "circle", false], { Area=[0,0,50] }, { Type="circle", Name="$Circle$", Relative=true, CanMoveCenter=true, Color=0xa000a0 }, "Area");
456 	// Script evaluators
457 	var variable_delegate = { Type="proplist", HideFullName=true, Display="{{Context}}::{{VariableName}}", EditorProps = {
458 		Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
459 		VariableName = new Evaluator.String { Name="$VariableName$", EditorHelp="$VariableNameHelp$", Priority=50 } } };
460 	for (var eval_type in EvaluatorTypes)
461 	{
462 		var data_type = EvaluatorReturnTypes[eval_type];
463 		var group = nil;
464 		if (eval_type != "Action")
465 		{
466 			AddEvaluator(eval_type, nil, "$Variable$", "$VariableHelp$", Format("%s_variable", eval_type), [def, def.EvalVariable, data_type], { VariableName={ Function="string_constant", Value="" } }, variable_delegate);
467 			AddEvaluator(eval_type, nil, "$ConditionalValue$", "$ConditionalValueHelp$", Format("%s_conditional", eval_type), [def, def.EvalConditionalValue, eval_type], { }, { Type="proplist", HideFullName=true, Display="{{Condition}} ? {{TrueEvaluator}} : {{FalseEvaluator}}", EditorProps = {
468 				Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$IfConditionValueHelp$", Priority=60 },
469 				TrueEvaluator = new Evaluator[eval_type] { Name="$TrueEvaluatorValue$", EditorHelp="$TrueEvaluatorValueHelp$", Priority=50 },
470 				FalseEvaluator = new Evaluator[eval_type] { Name="$FalseEvaluatorValue$", EditorHelp="$FalseEvaluatorValueHelp$", Priority=30 }
471 			} } );
472 		}
473 		else
474 		{
475 			group = "$Script$";
476 		}
477 		AddEvaluator(eval_type, group, "$Script$", "$ScriptHelp$", Format("%s_script", eval_type), [def, def.EvalScript, data_type], { }, { Type="proplist", HideFullName=true, Display="{{Context}}::{{Script}}", EditorProps = {
478 			Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
479 			Script = new Evaluator.String { Name="$ScriptCommand$", EditorHelp="$ScriptCommandHelp$" } } });
480 	}
481 	// User action editor props
482 	Prop = Evaluator.Action;
483 	PropProgressMode = { Name="$UserActionProgressMode$", EditorHelp="$UserActionProgressModeHelp$", Type="enum", Options = [ { Name="$Session$", Value="session" }, { Name="$PerPlayer$", Value="player" }, { Name="$Global$" } ] };
484 	PropParallel = { Name="$ParallelAction$", EditorHelp="$ParallelActionHelp$", Type="bool" };
485 	return true;
486 }
487 
GetObjectEvaluator(filter_def,name,help)488 public func GetObjectEvaluator(filter_def, name, help)
489 {
490 	// Create copy of the Evaluator.Object delegate, but with the object_constant proplist replaced by a version with filter_def
491 	var object_options = Evaluator.Object.Options[:];
492 	// Need to copy the option Value field to ensure it is owned by the correct parent.
493 	// Otherwise it would be assigned by reference in the editor
494 	var const_option = new EvaluatorDefs["object_constant"] { Value = { Function="object_constant", Value=nil } };
495 	const_option.Delegate = new const_option.Delegate { Filter=filter_def };
496 	object_options[const_option.OptionIndex] = const_option;
497 	var new_evaluator = new Evaluator.Object { Name=name, Options=object_options, EditorHelp=help };
498 	if (!object_evaluators) object_evaluators = [];
499 	object_evaluators[GetLength(object_evaluators)] = new_evaluator;
500 	return new_evaluator;
501 }
502 
CopyProplist(p)503 private func CopyProplist(p)
504 {
505 	// Create copy of p to ensure unique global reference
506 	if (GetType(p) != C4V_PropList) return p;
507 	var r = {};
508 	for (var k in GetProperties(p)) r[k] = p[k];
509 	return r;
510 }
511 
AddEvaluator(string eval_type,string group,name,string help,string identifier,callback_data,default_val,proplist delegate,string delegate_storage_key)512 public func AddEvaluator(string eval_type, string group, name, string help, string identifier, callback_data, default_val, proplist delegate, string delegate_storage_key)
513 {
514 	// Add an evaluator for one of the data types. Evaluators allow users to write small action sequences and scripts in the editor using dropdown lists.
515 	// eval_type: Return type of the evaluator (Action, Object, Boolean, Player, etc. as defined in UserAction.Evaluator)
516 	// group [optional] Localized name of sub-group for larger enums (i.e. actions)
517 	// name: Localized name as it appears in the dropdown list of evaluators. May also be an array [name, short_name].
518 	// identifier: Unique identifier that is used to store this action in savegames and look up the action def. Identifiers must be unique among all data types.
519 	// callback_data: Array of [definition, definition.Function, parameter (optional)]. Function to be called when this evaluator is called
520 	// default_val [optional]: Default value to be set when this evaluator is selected. Must be a proplist. Should contain values for all properties in the delegate
521 	// delegate: Parameters for this evaluator
522 	// Copy all value evaluations
523 	if (eval_type != "Action" && eval_type != "Any" && callback_data[1] != UserAction.EvalVariable)
524 	{
525 		var any_group;
526 		if (group) any_group = Format("%s/%s", EvaluatorTypeNames[eval_type], group); else any_group = EvaluatorTypeNames[eval_type];
527 		AddEvaluator("Any", any_group, name, help, identifier, callback_data, CopyProplist(default_val), delegate, delegate_storage_key);
528 	}
529 	// Dissect parameters
530 	if (group) group = GroupNames[group] ?? group; // resolve localized group name
531 	var short_name;
532 	if (GetType(name) == C4V_Array)
533 	{
534 		short_name = name[1];
535 		name = name[0];
536 	}
537 	else if (delegate && delegate.HideFullName)
538 	{
539 		// Some proplist delegates provide their own display string and need not show the option name
540 		short_name = "";
541 	}
542 	if (!default_val) default_val = {};
543 	var default_get;
544 	if (GetType(default_val) == C4V_Function)
545 	{
546 		default_get = default_val;
547 		default_val = Call(default_get, nil, {Function=identifier});
548 	}
549 	default_val.Function = identifier;
550 	var action_def = { Name=name, ShortName=short_name, EditorHelp=help, Group=group, Value=default_val, Delegate=delegate, DefaultValueFunction=default_get }, n;
551 	if (delegate)
552 	{
553 		if (delegate.EditorProps || delegate.Elements)
554 		{
555 			// Proplist of array parameter for this evaluator: Descend path title should be name
556 			delegate.Name = name;
557 			var child_delegate = delegate;
558 			if (delegate.DescendPath) child_delegate = delegate.EditorProps[delegate.DescendPath];
559 			if (!child_delegate.EditorHelp) child_delegate.EditorHelp = help;
560 		}
561 		else
562 		{
563 			// Any other parameter type: Store in value
564 			action_def.ValueKey = delegate_storage_key ?? "Value";
565 		}
566 	}
567 	// Constant has higher priority
568 	if (callback_data[1] == UserAction.EvalConstant)
569 	{
570 		action_def.Priority = 50;
571 	}
572 	Evaluator[eval_type].Options[n = GetLength(Evaluator[eval_type].Options)] = action_def;
573 	action_def.OptionIndex = n;
574 	// Remember lookup table through identifier (ignore duplicates)
575 	if (eval_type != "Any" || !group)
576 	{
577 		EvaluatorCallbacks[identifier] = callback_data;
578 		EvaluatorDefs[identifier] = action_def;
579 	}
580 	// Copy any object evaluators to existing evaluator lists
581 	if (eval_type == "Object" && object_evaluators)
582 		for (var obj_eval in object_evaluators)
583 			obj_eval.Options[GetLength(obj_eval.Options)] = action_def;
584 	return action_def;
585 }
586 
EvaluateValue(string eval_type,proplist props,proplist context)587 public func EvaluateValue(string eval_type, proplist props, proplist context)
588 {
589 	//Log("EvaluateValue %v %v %v", eval_type, props, context);
590 	if (!props) return nil;
591 	// Finish any hold-action
592 	if (context.hold == props)
593 	{
594 		context.hold = nil;
595 		return context.hold_result;
596 	}
597 	// Not on hold: Perform evaluation
598 	var cb = EvaluatorCallbacks[props.Function];
599 	/*var rval = cb[0]->Call(cb[1], props, context, cb[2]);
600 	Log("%v <- EvaluateValue %v %v %v", rval, eval_type, props, context);
601 	return rval;*/
602 	return cb[0]->Call(cb[1], props, context, cb[2]);
603 }
604 
EvaluateAction(proplist props,object action_object,object triggering_object,int triggering_player,string progress_mode,bool allow_parallel,finish_callback,array position)605 public func EvaluateAction(proplist props, object action_object, object triggering_object, int triggering_player, string progress_mode, bool allow_parallel, finish_callback, array position)
606 {
607 	// No action
608 	if (!props) if (finish_callback) return action_object->Call(finish_callback); else return;
609 	// Determine context
610 	var context;
611 	if (!progress_mode)
612 	{
613 		if (!(context = props._context))
614 			props._context = context = CreateObject(UserAction);
615 	}
616 	else if (progress_mode == "player")
617 	{
618 		if (!props._contexts) props._contexts = [];
619 		var plr_id = GetPlayerID(triggering_player);
620 		if (!(context = props._contexts[plr_id]))
621 			props._contexts[plr_id] = context = CreateObject(UserAction);
622 	}
623 	else // if (progress_mode == "session")
624 	{
625 		// Temporary context
626 		context = CreateObject(UserAction);
627 		context.temp = true;
628 	}
629 	// Prevent duplicate parallel execution
630 	if (!allow_parallel && (context.hold && !context.suspended)) return false;
631 	// Init context settings
632 	context->InitContext(action_object, triggering_player, triggering_object, props, finish_callback, position);
633 	// Execute action
634 	EvaluateValue("Action", props, context);
635 	FinishAction(context);
636 	return true;
637 }
638 
EvaluateCondition(proplist props,object action_object,object triggering_object,int triggering_player)639 public func EvaluateCondition(proplist props, object action_object, object triggering_object, int triggering_player)
640 {
641 	// Build temp context
642 	var context = CreateObject(UserAction);
643 	context.temp = true;
644 	// Init context settings
645 	context->InitContext(action_object, triggering_player, triggering_object, props);
646 	// Execute condition evaluator
647 	var result = EvaluateValue("Boolean", props, context);
648 	// Cleanup
649 	if (context) context->RemoveObject();
650 	// Done
651 	return result;
652 }
653 
EvaluateString(proplist props,object context)654 public func EvaluateString(proplist props, object context)
655 {
656 	return EvaluateValue("String", props, context) ?? "";
657 }
658 
EvaluatePosition(proplist props,object context)659 private func EvaluatePosition(proplist props, object context)
660 {
661 	// Execute position evaluator; fall back to position of action object
662 	var position = EvaluateValue("Position", props, context);
663 	if (!position)
664 	{
665 		if (context.action_object) position = [context.action_object->GetX(), context.action_object->GetY()];
666 		else position = [0,0];
667 	}
668 	return position;
669 }
670 
EvaluateOffset(proplist props,object context)671 private func EvaluateOffset(proplist props, object context)
672 {
673 	// Execute offset evaluator; fall back to [0, 0]
674 	return  EvaluateValue("Offset", props, context) ?? [0,0];
675 }
676 
EvaluatePlayer(proplist props,object context)677 private func EvaluatePlayer(proplist props, object context)
678 {
679 	// Execute player evaluator; nil means NO_OWNER
680 	var plr = EvaluateValue("Player", props, context);
681 	if (!GetType(plr)) plr = NO_OWNER;
682 	return plr;
683 }
684 
ResumeAction(proplist context,proplist resume_props)685 private func ResumeAction(proplist context, proplist resume_props)
686 {
687 	//Log("ResumeAction %v %v", context, resume_props);
688 	// Resume only if on hold for the same entry
689 	if (context.hold != resume_props) return;
690 	// Not if owning object is dead
691 	if (!context.action_object) return;
692 	// Resume action
693 	EvaluateValue("Action", context.root_action, context);
694 	// Cleanup action object (unless it ran into another hold)
695 	FinishAction(context);
696 }
697 
FinishAction(proplist context)698 private func FinishAction(proplist context)
699 {
700 	// Cleanup action object (unless it's kept around for callbacks or to store sequence progress)
701 	// Note that context.root_action.contexts is checked to kill session-contexts that try to suspend
702 	// There would be no way to resume so just kill the context
703 	if (!context.hold || context.temp)
704 	{
705 		if (context.action_object && context.finish_callback) context.action_object->Call(context.finish_callback, context);
706 		context->RemoveObject();
707 	}
708 }
709 
EvalConstant(proplist props,proplist context)710 private func EvalConstant(proplist props, proplist context) { return props.Value; }
EvalStringConstant(proplist props,proplist context)711 private func EvalStringConstant(proplist props, proplist context) { return GetTranslatedString(props.Value); }
EvalContextValue(proplist props,proplist context,string name)712 private func EvalContextValue(proplist props, proplist context, string name) { return context[name]; }
EvalObj_ActionObject(proplist props,proplist context)713 private func EvalObj_ActionObject(proplist props, proplist context) { return context.action_object; }
EvalObj_TriggeringObject(proplist props,proplist context)714 private func EvalObj_TriggeringObject(proplist props, proplist context) { return context.triggering_object; }
EvalObj_TriggeringClonk(proplist props,proplist context)715 private func EvalObj_TriggeringClonk(proplist props, proplist context) { return context.triggering_clonk; }
EvalObj_LastCreatedObject(proplist props,proplist context)716 private func EvalObj_LastCreatedObject(proplist props, proplist context) { return context.last_created_object; }
EvalPlr_Trigger(proplist props,proplist context)717 private func EvalPlr_Trigger(proplist props, proplist context) { return context.triggering_player; }
EvalPlrList_Single(proplist props,proplist context,fn)718 private func EvalPlrList_Single(proplist props, proplist context, fn) { return [Call(fn, props, context)]; }
719 
EvalCount(proplist props,proplist context,data_type)720 private func EvalCount(proplist props, proplist context, data_type)
721 {
722 	var list = EvaluateValue(data_type, props.Array, context);
723 	if (list) return GetLength(list); else return 0;
724 }
725 
EvalObjList_FindObjectsInArea(proplist props,proplist context,bool find_one)726 private func EvalObjList_FindObjectsInArea(proplist props, proplist context, bool find_one)
727 {
728 	var params = Find_And(), np=1;
729 	// Resolve area parameter
730 	if (props.Area)
731 	{
732 		var area = props.Area.Area;
733 		var fn = props.Area.Function;
734 		var area_criterion;
735 		if (fn == "InRect")
736 			area_criterion = Find_InRect(area[0], area[1], area[2], area[3]);
737 		else if (fn == "AtRect")
738 			area_criterion = Find_AtRect(area[0], area[1], area[2], area[3]);
739 		else if (fn == "Circle")
740 			area_criterion = Find_Distance(area[0], area[1], area[2]);
741 		else if (fn == "NearPosition")
742 		{
743 			var pos_params = props.Area.Parameters;
744 			var pos = EvaluatePosition(pos_params.Position, context);
745 			area_criterion = Find_Distance(pos_params.Radius, pos[0], pos[1]);
746 		}
747 		if (area_criterion) params[np++] = area_criterion;
748 	}
749 	// Other parameters
750 	var idobj = EvaluateValue("Definition", props.ID, context);
751 	if (idobj) params[np++] = Find_ID(idobj);
752 	if (!props.AllowContained) params[np++] = Find_NoContainer();
753 	// Find objects
754 	var result = FindObjects(params);
755 	if (find_one) return result[0]; else return result;
756 }
757 
EvalObjList_FindObjectInContainer(proplist props,proplist context)758 private func EvalObjList_FindObjectInContainer(proplist props, proplist context)
759 {
760 	var container = EvaluateValue("Object", props.Container, context);
761 	var idobj = EvaluateValue("Definition", props.ID, context);
762 	if (!container) return;
763 	if (idobj)
764 		return container->FindContents(idobj);
765 	else
766 		return container->Contents();
767 }
768 
EvalObjList_FindObjectsInContainer(proplist props,proplist context)769 private func EvalObjList_FindObjectsInContainer(proplist props, proplist context)
770 {
771 	// Return array of all objects contained in container
772 	var container = EvaluateValue("Object", props.Container, context);
773 	var idobj = EvaluateValue("Definition", props.ID, context);
774 	if (!container) return; // nil is treated as empty list
775 	var i, n = container->ContentsCount(idobj), j;
776 	if (!n) return;
777 	var result = CreateArray(n), obj;
778 	while ((obj = container->Contents(i++)))
779 		if (!idobj || obj->GetID() == idobj)
780 			result[j++] = obj;
781 	return result;
782 }
783 
EvalPlrList_All(proplist props,proplist context,fn)784 private func EvalPlrList_All(proplist props, proplist context, fn)
785 {
786 	var n = GetPlayerCount(C4PT_User);
787 	var result = CreateArray(n);
788 	for (var i=0; i<n; ++i) result[i] = GetPlayerByIndex(i);
789 	return result;
790 }
791 
EvalComparison(proplist props,proplist context,data_type)792 private func EvalComparison(proplist props, proplist context, data_type)
793 {
794 	var left = EvaluateValue(data_type, props.LeftOperand, context);
795 	var right = EvaluateValue(data_type, props.RightOperand, context);
796 	if (!left) left = nil; // 0 ==nil
797 	if (!right) right = nil; // 0 == nil
798 	var op = props.Operator;
799 	if (!op)
800 		return left == right;
801 	else if (op == "ne")
802 		return left != right;
803 	else if (op == "lt")
804 		return left < right;
805 	else if (op == "gt")
806 		return left > right;
807 	else if (op == "le")
808 		return left <= right;
809 	else if (op == "ge")
810 		return left >= right;
811 }
812 
EvalBool_Not(proplist props,proplist context)813 private func EvalBool_Not(proplist props, proplist context) { return !EvaluateValue("Boolean", props.Operand, context); }
814 
EvalBool_And(proplist props,proplist context)815 private func EvalBool_And(proplist props, proplist context)
816 {
817 	for (var cond in props.Operands)
818 		if (!EvaluateValue("Boolean", cond, context))
819 			return false;
820 	return true;
821 }
822 
EvalBool_Or(proplist props,proplist context)823 private func EvalBool_Or(proplist props, proplist context)
824 {
825 	for (var cond in props.Operands)
826 		if (EvaluateValue("Boolean", cond, context))
827 			return true;
828 	return false;
829 }
830 
EvalBool_ObjectExists(proplist props,proplist context)831 private func EvalBool_ObjectExists(proplist props, proplist context) { return !!EvaluateValue("Object", props.Object, context); }
832 
EvalBool_ObjectAlive(proplist props,proplist context)833 private func EvalBool_ObjectAlive(proplist props, proplist context)
834 {
835 	var obj = EvaluateValue("Object", props.Object, context);
836 	return obj && obj->GetAlive();
837 }
838 
EvalAct_Sequence(proplist props,proplist context)839 private func EvalAct_Sequence(proplist props, proplist context)
840 {
841 	// Sequence execution: Iterate over actions until one action puts the context on hold
842 	var n = GetLength(props.Actions), sid = props._sequence_id;
843 	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
844 	for (var progress = context.action_data[sid] ?? 0; progress < n; ++progress)
845 	{
846 		//Log("Sequence progress exec %v %v", progress, context.hold);
847 		// goto preparations
848 		context.sequence_had_goto[sid] = false;
849 		context.last_sequence = props;
850 		// Evaluate next sequence step
851 		EvaluateValue("Action", props.Actions[progress], context);
852 		if (context.hold || context.suspended)
853 		{
854 			// Execution on hold (i.e. wait action). Stop execution for now
855 			if (!context.hold) progress = 0; // No hold specified: Stop with sequence reset
856 			context.action_data[sid] = progress;
857 			return;
858 		}
859 		// Apply jump in the sequence
860 		if (context.sequence_had_goto[sid]) progress = context.action_data[sid] - 1;
861 	}
862 	// Sequence finished
863 	context.last_sequence = nil;
864 	// Reset for next execution.
865 	context.action_data[sid] = 0;
866 }
867 
EvalAct_Goto(proplist props,proplist context)868 private func EvalAct_Goto(proplist props, proplist context)
869 {
870 	// Apply goto by jumping in most recently executed sequence
871 	if (context.last_sequence)
872 	{
873 		var index = props.Index;
874 		if (GetType(index) != C4V_Int) index = EvaluateValue("Integer", index, context); // compatibility
875 		context.action_data[context.last_sequence._sequence_id] = index;
876 		context.sequence_had_goto[context.last_sequence._sequence_id] = true;
877 	}
878 }
879 
EvalAct_StopSequence(proplist props,proplist context)880 private func EvalAct_StopSequence(proplist props, proplist context)
881 {
882 	// Stop: Suspend without hold props, which causes all sequences to reset
883 	context.hold = nil;
884 	context.suspended = true;
885 }
886 
EvalAct_SuspendSequence(proplist props,proplist context)887 private func EvalAct_SuspendSequence(proplist props, proplist context)
888 {
889 	// Suspend: Remember hold position and stop action execution
890 	context.hold = props;
891 	context.suspended = true;
892 }
893 
EvalAct_For_IntRange(proplist props,proplist context)894 private func EvalAct_For_IntRange(proplist props, proplist context)
895 {
896 	// Create list with range of integers
897 	// Both start and end inclusive
898 	var start = EvaluateValue("Integer", props.Start, context);
899 	var end = EvaluateValue("Integer", props.End, context);
900 	var step = EvaluateValue("Integer", props.Step, context);
901 	if (!step) return [];
902 	var d = end - start;
903 	if (d * step < 0) return []; // wrong direction
904 	var n = (end - start) / step + 1;
905 	var list = CreateArray(n), i;
906 	for (var v = start; v <= end; v += step) list[i++] = v;
907 	return list;
908 }
909 
EvalAct_For_ObjectList(proplist props,proplist context)910 private func EvalAct_For_ObjectList(proplist props, proplist context)
911 {
912 	return EvaluateValue("ObjectList", props.Objects, context) ?? [];
913 }
914 
EvalAct_For_PlayerList(proplist props,proplist context)915 private func EvalAct_For_PlayerList(proplist props, proplist context)
916 {
917 	return EvaluateValue("PlayerList", props.Players, context) ?? [];
918 }
919 
EvalAct_For(proplist props,proplist context,list_function)920 private func EvalAct_For(proplist props, proplist context, list_function)
921 {
922 	// For: Iterate over elements until one action puts the context on hold
923 	// list_info: [list_function, current_item_field]
924 	var sid = props._sequence_id;
925 	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
926 	// Get list data
927 	var list, progress;
928 	if (!context.action_data[sid])
929 	{
930 		list = Call(list_function, props, context);
931 	}
932 	else
933 	{
934 		list = context.action_data[sid][0];
935 		progress = context.action_data[sid][1];
936 		context.action_data[sid] = nil;
937 	}
938 	var n = GetLength(list);
939 	for (; progress < n; ++progress)
940 	{
941 		// Get iterated item
942 		var curr_value = list[progress];
943 		if (!GetType(curr_value)) continue; // Ignore nil (deleted objects)
944 		context[props.Function] = curr_value;
945 		// Evaluate next sequence step
946 		EvaluateValue("Action", props.Action, context);
947 		if (context.hold || context.suspended)
948 		{
949 			// Execution on hold (i.e. wait action). Stop execution for now
950 			if (context.hold) context.action_data[sid] = [list, progress];
951 			return;
952 		}
953 	}
954 }
955 
EvalAct_Wait(proplist props,proplist context)956 private func EvalAct_Wait(proplist props, proplist context)
957 {
958 	// Wait for specified number of frames
959 	context.hold = props;
960 	ScheduleCall(context, UserAction.ResumeAction, props.Time, 1, context, props);
961 }
962 
EvalAct_WaitCondition(proplist props,proplist context)963 private func EvalAct_WaitCondition(proplist props, proplist context)
964 {
965 	// Poll condition and resume when it's met.
966 	var cond = props.Condition;
967 	// Invalid condition?
968 	if (!cond) return;
969 	// Condition fulfilled?
970 	if (EvaluateValue("Boolean", cond, context)) return;
971 	// Re-check condition periodically
972 	context.hold = props;
973 	ScheduleCall(context, UserAction.EvalAct_WaitConditionRe, props.Interval, 0x7fffffff, props, context);
974 }
975 
EvalAct_WaitConditionRe(proplist props,proplist context)976 private func EvalAct_WaitConditionRe(proplist props, proplist context)
977 {
978 	if (UserAction->EvaluateValue("Boolean", props.Condition, context))
979 	{
980 		ClearScheduleCall(context, UserAction.EvalAct_WaitConditionRe);
981 		UserAction->ResumeAction(context, props);
982 	}
983 }
984 
EvalAct_Sound(proplist props,proplist context)985 private func EvalAct_Sound(proplist props, proplist context)
986 {
987 	if (!props.Sound) return;
988 	var sound_context;
989 	if (props.SourceObject)
990 	{
991 		sound_context = EvaluateValue("Object", props.SourceObject, context);
992 		if (!sound_context) return;
993 	}
994 	else
995 	{
996 		sound_context = Global;
997 	}
998 	var volume = EvaluateValue("Integer", props.Volume, context);
999 	var pitch = EvaluateValue("Integer", props.Pitch, context);
1000 	if (props.TargetPlayers == "all_players")
1001 	{
1002 		sound_context->Sound(props.Sound, true, volume, nil, props.Loop, nil, pitch);
1003 	}
1004 	else
1005 	{
1006 		for (var plr in EvaluateValue("PlayerList", props.TargetPlayers, context))
1007 		{
1008 			sound_context->Sound(props.Sound, false, volume, plr, props.Loop, nil, pitch);
1009 		}
1010 	}
1011 }
1012 
EvalAct_CreateObject(proplist props,proplist context)1013 private func EvalAct_CreateObject(proplist props, proplist context)
1014 {
1015 	// Create a new object
1016 	var create_id = EvaluateValue("Definition", props.ID, context);
1017 	if (!create_id) return;
1018 	var owner = EvaluatePlayer(props.Owner, context);
1019 	var container = EvaluateValue("Object", props.Container, context);
1020 	var obj;
1021 	if (container)
1022 	{
1023 		// Contained object
1024 		obj = container->CreateContents(create_id);
1025 		if (obj) obj->SetOwner(owner);
1026 	}
1027 	else
1028 	{
1029 		// Uncontained object
1030 		var position = EvaluatePosition(props.Position, context);
1031 		var speed_x = EvaluateValue("Integer", props.SpeedX, context);
1032 		var speed_y = EvaluateValue("Integer", props.SpeedY, context);
1033 		var rotation = EvaluateValue("Integer", props.Rotation, context);
1034 		var speed_r = EvaluateValue("Integer", props.SpeedR, context);
1035 		if (props.CreateAbove)
1036 			obj = Global->CreateObjectAbove(create_id, position[0], position[1], owner);
1037 		else
1038 			obj = Global->CreateObject(create_id, position[0], position[1], owner);
1039 		// Default speed and rotation
1040 		if (obj) obj->SetXDir(speed_x);
1041 		if (obj) obj->SetYDir(speed_y);
1042 		if (obj) obj->SetR(rotation);
1043 		if (obj) obj->SetRDir(speed_r, 5); // Internal rdir value is in five degrees frame
1044 	}
1045 	// Remember object for later access
1046 	context.last_created_object = obj;
1047 }
1048 
EvalAct_CastObjects(proplist props,proplist context)1049 private func EvalAct_CastObjects(proplist props, proplist context)
1050 {
1051 	// Cast objects in multiple directions
1052 	var create_id = EvaluateValue("Definition", props.ID, context);
1053 	if (!create_id) return;
1054 	var owner = EvaluatePlayer(props.Owner, context);
1055 	var amount = EvaluateValue("Integer", props.Amount, context);
1056 	var speed = EvaluateValue("Integer", props.Speed, context);
1057 	var mean_angle = EvaluateValue("Integer", props.MeanAngle, context);
1058 	var angle_deviation = EvaluateValue("Integer", props.AngleDeviation, context);
1059 	var position = EvaluatePosition(props.Position, context);
1060 	context.last_casted_objects = CastObjects(create_id, amount, speed, position[0], position[1], mean_angle, angle_deviation);
1061 }
1062 
EvalAct_CastParticles(proplist props,proplist context)1063 private func EvalAct_CastParticles(proplist props, proplist context)
1064 {
1065 	var particle_name = props.Name;
1066 	var amount = EvaluateValue("Integer", props.Amount, context);
1067 	var speed = EvaluateValue("Integer", props.Speed, context);
1068 	var size_start = EvaluateValue("Integer", props.Size, context);
1069 	var size_end = EvaluateValue("Integer", props.SizeEnd, context);
1070 	var lifetime = EvaluateValue("Integer", props.Lifetime, context);
1071 	if (lifetime <= 0) return;
1072 	var position = EvaluatePosition(props.Position, context);
1073 	var color = (EvaluateValue("Color", props.Color, context) ?? 0xffffff) | 0xff000000;
1074 	var blit_mode = props.BlitMode;
1075 	var fadeout = props.FadeOut;
1076 	var gravity = EvaluateValue("Integer", props.Gravity, context);
1077 	var collision_func = props.CollisionFunc;
1078 	var prototype =
1079 	{
1080 		BlitMode = props.BlitMode,
1081 		Size = PV_Linear(size_start, size_end),
1082 		Rotation = PV_Direction(),
1083 		R = (color >> 16) & 0xff,
1084 		G = (color >>  8) & 0xff,
1085 		B = (color >>  0) & 0xff,
1086 		CollisionVertex = 500
1087 	};
1088 	if (fadeout) prototype.Alpha = PV_Linear(255, 0); else prototype.Alpha=255;
1089 	if (gravity) prototype.ForceY = PV_Gravity(gravity);
1090 	if (collision_func == "pass")
1091 	{
1092 		prototype.CollisionDensity = 9999;
1093 	}
1094 	else if (collision_func == "bounce")
1095 	{
1096 		prototype.OnCollision = PC_Bounce(500);
1097 	}
1098 	else if (collision_func == "die")
1099 	{
1100 		prototype.OnCollision = PC_Die();
1101 	}
1102 	else if (collision_func == "stop")
1103 	{
1104 		prototype.OnCollision = PC_Stop();
1105 	}
1106 	CreateParticle(particle_name, position[0], position[1], PV_Random(-speed, speed), PV_Random(-speed, speed), lifetime, prototype, amount);
1107 }
1108 
EvalAct_RemoveObject(proplist props,proplist context)1109 private func EvalAct_RemoveObject(proplist props, proplist context)
1110 {
1111 	var obj = EvaluateValue("Object", props.Object, context);
1112 	if (!obj) return;
1113 	obj->RemoveObject(props.EjectContents);
1114 }
1115 
EvalAct_SetPosition(proplist props,proplist context)1116 private func EvalAct_SetPosition(proplist props, proplist context)
1117 {
1118 	var obj = EvaluateValue("Object", props.Object, context);
1119 	if (!obj) return;
1120 	var pos = EvaluatePosition(props.Position, context);
1121 	obj->SetPosition(pos[0], pos[1]);
1122 }
1123 
EvalAct_Fling(proplist props,proplist context)1124 private func EvalAct_Fling(proplist props, proplist context)
1125 {
1126 	var obj = EvaluateValue("Object", props.Object, context);
1127 	if (!obj) return;
1128 	var vx = EvaluateValue("Integer", props.SpeedX, context);
1129 	var vy = EvaluateValue("Integer", props.SpeedY, context);
1130 	var add_speed = EvaluateValue("Boolean", props.AddSpeed, context);
1131 	obj->Fling(vx, vy, 10, add_speed);
1132 }
1133 
EvalAct_EnterObject(proplist props,proplist context)1134 private func EvalAct_EnterObject(proplist props, proplist context)
1135 {
1136 	var object = EvaluateValue("Object", props.Object, context);
1137 	var container = EvaluateValue("Object", props.Container, context);
1138 	if (!container || !object) return;
1139 	// Enter either with a check (Collect) or just force-Enter
1140 	if (!props.CollectionCheck)
1141 	{
1142 		object->Enter(container);
1143 	}
1144 	else
1145 	{
1146 		if (!container->Collect(object, true))
1147 		{
1148 			if (props.CollectionCheck == "exit")
1149 			{
1150 				object->SetPosition(container->GetX(), container->GetY());
1151 			}
1152 		}
1153 	}
1154 }
1155 
EvalAct_ExitObject(proplist props,proplist context)1156 private func EvalAct_ExitObject(proplist props, proplist context)
1157 {
1158 	var object = EvaluateValue("Object", props.Object, context);
1159 	if (object) object->Exit();
1160 }
1161 
EvalAct_SetDirection(proplist props,proplist context)1162 private func EvalAct_SetDirection(proplist props, proplist context)
1163 {
1164 	var object = EvaluateValue("Object", props.Object, context);
1165 	if (object) object->SetDir(props.Direction);
1166 }
1167 
EvalAct_SetVisibility(proplist props,proplist context)1168 private func EvalAct_SetVisibility(proplist props, proplist context)
1169 {
1170 	var object = EvaluateValue("Object", props.Object, context);
1171 	if (object) object.Visibility = props.Visibility;
1172 }
1173 
EvalAct_SetVincibility(proplist props,proplist context)1174 private func EvalAct_SetVincibility(proplist props, proplist context)
1175 {
1176 	var object = EvaluateValue("Object", props.Object, context);
1177 	if (object) object->SetInvincibility(!props.Vincibility);
1178 }
1179 
EvalAct_DoWealth(proplist props,proplist context)1180 private func EvalAct_DoWealth(proplist props, proplist context)
1181 {
1182 	var player = EvaluatePlayer(props.Player, context);
1183 	var change = EvaluateValue("Integer", props.Change, context);
1184 	if (player != NO_OWNER && change)
1185 	{
1186 		SetWealth(player, GetWealth(player) + change);
1187 		var do_sound = EvaluateValue("Boolean", props.DoSound, context);
1188 		if (do_sound)
1189 		{
1190 			if (change < 0) Sound("UI::Cash*", true, nil, player); else Sound("UI::UnCash*", true, nil, player);
1191 		}
1192 	}
1193 }
1194 
EvalAct_PlrKnowledge(proplist props,proplist context)1195 private func EvalAct_PlrKnowledge(proplist props, proplist context)
1196 {
1197 	var players = EvaluateValue("PlayerList", props.Players, context) ?? [];
1198 	var def = EvaluateValue("Definition", props.ID, context);
1199 	if (!def) return;
1200 	for (var plr in players) SetPlrKnowledge(plr, def);
1201 }
1202 
EvalAct_PlrView(proplist props,proplist context)1203 private func EvalAct_PlrView(proplist props, proplist context)
1204 {
1205 	var players = EvaluateValue("PlayerList", props.Players, context) ?? [];
1206 	var target = EvaluateValue("Object", props.Target, context);
1207 	var immediate = props.Immediate;
1208 	if (!target) return;
1209 	for (var plr in players) SetPlrView(plr, target, immediate);
1210 }
1211 
EvalAct_ObjectCallInt(proplist props,proplist context,func call_fn)1212 private func EvalAct_ObjectCallInt(proplist props, proplist context, func call_fn)
1213 {
1214 	var obj = EvaluateValue("Object", props.Object, context);
1215 	if (!obj) return;
1216 	var parameter = EvaluateValue("Integer", props.Value, context);
1217 	obj->Call(call_fn, parameter);
1218 }
1219 
EvalAct_If(proplist props,proplist context)1220 private func EvalAct_If(proplist props, proplist context)
1221 {
1222 	// Do evaluation on first pass. After that, take context value.
1223 	var sid = props._sequence_id;
1224 	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
1225 	var cond = context.action_data[sid] ?? !!EvaluateValue("Boolean", props.Condition, context);
1226 	if (cond)
1227 		EvaluateValue("Action", props.TrueEvaluator, context);
1228 	else
1229 		EvaluateValue("Action", props.FalseEvaluator, context);
1230 	// Only keep conditional value within a held action
1231 	if (context.hold) context.action_data[sid] = cond;
1232 }
1233 
EvalConditionalValue(proplist props,proplist context,eval_type)1234 private func EvalConditionalValue(proplist props, proplist context, eval_type)
1235 {
1236 	// Return value by condition
1237 	if (EvaluateValue("Boolean", props.Condition, context))
1238 		return EvaluateValue(eval_type, props.TrueEvaluator, context);
1239 	else
1240 		return EvaluateValue(eval_type, props.FalseEvaluator, context);
1241 }
1242 
EvalAct_Log(proplist props,proplist context)1243 private func EvalAct_Log(proplist props, proplist context)
1244 {
1245 	Log(EvaluateString(props.Message, context));
1246 }
1247 
EvalAct_Nop(proplist props,proplist context)1248 private func EvalAct_Nop(proplist props, proplist context) {}
1249 
GetVariableContext(proplist props,proplist context)1250 private func GetVariableContext(proplist props, proplist context)
1251 {
1252 	// Resolve context for variable. Global or object context.
1253 	var var_context = EvaluateValue("Object", props, context);
1254 	if (!var_context)
1255 	{
1256 		if (!g_UserAction_global_vars) g_UserAction_global_vars = {};
1257 		var_context = g_UserAction_global_vars;
1258 	}
1259 	else
1260 	{
1261 		if (!var_context.user_variables) var_context.user_variables = {};
1262 		var_context = var_context.user_variables;
1263 	}
1264 	return var_context;
1265 }
1266 
EvalAct_SetVariable(proplist props,proplist context)1267 private func EvalAct_SetVariable(proplist props, proplist context)
1268 {
1269 	// Assign variable
1270 	var var_context = GetVariableContext(props.Context, context);
1271 	var var_name = StringToIdentifier(EvaluateString(props.VariableName, context));
1272 	var value = EvaluateValue("Any", props.Value, context);
1273 	var_context[var_name] = value;
1274 }
1275 
EvalVariable(proplist props,proplist context,expected_type)1276 private func EvalVariable(proplist props, proplist context, expected_type)
1277 {
1278 	// Get variable value
1279 	var var_context = GetVariableContext(props.Context, context);
1280 	var var_name = StringToIdentifier(EvaluateString(props.VariableName, context));
1281 	var value = var_context[var_name];
1282 	// Check type (except for C4V_Nil which means "Any" here)
1283 	var val_type = GetType(value);
1284 	if (val_type != expected_type && expected_type)
1285 	{
1286 		// Array types have special checking
1287 		if (GetType(expected_type) == C4V_Array)
1288 		{
1289 			var expected_len = expected_type[1];
1290 			var valid;
1291 			if (val_type == C4V_Array)
1292 			{
1293 				// Check required length
1294 				if (!expected_len || GetLength(value) == expected_len)
1295 				{
1296 					// Check data type of contents
1297 					var subtype = expected_type[0];
1298 					valid = true;
1299 					for (var v in value) if (v && GetType(v) != subtype) valid = false;
1300 				}
1301 			}
1302 			// Invalid value for expected array: Return default array.
1303 			// All nil is OK because there is no string list type and nil converts to 0.
1304 			if (!valid) return CreateArray(expected_len);
1305 		}
1306 		else
1307 		{
1308 			// Type not matching. Construct default.
1309 			if (expected_type == C4V_Int) return 0;
1310 			if (expected_type == C4V_Bool) return !!value; // This conversion is OK
1311 			if (expected_type == C4V_String) return "";
1312 			return nil;
1313 		}
1314 	}
1315 	// Value OK. Return it.
1316 	return value;
1317 }
1318 
EvalScript(proplist props,proplist context)1319 private func EvalScript(proplist props, proplist context)
1320 {
1321 	var script_context;
1322 	if (props.Context)
1323 	{
1324 		if (!(script_context = EvaluateValue("Object", props.Context, context))) return;
1325 	}
1326 	else
1327 	{
1328 		script_context = Global;
1329 	}
1330 	var script = EvaluateString(props.Script, context);
1331 	return script_context->eval(script, true);
1332 }
1333 
EvalAct_GameOver(proplist props,proplist context)1334 private func EvalAct_GameOver(proplist props, proplist context) { GameOver(); }
1335 
GetDefaultPosition(object target_object)1336 private func GetDefaultPosition(object target_object)
1337 {
1338 	// Default position for constant absolute position evaluator: Use selected object position
1339 	var value;
1340 	if (target_object)
1341 		value = [target_object->GetX(), target_object->GetY()];
1342 	else
1343 		value = [0,0];
1344 	return { Function="position_constant", Value=value };
1345 }
1346 
GetDefaultCoordinates(object target_object)1347 private func GetDefaultCoordinates(object target_object)
1348 {
1349 	// Default position for constant absolute position evaluator: Use selected object position
1350 	var value;
1351 	if (target_object)
1352 		value = {X={Function="int_constant", Value=target_object->GetX()}, Y={Function="int_constant", Value=target_object->GetY()}};
1353 	else
1354 		value = {X=0, Y=0};
1355 	value.Function="position_coordinates";
1356 	return value;
1357 }
1358 
GetDefaultRect(object target_object,proplist props)1359 private func GetDefaultRect(object target_object, proplist props)
1360 {
1361 	// Default rectangle around target object
1362 	var r;
1363 	if (target_object) r = [target_object->GetX()-30, target_object->GetY()-30, 60, 60]; else r = [100,100,100,100];
1364 	return { Function=props.Function, Area=r };
1365 }
1366 
GetDefaultCircle(object target_object,proplist props)1367 private func GetDefaultCircle(object target_object, proplist props)
1368 {
1369 	// Default circle around target object
1370 	var r;
1371 	if (target_object) r = [50, target_object->GetX(), target_object->GetY()]; else r = [50,100,100];
1372 	return { Function=props.Function, Area=r };
1373 }
1374 
GetDefaultSearchRect(object target_object)1375 private func GetDefaultSearchRect(object target_object)
1376 {
1377 	// Default rectangle around target object
1378 	var r;
1379 	if (target_object) r = [target_object->GetX()-30, target_object->GetY()-30, 60, 60]; else r = [100,100,100,100];
1380 	return { Function="Rect", Area=r };
1381 }
1382 
GetDefaultSearchCircle(object target_object)1383 private func GetDefaultSearchCircle(object target_object)
1384 {
1385 	// Default circle around target object
1386 	var r;
1387 	if (target_object) r = [50, target_object->GetX(), target_object->GetY()]; else r = [50,100,100];
1388 	return { Function="Circle", Area=r };
1389 }
1390 
EvalIntRandom(proplist props,proplist context)1391 private func EvalIntRandom(proplist props, proplist context)
1392 {
1393 	// Random value between min and max. Also allow them to be swapped.
1394 	var min = EvaluateValue("Integer", props.Min, context);
1395 	var max = EvaluateValue("Integer", props.Max, context);
1396 	var rmin = Min(min,max);
1397 	return Random(Max(max,min)-rmin) + rmin;
1398 }
1399 
EvalPositionRelative(proplist props,proplist context)1400 private func EvalPositionRelative(proplist props, proplist context)
1401 {
1402 	// Return position relative to action_object
1403 	if (context.action_object)
1404 		return [props.Value[0] + context.action_object->GetX(), props.Value[1] + context.action_object->GetY()];
1405 	else
1406 		return props.Value;
1407 }
1408 
EvalCoordinates(proplist props,proplist context)1409 private func EvalCoordinates(proplist props, proplist context)
1410 {
1411 	// Coordinate evaluation: Make array [X, Y]
1412 	return [EvaluateValue("Integer", props.X, context), EvaluateValue("Integer", props.Y, context)];
1413 }
1414 
EvalPositionOffset(proplist props,proplist context)1415 private func EvalPositionOffset(proplist props, proplist context)
1416 {
1417 	var p = EvaluatePosition(props.Position, context);
1418 	var o = EvaluateOffset(props.Offset, context);
1419 	return [p[0]+o[0], p[1]+o[1]];
1420 }
1421 
EvalPositionObject(proplist props,proplist context)1422 private func EvalPositionObject(proplist props, proplist context)
1423 {
1424 	var obj = EvaluateValue("Object", props.Object, context);
1425 	if (obj) return [obj->GetX(), obj->GetY()];
1426 	return [0,0]; // undefined object: Position is 0/0 default
1427 }
1428 
EvalPos_LastUse(proplist props,proplist context)1429 private func EvalPos_LastUse(proplist props, proplist context) { return context.position; }
1430 
EvalPos_RandomRect(proplist props,proplist context,bool relative)1431 private func EvalPos_RandomRect(proplist props, proplist context, bool relative)
1432 {
1433 	// Constant random distribution in rectangle
1434 	var a = props.Area;
1435 	var rval = [a[0] + Random(a[2]), a[1] + Random(a[3])];
1436 	// Apply relative offset
1437 	if (relative && context.action_object)
1438 	{
1439 		rval[0] += context.action_object->GetX();
1440 		rval[1] += context.action_object->GetY();
1441 	}
1442 	return rval;
1443 }
1444 
EvalPos_RandomCircle(proplist props,proplist context,bool relative)1445 private func EvalPos_RandomCircle(proplist props, proplist context, bool relative)
1446 {
1447 	// Constant random distribution in circle
1448 	var a = props.Area;
1449 	var r = a[0];
1450 	r = Sqrt(Random(r*r));
1451 	var ang = Random(360);
1452 	var x = Sin(ang, r), y = Cos(ang, r);
1453 	var rval = [a[1]+x, a[2]+y];
1454 	// Apply relative offset
1455 	if (relative && context.action_object)
1456 	{
1457 		rval[0] += context.action_object->GetX();
1458 		rval[1] += context.action_object->GetY();
1459 	}
1460 	return rval;
1461 }
1462 
EvalOffsetAdd(proplist props,proplist context)1463 private func EvalOffsetAdd(proplist props, proplist context)
1464 {
1465 	var o1 = EvaluateOffset(props.Offset1, context);
1466 	var o2 = EvaluateOffset(props.Offset2, context);
1467 	return [o1[0]+o2[0], o1[1]+o2[1]];
1468 }
1469 
EvalOffsetDiff(proplist props,proplist context)1470 private func EvalOffsetDiff(proplist props, proplist context)
1471 {
1472 	var pA = EvaluatePosition(props.PositionA, context);
1473 	var pB = EvaluatePosition(props.PositionB, context);
1474 	return [pB[0]-pA[0], pB[1]-pA[1]];
1475 }
1476 
EvalInt_Add(proplist props,proplist context)1477 private func EvalInt_Add(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) + EvaluateValue("Integer", props.RightOperand, context); }
EvalInt_Sub(proplist props,proplist context)1478 private func EvalInt_Sub(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) - EvaluateValue("Integer", props.RightOperand, context); }
EvalInt_Mul(proplist props,proplist context)1479 private func EvalInt_Mul(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) * EvaluateValue("Integer", props.RightOperand, context); }
1480 
EvalInt_Div(proplist props,proplist context)1481 private func EvalInt_Div(proplist props, proplist context)
1482 {
1483 	var a = EvaluateValue("Integer", props.LeftOperand, context), b = EvaluateValue("Integer", props.RightOperand, context);
1484 	return a/(b+!b);
1485 }
1486 
EvalInt_Mod(proplist props,proplist context)1487 private func EvalInt_Mod(proplist props, proplist context)
1488 {
1489 	var a = EvaluateValue("Integer", props.LeftOperand, context), b = EvaluateValue("Integer", props.RightOperand, context);
1490 	return a%(b+!b);
1491 }
1492 
EvalInt_Distance(proplist props,proplist context)1493 private func EvalInt_Distance(proplist props, proplist context)
1494 {
1495 	var pA = EvaluatePosition(props.PositionA, context);
1496 	var pB = EvaluatePosition(props.PositionB, context);
1497 	return Distance(pA[0], pA[1], pB[0], pB[1]);
1498 }
1499 
EvalInt_Wealth(proplist props,proplist context)1500 private func EvalInt_Wealth(proplist props, proplist context) { return GetWealth(EvaluatePlayer(props.Player, context)); }
1501 
EvalInt_PosCoord(proplist props,proplist context,int idx)1502 private func EvalInt_PosCoord(proplist props, proplist context, int idx) { return EvaluatePosition(props.Position, context)[idx]; }
1503 
EvalClr_PlayerColor(proplist props,proplist context)1504 private func EvalClr_PlayerColor(proplist props, proplist context) { return GetPlayerColor(EvaluatePlayer(props.Player, context)); }
1505 
EvalClr_RGB(proplist props,proplist context)1506 private func EvalClr_RGB(proplist props, proplist context)
1507 {
1508 	var R = EvaluateValue("Integer", props.R, context);
1509 	var G = EvaluateValue("Integer", props.G, context);
1510 	var B = EvaluateValue("Integer", props.B, context);
1511 	return RGB(R, G, B);
1512 }
1513 
EvalClr_Random(proplist props,proplist context)1514 private func EvalClr_Random(proplist props, proplist context)
1515 {
1516 	var a = EvaluateValue("Color", props.ColorA, context);
1517 	var b = EvaluateValue("Color", props.ColorB, context);
1518 	var result;
1519 	for (var i=0; i<3; ++i)
1520 	{
1521 		var ca = (a>>(i*8)) & 0xff;
1522 		var cb = (b>>(i*8)) & 0xff;
1523 		result |= ((Random(Abs(ca-cb+1)) + Min(ca, cb)) << (i*8));
1524 	}
1525 	return result;
1526 }
1527 
EvalStr_ValueToString(proplist props,proplist context)1528 private func EvalStr_ValueToString(proplist props, proplist context)
1529 {
1530 	return Format("%v", EvaluateValue("Any", props.Value, context));
1531 }
1532 
EvalStr_Concat(proplist props,proplist context)1533 private func EvalStr_Concat(proplist props, proplist context)
1534 {
1535 	var result="";
1536 	for (var s in props.Substrings) result = Format("%s%s", result, EvaluateString(s, context));
1537 	return result;
1538 }
1539 
EvalObjProp(proplist props,proplist context,prop_fn)1540 private func EvalObjProp(proplist props, proplist context, prop_fn)
1541 {
1542 	var obj = EvaluateValue("Object", props.Object, context);
1543 	if (!obj) return nil;
1544 	return obj->Call(prop_fn);
1545 }
1546 
1547 
1548 
1549 /* Context instance */
1550 
1551 // Proplist holding progress in each sequence
1552 local action_data, sequence_had_goto;
1553 static UserAction_SequenceIDs;
1554 
1555 // Set to innermost sequence (for goto)
1556 local last_sequence;
1557 
1558 // If this action is paused and will be resumed by a callback or by re-execution of the action, this property is set to the props of the holding action
1559 local hold;
1560 
1561 // Set to true if action is on hold but waiting for re-execution
1562 local suspended;
1563 
1564 // Return value if a value-providing evaluator is held
1565 local hold_result;
1566 
Initialize()1567 public func Initialize()
1568 {
1569 	action_data = {};
1570 	sequence_had_goto = {};
1571 	return true;
1572 }
1573 
InitContext(object action_object,int triggering_player,object triggering_object,proplist props,finish_callback,position)1574 public func InitContext(object action_object, int triggering_player, object triggering_object, proplist props, finish_callback, position)
1575 {
1576 	// Determine triggering player+objects
1577 	var triggering_clonk;
1578 	// Triggering player unknown? Try fallback to the controller of the triggering object
1579 	if (!GetType(triggering_player) && triggering_object)
1580 	{
1581 		triggering_player = triggering_object->GetController();
1582 	}
1583 	// Triggering clonk is the selected clonk of the triggering player
1584 	if (GetType(triggering_player))
1585 	{
1586 		triggering_clonk = GetCursor(triggering_player);;
1587 		if (!triggering_clonk) triggering_clonk = GetCrew(triggering_player);
1588 	}
1589 	// Triggering object / Triggering player clonk fallbacks
1590 	if (!triggering_object)
1591 		triggering_object = triggering_clonk;
1592 	else if (triggering_object->~IsClonk())
1593 		triggering_clonk = triggering_object;
1594 	// Position default
1595 	if (!position && triggering_object)
1596 		position = [triggering_object->GetX(), triggering_object->GetY()];
1597 	// Init context settings
1598 	this.action_object = action_object;
1599 	this.triggering_object = triggering_object;
1600 	this.triggering_clonk = triggering_clonk;
1601 	this.triggering_player = triggering_player;
1602 	this.position = position;
1603 	this.root_action = props;
1604 	this.suspended = false;
1605 	this.finish_callback = finish_callback;
1606 	return true;
1607 }
1608 
MenuOK(proplist menu_id,object clonk)1609 public func MenuOK(proplist menu_id, object clonk)
1610 {
1611 	// Pressed 'Next' in dialogue: Resume in user action
1612 	UserAction->ResumeAction(this, this.hold);
1613 }
1614 
MenuSelectOption(int index)1615 public func MenuSelectOption(int index)
1616 {
1617 	// Selected an option in dialogue: Resume at specified position in innermost sequence
1618 	if (!hold || !hold.Options) return;
1619 	var opt = this.hold.Options[index];
1620 	if (opt && last_sequence)
1621 	{
1622 		action_data[last_sequence._sequence_id] = opt._goto;
1623 		hold = nil;
1624 	}
1625 	UserAction->ResumeAction(this, hold);
1626 }
1627 
SaveScenarioObject(props)1628 public func SaveScenarioObject(props) { return false; } // temp. don't save.
1629