1 /**
2 	Sequence
3 	Cutscene to be watched by all players.
4 	Start calling global func StartSequence, stop using StopSequence
5 
6 	Can also be used as a trigger object for UserActions.
7 
8 	@author Sven
9 */
10 
11 local seq_name;
12 local seq_progress;
13 local started;
14 
15 /* Start and stop */
16 
Start(string name,int progress,...)17 public func Start(string name, int progress, ...)
18 {
19 	if (started)
20 		Stop();
21 	// Force global coordinates for the script execution.
22 	SetPosition(0, 0);
23 	// Store sequence name and progress.
24 	this.seq_name = name;
25 	this.seq_progress = progress;
26 	// Call init function of this scene - difference to start function is that it is called before any player joins.
27 	var fn_init = Format("~%s_Init", seq_name);
28 	if (!Call(fn_init, ...))
29 		GameCall(fn_init, this, ...);
30 	// Join all players: disable player controls and call join player of this scene.
31 	for (var i = 0; i < GetPlayerCount(C4PT_User); ++i)
32 	{
33 		var plr = GetPlayerByIndex(i, C4PT_User);
34 		JoinPlayer(plr);
35 	}
36 	started = true;
37 	// Sound effect.
38 	Sound("UI::Ding", true);
39 	// Call start function of this scene.
40 	var fn_start = Format("%s_Start", seq_name);
41 	if (!Call(fn_start, ...))
42 		GameCall(fn_start, this, ...);
43 	return true;
44 }
45 
InitializePlayer(int plr)46 protected func InitializePlayer(int plr)
47 {
48 	if (seq_name)
49 	{
50 		// Scripted sequence
51 		JoinPlayer(plr);
52 	}
53 	else
54 	{
55 		// Editor-made sequence
56 		if (trigger && trigger.Trigger == "player_join") OnTrigger(nil, plr);
57 	}
58 	return true;
59 }
60 
InitializePlayers()61 protected func InitializePlayers()
62 {
63 	if (!seq_name)
64 	{
65 		// Editor-made sequence
66 		if (trigger && trigger.Trigger == "game_start") OnTrigger(nil, GetPlayerByIndex(0, C4PT_User));
67 	}
68 	return true;
69 }
70 
RemovePlayer(int plr)71 public func RemovePlayer(int plr)
72 {
73 	if (seq_name)
74 	{
75 		// Scripted sequence
76 		// Called by sequence if it ends and by engine if player leaves.
77 		var fn_remove = Format("~%s_RemovePlayer", seq_name);
78 		if (!Call(fn_remove, plr))
79 			GameCall(fn_remove, this, plr);
80 	}
81 	else
82 	{
83 		// Editor-made sequence
84 		if (trigger && trigger.Trigger == "player_remove") OnTrigger(nil, plr);
85 	}
86 	return true;
87 }
88 
JoinPlayer(int plr)89 public func JoinPlayer(int plr)
90 {
91 	DeactivatePlayerControls(plr, true);
92 	// Per-player sequence callback.
93 	var fn_join = Format("~%s_JoinPlayer", seq_name);
94 	if (!Call(fn_join, plr))
95 		GameCall(fn_join, this, plr);
96 	return true;
97 }
98 
DeactivatePlayerControls(int plr,bool make_invincible)99 public func DeactivatePlayerControls(int plr, bool make_invincible)
100 {
101 	var j = 0, crew;
102 	while (crew = GetCrew(plr, j++))
103 	{
104 		//if (crew == GetCursor(plr)) crew.Sequence_was_cursor = true; else crew.Sequence_was_cursor = nil;
105 		crew->SetCrewEnabled(false);
106 		crew->CancelUse();
107 		if (crew->GetMenu())
108 			if (!crew->GetMenu()->~Uncloseable())
109 				crew->CancelMenu();
110 		if (make_invincible)
111 		{
112 			crew->MakeInvincible();
113 			crew.Sequence_stored_breath = crew->GetBreath();
114 			crew.Sequence_made_invincible = true;
115 		}
116 		crew->SetCommand("None");
117 		crew->SetComDir(COMD_Stop);
118 	}
119 	return true;
120 }
121 
ReactivatePlayerControls(int plr)122 public func ReactivatePlayerControls(int plr)
123 {
124 	var j = 0, crew;
125 	while (crew = GetCrew(plr, j++))
126 	{
127 		crew->SetCrewEnabled(true);
128 		if (crew.Sequence_made_invincible)
129 		{
130 			crew->ClearInvincible();
131 			// just in case clonk was underwater
132 			var breath_diff = crew.Sequence_stored_breath - crew->GetBreath();
133 			crew.Sequence_stored_breath = nil;
134 			if (breath_diff) crew->DoBreath(breath_diff + 100); // give some bonus breath for the distraction
135 			crew.Sequence_made_invincible = nil;
136 		}
137 	}
138 	// Ensure proper cursor.
139 	if (!GetCursor(plr))
140 		SetCursor(plr, GetCrew(plr));
141 	if (crew = GetCursor(plr))
142 		SetPlrView(plr, crew);
143 	return true;
144 }
145 
Stop(bool no_remove)146 public func Stop(bool no_remove)
147 {
148 	if (started)
149 	{
150 		SetViewTarget(nil);
151 		// Reenable crew and reset cursor.
152 		for (var i = 0; i<GetPlayerCount(C4PT_User); ++i)
153 		{
154 			var plr = GetPlayerByIndex(i, C4PT_User);
155 			ReactivatePlayerControls(plr);
156 			// Per-player sequence callback.
157 			RemovePlayer(plr);
158 		}
159 		Sound("UI::Ding", true);
160 		started = false;
161 		// Call stop function of this scene.
162 		var fn_init = Format("~%s_Stop", seq_name);
163 		if (!Call(fn_init))
164 			GameCall(fn_init, this);
165 	}
166 	if (!no_remove)
167 		RemoveObject();
168 	return true;
169 }
170 
Destruction()171 protected func Destruction()
172 {
173 	Stop(true);
174 	return;
175 }
176 
177 
178 /*-- Sequence callbacks --*/
179 
ScheduleNext(int delay,next_idx)180 public func ScheduleNext(int delay, next_idx)
181 {
182 	return ScheduleCall(this, this.CallNext, delay, 1, next_idx);
183 }
184 
ScheduleSame(int delay)185 public func ScheduleSame(int delay) { return ScheduleNext(delay, seq_progress); }
186 
CallNext(next_idx)187 public func CallNext(next_idx)
188 {
189 	// Start conversation context.
190 	// Update dialogue progress first.
191 	if (GetType(next_idx))
192 		seq_progress = next_idx;
193 	else
194 		++seq_progress;
195 	// Then call relevant functions.
196 	var fn_progress = Format("%s_%d", seq_name, seq_progress);
197 	if (!Call(fn_progress))
198 		GameCall(fn_progress, this);
199 	return true;
200 }
201 
202 
203 /*-- Force view on target --*/
204 
205 // Force all player views on given target
SetViewTarget(object view_target)206 public func SetViewTarget(object view_target)
207 {
208 	ClearScheduleCall(this, this.UpdateViewTarget);
209 	if (view_target)
210 	{
211 		UpdateViewTarget(view_target);
212 		ScheduleCall(this, this.UpdateViewTarget, 30, 999999999, view_target);
213 	}
214 	else
215 	{
216 		for (var i = 0; i < GetPlayerCount(C4PT_User); ++i)
217 		{
218 			var plr = GetPlayerByIndex(i, C4PT_User);
219 			SetPlrView(plr, GetCursor(plr));
220 		}
221 	}
222 	return true;
223 }
224 
UpdateViewTarget(object view_target)225 private func UpdateViewTarget(object view_target)
226 {
227 	// Force view of all players on target.
228 	if (!view_target)
229 		return;
230 	for (var i=0; i < GetPlayerCount(C4PT_User); ++i)
231 	{
232 		var plr = GetPlayerByIndex(i, C4PT_User);
233 		SetPlrView(plr, view_target);
234 	}
235 	return;
236 }
237 
238 
239 
240 /*-- Message function forwards --*/
241 
MessageBoxAll(string message,object talker,bool as_message,...)242 public func MessageBoxAll(string message, object talker, bool as_message, ...)
243 {
244 	return Dialogue->MessageBoxAll(message, talker, as_message, ...);
245 }
246 
MessageBox(string message,object clonk,object talker,int for_player,bool as_message,...)247 private func MessageBox(string message, object clonk, object talker, int for_player, bool as_message, ...)
248 {
249 	return Dialogue->MessageBox(message, clonk, talker, for_player, as_message, ...);
250 }
251 
252 
253 /*-- Helper Functions --*/
254 
255 // Helper function to get a speaker in sequences.
GetHero(object nearest_obj)256 public func GetHero(object nearest_obj)
257 {
258 	// Prefer object stored as hero - if not assigned, find someone close to specified object.
259 	if (!this.hero)
260 	{
261 		if (nearest_obj)
262 			this.hero = nearest_obj->FindObject(Find_ID(Clonk), Find_Not(Find_Owner(NO_OWNER)), nearest_obj->Sort_Distance());
263 		else
264 			this.hero = FindObject(Find_ID(Clonk), Find_Not(Find_Owner(NO_OWNER)));
265 	}
266 	// If there is still no hero, take any clonk. Let the NPCs do the sequence among themselves.
267 	// (to prevent null pointer exceptions if all players left during the sequence)
268 	if (!this.hero)
269 		this.hero = FindObject(Find_ID(Clonk));
270 	// Might return nil if all players are gone and there are no NPCs. Well, there was noone to listen anyway.
271 	return this.hero;
272 }
273 
274 // Scenario section overload: automatically transfers all player clonks.
LoadScenarioSection(name,...)275 public func LoadScenarioSection(name, ...)
276 {
277 	// Store objects: All clonks and sequence object
278 	this.save_objs = [];
279 	AddSectSaveObj(this);
280 	var iplr, plr;
281 	for (iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr)
282 	{
283 		plr = GetPlayerByIndex(iplr, C4PT_User);
284 		for (var icrew = 0, crew; icrew < GetCrewCount(iplr); ++icrew)
285 			if (crew = GetCrew(plr, icrew))
286 				AddSectSaveObj(crew);
287 	}
288 	var save_objs = this.save_objs;
289 	// Load new section
290 	var result = inherited(name, ...);
291 	// Restore objects
292 	for (var obj in save_objs)
293 		if (obj)
294 			obj->SetObjectStatus(C4OS_NORMAL);
295 	if (this)
296 		this.save_objs = nil;
297 	// Recover HUD
298 	for (iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr)
299 	{
300 		plr = GetPlayerByIndex(iplr, C4PT_User);
301 		var controllerDef = Library_HUDController->GetGUIControllerID();
302 		var HUDcontroller = FindObject(Find_ID(controllerDef), Find_Owner(plr));
303 		if (!HUDcontroller) HUDcontroller = CreateObjectAbove(controllerDef, 10, 10, plr);
304 	}
305 	return result;
306 }
307 
308 // Flag obj and any contained stuff for scenario saving.
AddSectSaveObj(object obj)309 public func AddSectSaveObj(object obj)
310 {
311 	if (!obj)
312 		return false;
313 	this.save_objs[GetLength(this.save_objs)] = obj;
314 	var cont, i = 0;
315 	while (cont = obj->Contents(i++))
316 		AddSectSaveObj(cont);
317 	return obj->SetObjectStatus(C4OS_INACTIVE);
318 }
319 
320 
321 /*-- Global helper functions --*/
322 
323 // Starts the specified sequence at indicated progress.
StartSequence(string name,int progress,...)324 global func StartSequence(string name, int progress, ...)
325 {
326 	var seq = CreateObject(Sequence, 0, 0, NO_OWNER);
327 	seq->Start(name, progress, ...);
328 	return seq;
329 }
330 
331 // Stops the currently active sequence.
StopSequence()332 global func StopSequence()
333 {
334 	var seq = FindObject(Find_ID(Sequence));
335 	if (!seq)
336 		return false;
337 	return seq->Stop();
338 }
339 
340 // Returns the currently active sequence.
GetActiveSequence()341 global func GetActiveSequence()
342 {
343 	var seq = FindObject(Find_ID(Sequence));
344 	return seq;
345 }
346 
347 
348 /* User-made sequences from the editor */
349 
350 local Name="$Name$";
351 local Description="$Description$";
352 local trigger, condition, action, action_progress_mode, action_allow_parallel;
353 local active=true;
354 local check_interval=12;
355 local deactivate_after_action; // If true, finished is set to true after the first execution and the trigger deactivated
356 local Visibility=VIS_Editor;
357 local trigger_started;
358 local trigger_offset; // Timer offset of trigger to allow non-synced triggers
IsSequence()359 public func IsSequence() { return true; }
360 
361 // finished: Disables the trigger. true if trigger has run and deactivate_after_action is set to true.
362 // Note that this flag is not saved in scenarios, so saving as scenario and reloading will re-enable all triggers (for editor mode)
363 local finished;
364 
Initialize()365 public func Initialize()
366 {
367 	// The default action is an empty sequence, because that's usually what the user wants
368 	// Must init this in initialize to force a writable array
369 	action = { Function="sequence", Actions=[] };
370 }
371 
Definition(def)372 public func Definition(def)
373 {
374 	// EditorActions
375 	if (!def.EditorActions) def.EditorActions = {};
376 	def.EditorActions.Test = { Name="$Test$", EditorHelp = "$TestHelp$", Command="OnTrigger(nil, %player%, true)" };
377 	// UserActions
378 	UserAction->AddEvaluator("Action", "$Name$", "$SetActive$", "$SetActiveDesc$", "sequence_set_active", [def, def.EvalAct_SetActive], { Target = { Function="action_object" }, Status = { Function="bool_constant", Value=true } }, { Type="proplist", Display="{{Target}}: {{Status}}", EditorProps = {
379 		Target = UserAction->GetObjectEvaluator("IsSequence", "$Name$"),
380 		Status = new UserAction.Evaluator.Boolean { Name="$Status$", EditorHelp="$SetActiveStatusHelp$" }
381 		} } );
382 	UserAction->AddEvaluator("Action", "$Name$", "$ActTrigger$", "$ActTriggerDesc$", "sequence_trigger", [def, def.EvalAct_Trigger], { Target = { Function="action_object" }, TriggeringObject = { Function="triggering_object" } }, { Type="proplist", Display="{{Target}}({{TriggeringObject}})", EditorProps = {
383 		Target = new UserAction->GetObjectEvaluator("IsSequence", "$Name$") { Priority=60 },
384 		TriggeringObject = new UserAction.Evaluator.Object { Name="$TriggeringObject$", EditorHelp="$TriggeringObjectHelp$" }
385 		} } );
386 	UserAction->AddEvaluator("Action", "$Name$", "$DisablePlayerControls$", "$DisablePlayerControlsHelp$", "sequence_disable_player_controls", [def, def.EvalAct_DisablePlayerControls], { Players = { Function="all_players" }, MakeInvincible = { Function="bool_constant", Value=true } }, { Type="proplist", Display="{{Players}}", EditorProps = {
387 		Players = new UserAction.Evaluator.PlayerList { Name="$Players$", EditorHelp="$PlayerControlsPlayersHelp$" },
388 		MakeInvincible = new UserAction.Evaluator.Boolean { Name="$MakeInvincible$", EditorHelp="$MakeInvincibleHelp$" }
389 		} } );
390 	UserAction->AddEvaluator("Action", "$Name$", "$EnablePlayerControls$", "$EnablePlayerControlsHelp$", "sequence_enable_player_controls", [def, def.EvalAct_EnablePlayerControls], { Players = { Function="all_players" } }, new UserAction.Evaluator.PlayerList { Name="$Players$", EditorHelp="$PlayerControlsPlayersHelp$" }, "Players");
391 	// EditorProps
392 	if (!def.EditorProps) def.EditorProps = {};
393 	def.EditorProps.active = { Name="$Active$", Type="bool", Set="SetActive" };
394 	def.EditorProps.finished = { Name="$Finished$", Type="bool", Set="SetFinished" };
395 	def.EditorProps.trigger = { Name="$Trigger$", Type="enum", OptionKey="Trigger", Set="SetTrigger", Priority=110, Options = [
396 		{ Name="$None$" },
397 		{ Name="$PlayerEnterRegionRect$", EditorHelp="$PlayerEnterRegionHelp$", Value={ Trigger="player_enter_region_rect", Rect=[-20, -20, 40, 40] }, ValueKey="Rect", Delegate={ Type="rect", Color=0xff8000, Relative=true, Set="SetTriggerRect", SetRoot=true } },
398 		{ Name="$PlayerEnterRegionCircle$", EditorHelp="$PlayerEnterRegionHelp$", Value={ Trigger="player_enter_region_circle", Radius=25 }, ValueKey="Radius", Delegate={ Type="circle", Color=0xff8000, Relative=true, Set="SetTriggerRadius", SetRoot=true } },
399 		{ Name="$ObjectEnterRegionRect$", EditorHelp="$ObjectEnterRegionHelp$", Value={ Trigger="object_enter_region_rect", Rect=[-20, -20, 40, 40] }, Delegate={ Name="$ObjectEnterRegionRect$", EditorHelp="$ObjectEnterRegionHelp$", Type="proplist", EditorProps = {
400 			ID = { Name="$ID$", EditorHelp="$IDHelp$", Type="def", Set="SetTriggerID", SetRoot=true },
401 			Rect = { Type="rect", Color=0xff8000, Relative=true, Set="SetTriggerRect", SetRoot=true }
402 			} } },
403 		{ Name="$ObjectEnterRegionCircle$", EditorHelp="$ObjectEnterRegionHelp$", Value={ Trigger="object_enter_region_circle", Radius=25 }, Delegate={ Name="$ObjectEnterRegionCircle$", EditorHelp="$ObjectEnterRegionHelp$", Type="proplist", EditorProps = {
404 			ID = { Name="$ID$", EditorHelp="$IDHelp$", Type="def", Set="SetTriggerID", SetRoot=true },
405 			Radius = { Type="circle", Color=0xff8000, Relative=true, Set="SetTriggerRadius", SetRoot=true }
406 			} } },
407 		{ Name="$ObjectCountInContainer$", EditorHelp="$ObjectCountInContainerHelp$", Value={ Trigger="contained_object_count", Count=1 }, Delegate={ Name="$ObjectCountInContainer$", EditorHelp="$ObjectCountInContainerHelp$", Type="proplist", EditorProps = {
408 			Container = { Name="$Container$", EditorHelp="$CountContainerHelp$", Type="object" },
409 			ID = { Name="$ID$", EditorHelp="$CountIDHelp$", Type="def", EmptyName="$AnyID$" },
410 			Count = { Name="$Count$", Type="int", Min=1 },
411 			Operation = { Name="$Operation$", EditorHelp="$CountOperationHelp$", Type="enum", Options = [
412 				{ Name="$GreaterEqual$", EditorHelp="$GreaterEqualHelp$" },
413 				{ Name="$LessThan$", EditorHelp="$LessThanHelp$", Value="lt" }
414 				] }
415 			} } },
416 		{ Name="$Interval$", EditorHelp="$IntervalHelp$", Value={ Trigger="interval", Interval=60 }, ValueKey="Interval", Delegate={ Name="$IntervalTime$", Type="int", Min=1, Set="SetIntervalTimer", SetRoot=true } },
417 		{ Name="$GameStart$", Value={ Trigger="game_start" } },
418 		{ Name="$PlayerJoin$", Value={ Trigger="player_join" } },
419 		{ Name="$PlayerRemove$", Value={ Trigger="player_remove" } },
420 		{ Name="$GoalsFulfilled$", Value={ Trigger="goals_fulfilled" } },
421 		{ Group="$ClonkDeath$", Name="$AnyClonkDeath$", Value={ Trigger="any_clonk_death" } },
422 		{ Group="$ClonkDeath$", Name="$PlayerClonkDeath$", Value={ Trigger="player_clonk_death" } },
423 		{ Group="$ClonkDeath$", Name="$NeutralClonkDeath$", Value={ Trigger="neutral_clonk_death" } },
424 		{ Group="$ClonkDeath$", Name="$SpecificClonkDeath$", Value={ Trigger="specific_clonk_death" }, ValueKey="Object", Delegate={ Type="object", Filter="IsClonk" } },
425 		{ Name="$Construction$", Value={ Trigger="construction" }, ValueKey="ID", Delegate={ Type="def", Filter="IsStructure", EmptyName="$Anything$" } },
426 		{ Name="$Production$", Value={ Trigger="production" }, ValueKey="ID", Delegate={ Type="def", EmptyName="$Anything$" } },
427 		] };
428 	def.EditorProps.condition = new UserAction.Evaluator.Boolean { Name="$Condition$" };
429 	def.EditorProps.action = new UserAction.Prop { Priority=105 };
430 	def.EditorProps.action_progress_mode = UserAction.PropProgressMode;
431 	def.EditorProps.action_allow_parallel = UserAction.PropParallel;
432 	def.EditorProps.deactivate_after_action = { Name="$DeactivateAfterAction$", Type="bool" };
433 	def.EditorProps.check_interval = { Name="$CheckInterval$", EditorHelp="$CheckIntervalHelp$", Type="int", Set="SetCheckInterval", Save="Interval" };
434 	def.EditorProps.trigger_offset = { Name="$CheckOffset$", EditorHelp="$CheckOffsetHelp$", Type="int", Set="SetTriggerOffset" };
435 }
436 
SetTrigger(proplist new_trigger,int check_offset)437 public func SetTrigger(proplist new_trigger, int check_offset)
438 {
439 	trigger = new_trigger;
440 	// Compute actual trigger time offset based on current frame counter
441 	if (GetType(check_offset) && check_interval > 0)
442 	{
443 		check_offset -= FrameCounter();
444 		if (check_offset < 0) check_offset -= ((check_offset/check_interval)-1) * check_interval;
445 		check_offset %= check_interval;
446 	}
447 	// Set trigger: Restart any specific trigger timers
448 	if (active && !finished) StartTrigger(check_offset);
449 	return true;
450 }
451 
SetTriggerRect(array new_trigger_rect)452 public func SetTriggerRect(array new_trigger_rect)
453 {
454 	if (trigger && trigger.Rect)
455 	{
456 		trigger.Rect = new_trigger_rect;
457 		SetTrigger(trigger); // restart trigger
458 	}
459 	return true;
460 }
461 
SetTriggerRadius(int new_trigger_radius)462 public func SetTriggerRadius(int new_trigger_radius)
463 {
464 	if (trigger)
465 	{
466 		trigger.Radius = new_trigger_radius;
467 		SetTrigger(trigger); // restart trigger
468 	}
469 	return true;
470 }
471 
SetTriggerID(id new_id)472 public func SetTriggerID(id new_id)
473 {
474 	if (trigger)
475 	{
476 		trigger.ID = new_id;
477 		SetTrigger(trigger); // restart trigger
478 	}
479 	return true;
480 }
481 
GetCheckOffset()482 public func GetCheckOffset()
483 {
484 	// Get timer offset of check function
485 }
486 
SetAction(new_action,new_action_progress_mode,new_action_allow_parallel)487 public func SetAction(new_action, new_action_progress_mode, new_action_allow_parallel)
488 {
489 	action = new_action;
490 	action_progress_mode = new_action_progress_mode;
491 	action_allow_parallel = new_action_allow_parallel;
492 	return true;
493 }
494 
SetCondition(new_condition)495 public func SetCondition(new_condition)
496 {
497 	condition = new_condition;
498 	return true;
499 }
500 
SetActive(bool new_active,bool force_triggers)501 public func SetActive(bool new_active, bool force_triggers)
502 {
503 	if (active == new_active && !force_triggers) return true;
504 	active = new_active;
505 	if (active && !finished)
506 	{
507 		// Activated: Start trigger
508 		StartTrigger();
509 	}
510 	else
511 	{
512 		// Inactive or inactive by editor run: Stop trigger
513 		StopTrigger();
514 	}
515 	return true;
516 }
517 
SetFinished(bool new_finished)518 public func SetFinished(bool new_finished)
519 {
520 	finished = new_finished;
521 	return SetActive(active, true);
522 }
523 
SetDeactivateAfterAction(bool new_val)524 public func SetDeactivateAfterAction(bool new_val)
525 {
526 	deactivate_after_action = new_val;
527 	return true;
528 }
529 
StartTrigger(int start_delay)530 public func StartTrigger(int start_delay)
531 {
532 	if (!trigger) return false;
533 	if (trigger_started) StopTrigger();
534 	trigger_started = true;
535 	SetGraphics("Active");
536 	var fn = trigger.Trigger;
537 	var id_search, timer_fn;
538 	if (trigger.ID) id_search = Find_ID(trigger.ID);
539 	if (fn == "player_enter_region_rect")
540 	{
541 		this.search_mask = Find_And(Find_InRect(trigger.Rect[0], trigger.Rect[1], trigger.Rect[2], trigger.Rect[3]), Find_OCF(OCF_Alive), Find_Func("IsClonk"), Find_Not(Find_Owner(NO_OWNER)));
542 		timer_fn = this.EnterRegionTimer;
543 	}
544 	else if (fn == "player_enter_region_circle")
545 	{
546 		this.search_mask = Find_And(Find_Distance(trigger.Radius), Find_OCF(OCF_Alive), Find_Func("IsClonk"), Find_Not(Find_Owner(NO_OWNER)));
547 		timer_fn = this.EnterRegionTimer;
548 	}
549 	else if (fn == "object_enter_region_rect")
550 	{
551 		this.search_mask = Find_And(Find_InRect(trigger.Rect[0], trigger.Rect[1], trigger.Rect[2], trigger.Rect[3]), id_search);
552 		timer_fn = this.EnterRegionTimer;
553 	}
554 	else if (fn == "object_enter_region_circle")
555 	{
556 		this.search_mask = Find_And(Find_Distance(trigger.Radius), Find_OCF(OCF_Alive), Find_Func("IsClonk"), id_search);
557 		timer_fn = this.EnterRegionTimer;
558 	}
559 	else if (fn == "contained_object_count")
560 	{
561 		timer_fn = this.CountContainedObjectsTimer;
562 	}
563 	else if (fn == "interval")
564 	{
565 		check_interval = trigger.Interval;
566 		timer_fn = this.OnTrigger;
567 	}
568 	else
569 	{
570 		trigger_offset = 0;
571 		return false;
572 	}
573 	// If a timer was started, remember its offset
574 	trigger_offset = (FrameCounter() + start_delay) % Max(1, check_interval);
575 	// Start directly or delayed
576 	if (start_delay > 0)
577 	{
578 		ScheduleCall(this, Global.AddTimer, start_delay, 1, timer_fn, check_interval);
579 	}
580 	else
581 	{
582 		AddTimer(timer_fn, check_interval);
583 	}
584 	return true;
585 }
586 
SetTriggerOffset(int new_trigger_offset)587 public func SetTriggerOffset(int new_trigger_offset)
588 {
589 	if (trigger_offset != new_trigger_offset)
590 	{
591 		// Schedule trigger restart to set correct offset
592 		SetTrigger(trigger, (trigger_offset = new_trigger_offset));
593 	}
594 	return true;
595 }
596 
StopTrigger()597 public func StopTrigger()
598 {
599 	SetGraphics();
600 	// Remove any timers that may have been added
601 	RemoveTimer(this.EnterRegionTimer);
602 	RemoveTimer(this.CountContainedObjectsTimer);
603 	RemoveTimer(this.OnTrigger);
604 	ClearScheduleCall(this, Global.AddTimer);
605 	trigger_started = false;
606 	return true;
607 }
608 
SetCheckInterval(new_interval)609 public func SetCheckInterval(new_interval)
610 {
611 	check_interval = Max(1, new_interval);
612 	return SetTrigger(trigger); // restart trigger
613 }
614 
SetIntervalTimer(int new_interval)615 public func SetIntervalTimer(int new_interval)
616 {
617 	if (trigger) trigger.Interval = new_interval;
618 	return SetTrigger(trigger); // restart trigger
619 }
620 
EnterRegionTimer()621 private func EnterRegionTimer()
622 {
623 	for (var clonk in FindObjects(this.search_mask))
624 	{
625 		if (!clonk) continue; // deleted by previous execution
626 		OnTrigger(clonk, clonk->GetOwner());
627 		if (active != true) break; // deactivated by trigger
628 	}
629 }
630 
CountContainedObjectsTimer()631 private func CountContainedObjectsTimer()
632 {
633 	if (trigger.Container)
634 	{
635 		var n = trigger.Container->ContentsCount(trigger.ID), f;
636 		if (!trigger.Operation)
637 			f = (n >= trigger.Count); // Operation == nil: greater than
638 		else
639 			f = (n < trigger.Count); // Operation == "lt": less than
640 		if (f) OnTrigger(nil, NO_OWNER);
641 	}
642 }
643 
OnTrigger(object triggering_clonk,int triggering_player,bool is_editor_test)644 public func OnTrigger(object triggering_clonk, int triggering_player, bool is_editor_test)
645 {
646 	// Check condition
647 	if (condition && !UserAction->EvaluateCondition(condition, this, triggering_clonk, triggering_player)) return false;
648 	// Only one action at the time
649 	if (!action_allow_parallel && !action_progress_mode) StopTrigger();
650 	// Execute action
651 	return UserAction->EvaluateAction(action, this, triggering_clonk, triggering_player, action_progress_mode, action_allow_parallel, this.OnActionFinished);
652 }
653 
OnActionFinished(context)654 private func OnActionFinished(context)
655 {
656 	// Callback from EvaluateAction: Action finished. Deactivate action if desired.
657 	if (deactivate_after_action)
658 		SetFinished(true);
659 	else if (active && !finished && !trigger_started)
660 		StartTrigger();
661 	return true;
662 }
663 
OnClonkDeath(object clonk,int killer)664 public func OnClonkDeath(object clonk, int killer)
665 {
666 	// Is this a clonk death trigger?
667 	if (!trigger || !clonk) return false;
668 	var t = trigger.Trigger;
669 	if (!WildcardMatch(t, "*_clonk_death")) return false;
670 	// Specific trigger check
671 	if (t == "player_clonk_death")
672 	{
673 		if (clonk->GetOwner() == NO_OWNER) return false;
674 	}
675 	else if (t == "neutral_clonk_death")
676 	{
677 		if (clonk->GetOwner() != NO_OWNER) return false;
678 	}
679 	else if (t == "specific_clonk_death")
680 	{
681 		if (trigger.Object != clonk) return false;
682 	}
683 	// OK, trigger it!
684 	return OnTrigger(clonk, killer);
685 }
686 
OnConstructionFinished(object structure,int constructing_player)687 public func OnConstructionFinished(object structure, int constructing_player)
688 {
689 	// Is this a structure finished trigger?
690 	if (!trigger || !structure) return false;
691 	if (trigger.Trigger != "construction") return false;
692 	if (trigger.ID) if (structure->GetID() != trigger.ID) return false;
693 	// OK, trigger it!
694 	return OnTrigger(structure, constructing_player);
695 }
696 
OnProductionFinished(object product,int producing_player)697 public func OnProductionFinished(object product, int producing_player)
698 {
699 	// Is this a structure finished trigger?
700 	if (!trigger || !product) return false;
701 	if (trigger.Trigger != "production") return false;
702 	if (trigger.ID) if (product->GetID() != trigger.ID) return false;
703 	// OK, trigger it!
704 	return OnTrigger(product, producing_player);
705 }
706 
OnGoalsFulfilled()707 public func OnGoalsFulfilled()
708 {
709 	// All goals fulfilled: Return true if any action is executed (stops regular GameOver)
710 	if (!trigger) return false;
711 	if (trigger.Trigger != "goals_fulfilled") return false;
712 	return OnTrigger();
713 }
714 
SetName(string new_name,...)715 public func SetName(string new_name, ...)
716 {
717 	if (new_name == GetID()->GetName())
718 	{
719 		Message("");
720 	}
721 	else
722 	{
723 		if (trigger)
724 			Message(Format("@<c ff8000>%s</c>", new_name));
725 		else
726 			Message(Format("@<c 808080>%s</c>", new_name));
727 	}
728 	return inherited(new_name, ...);
729 }
730 
EvalAct_SetActive(proplist props,proplist context)731 private func EvalAct_SetActive(proplist props, proplist context)
732 {
733 	// User action: Enable/disable sequence
734 	var target = UserAction->EvaluateValue("Object", props.Target, context);
735 	var status = UserAction->EvaluateValue("Boolean", props.Status, context);
736 	if (!target) return;
737 	if (status && target.finished) target->~SetFinished(false);
738 	target->~SetActive(status);
739 }
740 
EvalAct_Trigger(proplist props,proplist context)741 private func EvalAct_Trigger(proplist props, proplist context)
742 {
743 	var target = UserAction->EvaluateValue("Object", props.Target, context);
744 	var triggering_object = UserAction->EvaluateValue("Object", props.TriggeringObject, context);
745 	if (target && target->~IsSequence()) target->OnTrigger(triggering_object, nil, false);
746 }
747 
EvalAct_DisablePlayerControls(proplist props,proplist context)748 private func EvalAct_DisablePlayerControls(proplist props, proplist context)
749 {
750 	var players = UserAction->EvaluateValue("PlayerList", props.Players, context) ?? [];
751 	var is_invincible = UserAction->EvaluateValue("Boolean", props.MakeInvincible, context);
752 	for (var player in players) DeactivatePlayerControls(player, is_invincible);
753 }
754 
EvalAct_EnablePlayerControls(proplist props,proplist context)755 private func EvalAct_EnablePlayerControls(proplist props, proplist context)
756 {
757 	var players = UserAction->EvaluateValue("PlayerList", props.Players, context) ?? [];
758 	for (var player in players) ReactivatePlayerControls(player);
759 }
760 
761 
762 /*-- Saving --*/
763 
764 // No scenario saving.
SaveScenarioObject(props,...)765 public func SaveScenarioObject(props, ...)
766 {
767 	if (!_inherited(props, ...)) return false;
768 	// Do not save script-created sequences
769 	if (this.seq_name) return false;
770 	// Save editor-made sequences
771 	if (save_scenario_dup_objects && finished) // finished flag only copied for object duplication; not saved in savegames
772 		props->AddCall("Active", this, "SetFinished", finished);
773 	if (!active) props->AddCall("Active", this, "SetActive", active);
774 	if (trigger) props->AddCall("Trigger", this, "SetTrigger", trigger, trigger_offset);
775 	if (condition) props->AddCall("Condition", this, "SetCondition", condition);
776 	if ((action && !DeepEqual(action, { Function="sequence", Actions=[] })) || action_progress_mode || action_allow_parallel) props->AddCall("Action", this, "SetAction", action, Format("%v", action_progress_mode), action_allow_parallel);
777 	if (deactivate_after_action) props->AddCall("DeactivateAfterAction", this, "SetDeactivateAfterAction", deactivate_after_action);
778 	return true;
779 }
780