/* User action execution handler */
// Handles actions set in editor e.g. for dialogues, switches, etc.
// An object is sometimes needed to show a menu or start a timer, so this definition is created whenever a user action is run

local Name = "UserAction";
local Plane=0;

/* UserAction definition */

// Base classes for EditorProps using actions 
local Evaluator;

// EditorProps for generic user action callbacks
local Prop, PropProgressMode, PropParallel;

// Base props for action execution conditions
local ActionEvaluation ;

// Proplist containing callback function. Indexed by option names.
local EvaluatorCallbacks;

// Proplist containing option definitions. Indexed by option names.
local EvaluatorDefs;

// Call this definition early (but after EditorBase) to allow EditorProp initialization
local DefinitionPriority=99;

// Localized group names
local GroupNames = { Structure="$Structure$", Game="$Game$", Ambience="$Ambience$", Disasters="$Disasters$" };

// Storage for global user variables
static g_UserAction_global_vars;

// Localized evaluator names
local EvaluatorTypeNames = {
	Action = "$UserAction$",
	Color = "$UserColor$",
	Object = "$UserObject$",
	ObjectList = "$UserObjectList$",
	Definition = "$UserDefinition$",
	Player = "$UserPlayer$",
	PlayerList = "$UserPlayerList$",
	Boolean = "$UserBoolean$",
	Integer = "$UserInteger$",
	String = "$UserString$",
	Position = "$UserPosition$",
	Offset = "$UserOffset$"
};

// All evaluator types (unfortunately, EvaluatorReturnTypes->GetProperties() does not work)
local EvaluatorTypes = ["Action", "Object", "ObjectList", "Definition", "Player", "PlayerList", "Boolean", "Integer", "Color", "String", "Position", "Offset", "Any"];

// Evaluator return types
local EvaluatorReturnTypes = {
	Action = C4V_Nil,
	Object = C4V_C4Object,
	ObjectList = [C4V_C4Object],
	Definition = C4V_Def,
	Player = C4V_Int,
	PlayerList = [C4V_Int],
	Boolean = C4V_Bool,
	Integer = C4V_Int,
	String = C4V_String,
	Position = [C4V_Int, 2],
	Offset = [C4V_Int, 2],
	Any = C4V_Nil
};

// Array of specialized object evaluators
local object_evaluators;

func Definition(def)
{
	// Typed evaluator base definitions
	Evaluator = {};
	Evaluator.Action = { Name="$UserAction$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.Object = { Name="$UserObject$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.ObjectList = { Name="$UserObjectList$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.Definition = { Name="$UserDefinition$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.Player = { Name="$UserPlayer$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Noone$", Priority=100 } ] };
	Evaluator.PlayerList = { Name="$UserPlayerList$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Noone$", Priority=100 } ] };
	Evaluator.Boolean = { Name="$UserBoolean$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.Integer = { Name="$UserInteger$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="0", Priority=100 } ] };
	Evaluator.Color = { Name="$UserColor$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="$Default$", Priority=100 } ] };
	Evaluator.String = { Name="$UserString$", Type="enum", OptionKey="Function", Sorted=true, Options = [ {Name="($EmptyString$)", Priority=100 } ] };
	Evaluator.Position = { Name="$UserPosition$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$Here$", Priority=100 } ] };
	Evaluator.Offset = { Name="$UserOffset$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	Evaluator.Any = { Name="$UserAny$", Type="enum", OptionKey="Function", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
	EvaluatorCallbacks = {};
	EvaluatorDefs = {};
	// Object constant evaluator may be needed early be evaluators referencing filtered objects
	AddEvaluator("Object", nil, ["$ConstantObject$", ""], "$ConstantObjectHelp$", "object_constant", [def, def.EvalConstant], { Value=nil }, { Type="object", Name="$Value$" });
		// Action evaluators
	AddEvaluator("Action", "$Sequence$", "$Sequence$", "$SequenceHelp$", "sequence", [def, def.EvalAct_Sequence], { Actions=[] }, { Type="proplist", DescendPath="Actions", HideFullName=true, Display="{{Actions}}", EditorProps = {
		Actions = { Name="$Actions$", Type="array", Elements=Evaluator.Action },
		} } );
	AddEvaluator("Action", "$Sequence$", "$Goto$", "$GotoHelp$", "goto", [def, def.EvalAct_Goto], { Index={Function="int_constant", Value=0} }, new Evaluator.Integer { Name="$Index$" }, "Index");
	AddEvaluator("Action", "$Sequence$", "$StopSequence$", "$StopSequenceHelp$", "stop_sequence", [def, def.EvalAct_StopSequence]);
	AddEvaluator("Action", "$Sequence$", "$SuspendSequence$", "$SuspendSequenceHelp$", "suspend_sequence", [def, def.EvalAct_SuspendSequence]);
	AddEvaluator("Action", "$Sequence$", "$Wait$", "$WaitHelp$", "wait", [def, def.EvalAct_Wait], { Time=60 }, { Type="proplist", Display="{{Time}}", EditorProps = {
		Time = { Name="$Time$", Type="int", Min=1 }
		} } );
	AddEvaluator("Action", "$Sequence$", "$WaitForcondition$", "$WaitForConditionHelp$", "wait_condition", [def, def.EvalAct_WaitCondition], { Interval=20 }, { Type="proplist", Display="{{Condition}}", EditorProps = {
		Interval = { Name="$CheckInterval$", Type="int", Min=1 },
		Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$WaitConditionHelp$", Priority=60 }
		} } );
	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 = {
		Sound = { Name="$SoundName$", EditorHelp="$SoundNameHelp$", Type="sound", AllowEditing=true, Priority=100 },
		Pitch = new Evaluator.Integer { Name="$SoundPitch$", EditorHelp="$SoundPitchHelp$" },
		Volume = new Evaluator.Integer { Name="$SoundVolume$", EditorHelp="$SoundVolumeHelp$" },
		Loop = { Name="$SoundLoop$", EditorHelp="$SoundLoopHelp$", Type="enum", Options=[
			{ Name="$SoundLoopNone$" },
			{ Name="$SoundLoopOn$", Value=+1 },
			{ Name="$SoundLoopOff$", Value=-1 }
			] },
		TargetPlayers = new Evaluator.PlayerList { EditorHelp="$SoundTargetPlayersHelp$" },
		SourceObject = new Evaluator.Object { Name="$SoundSourceObject$", EditorHelp="$SoundSourceObjectHelp$", EmptyName="$Global$" }
		} } );
	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 = {
		ID = new Evaluator.Definition { EditorHelp="$CreateObjectDefinitionHelp$", Priority=100 },
		Position = new Evaluator.Position { EditorHelp="$CreateObjectPositionHelp$" },
		CreateAbove = { Name="$CreateObjectCreationOffset$", EditorHelp="$CreateObjectCreationOffsetHelp$", Type="enum", Options=[
			{ Name="$Center$" },
			{ Name="$Bottom$", Value=true }
			]},
		Owner = new Evaluator.Player { Name="$Owner$", EditorHelp="$CreateObjectOwnerHelp$" },
		Container = new Evaluator.Object { Name="$Container$", EditorHelp="$CreateObjectContainerHelp$" },
		SpeedX = new Evaluator.Integer { Name="$SpeedX$", EditorHelp="$CreateObjectSpeedXHelp$" },
		SpeedY = new Evaluator.Integer { Name="$SpeedY$", EditorHelp="$CreateObjectSpeedYHelp$" },
		Rotation = new Evaluator.Integer { Name="$Rotation$", EditorHelp="$CreateObjectRotationHelp$" },
		SpeedR = new Evaluator.Integer { Name="$SpeedR$", EditorHelp="$CreateObjectSpeedRHelp$" }
		} } );
	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 = {
		ID = new Evaluator.Definition { EditorHelp="$CastObjectsDefinitionHelp$", Priority=100 },
		Position = new Evaluator.Position { EditorHelp="$CastObjectsPositionHelp$" },
		Amount = new Evaluator.Integer { Name="$Amount$", EditorHelp="$CastObjectsAmountHelp$" },
		Speed = new Evaluator.Integer { Name="$Speed$", EditorHelp="$CastObjectsSpeedHelp$" },
		MeanAngle = new Evaluator.Integer { Name="$MeanAngle$", EditorHelp="$CastObjectsMeanAngleHelp$" },
		AngleDeviation = new Evaluator.Integer { Name="$AngleDeviation$", EditorHelp="$CastObjectsAngleDeviationHelp$" },
		Owner = new Evaluator.Player { Name="$Owner$", EditorHelp="$CastObjectsOwnerHelp$" }
		} } );
	AddEvaluator("Action", "$Object$", "$RemoveObject$", "$RemoveObjectHelp$", "remove_object", [def, def.EvalAct_RemoveObject], { }, { Type="proplist", Display="{{Object}}", EditorProps = {
		Object = new Evaluator.Object { EditorHelp="$RemoveObjectObject$", Priority=100 },
		EjectContents = { Name="$EjectContents$", EditorHelp="$EjectContentsHelp$", Type="enum", Options=[
			{ Name="$EjectContentsNo$" },
			{ Name="$EjectContentsYes$", Value=true }
			] },
		} } );
	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 = {
		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$SetPositionObjectHelp$" },
		Position = new Evaluator.Position { Name="$Position$", EditorHelp="$SetPositionPositionHelp$" }
		} } );
	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 = {
		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$FlingObjectHelp$" },
		SpeedX = new Evaluator.Integer { Name="$SpeedX$", EditorHelp="$FlingSpeedXHelp$" },
		SpeedY = new Evaluator.Integer { Name="$SpeedY$", EditorHelp="$FlingSpeedYHelp$" },
		AddSpeed = new Evaluator.Boolean { Name="$AddSpeedY$", EditorHelp="$FlingAddSpeedHelp$" },
		} } );
	AddEvaluator("Action", "$Object$", "$EnterObject$", "$EnterObjectHelp$", "enter_object", [def, def.EvalAct_EnterObject], { }, { Type="proplist", Display="{{Object}} -> {{Container}}", EditorProps = {
		Object = new Evaluator.Object { EditorHelp="$EnterObjectObjectHelp$", Priority=90 },
		Container = new Evaluator.Object { Name="$Container$", EditorHelp="$EnterObjectContainerHelp$", Priority=80 },
		CollectionCheck = { Name="$CollectionCheck$", EditorHelp="$CollectionCheckHelp$", Type="enum", Options=[
			{ Name="$CollectionCheckIgnore$" },
			{ Name="$CollectionCheckCheck$", Value="check" },
			{ Name="$CollectionCheckExit$", Value="exit" }
			] }
		} } );
	AddEvaluator("Action", "$Object$", "$ExitObject$", "$ExitObjectHelp$", "exit_object", [def, def.EvalAct_ExitObject], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Action", "$Object$", "$SetVisibility$", "$SetVisibilityHelp$", "set_visibility", [def, def.EvalAct_SetVisibility], { Object={ Function="triggering_clonk" }, Visibility=VIS_All }, { Type="proplist", Display="({{Object}}, {{Visibility}})", EditorProps = {
		Object = new Evaluator.Object { Name="$Object$" },
		Visibility = { Name="$Visibility$", Type="enum", Options = [{ Name="$Visible$", Value=VIS_All }, { Name="$Invisible$", Value=VIS_None }] }
		} } );
	AddEvaluator("Action", "$Object$", "$SetVincibility$", "$SetVincibilityHelp$", "set_vincibility", [def, def.EvalAct_SetVincibility], { Object={ Function="triggering_clonk" }, Vincibility=false }, { Type="proplist", Display="({{Object}}, {{Vincibility}})", EditorProps = {
		Object = new Evaluator.Object { Name="$Object$" },
		Vincibility = { Name="$Vincibility$", Type="enum", Options = [{ Name="$Invincible$", Value=false }, { Name="$Vincible$", Value=true }] }
		} } );
	AddEvaluator("Action", "Clonk", "$DoEnergy$", "$DoEnergyHelp$", "do_energy", [def, def.EvalAct_ObjectCallInt, Global.DoEnergy], { Object={ Function="triggering_clonk" } }, { Type="proplist", Display="({{Object}}, {{Value}})", EditorProps = {
		Object = new Evaluator.Object { Name="$Object$", EditorHelp="$DoEnergyObjectHelp$" },
		Value = new Evaluator.Integer { Name="$ValueChange$", EditorHelp="$DoEnergyValueChangeHelp$" }
		} } );
	AddEvaluator("Action", "Clonk", "$SetDirection$", "$SetDirectionHelp$", "set_direction", [def, def.EvalAct_SetDirection], { Object={ Function="triggering_clonk" }, Direction=DIR_Left }, { Type="proplist", Display="({{Object}}, {{Direction}})", EditorProps = {
		Object = GetObjectEvaluator("IsClonk", "$Clonk$"),
		Direction = { Name="$Direction$", Type="enum", Options=[{ Name="$Left$", Value=DIR_Left }, { Name="$Right$", Value=DIR_Right }] }
		} } );
	AddEvaluator("Action", "Ambience", "$CastParticles$", "$CastParticlesHelp$", "cast_particles", [def, def.EvalAct_CastParticles], {
			Name="StarFlash",
			Amount={Function="int_constant", Value=8},
			Speed={Function="int_constant", Value=20},
			Lifetime={Function="int_constant", Value=100},
			Size={Function="int_constant", Value=10},
			SizeEnd={Function="int_constant", Value=1},
			Color={Function="color_constant", Value=0xffff},
			BlitMode=0,
			Gravity={Function="int_constant", Value=100},
			FadeOut=true,
			CollisionFunc="bounce"
		}, { Type="proplist", Display="{{Amount}}x{{Name}}", EditorProps = {
		Name = { Name="$ParticleName$", EditorHelp="$ParticleNameHelp$", Type="enum", Priority=50, Sorted=true, Options = [
			{ Name="$Dust$", Value="Dust" },
			{ Name="$Flash$", Value="Flash" },
			{ Name="$Magic$", Value="Magic" },
			{ Name="$Smoke$", Value="Smoke" },
			{ Name="$Sphere$", Value="Sphere" },
			{ Name="$StarFlash$", Value="StarFlash" },
			{ Name="$StarSpark$", Value="StarSpark" },
			{ Name="$Fire$", Value="MagicFire" },
			{ Name="$Ring$", Value="MagicRing" }
			] },
		Position = new Evaluator.Position { EditorHelp="$CastObjectsPositionHelp$" },
		Amount = new Evaluator.Integer { Name="$Amount$", EditorHelp="$CastParticlesAmountHelp$" },
		Speed = new Evaluator.Integer { Name="$Speed$", EditorHelp="$CastParticlesSpeedHelp$" },
		Lifetime = new Evaluator.Integer { Name="$Lifetime$", EditorHelp="$CastParticlesLifetimeHelp$" },
		Size = new Evaluator.Integer { Name="$Size$", EditorHelp="$CastParticlesSizeHelp$" },
		SizeEnd = new Evaluator.Integer { Name="$SizeEnd$", EditorHelp="$CastParticlesSizeEndHelp$" },
		Color = new Evaluator.Color { Name="$Color$", EditorHelp="$CastParticlesColorHelp$" },
		BlitMode = { Name="$BlitMode$", EditorHelp="$ParticleBlitModeHelp$", Type="enum", Options = [
			{ Name="$Normal$", Value=0 },
			{ Name="$Additive$", Value=GFX_BLIT_Additive },
			{ Name="$Mod2$", Value=GFX_BLIT_Mod2 }
			] },
		Gravity = new Evaluator.Integer { Name="$Gravity$", EditorHelp="$ParticleGravityHelp$" },
		FadeOut = { Name="$FadeOut$", EditorHelp="$ParticleFadeOutHelp$", Type="bool" },
		CollisionFunc = { Name="$CollisionFunc$", EditorHelp="$ParticleCollisionFuncHelp$", Type="enum", Options = [
			{ Value="pass", Name="$Pass$" },
			{ Value="stop", Name="$Stop$" },
			{ Value="bounce", Name="$Bounce$" },
			{ Value="die", Name="$Die$" }
			] }
		} } );
	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 = {
		Player = Evaluator.Player,
		Change = new Evaluator.Integer { Name="$Change$", EditorHelp="$DoWealthChangeHelp$" },
		DoSound = new Evaluator.Boolean { Name="$Sound$", EditorHelp="$DoWealthSoundHelp$", Priority=-1 }
		} } );
	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 = {
		Players = Evaluator.PlayerList,
		ID = Evaluator.Definition
		} } );
	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 = {
		Players = Evaluator.PlayerList,
		Target = new Evaluator.Object { Name="$Target$", EditorHelp="$PlrViewTargetHelp$" },
		Immediate = { Name="$ScrollMode$", EditorHelp="$SetPlrViewScrollModeHelp$", Type="enum", Priority=-10, Options = [
			{ Name="$Smooth$" },
			{ Value=true, Name="$Immediate$" }
			] }
		} } );
	AddEvaluator("Action", "$Script$", "$ConditionalAction$", "$ConditionalActionHelp$", "if", [def, def.EvalAct_If], { }, { Type="proplist", Display="if({{Condition}}) {{TrueEvaluator}} else {{FalseEvaluator}}", EditorProps = {
		Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$IfConditionHelp$", Priority=60 },
		TrueEvaluator = new Evaluator.Action { Name="$TrueEvaluator$", EditorHelp="$TrueEvaluatorHelp$", Priority=50 },
		FalseEvaluator = new Evaluator.Action { Name="$FalseEvaluator$", EditorHelp="$FalseEvaluatorHelp$", Priority=30 }
		} } );
	AddEvaluator("Action", "$Script$", "$SetVariable$", "$SetVariableHelp$", "set_variable", [def, def.EvalAct_SetVariable], { VariableName={ Function="string_constant", Value="" } }, { Type="proplist", Display="{{Context}}::{{VariableName}}={{Value}}", EditorProps = {
		Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
		VariableName = new Evaluator.String { Name="$VariableName$", EditorHelp="$VariableNameHelp$" },
		Value = new Evaluator.Any { Name="$Value$", EditorHelp="$SetVariableValueHelp$" }
		} } );
	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 = {
		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
		Start = new Evaluator.Integer { Name="$Start$", EditorHelp="$ForStartHelp$", Priority=90 },
		End = new Evaluator.Integer { Name="$End$", EditorHelp="$ForEndHelp$", Priority=80 },
		Step = new Evaluator.Integer { Name="$Step$", EditorHelp="$ForStepHelp$", Priority=70 }
		} } );
	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 = {
		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
		Players = new Evaluator.PlayerList { EditorHelp="$ForPlayersHelp$" }
		} } );
	AddEvaluator("Action", "$Script$", "$ForObject$", "$ForObjectHelp$", "for_object", [def, def.EvalAct_For, def.EvalAct_For_ObjectList], { }, { Type="proplist", HideFullName=true, Display="for({{Objects}}) {{Action}}", EditorProps = {
		Action = new Evaluator.Action { Name="$UserAction$", EditorHelp="$ForActionHelp$", Priority=100 },
		Objects = new Evaluator.ObjectList { EditorHelp="$ForObjectsHelp$" }
		} } );
	AddEvaluator("Action", "$Script$", "$Log$", "$LogHelp$", "log", [def, def.EvalAct_Log], { }, new Evaluator.String { Name="$LogMessage$", EditorHelp="$LogMessageHelp$" }, "Message");
	AddEvaluator("Action", "$Script$", "$Comment$", "$CommentHelp$", "comment", [def, def.EvalAct_Nop], { Comment="" }, { Name="$Comment$", EditorHelp="$CommentHelp$", Type="string" }, "Comment");
	AddEvaluator("Action", "Game", "$GameOver$", "$GameOverHelp$", "game_over", [def, def.EvalAct_GameOver]);
	// Object evaluators
	AddEvaluator("Object", nil, "$ActionObject$", "$ActionObjectHelp$", "action_object", [def, def.EvalContextValue, "action_object"]);
	AddEvaluator("Object", nil, "$TriggerClonk$", "$TriggerClonkHelp$", "triggering_clonk", [def, def.EvalContextValue, "triggering_clonk"]);
	AddEvaluator("Object", nil, "$TriggerObject$", "$TriggerObjectHelp$", "triggering_object", [def, def.EvalContextValue, "triggering_object"]);
	AddEvaluator("Object", nil, "$IteratedObject$", "$IteratedObjectHelp$", "iterated_object", [def, def.EvalContextValue, "for_object"]);
	AddEvaluator("Object", nil, "$LastCreatedObject$", "$LastCreatedObjectHelp$", "last_created_object", [def, def.EvalContextValue, "last_created_object"]);
	var find_object_in_area_delegate = { Type="proplist", Display="{{ID}}", EditorProps = {
		ID = new Evaluator.Definition { Name="$ID$", EditorHelp="$FindObjectsIDHelp$", EmptyName="$Any$", Priority=51 },
		Area = { Name="$SearchArea$", EditorHelp="$SearchAreaHelp$", Type="enum", OptionKey="Function", Priority=41, Options=[
			{ Name="$SearchAreaWholeMap$", EditorHelp="$SearchAreaWholeMapHelp$" },
			{ Name="$SearchAreaInRect$", EditorHelp="$SearchAreaInRectHelp$", Value={ Function="InRect" }, DefaultValueFunction=def.GetDefaultRect, ValueKey="Area", Delegate={ Type="rect", Name="$Rectangle$", Relative=false, Color=0xffff00 } },
			{ Name="$SearchAreaAtRect$", EditorHelp="$SearchAreaAtRectHelp$", Value={ Function="AtRect" }, DefaultValueFunction=def.GetDefaultRect, ValueKey="Area", Delegate={ Type="rect", Name="$Rectangle$", Relative=false, Color=0xffff80 } },
			{ Name="$SearchAreaCircle$", EditorHelp="$SearchAreaCircleHelp$", Value={ Function="Circle" }, DefaultValueFunction=def.GetDefaultCircle, ValueKey="Area", Delegate={ Type="circle", Name="$Circle$", Relative=false, CanMoveCenter=true, Color=0xff00ff } },
			{ Name="$SearchAreaNearPosition$", EditorHelp="$SearchAreaNearPositionHelp$", Value={ Function="NearPosition", Parameters={Radius=25} }, ValueKey="Parameters", Delegate={ Type="proplist", Display="({{Position}}, {{Radius}})", EditorProps = {
				Position = new Evaluator.Position { EditorHelp="$SearchAreaNearPositionPositionHelp$"},
				Radius = { Type="circle", Relative=true, Name="$Radius$", Color=0xff80ff }
				} } }
			] },
		AllowContained = { Name="$AllowContained$", EditorHelp="$AllowContainedHelp$", Type="bool", Priority=31 }
		} };
	var find_object_in_container_delegate = { Type="proplist", Display="{{ID}} in {{Container}}", EditorProps = {
		ID = new Evaluator.Definition { Name="$ID$", EditorHelp="$FindObjectsIDHelp$", EmptyName="$Any$" },
		Container = new Evaluator.Object { Name="$Container$", EditorHelp="FindObjectsContainerHelp" }
		} };
	AddEvaluator("Object", nil, "$FindObjectInArea$", "$FindObjectInAreaHelp$", "find_object_in_area", [def, def.EvalObjList_FindObjectsInArea, true], {}, find_object_in_area_delegate);
	AddEvaluator("Object", nil, "$FindObjectInContainer$", "$FindObjectInContainerHelp$", "find_object_in_container", [def, def.EvalObjList_FindObjectInContainer], {}, find_object_in_container_delegate);
	// Object list evaluators
	AddEvaluator("ObjectList", nil, "$FindObjectsInArea$", "$FindObjectsInAreaHelp$", "find_objects_in_area", [def, def.EvalObjList_FindObjectsInArea], {}, find_object_in_area_delegate);
	AddEvaluator("ObjectList", nil, "$FindObjectsInContainer$", "$FindObjectsInContainerHelp$", "find_objects_in_container", [def, def.EvalObjList_FindObjectsInContainer], {}, find_object_in_container_delegate);
	// Definition evaluators
	AddEvaluator("Definition", nil, ["$Constant$", ""], "$ConstantHelp$", "def_constant", [def, def.EvalConstant], { Value=nil }, { Type="def", Name="$Value$" });
	AddEvaluator("Definition", nil, "$TypeOfObject$", "$TypeOfObjectHelp$", "type_of_object", [def, def.EvalObjProp, Global.GetID], { }, new Evaluator.Object { }, "Object");
	// Player evaluators
	AddEvaluator("Player", nil, "$TriggeringPlayer$", "$TriggeringPlayerHelp$", "triggering_player", [def, def.EvalContextValue, "triggering_player"]);
	AddEvaluator("Player", nil, "$OwnerOfObject$", "$OwnerOfObjectHelp$", "owner", [def, def.EvalObjProp, Global.GetOwner], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Player", nil, "$ControllerOfObject$", "$ControllerOfObjectHelp$", "owner", [def, def.EvalObjProp, Global.GetController], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Player", nil, "$IteratedPlayer$", "$IteratedPlayerHelp$", "iterated_player", [def, def.EvalContextValue, "for_player"]);
	// Player list evaluators
	AddEvaluator("PlayerList", nil, "$TriggeringPlayer$", "$TriggeringPlayerHelp$", "triggering_player_list", [def, def.EvalPlrList_Single, def.EvalPlr_Trigger]);
	AddEvaluator("PlayerList", nil, "$AllPlayers$", "$AllPlayersHelp$", "all_players", [def, def.EvalPlrList_All]);
	// Boolean (condition) evaluators
	AddEvaluator("Boolean", nil, ["$Constant$", ""], "$ConstantHelp$", "bool_constant", [def, def.EvalConstant], { Value=true }, { Type="bool", Name="$Value$" });
	AddEvaluator("Boolean", "$Comparison$", "$CompareInteger$", "$ComparisonHelp$", "compare_int", [def, def.EvalComparison, "Integer"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.Integer { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			{ Name="<", EditorHelp="$LessThanHelp$", Value="lt" },
			{ Name=">", EditorHelp="$GreaterThanHelp$", Value="gt" },
			{ Name="<=", EditorHelp="$LessOrEqualHelp$", Value="le" },
			{ Name=">=", EditorHelp="$GreaterOrEqualHelp$", Value="ge" }
			] },
		RightOperand = new Evaluator.Integer { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Comparison$", "$CompareBoolean$", "$ComparisonHelp$", "compare_bool", [def, def.EvalComparison, "Boolean"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.Object { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			] },
		RightOperand = new Evaluator.Object { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Comparison$", "$CompareObject$", "$ComparisonHelp$", "compare_object", [def, def.EvalComparison, "Object"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.Object { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			] },
		RightOperand = new Evaluator.Object { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Comparison$", "$CompareString$", "$ComparisonHelp$", "compare_string", [def, def.EvalComparison, "String"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.String { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			] },
		RightOperand = new Evaluator.String { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Comparison$", "$CompareDefinition$", "$ComparisonHelp$", "compare_definition", [def, def.EvalComparison, "Definition"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.Definition { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			] },
		RightOperand = new Evaluator.Definition { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Comparison$", "$ComparePlayer$", "$ComparisonHelp$", "compare_player", [def, def.EvalComparison, "Player"], { }, { Type="proplist", Display="{{LeftOperand}}{{Operator}}{{RightOperand}}", EditorProps = {
		LeftOperand = new Evaluator.Player { Name="$LeftOperand$", EditorHelp="$LeftOperandHelp$", Priority=44 },
		Operator = { Type="enum", Name="$Operator$", EditorHelp="$OperatorHelp$", Priority=43, Options = [
			{ Name="==", EditorHelp="$EqualHelp$" },
			{ Name="!=", EditorHelp="$NotEqualHelp$", Value="ne" },
			] },
		RightOperand = new Evaluator.Player { Name="$RightOperand$", EditorHelp="$RightOperandHelp$", Priority=42 }
		} } );
	AddEvaluator("Boolean", "$Logic$", "$Not$", "$NotHelp$", "not", [def, def.EvalBool_Not], { }, new Evaluator.Boolean { }, "Operand");
	AddEvaluator("Boolean", "$Logic$", "$And$", "$AndHelp$", "and", [def, def.EvalBool_And], { Operands=[] }, { Type="proplist", DescendPath="Operands", Display="{{Operands}}", EditorProps = {
		Operands = { Name="$Operands$", Type="array", Elements=Evaluator.Boolean }
		} } );
	AddEvaluator("Boolean", "$Logic$", "$Or$", "$OrHelp$", "or", [def, def.EvalBool_Or], { Operands=[] }, { Type="proplist", DescendPath="Operands", Display="{{Operands}}", EditorProps = {
		Operands = { Name="$Operands$", Type="array", Elements=Evaluator.Boolean }
		} } );
	AddEvaluator("Boolean", nil, "$ObjectExists$", "$ObjectExistsHelp$", "object_exists", [def, def.EvalBool_ObjectExists], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Boolean", nil, "$ObjectAlive$", "$ObjectAliveHelp$", "object_alive", [def, def.EvalBool_ObjectAlive], { }, new Evaluator.Object { }, "Object");
	// Integer evaluators
	AddEvaluator("Integer", nil, ["$Constant$", ""], "$ConstantHelp$", "int_constant", [def, def.EvalConstant], { Value=0 }, { Type="int", Name="$Value$" });
	var arithmetic_delegate = { Type="proplist", HideFullName=true, EditorProps = {
		LeftOperand = new Evaluator.Integer { Name="$LeftOperand$", EditorHelp="$LeftArithmeticOperandHelp$", Priority=44 },
		RightOperand = new Evaluator.Integer { Name="$RightOperand$", EditorHelp="$RightArithmeticOperandHelp$", Priority=42 }
		} };
	AddEvaluator("Integer", "$Arithmetic$", "$Sum$ (+)", "$SumHelp$", "add", [def, def.EvalInt_Add], { }, new arithmetic_delegate { Display="{{LeftOperand}}+{{RightOperand}}" });
	AddEvaluator("Integer", "$Arithmetic$", "$Sub$ (-)", "$SumHelp$", "subtract", [def, def.EvalInt_Sub], { }, new arithmetic_delegate { Display="{{LeftOperand}}-{{RightOperand}}" });
	AddEvaluator("Integer", "$Arithmetic$", "$Mul$ (*)", "$MulHelp$", "multiply", [def, def.EvalInt_Mul], { }, new arithmetic_delegate { Display="{{LeftOperand}}*{{RightOperand}}" });
	AddEvaluator("Integer", "$Arithmetic$", "$Div$ (/)", "$DivHelp$", "divide", [def, def.EvalInt_Div], { }, new arithmetic_delegate { Display="{{LeftOperand}}/{{RightOperand}}" });
	AddEvaluator("Integer", "$Arithmetic$", "$Mod$ (%)", "$ModHelp$", "modulo", [def, def.EvalInt_Mod], { }, new arithmetic_delegate { Display="{{LeftOperand}}%{{RightOperand}}" });
	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 = {
		Min = new Evaluator.Integer { Name="$Min$", EditorHelp="$RandomMinHelp$", Priority=51 },
		Max = new Evaluator.Integer { Name="$Max$", EditorHelp="$RandomMaxHelp$", Priority=21 }
		} } );
	AddEvaluator("Integer", nil, "$Distance$", "$DistanceHelp$", "distance", [def, def.EvalInt_Distance], { }, { Type="proplist", HideFullName=true, Display="d({{PositionA}}..{{PositionB}})", EditorProps = {
		PositionA = new Evaluator.Position { Name="$PositionA$", EditorHelp="$PositionAHelp$" },
		PositionB = new Evaluator.Position { Name="$PositionB$", EditorHelp="$PositionBHelp$" }
		} } );
	AddEvaluator("Integer", nil, "$NumberOfObjects$", "$NumberOfObjectsHelp$", "object_count", [def, def.EvalCount, "ObjectList"], { }, new Evaluator.ObjectList { }, "Array");
	AddEvaluator("Integer", nil, "$NumberOfPlayers$", "$NumberOfPlayersHelp$", "player_count", [def, def.EvalCount, "PlayerList"], { }, new Evaluator.PlayerList { }, "Array");
	AddEvaluator("Integer", nil, "$PlayerWealth$", "$PlayerWealthHelp$", "player_wealth", [def, def.EvalInt_Wealth], { }, new Evaluator.Player { }, "Player");
	AddEvaluator("Integer", nil, "$ClonkEnergy$", "$ClonkEnergyHelp$", "clonk_energy", [def, def.EvalObjProp, Global.GetEnergy], { }, GetObjectEvaluator("IsClonk", "$Clonk$"), "Object");
	AddEvaluator("Integer", nil, "$ObjectMass$", "$ObjectMassHelp$", "object_mass", [def, def.EvalObjProp, Global.GetMass], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Integer", nil, "$ObjectSpeed$", "$ObjectSpeedHelp$", "object_speed", [def, def.EvalObjProp, Global.GetSpeed], { }, new Evaluator.Object { }, "Object");
	AddEvaluator("Integer", nil, "$PositionX$", "$PositionXHelp$", "position_x", [def, def.EvalInt_PosCoord, 0], { }, new Evaluator.Position { }, "Position");
	AddEvaluator("Integer", nil, "$PositionY$", "$PositionYHelp$", "position_y", [def, def.EvalInt_PosCoord, 1], { }, new Evaluator.Position { }, "Position");
	AddEvaluator("Integer", nil, "$IteratedInteger$", "$IteratedIntegerHelp$", "iterated_int", [def, def.EvalContextValue, "for_int"]);
	// String evaluators
	AddEvaluator("String", nil, ["$Constant$", ""], "$ConstantHelp$", "string_constant", [def, def.EvalStringConstant], { Value="" }, { Type="string", Name="$Value$", Translatable=true });
	AddEvaluator("String", nil, ["$ValueToString$", ""], "$ValueToStringHelp$", "value_to_string", [def, def.EvalStr_ValueToString], { }, new Evaluator.Any { });
	AddEvaluator("String", nil, "$Concat$", "$ConcatHelp$", "string_concat", [def, def.EvalStr_Concat], { Substrings=[] }, { Type="proplist", HideFullName=true, DescendPath="Substrings", Display="{{Substrings}}", EditorProps = {
		Substrings = { Name="$Substrings$", Type="array", Elements=Evaluator.String }
		} } );
	// Color evaluators
	AddEvaluator("Color", nil, ["$Constant$", ""], "$ConstantHelp$", "color_constant", [def, def.EvalConstant], { Value=0xffffff }, { Type="color", Name="$Value$" });
	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 = {
		ColorA = new Evaluator.Color { Name="$ColorA$" },
		ColorB = new Evaluator.Color { Name="$ColorB$" }
		} } );
	AddEvaluator("Color", nil, "$PlayerColor$", "$PlayerColorHelp$", "player_color", [def, def.EvalClr_PlayerColor], { Player={ Function="triggering_player" } }, new Evaluator.Player { }, "Player");
	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 = {
		R = new Evaluator.Integer { Name="$Red$", Priority=51 },
		G = new Evaluator.Integer { Name="$Green$", Priority=41 },
		B = new Evaluator.Integer { Name="$Blue$", Priority=31 }
		} } );
	// Position evaluators
	AddEvaluator("Position", nil, ["$ConstantPositionAbsolute$", ""], "$ConstantPositionAbsoluteHelp$", "position_constant", [def, def.EvalConstant], def.GetDefaultPosition, { Type="point", Name="$Position$", Relative=false, Color=0xff2000 });
	AddEvaluator("Position", nil, ["$ConstantPositionRelative$", "+"], "$ConstantPositionRelativeHelp$", "position_constant_rel", [def, def.EvalPositionRelative], { Value=[0,0] }, { Type="point", Name="$Position$", Relative=true, Color=0xff0050 });
	AddEvaluator("Position", nil, "$Coordinates$", "$CoordinatesHelp$", "position_coordinates", [def, def.EvalCoordinates], def.GetDefaultCoordinates, { Type="proplist", HideFullName=true, Display="({{X}},{{Y}})", EditorProps = {
		X = new Evaluator.Integer { Name="X", EditorHelp="$PosXHelp$" },
		Y = new Evaluator.Integer { Name="Y", EditorHelp="$PosYHelp$" }
		} } );
	AddEvaluator("Position", nil, "$PositionOffset$", "$PositionOffsetHelp$", "position_offset", [def, def.EvalPositionOffset], { }, { Type="proplist", HideFullName=true, Display="{{Position}}+{{Offset}}", EditorProps = {
		Position = new Evaluator.Position { EditorHelp="$PositionOffsetPositionHelp$", Priority=51 },
		Offset = new Evaluator.Offset { EditorHelp="$PositionOffsetOffsetHelp$", Priority=21 }
		} } );
	AddEvaluator("Position", nil, "$ObjectPosition$", "$ObjectPositionHelp$", "object_position", [def, def.EvalPositionObject], { Object={Function="triggering_object"} }, new Evaluator.Object { EditorHelp="$ObjectPositionObjectHelp$" }, "Object");
	AddEvaluator("Position", nil, "$LastUsePosition$", "$LastUsePositionHelp$", "use_position", [def, def.EvalPos_LastUse]);
	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");
	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");
	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");
	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");
	// Offset evaluators
	AddEvaluator("Offset", nil, ["$ConstantOffsetRelative$", ""], "$ConstantOffsetRelativeHelp$", "offset_constant", [def, def.EvalConstant], { Value=[0,0] }, { Type="point", Name="$Position$", Relative=true, Color=0xff30ff });
	AddEvaluator("Offset", nil, ["$Coordinates$", ""], "$CoordinatesHelp$", "offset_coordinates", [def, def.EvalCoordinates], { Value={X=0,Y=0} }, { Type="proplist", HideFullName=true, Display="({{X}},{{Y}})", EditorProps = {
		X = new Evaluator.Integer { Name="X", EditorHelp="$OffXHelp$" },
		Y = new Evaluator.Integer { Name="Y", EditorHelp="$OffYHelp$" },
		} } );
	AddEvaluator("Offset", nil, "$AddOffsets$", "$AddOffsetsHelp$", "add_offsets", [def, def.EvalOffsetAdd], { }, { Type="proplist", HideFullName=true, Display="{{Offset1}}+{{Offset2}}", EditorProps = {
		Offset1 = new Evaluator.Offset { EditorHelp="$AddOffsetOffsetHelp$" },
		Offset2 = new Evaluator.Offset { EditorHelp="$AddOffsetOffsetHelp$" }
		} } );
	AddEvaluator("Offset", nil, "$DiffPositions$", "$DiffPositionsHelp$", "diff_positions", [def, def.EvalOffsetDiff], { }, { Type="proplist", HideFullName=true, Display="{{PositionB}}-{{PositionA}}", EditorProps = {
		PositionA = new Evaluator.Position { Name="$PositionA$", EditorHelp="$PositionAHelp$" },
		PositionB = new Evaluator.Position { Name="$PositionB$", EditorHelp="$PositionBHelp$" }
		} } );
	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");
	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");
	// Script evaluators
	var variable_delegate = { Type="proplist", HideFullName=true, Display="{{Context}}::{{VariableName}}", EditorProps = {
		Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
		VariableName = new Evaluator.String { Name="$VariableName$", EditorHelp="$VariableNameHelp$", Priority=50 } } };
	for (var eval_type in EvaluatorTypes)
	{
		var data_type = EvaluatorReturnTypes[eval_type];
		var group = nil;
		if (eval_type != "Action")
		{
			AddEvaluator(eval_type, nil, "$Variable$", "$VariableHelp$", Format("%s_variable", eval_type), [def, def.EvalVariable, data_type], { VariableName={ Function="string_constant", Value="" } }, variable_delegate);
			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 = {
				Condition = new Evaluator.Boolean { Name="$Condition$", EditorHelp="$IfConditionValueHelp$", Priority=60 },
				TrueEvaluator = new Evaluator[eval_type] { Name="$TrueEvaluatorValue$", EditorHelp="$TrueEvaluatorValueHelp$", Priority=50 },
				FalseEvaluator = new Evaluator[eval_type] { Name="$FalseEvaluatorValue$", EditorHelp="$FalseEvaluatorValueHelp$", Priority=30 }
			} } );
		}
		else
		{
			group = "$Script$";
		}
		AddEvaluator(eval_type, group, "$Script$", "$ScriptHelp$", Format("%s_script", eval_type), [def, def.EvalScript, data_type], { }, { Type="proplist", HideFullName=true, Display="{{Context}}::{{Script}}", EditorProps = {
			Context = new Evaluator.Object { Name="$Context$", EditorHelp="$VariableContextHelp$", EmptyName="$Global$" },
			Script = new Evaluator.String { Name="$ScriptCommand$", EditorHelp="$ScriptCommandHelp$" } } });
	}
	// User action editor props
	Prop = Evaluator.Action;
	PropProgressMode = { Name="$UserActionProgressMode$", EditorHelp="$UserActionProgressModeHelp$", Type="enum", Options = [ { Name="$Session$", Value="session" }, { Name="$PerPlayer$", Value="player" }, { Name="$Global$" } ] };
	PropParallel = { Name="$ParallelAction$", EditorHelp="$ParallelActionHelp$", Type="bool" };
	return true;
}

public func GetObjectEvaluator(filter_def, name, help)
{
	// Create copy of the Evaluator.Object delegate, but with the object_constant proplist replaced by a version with filter_def
	var object_options = Evaluator.Object.Options[:];
	// Need to copy the option Value field to ensure it is owned by the correct parent.
	// Otherwise it would be assigned by reference in the editor
	var const_option = new EvaluatorDefs["object_constant"] { Value = { Function="object_constant", Value=nil } };
	const_option.Delegate = new const_option.Delegate { Filter=filter_def };
	object_options[const_option.OptionIndex] = const_option;
	var new_evaluator = new Evaluator.Object { Name=name, Options=object_options, EditorHelp=help };
	if (!object_evaluators) object_evaluators = [];
	object_evaluators[GetLength(object_evaluators)] = new_evaluator;
	return new_evaluator;
}

private func CopyProplist(p)
{
	// Create copy of p to ensure unique global reference
	if (GetType(p) != C4V_PropList) return p;
	var r = {};
	for (var k in GetProperties(p)) r[k] = p[k];
	return r;
}

public func AddEvaluator(string eval_type, string group, name, string help, string identifier, callback_data, default_val, proplist delegate, string delegate_storage_key)
{
	// 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.
	// eval_type: Return type of the evaluator (Action, Object, Boolean, Player, etc. as defined in UserAction.Evaluator)
	// group [optional] Localized name of sub-group for larger enums (i.e. actions)
	// name: Localized name as it appears in the dropdown list of evaluators. May also be an array [name, short_name].
	// 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.
	// callback_data: Array of [definition, definition.Function, parameter (optional)]. Function to be called when this evaluator is called
	// 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
	// delegate: Parameters for this evaluator
	// Copy all value evaluations
	if (eval_type != "Action" && eval_type != "Any" && callback_data[1] != UserAction.EvalVariable)
	{
		var any_group;
		if (group) any_group = Format("%s/%s", EvaluatorTypeNames[eval_type], group); else any_group = EvaluatorTypeNames[eval_type];
		AddEvaluator("Any", any_group, name, help, identifier, callback_data, CopyProplist(default_val), delegate, delegate_storage_key);
	}
	// Dissect parameters
	if (group) group = GroupNames[group] ?? group; // resolve localized group name
	var short_name;
	if (GetType(name) == C4V_Array)
	{
		short_name = name[1];
		name = name[0];
	}
	else if (delegate && delegate.HideFullName)
	{
		// Some proplist delegates provide their own display string and need not show the option name
		short_name = "";
	}
	if (!default_val) default_val = {};
	var default_get;
	if (GetType(default_val) == C4V_Function)
	{
		default_get = default_val;
		default_val = Call(default_get, nil, {Function=identifier});
	}
	default_val.Function = identifier;
	var action_def = { Name=name, ShortName=short_name, EditorHelp=help, Group=group, Value=default_val, Delegate=delegate, DefaultValueFunction=default_get }, n;
	if (delegate)
	{
		if (delegate.EditorProps || delegate.Elements)
		{
			// Proplist of array parameter for this evaluator: Descend path title should be name
			delegate.Name = name;
			var child_delegate = delegate;
			if (delegate.DescendPath) child_delegate = delegate.EditorProps[delegate.DescendPath];
			if (!child_delegate.EditorHelp) child_delegate.EditorHelp = help;
		}
		else
		{
			// Any other parameter type: Store in value
			action_def.ValueKey = delegate_storage_key ?? "Value";
		}
	}
	// Constant has higher priority
	if (callback_data[1] == UserAction.EvalConstant)
	{
		action_def.Priority = 50;
	}
	Evaluator[eval_type].Options[n = GetLength(Evaluator[eval_type].Options)] = action_def;
	action_def.OptionIndex = n;
	// Remember lookup table through identifier (ignore duplicates)
	if (eval_type != "Any" || !group)
	{
		EvaluatorCallbacks[identifier] = callback_data;
		EvaluatorDefs[identifier] = action_def;
	}
	// Copy any object evaluators to existing evaluator lists
	if (eval_type == "Object" && object_evaluators)
		for (var obj_eval in object_evaluators)
			obj_eval.Options[GetLength(obj_eval.Options)] = action_def;
	return action_def;
}

public func EvaluateValue(string eval_type, proplist props, proplist context)
{
	//Log("EvaluateValue %v %v %v", eval_type, props, context);
	if (!props) return nil;
	// Finish any hold-action
	if (context.hold == props)
	{
		context.hold = nil;
		return context.hold_result;
	}
	// Not on hold: Perform evaluation
	var cb = EvaluatorCallbacks[props.Function];
	/*var rval = cb[0]->Call(cb[1], props, context, cb[2]);
	Log("%v <- EvaluateValue %v %v %v", rval, eval_type, props, context);
	return rval;*/
	return cb[0]->Call(cb[1], props, context, cb[2]);
}

public func EvaluateAction(proplist props, object action_object, object triggering_object, int triggering_player, string progress_mode, bool allow_parallel, finish_callback, array position)
{
	// No action
	if (!props) if (finish_callback) return action_object->Call(finish_callback); else return;
	// Determine context
	var context;
	if (!progress_mode)
	{
		if (!(context = props._context))
			props._context = context = CreateObject(UserAction);
	}
	else if (progress_mode == "player")
	{
		if (!props._contexts) props._contexts = [];
		var plr_id = GetPlayerID(triggering_player);
		if (!(context = props._contexts[plr_id]))
			props._contexts[plr_id] = context = CreateObject(UserAction);
	}
	else // if (progress_mode == "session")
	{
		// Temporary context
		context = CreateObject(UserAction);
		context.temp = true;
	}
	// Prevent duplicate parallel execution
	if (!allow_parallel && (context.hold && !context.suspended)) return false;
	// Init context settings
	context->InitContext(action_object, triggering_player, triggering_object, props, finish_callback, position);
	// Execute action
	EvaluateValue("Action", props, context);
	FinishAction(context);
	return true;
}

public func EvaluateCondition(proplist props, object action_object, object triggering_object, int triggering_player)
{
	// Build temp context
	var context = CreateObject(UserAction);
	context.temp = true;
	// Init context settings
	context->InitContext(action_object, triggering_player, triggering_object, props);
	// Execute condition evaluator
	var result = EvaluateValue("Boolean", props, context);
	// Cleanup
	if (context) context->RemoveObject();
	// Done
	return result;
}

public func EvaluateString(proplist props, object context)
{
	return EvaluateValue("String", props, context) ?? "";
}

private func EvaluatePosition(proplist props, object context)
{
	// Execute position evaluator; fall back to position of action object
	var position = EvaluateValue("Position", props, context);
	if (!position)
	{
		if (context.action_object) position = [context.action_object->GetX(), context.action_object->GetY()];
		else position = [0,0];
	}
	return position;
}

private func EvaluateOffset(proplist props, object context)
{
	// Execute offset evaluator; fall back to [0, 0]
	return  EvaluateValue("Offset", props, context) ?? [0,0];
}

private func EvaluatePlayer(proplist props, object context)
{
	// Execute player evaluator; nil means NO_OWNER
	var plr = EvaluateValue("Player", props, context);
	if (!GetType(plr)) plr = NO_OWNER;
	return plr;
}

private func ResumeAction(proplist context, proplist resume_props)
{
	//Log("ResumeAction %v %v", context, resume_props);
	// Resume only if on hold for the same entry
	if (context.hold != resume_props) return;
	// Not if owning object is dead
	if (!context.action_object) return;
	// Resume action
	EvaluateValue("Action", context.root_action, context);
	// Cleanup action object (unless it ran into another hold)
	FinishAction(context);
}

private func FinishAction(proplist context)
{
	// Cleanup action object (unless it's kept around for callbacks or to store sequence progress)
	// Note that context.root_action.contexts is checked to kill session-contexts that try to suspend
	// There would be no way to resume so just kill the context
	if (!context.hold || context.temp)
	{
		if (context.action_object && context.finish_callback) context.action_object->Call(context.finish_callback, context);
		context->RemoveObject();
	}
}

private func EvalConstant(proplist props, proplist context) { return props.Value; }
private func EvalStringConstant(proplist props, proplist context) { return GetTranslatedString(props.Value); }
private func EvalContextValue(proplist props, proplist context, string name) { return context[name]; }
private func EvalObj_ActionObject(proplist props, proplist context) { return context.action_object; }
private func EvalObj_TriggeringObject(proplist props, proplist context) { return context.triggering_object; }
private func EvalObj_TriggeringClonk(proplist props, proplist context) { return context.triggering_clonk; }
private func EvalObj_LastCreatedObject(proplist props, proplist context) { return context.last_created_object; }
private func EvalPlr_Trigger(proplist props, proplist context) { return context.triggering_player; }
private func EvalPlrList_Single(proplist props, proplist context, fn) { return [Call(fn, props, context)]; }

private func EvalCount(proplist props, proplist context, data_type)
{
	var list = EvaluateValue(data_type, props.Array, context);
	if (list) return GetLength(list); else return 0;
}

private func EvalObjList_FindObjectsInArea(proplist props, proplist context, bool find_one)
{
	var params = Find_And(), np=1;
	// Resolve area parameter
	if (props.Area)
	{
		var area = props.Area.Area;
		var fn = props.Area.Function;
		var area_criterion;
		if (fn == "InRect")
			area_criterion = Find_InRect(area[0], area[1], area[2], area[3]);
		else if (fn == "AtRect")
			area_criterion = Find_AtRect(area[0], area[1], area[2], area[3]);
		else if (fn == "Circle")
			area_criterion = Find_Distance(area[0], area[1], area[2]);
		else if (fn == "NearPosition")
		{
			var pos_params = props.Area.Parameters;
			var pos = EvaluatePosition(pos_params.Position, context);
			area_criterion = Find_Distance(pos_params.Radius, pos[0], pos[1]);
		}
		if (area_criterion) params[np++] = area_criterion;
	}
	// Other parameters
	var idobj = EvaluateValue("Definition", props.ID, context);
	if (idobj) params[np++] = Find_ID(idobj);
	if (!props.AllowContained) params[np++] = Find_NoContainer();
	// Find objects
	var result = FindObjects(params);
	if (find_one) return result[0]; else return result;
}

private func EvalObjList_FindObjectInContainer(proplist props, proplist context)
{
	var container = EvaluateValue("Object", props.Container, context);
	var idobj = EvaluateValue("Definition", props.ID, context);
	if (!container) return;
	if (idobj)
		return container->FindContents(idobj);
	else
		return container->Contents();
}

private func EvalObjList_FindObjectsInContainer(proplist props, proplist context)
{
	// Return array of all objects contained in container
	var container = EvaluateValue("Object", props.Container, context);
	var idobj = EvaluateValue("Definition", props.ID, context);
	if (!container) return; // nil is treated as empty list
	var i, n = container->ContentsCount(idobj), j;
	if (!n) return;
	var result = CreateArray(n), obj;
	while ((obj = container->Contents(i++)))
		if (!idobj || obj->GetID() == idobj)
			result[j++] = obj;
	return result;
}

private func EvalPlrList_All(proplist props, proplist context, fn)
{
	var n = GetPlayerCount(C4PT_User);
	var result = CreateArray(n);
	for (var i=0; i<n; ++i) result[i] = GetPlayerByIndex(i);
	return result;
}

private func EvalComparison(proplist props, proplist context, data_type)
{
	var left = EvaluateValue(data_type, props.LeftOperand, context);
	var right = EvaluateValue(data_type, props.RightOperand, context);
	if (!left) left = nil; // 0 ==nil
	if (!right) right = nil; // 0 == nil
	var op = props.Operator;
	if (!op)
		return left == right;
	else if (op == "ne")
		return left != right;
	else if (op == "lt")
		return left < right;
	else if (op == "gt")
		return left > right;
	else if (op == "le")
		return left <= right;
	else if (op == "ge")
		return left >= right;
}

private func EvalBool_Not(proplist props, proplist context) { return !EvaluateValue("Boolean", props.Operand, context); }

private func EvalBool_And(proplist props, proplist context)
{
	for (var cond in props.Operands)
		if (!EvaluateValue("Boolean", cond, context))
			return false;
	return true;
}

private func EvalBool_Or(proplist props, proplist context)
{
	for (var cond in props.Operands)
		if (EvaluateValue("Boolean", cond, context))
			return true;
	return false;
}

private func EvalBool_ObjectExists(proplist props, proplist context) { return !!EvaluateValue("Object", props.Object, context); }

private func EvalBool_ObjectAlive(proplist props, proplist context)
{
	var obj = EvaluateValue("Object", props.Object, context);
	return obj && obj->GetAlive();
}

private func EvalAct_Sequence(proplist props, proplist context)
{
	// Sequence execution: Iterate over actions until one action puts the context on hold
	var n = GetLength(props.Actions), sid = props._sequence_id;
	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
	for (var progress = context.action_data[sid] ?? 0; progress < n; ++progress)
	{
		//Log("Sequence progress exec %v %v", progress, context.hold);
		// goto preparations
		context.sequence_had_goto[sid] = false;
		context.last_sequence = props;
		// Evaluate next sequence step
		EvaluateValue("Action", props.Actions[progress], context);
		if (context.hold || context.suspended)
		{
			// Execution on hold (i.e. wait action). Stop execution for now
			if (!context.hold) progress = 0; // No hold specified: Stop with sequence reset
			context.action_data[sid] = progress;
			return;
		}
		// Apply jump in the sequence
		if (context.sequence_had_goto[sid]) progress = context.action_data[sid] - 1;
	}
	// Sequence finished
	context.last_sequence = nil;
	// Reset for next execution.
	context.action_data[sid] = 0;
}

private func EvalAct_Goto(proplist props, proplist context)
{
	// Apply goto by jumping in most recently executed sequence
	if (context.last_sequence)
	{
		var index = props.Index;
		if (GetType(index) != C4V_Int) index = EvaluateValue("Integer", index, context); // compatibility
		context.action_data[context.last_sequence._sequence_id] = index;
		context.sequence_had_goto[context.last_sequence._sequence_id] = true;
	}
}

private func EvalAct_StopSequence(proplist props, proplist context)
{
	// Stop: Suspend without hold props, which causes all sequences to reset
	context.hold = nil;
	context.suspended = true;
}

private func EvalAct_SuspendSequence(proplist props, proplist context)
{
	// Suspend: Remember hold position and stop action execution
	context.hold = props;
	context.suspended = true;
}

private func EvalAct_For_IntRange(proplist props, proplist context)
{
	// Create list with range of integers
	// Both start and end inclusive
	var start = EvaluateValue("Integer", props.Start, context);
	var end = EvaluateValue("Integer", props.End, context);
	var step = EvaluateValue("Integer", props.Step, context);
	if (!step) return [];
	var d = end - start;
	if (d * step < 0) return []; // wrong direction
	var n = (end - start) / step + 1;
	var list = CreateArray(n), i;
	for (var v = start; v <= end; v += step) list[i++] = v;
	return list;
}

private func EvalAct_For_ObjectList(proplist props, proplist context)
{
	return EvaluateValue("ObjectList", props.Objects, context) ?? [];
}

private func EvalAct_For_PlayerList(proplist props, proplist context)
{
	return EvaluateValue("PlayerList", props.Players, context) ?? [];
}

private func EvalAct_For(proplist props, proplist context, list_function)
{
	// For: Iterate over elements until one action puts the context on hold
	// list_info: [list_function, current_item_field]
	var sid = props._sequence_id;
	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
	// Get list data
	var list, progress;
	if (!context.action_data[sid])
	{
		list = Call(list_function, props, context);
	}
	else
	{
		list = context.action_data[sid][0];
		progress = context.action_data[sid][1];
		context.action_data[sid] = nil;
	}
	var n = GetLength(list);
	for (; progress < n; ++progress)
	{
		// Get iterated item
		var curr_value = list[progress];
		if (!GetType(curr_value)) continue; // Ignore nil (deleted objects)
		context[props.Function] = curr_value;
		// Evaluate next sequence step
		EvaluateValue("Action", props.Action, context);
		if (context.hold || context.suspended)
		{
			// Execution on hold (i.e. wait action). Stop execution for now
			if (context.hold) context.action_data[sid] = [list, progress];
			return;
		}
	}
}

private func EvalAct_Wait(proplist props, proplist context)
{
	// Wait for specified number of frames
	context.hold = props;
	ScheduleCall(context, UserAction.ResumeAction, props.Time, 1, context, props);
}

private func EvalAct_WaitCondition(proplist props, proplist context)
{
	// Poll condition and resume when it's met.
	var cond = props.Condition;
	// Invalid condition?
	if (!cond) return;
	// Condition fulfilled?
	if (EvaluateValue("Boolean", cond, context)) return;	
	// Re-check condition periodically
	context.hold = props;
	ScheduleCall(context, UserAction.EvalAct_WaitConditionRe, props.Interval, 0x7fffffff, props, context);
}

private func EvalAct_WaitConditionRe(proplist props, proplist context)
{
	if (UserAction->EvaluateValue("Boolean", props.Condition, context))
	{
		ClearScheduleCall(context, UserAction.EvalAct_WaitConditionRe);
		UserAction->ResumeAction(context, props);
	}
}

private func EvalAct_Sound(proplist props, proplist context)
{
	if (!props.Sound) return;
	var sound_context;
	if (props.SourceObject)
	{
		sound_context = EvaluateValue("Object", props.SourceObject, context);
		if (!sound_context) return;
	}
	else
	{
		sound_context = Global;
	}
	var volume = EvaluateValue("Integer", props.Volume, context);
	var pitch = EvaluateValue("Integer", props.Pitch, context);
	if (props.TargetPlayers == "all_players")
	{
		sound_context->Sound(props.Sound, true, volume, nil, props.Loop, nil, pitch);
	}
	else
	{
		for (var plr in EvaluateValue("PlayerList", props.TargetPlayers, context))
		{
			sound_context->Sound(props.Sound, false, volume, plr, props.Loop, nil, pitch);
		}
	}
}

private func EvalAct_CreateObject(proplist props, proplist context)
{
	// Create a new object
	var create_id = EvaluateValue("Definition", props.ID, context);
	if (!create_id) return;
	var owner = EvaluatePlayer(props.Owner, context);
	var container = EvaluateValue("Object", props.Container, context);
	var obj;
	if (container)
	{
		// Contained object
		obj = container->CreateContents(create_id);
		if (obj) obj->SetOwner(owner);
	}
	else
	{
		// Uncontained object
		var position = EvaluatePosition(props.Position, context);
		var speed_x = EvaluateValue("Integer", props.SpeedX, context);
		var speed_y = EvaluateValue("Integer", props.SpeedY, context);
		var rotation = EvaluateValue("Integer", props.Rotation, context);
		var speed_r = EvaluateValue("Integer", props.SpeedR, context);
		if (props.CreateAbove)
			obj = Global->CreateObjectAbove(create_id, position[0], position[1], owner);
		else
			obj = Global->CreateObject(create_id, position[0], position[1], owner);
		// Default speed and rotation
		if (obj) obj->SetXDir(speed_x);
		if (obj) obj->SetYDir(speed_y);
		if (obj) obj->SetR(rotation);
		if (obj) obj->SetRDir(speed_r, 5); // Internal rdir value is in five degrees frame
	}
	// Remember object for later access
	context.last_created_object = obj;
}

private func EvalAct_CastObjects(proplist props, proplist context)
{
	// Cast objects in multiple directions
	var create_id = EvaluateValue("Definition", props.ID, context);
	if (!create_id) return;
	var owner = EvaluatePlayer(props.Owner, context);
	var amount = EvaluateValue("Integer", props.Amount, context);
	var speed = EvaluateValue("Integer", props.Speed, context);
	var mean_angle = EvaluateValue("Integer", props.MeanAngle, context);
	var angle_deviation = EvaluateValue("Integer", props.AngleDeviation, context);
	var position = EvaluatePosition(props.Position, context);
	context.last_casted_objects = CastObjects(create_id, amount, speed, position[0], position[1], mean_angle, angle_deviation);
}

private func EvalAct_CastParticles(proplist props, proplist context)
{
	var particle_name = props.Name;
	var amount = EvaluateValue("Integer", props.Amount, context);
	var speed = EvaluateValue("Integer", props.Speed, context);
	var size_start = EvaluateValue("Integer", props.Size, context);
	var size_end = EvaluateValue("Integer", props.SizeEnd, context);
	var lifetime = EvaluateValue("Integer", props.Lifetime, context);
	if (lifetime <= 0) return;
	var position = EvaluatePosition(props.Position, context);
	var color = (EvaluateValue("Color", props.Color, context) ?? 0xffffff) | 0xff000000;
	var blit_mode = props.BlitMode;
	var fadeout = props.FadeOut;
	var gravity = EvaluateValue("Integer", props.Gravity, context);
	var collision_func = props.CollisionFunc;
	var prototype = 
	{
		BlitMode = props.BlitMode,
		Size = PV_Linear(size_start, size_end),
		Rotation = PV_Direction(),
		R = (color >> 16) & 0xff,
		G = (color >>  8) & 0xff,
		B = (color >>  0) & 0xff,
		CollisionVertex = 500
	};
	if (fadeout) prototype.Alpha = PV_Linear(255, 0); else prototype.Alpha=255;
	if (gravity) prototype.ForceY = PV_Gravity(gravity);
	if (collision_func == "pass")
	{
		prototype.CollisionDensity = 9999;
	}
	else if (collision_func == "bounce")
	{
		prototype.OnCollision = PC_Bounce(500);
	}
	else if (collision_func == "die")
	{
		prototype.OnCollision = PC_Die();
	}
	else if (collision_func == "stop")
	{
		prototype.OnCollision = PC_Stop();
	}
	CreateParticle(particle_name, position[0], position[1], PV_Random(-speed, speed), PV_Random(-speed, speed), lifetime, prototype, amount);
}

private func EvalAct_RemoveObject(proplist props, proplist context)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (!obj) return;
	obj->RemoveObject(props.EjectContents);
}

private func EvalAct_SetPosition(proplist props, proplist context)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (!obj) return;
	var pos = EvaluatePosition(props.Position, context);
	obj->SetPosition(pos[0], pos[1]);
}

private func EvalAct_Fling(proplist props, proplist context)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (!obj) return;
	var vx = EvaluateValue("Integer", props.SpeedX, context);
	var vy = EvaluateValue("Integer", props.SpeedY, context);
	var add_speed = EvaluateValue("Boolean", props.AddSpeed, context);
	obj->Fling(vx, vy, 10, add_speed);
}

private func EvalAct_EnterObject(proplist props, proplist context)
{
	var object = EvaluateValue("Object", props.Object, context);
	var container = EvaluateValue("Object", props.Container, context);
	if (!container || !object) return;
	// Enter either with a check (Collect) or just force-Enter
	if (!props.CollectionCheck)
	{
		object->Enter(container);
	}
	else
	{
		if (!container->Collect(object, true))
		{
			if (props.CollectionCheck == "exit")
			{
				object->SetPosition(container->GetX(), container->GetY());
			}
		}
	}
}

private func EvalAct_ExitObject(proplist props, proplist context)
{
	var object = EvaluateValue("Object", props.Object, context);
	if (object) object->Exit();
}

private func EvalAct_SetDirection(proplist props, proplist context)
{
	var object = EvaluateValue("Object", props.Object, context);
	if (object) object->SetDir(props.Direction);
}

private func EvalAct_SetVisibility(proplist props, proplist context)
{
	var object = EvaluateValue("Object", props.Object, context);
	if (object) object.Visibility = props.Visibility;
}

private func EvalAct_SetVincibility(proplist props, proplist context)
{
	var object = EvaluateValue("Object", props.Object, context);
	if (object) object->SetInvincibility(!props.Vincibility);
}

private func EvalAct_DoWealth(proplist props, proplist context)
{
	var player = EvaluatePlayer(props.Player, context);
	var change = EvaluateValue("Integer", props.Change, context);
	if (player != NO_OWNER && change)
	{
		SetWealth(player, GetWealth(player) + change);
		var do_sound = EvaluateValue("Boolean", props.DoSound, context);
		if (do_sound)
		{
			if (change < 0) Sound("UI::Cash*", true, nil, player); else Sound("UI::UnCash*", true, nil, player);
		}
	}
}

private func EvalAct_PlrKnowledge(proplist props, proplist context)
{
	var players = EvaluateValue("PlayerList", props.Players, context) ?? [];
	var def = EvaluateValue("Definition", props.ID, context);
	if (!def) return;
	for (var plr in players) SetPlrKnowledge(plr, def);
}

private func EvalAct_PlrView(proplist props, proplist context)
{
	var players = EvaluateValue("PlayerList", props.Players, context) ?? [];
	var target = EvaluateValue("Object", props.Target, context);
	var immediate = props.Immediate;
	if (!target) return;
	for (var plr in players) SetPlrView(plr, target, immediate);
}

private func EvalAct_ObjectCallInt(proplist props, proplist context, func call_fn)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (!obj) return;
	var parameter = EvaluateValue("Integer", props.Value, context);
	obj->Call(call_fn, parameter);
}

private func EvalAct_If(proplist props, proplist context)
{
	// Do evaluation on first pass. After that, take context value.
	var sid = props._sequence_id;
	if (!sid) sid = props._sequence_id = Format("%d", ++UserAction_SequenceIDs);
	var cond = context.action_data[sid] ?? !!EvaluateValue("Boolean", props.Condition, context);
	if (cond)
		EvaluateValue("Action", props.TrueEvaluator, context);
	else
		EvaluateValue("Action", props.FalseEvaluator, context);
	// Only keep conditional value within a held action
	if (context.hold) context.action_data[sid] = cond;
}

private func EvalConditionalValue(proplist props, proplist context, eval_type)
{
	// Return value by condition
	if (EvaluateValue("Boolean", props.Condition, context))
		return EvaluateValue(eval_type, props.TrueEvaluator, context);
	else
		return EvaluateValue(eval_type, props.FalseEvaluator, context);
}

private func EvalAct_Log(proplist props, proplist context)
{
	Log(EvaluateString(props.Message, context));
}

private func EvalAct_Nop(proplist props, proplist context) {}

private func GetVariableContext(proplist props, proplist context)
{
	// Resolve context for variable. Global or object context.
	var var_context = EvaluateValue("Object", props, context);
	if (!var_context)
	{
		if (!g_UserAction_global_vars) g_UserAction_global_vars = {};
		var_context = g_UserAction_global_vars;
	}
	else
	{
		if (!var_context.user_variables) var_context.user_variables = {};
		var_context = var_context.user_variables;
	}
	return var_context;
}

private func EvalAct_SetVariable(proplist props, proplist context)
{
	// Assign variable
	var var_context = GetVariableContext(props.Context, context);
	var var_name = StringToIdentifier(EvaluateString(props.VariableName, context));
	var value = EvaluateValue("Any", props.Value, context);
	var_context[var_name] = value;
}

private func EvalVariable(proplist props, proplist context, expected_type)
{
	// Get variable value
	var var_context = GetVariableContext(props.Context, context);
	var var_name = StringToIdentifier(EvaluateString(props.VariableName, context));
	var value = var_context[var_name];
	// Check type (except for C4V_Nil which means "Any" here)
	var val_type = GetType(value);
	if (val_type != expected_type && expected_type)
	{
		// Array types have special checking
		if (GetType(expected_type) == C4V_Array)
		{
			var expected_len = expected_type[1];
			var valid;
			if (val_type == C4V_Array)
			{
				// Check required length
				if (!expected_len || GetLength(value) == expected_len)
				{
					// Check data type of contents
					var subtype = expected_type[0];
					valid = true;
					for (var v in value) if (v && GetType(v) != subtype) valid = false;
				}
			}
			// Invalid value for expected array: Return default array.
			// All nil is OK because there is no string list type and nil converts to 0.
			if (!valid) return CreateArray(expected_len);
		}
		else
		{
			// Type not matching. Construct default.
			if (expected_type == C4V_Int) return 0;
			if (expected_type == C4V_Bool) return !!value; // This conversion is OK
			if (expected_type == C4V_String) return "";
			return nil;
		}
	}
	// Value OK. Return it.
	return value;
}

private func EvalScript(proplist props, proplist context)
{
	var script_context;
	if (props.Context)
	{
		if (!(script_context = EvaluateValue("Object", props.Context, context))) return;
	}
	else
	{
		script_context = Global;
	}
	var script = EvaluateString(props.Script, context);
	return script_context->eval(script, true);
}

private func EvalAct_GameOver(proplist props, proplist context) { GameOver(); }

private func GetDefaultPosition(object target_object)
{
	// Default position for constant absolute position evaluator: Use selected object position
	var value;
	if (target_object)
		value = [target_object->GetX(), target_object->GetY()];
	else
		value = [0,0];
	return { Function="position_constant", Value=value };
}

private func GetDefaultCoordinates(object target_object)
{
	// Default position for constant absolute position evaluator: Use selected object position
	var value;
	if (target_object)
		value = {X={Function="int_constant", Value=target_object->GetX()}, Y={Function="int_constant", Value=target_object->GetY()}};
	else
		value = {X=0, Y=0};
	value.Function="position_coordinates";
	return value;
}

private func GetDefaultRect(object target_object, proplist props)
{
	// Default rectangle around target object
	var r;
	if (target_object) r = [target_object->GetX()-30, target_object->GetY()-30, 60, 60]; else r = [100,100,100,100];
	return { Function=props.Function, Area=r };
}

private func GetDefaultCircle(object target_object, proplist props)
{
	// Default circle around target object
	var r;
	if (target_object) r = [50, target_object->GetX(), target_object->GetY()]; else r = [50,100,100];
	return { Function=props.Function, Area=r };
}

private func GetDefaultSearchRect(object target_object)
{
	// Default rectangle around target object
	var r;
	if (target_object) r = [target_object->GetX()-30, target_object->GetY()-30, 60, 60]; else r = [100,100,100,100];
	return { Function="Rect", Area=r };
}

private func GetDefaultSearchCircle(object target_object)
{
	// Default circle around target object
	var r;
	if (target_object) r = [50, target_object->GetX(), target_object->GetY()]; else r = [50,100,100];
	return { Function="Circle", Area=r };
}

private func EvalIntRandom(proplist props, proplist context)
{
	// Random value between min and max. Also allow them to be swapped.
	var min = EvaluateValue("Integer", props.Min, context);
	var max = EvaluateValue("Integer", props.Max, context);
	var rmin = Min(min,max);
	return Random(Max(max,min)-rmin) + rmin;
}

private func EvalPositionRelative(proplist props, proplist context)
{
	// Return position relative to action_object
	if (context.action_object)
		return [props.Value[0] + context.action_object->GetX(), props.Value[1] + context.action_object->GetY()];
	else
		return props.Value;
}

private func EvalCoordinates(proplist props, proplist context)
{
	// Coordinate evaluation: Make array [X, Y]
	return [EvaluateValue("Integer", props.X, context), EvaluateValue("Integer", props.Y, context)];
}

private func EvalPositionOffset(proplist props, proplist context)
{
	var p = EvaluatePosition(props.Position, context);
	var o = EvaluateOffset(props.Offset, context);
	return [p[0]+o[0], p[1]+o[1]];
}

private func EvalPositionObject(proplist props, proplist context)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (obj) return [obj->GetX(), obj->GetY()];
	return [0,0]; // undefined object: Position is 0/0 default
}

private func EvalPos_LastUse(proplist props, proplist context) { return context.position; }

private func EvalPos_RandomRect(proplist props, proplist context, bool relative)
{
	// Constant random distribution in rectangle
	var a = props.Area;
	var rval = [a[0] + Random(a[2]), a[1] + Random(a[3])];
	// Apply relative offset
	if (relative && context.action_object)
	{
		rval[0] += context.action_object->GetX(); 
		rval[1] += context.action_object->GetY(); 
	}
	return rval;
}

private func EvalPos_RandomCircle(proplist props, proplist context, bool relative)
{
	// Constant random distribution in circle
	var a = props.Area;
	var r = a[0];
	r = Sqrt(Random(r*r));
	var ang = Random(360);
	var x = Sin(ang, r), y = Cos(ang, r);
	var rval = [a[1]+x, a[2]+y];
	// Apply relative offset
	if (relative && context.action_object)
	{
		rval[0] += context.action_object->GetX(); 
		rval[1] += context.action_object->GetY(); 
	}
	return rval;
}

private func EvalOffsetAdd(proplist props, proplist context)
{
	var o1 = EvaluateOffset(props.Offset1, context);
	var o2 = EvaluateOffset(props.Offset2, context);
	return [o1[0]+o2[0], o1[1]+o2[1]];
}

private func EvalOffsetDiff(proplist props, proplist context)
{
	var pA = EvaluatePosition(props.PositionA, context);
	var pB = EvaluatePosition(props.PositionB, context);
	return [pB[0]-pA[0], pB[1]-pA[1]];
}

private func EvalInt_Add(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) + EvaluateValue("Integer", props.RightOperand, context); }
private func EvalInt_Sub(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) - EvaluateValue("Integer", props.RightOperand, context); }
private func EvalInt_Mul(proplist props, proplist context) { return EvaluateValue("Integer", props.LeftOperand, context) * EvaluateValue("Integer", props.RightOperand, context); }

private func EvalInt_Div(proplist props, proplist context)
{
	var a = EvaluateValue("Integer", props.LeftOperand, context), b = EvaluateValue("Integer", props.RightOperand, context);
	return a/(b+!b);
}

private func EvalInt_Mod(proplist props, proplist context)
{
	var a = EvaluateValue("Integer", props.LeftOperand, context), b = EvaluateValue("Integer", props.RightOperand, context);
	return a%(b+!b);
}

private func EvalInt_Distance(proplist props, proplist context)
{
	var pA = EvaluatePosition(props.PositionA, context);
	var pB = EvaluatePosition(props.PositionB, context);
	return Distance(pA[0], pA[1], pB[0], pB[1]);
}

private func EvalInt_Wealth(proplist props, proplist context) { return GetWealth(EvaluatePlayer(props.Player, context)); }

private func EvalInt_PosCoord(proplist props, proplist context, int idx) { return EvaluatePosition(props.Position, context)[idx]; }

private func EvalClr_PlayerColor(proplist props, proplist context) { return GetPlayerColor(EvaluatePlayer(props.Player, context)); }

private func EvalClr_RGB(proplist props, proplist context)
{
	var R = EvaluateValue("Integer", props.R, context);
	var G = EvaluateValue("Integer", props.G, context);
	var B = EvaluateValue("Integer", props.B, context);
	return RGB(R, G, B);
}

private func EvalClr_Random(proplist props, proplist context)
{
	var a = EvaluateValue("Color", props.ColorA, context);
	var b = EvaluateValue("Color", props.ColorB, context);
	var result;
	for (var i=0; i<3; ++i)
	{
		var ca = (a>>(i*8)) & 0xff;
		var cb = (b>>(i*8)) & 0xff;
		result |= ((Random(Abs(ca-cb+1)) + Min(ca, cb)) << (i*8));
	}
	return result;
}

private func EvalStr_ValueToString(proplist props, proplist context)
{
	return Format("%v", EvaluateValue("Any", props.Value, context));
}

private func EvalStr_Concat(proplist props, proplist context)
{
	var result="";
	for (var s in props.Substrings) result = Format("%s%s", result, EvaluateString(s, context));
	return result;
}

private func EvalObjProp(proplist props, proplist context, prop_fn)
{
	var obj = EvaluateValue("Object", props.Object, context);
	if (!obj) return nil;
	return obj->Call(prop_fn);
}



/* Context instance */

// Proplist holding progress in each sequence
local action_data, sequence_had_goto;
static UserAction_SequenceIDs;

// Set to innermost sequence (for goto)
local last_sequence;

// 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
local hold;

// Set to true if action is on hold but waiting for re-execution
local suspended;

// Return value if a value-providing evaluator is held
local hold_result;

public func Initialize()
{
	action_data = {};
	sequence_had_goto = {};
	return true;
}

public func InitContext(object action_object, int triggering_player, object triggering_object, proplist props, finish_callback, position)
{
	// Determine triggering player+objects
	var triggering_clonk;
	// Triggering player unknown? Try fallback to the controller of the triggering object
	if (!GetType(triggering_player) && triggering_object)
	{
		triggering_player = triggering_object->GetController();
	}
	// Triggering clonk is the selected clonk of the triggering player
	if (GetType(triggering_player))
	{
		triggering_clonk = GetCursor(triggering_player);;
		if (!triggering_clonk) triggering_clonk = GetCrew(triggering_player);
	}
	// Triggering object / Triggering player clonk fallbacks
	if (!triggering_object)
		triggering_object = triggering_clonk;
	else if (triggering_object->~IsClonk())
		triggering_clonk = triggering_object;
	// Position default
	if (!position && triggering_object)
		position = [triggering_object->GetX(), triggering_object->GetY()];
	// Init context settings
	this.action_object = action_object;
	this.triggering_object = triggering_object;
	this.triggering_clonk = triggering_clonk;
	this.triggering_player = triggering_player;
	this.position = position;
	this.root_action = props;
	this.suspended = false;
	this.finish_callback = finish_callback;
	return true;
}

public func MenuOK(proplist menu_id, object clonk)
{
	// Pressed 'Next' in dialogue: Resume in user action
	UserAction->ResumeAction(this, this.hold);
}

public func MenuSelectOption(int index)
{
	// Selected an option in dialogue: Resume at specified position in innermost sequence
	if (!hold || !hold.Options) return;
	var opt = this.hold.Options[index];
	if (opt && last_sequence)
	{
		action_data[last_sequence._sequence_id] = opt._goto;
		hold = nil;
	}
	UserAction->ResumeAction(this, hold);
}

public func SaveScenarioObject(props) { return false; } // temp. don't save.
