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