1 /**
2 Parkour
3
4 The goal is to be the first to reach the finish, the team or player to do so wins the round.
5 Checkpoints can be added to make the path more interesting and more complex.
6 Checkpoints can have different functionalities:
7 * Respawn: On/Off - The clonk respawns at the last passed checkpoint.
8 * Check: On/Off - The clonk must pass through these checkpoints before being able to finish.
9 * Ordered: On/Off - The checkpoints mussed be passed in the order specified.
10 * The start and finish are also checkpoints.
11
12 @author Maikel
13 */
14
15
16 #include Library_Goal
17
18 local finished; // Whether the goal has been reached by some player.
19 local cp_list; // List of checkpoints.
20 local cp_count; // Number of checkpoints.
21 local respawn_list; // List of last reached respawn CP per player.
22 local plr_list; // Number of checkpoints the player completed.
23 local team_list; // Number of checkpoints the team completed.
24 local time_store; // String for best time storage in player file.
25 local no_respawn_handling; // Set to true if this goal should not handle respawn.
26 local transfer_contents; // Set to true if contents should be transferred on respawn.
27
28
29 /*-- General --*/
30
Initialize(...)31 protected func Initialize(...)
32 {
33 finished = false;
34 no_respawn_handling = false;
35 transfer_contents = false;
36 cp_list = [];
37 cp_count = 0;
38 respawn_list = [];
39 plr_list = [];
40 team_list = [];
41 // Best time tracking.
42 time_store = Format("Parkour_%s_BestTime", GetScenTitle());
43 AddEffect("IntBestTime", this, 100, 1, this);
44 // Add a message board command "/resetpb" to reset the pb for this round.
45 AddMsgBoardCmd("resetpb", "Goal_Parkour->~ResetPersonalBest(%player%)");
46 // Activate restart rule, if there isn't any. But check delayed because it may be created later.
47 ScheduleCall(this, this.EnsureRestartRule, 1, 1);
48 // Scoreboard.
49 InitScoreboard();
50 // Assign unassigned checkpoints
51 for (var obj in FindObjects(Find_ID(ParkourCheckpoint)))
52 if (!obj->GetCPController())
53 obj->SetCPController(this);
54 return _inherited(...);
55 }
56
EnsureRestartRule()57 private func EnsureRestartRule()
58 {
59 var relaunch = GetRelaunchRule();
60 relaunch->SetAllowPlayerRestart(true);
61 relaunch->SetPerformRestart(false);
62 return true;
63 }
64
Destruction(...)65 protected func Destruction(...)
66 {
67 // Unassign checkpoints (updates editor help message)
68 for (var obj in FindObjects(Find_ID(ParkourCheckpoint)))
69 if (obj->GetCPController() == this)
70 obj->SetCPController(nil);
71 return _inherited(...);
72 }
73
74
75 /*-- Checkpoint creation --*/
76
SetStartpoint(int x,int y)77 public func SetStartpoint(int x, int y)
78 {
79 // Safety, x and y inside landscape bounds.
80 x = BoundBy(x, 0, LandscapeWidth());
81 y = BoundBy(y, 0, LandscapeHeight());
82 var cp = FindObject(Find_ID(ParkourCheckpoint), Find_Func("FindCPMode", PARKOUR_CP_Start));
83 if (!cp)
84 cp = CreateObjectAbove(ParkourCheckpoint, x, y, NO_OWNER);
85 cp->SetCPController(this);
86 cp->SetPosition(x, y);
87 cp->SetCPMode(PARKOUR_CP_Start);
88 return cp;
89 }
90
SetFinishpoint(int x,int y,bool team)91 public func SetFinishpoint(int x, int y, bool team)
92 {
93 // Safety, x and y inside landscape bounds.
94 x = BoundBy(x, 0, LandscapeWidth());
95 y = BoundBy(y, 0, LandscapeHeight());
96 var cp = FindObject(Find_ID(ParkourCheckpoint), Find_Func("FindCPMode", PARKOUR_CP_Finish));
97 if (!cp)
98 cp = CreateObjectAbove(ParkourCheckpoint, x, y, NO_OWNER);
99 cp->SetCPController(this);
100 cp->SetPosition(x, y);
101 var mode = PARKOUR_CP_Finish;
102 if (team)
103 mode = mode | PARKOUR_CP_Team;
104 cp->SetCPMode(mode);
105 return cp;
106 }
107
AddCheckpoint(int x,int y,int mode)108 public func AddCheckpoint(int x, int y, int mode)
109 {
110 // Safety, x and y inside landscape bounds.
111 x = BoundBy(x, 0, LandscapeWidth());
112 y = BoundBy(y, 0, LandscapeHeight());
113 var cp = CreateObjectAbove(ParkourCheckpoint, x, y, NO_OWNER);
114 cp->SetCPController(this);
115 cp->SetPosition(x, y);
116 cp->SetCPMode(mode);
117 return cp;
118 }
119
DisableRespawnHandling()120 public func DisableRespawnHandling()
121 {
122 // Call this to disable respawn handling by goal. This might be useful if
123 // a) you don't want any respawns, or
124 // b) the scenario already provides an alternate respawn handling.
125 no_respawn_handling = true;
126 return true;
127 }
128
TransferContentsOnRelaunch(bool on)129 public func TransferContentsOnRelaunch(bool on)
130 {
131 transfer_contents = on;
132 return;
133 }
134
SetIndexedCP(object cp,int index)135 public func SetIndexedCP(object cp, int index)
136 {
137 // Called directly from checkpoints after index assignment, resorting, etc.
138 // Update internal list
139 cp_list[index] = cp;
140 if (cp->GetCPMode() & PARKOUR_CP_Finish)
141 {
142 cp_count = index;
143 SetLength(cp_list, cp_count+1);
144 }
145 UpdateScoreboardTitle();
146 return true;
147 }
148
149
150 /*-- Checkpoint interaction --*/
151
152 // Called from a finish CP to indicate that plr has reached it.
PlayerReachedFinishCP(int plr,object cp,bool is_first_clear)153 public func PlayerReachedFinishCP(int plr, object cp, bool is_first_clear)
154 {
155 if (finished)
156 return;
157 var plrid = GetPlayerID(plr);
158 var team = GetPlayerTeam(plr);
159 plr_list[plrid]++;
160 if (team)
161 team_list[team]++;
162 UpdateScoreboard(plr);
163 DoBestTime(plr);
164 SetEvalData(plr);
165 EliminatePlayers(plr);
166 finished = true;
167 if (is_first_clear) UserAction->EvaluateAction(on_checkpoint_first_cleared, this, cp, plr);
168 UserAction->EvaluateAction(on_checkpoint_cleared, this, cp, plr);
169 return;
170 }
171
172 // Called from a respawn CP to indicate that plr has reached it.
SetPlayerRespawnCP(int plr,object cp)173 public func SetPlayerRespawnCP(int plr, object cp)
174 {
175 if (respawn_list[plr] == cp)
176 return;
177 respawn_list[plr] = cp;
178 cp->PlayerMessage(plr, "$MsgNewRespawn$");
179 return;
180 }
181
182 // Called from a check CP to indicate that plr has cleared it.
AddPlayerClearedCP(int plr,object cp,bool is_first_clear,bool is_team_auto_clear)183 public func AddPlayerClearedCP(int plr, object cp, bool is_first_clear, bool is_team_auto_clear)
184 {
185 if (finished)
186 return;
187 var plrid = GetPlayerID(plr);
188 plr_list[plrid]++;
189 UpdateScoreboard(plr);
190 if (!is_team_auto_clear) // No callback if only auto-cleared for other team members after another player cleared it
191 {
192 if (is_first_clear) UserAction->EvaluateAction(on_checkpoint_first_cleared, this, cp, plr);
193 UserAction->EvaluateAction(on_checkpoint_cleared, this, cp, plr);
194 }
195 return;
196 }
197
198 // Called from a check CP to indicate that plr has cleared it.
AddTeamClearedCP(int team,object cp)199 public func AddTeamClearedCP(int team, object cp)
200 {
201 if (finished)
202 return;
203 if (team)
204 team_list[team]++;
205 return;
206 }
207
ResetAllClearedCP()208 private func ResetAllClearedCP()
209 {
210 plr_list = [];
211 team_list = [];
212 respawn_list = [];
213 for (var cp in FindObjects(Find_ID(ParkourCheckpoint)))
214 cp->ResetCleared();
215 return true;
216 }
217
218
219 /*-- Goal interface --*/
220
221 // Eliminates all players apart from the winner and his team.
EliminatePlayers(int winner)222 private func EliminatePlayers(int winner)
223 {
224 var winteam = GetPlayerTeam(winner);
225 for (var i = 0; i < GetPlayerCount(); i++)
226 {
227 var plr = GetPlayerByIndex(i);
228 var team = GetPlayerTeam(plr);
229 if (plr == winner) // The winner self.
230 continue;
231 if (team && team == winteam) // In the same team as the winner.
232 continue;
233 EliminatePlayer(plr);
234 }
235 return;
236 }
237
IsFulfilled()238 public func IsFulfilled()
239 {
240 return finished;
241 }
242
GetDescription(int plr)243 public func GetDescription(int plr)
244 {
245 var team = GetPlayerTeam(plr);
246 var msg;
247 if (finished)
248 {
249 if (team)
250 {
251 if (IsWinner(plr))
252 msg = "$MsgParkourWonTeam$";
253 else
254 msg = "$MsgParkourLostTeam$";
255 }
256 else
257 {
258 if (IsWinner(plr))
259 msg = "$MsgParkourWon$";
260 else
261 msg = "$MsgParkourLost$";
262 }
263 }
264 else
265 msg = Format("$MsgParkour$", cp_count);
266
267 return msg;
268 }
269
Activate(int plr)270 public func Activate(int plr)
271 {
272 var team = GetPlayerTeam(plr);
273 var msg;
274 if (finished)
275 {
276 if (team)
277 {
278 if (IsWinner(plr))
279 msg = "$MsgParkourWonTeam$";
280 else
281 msg = "$MsgParkourLostTeam$";
282 }
283 else
284 {
285 if (IsWinner(plr))
286 msg = "$MsgParkourWon$";
287 else
288 msg = "$MsgParkourLost$";
289 }
290 }
291 else
292 msg = Format("$MsgParkour$", cp_count);
293 // Show goal message.
294 MessageWindow(msg, plr);
295 return;
296 }
297
GetShortDescription(int plr)298 public func GetShortDescription(int plr)
299 {
300 var team = GetPlayerTeam(plr);
301 var parkour_length = GetParkourLength();
302 if (parkour_length == 0)
303 return "";
304 var length;
305 if (team)
306 length = GetTeamPosition(team);
307 else
308 length = GetPlayerPosition(plr);
309 var percentage = 100 * length / parkour_length;
310 var red = BoundBy(255 - percentage * 255 / 100, 0, 255);
311 var green = BoundBy(percentage * 255 / 100, 0, 255);
312 var color = RGB(red, green, 0);
313 return Format("<c %x>$MsgShortDesc$</c>", color, percentage, color);
314 }
315
316 // Returns the length the player has completed.
GetPlayerPosition(int plr)317 private func GetPlayerPosition(int plr)
318 {
319 var plrid = GetPlayerID(plr);
320 var cleared = plr_list[plrid];
321 var length = 0;
322 // Add length of cleared checkpoints.
323 for (var i = 0; i < cleared; i++)
324 length += ObjectDistance(cp_list[i], cp_list[i + 1]);
325 // Add length of current checkpoint.
326 var add_length = 0;
327 if (cleared < cp_count)
328 {
329 var path_length = ObjectDistance(cp_list[cleared], cp_list[cleared + 1]);
330 add_length = Max(path_length - ObjectDistance(cp_list[cleared + 1], GetCursor(plr)), 0);
331 }
332 return length + add_length;
333 }
334
335 // Returns the length the team has completed.
GetTeamPosition(int team)336 private func GetTeamPosition(int team)
337 {
338 var cleared = team_list[team];
339 var length = 0;
340 // Add length of cleared checkpoints.
341 for (var i = 0; i < cleared; i++)
342 length += ObjectDistance(cp_list[i], cp_list[i + 1]);
343 // Add length of current checkpoint.
344 var add_length = 0;
345 if (cleared < cp_count)
346 {
347 for (var i = 0; i < GetPlayerCount(); i++)
348 {
349 var plr = GetPlayerByIndex(i);
350 if (GetPlayerTeam(plr) == team)
351 {
352 var path_length = ObjectDistance(cp_list[cleared], cp_list[cleared + 1]);
353 var test_length = Max(path_length - ObjectDistance(cp_list[cleared + 1], GetCursor(plr)), 0);
354 if (test_length > add_length)
355 add_length = test_length;
356 }
357 }
358 }
359 return length + add_length;
360 }
361
362 // Returns the length of this parkour.
GetParkourLength()363 private func GetParkourLength()
364 {
365 var length = 0;
366 for (var i = 0; i < cp_count; i++)
367 length += ObjectDistance(cp_list[i], cp_list[i + 1]);
368 return length;
369 }
370
371 // Returns the number of checkpoints cleared by the player.
GetPlayerClearedCheckpoints(int plr)372 public func GetPlayerClearedCheckpoints(int plr)
373 {
374 var plrid = GetPlayerID(plr);
375 return plr_list[plrid];
376 }
377
GetLeaderClearedCheckpoints()378 public func GetLeaderClearedCheckpoints()
379 {
380 return Max(plr_list);
381 }
382
IsWinner(int plr)383 private func IsWinner(int plr)
384 {
385 var team = GetPlayerTeam(plr);
386 var finish = cp_list[cp_count];
387 if (!finish)
388 return false;
389 if (team)
390 {
391 if (finish->ClearedByTeam(team))
392 return true;
393 }
394 else
395 {
396 if (finish->ClearedByPlayer(plr))
397 return true;
398 }
399 return false;
400 }
401
402 /*-- Player section --*/
403
InitializePlayer(int plr,int x,int y,object base,int team)404 protected func InitializePlayer(int plr, int x, int y, object base, int team)
405 {
406 // If the parkour is already finished, then immediately eliminate player.
407 if (finished)
408 return EliminatePlayer(plr);
409 // Remove all hostilities.
410 for (var i = 0; i < GetPlayerCount(); i++)
411 {
412 SetHostility(plr, GetPlayerByIndex(i), false, true);
413 SetHostility(GetPlayerByIndex(i), plr, false, true);
414 }
415 // Init Respawn CP to start CP.
416 var plrid = GetPlayerID(plr);
417 respawn_list[plr] = cp_list[0];
418 plr_list[plrid] = 0;
419 if (team)
420 if (!team_list[team])
421 team_list[team] = 0;
422 // Scoreboard.
423 Scoreboard->NewPlayerEntry(plr);
424 UpdateScoreboard(plr);
425 DoScoreboardShow(1, plr + 1);
426 JoinPlayer(plr);
427 // Scenario script callback.
428 GameCall("OnPlayerRespawn", plr, FindRespawnCP(plr));
429 return;
430 }
431
OnClonkDeath(object clonk,int killed_by)432 protected func OnClonkDeath(object clonk, int killed_by)
433 {
434 var plr = clonk->GetOwner();
435 // Only respawn if required and if the player still exists.
436 if (no_respawn_handling || !GetPlayerName(plr) || GetCrewCount(plr))
437 return;
438 var new_clonk = CreateObjectAbove(Clonk, 0, 0, plr);
439 new_clonk->MakeCrewMember(plr);
440 SetCursor(plr, new_clonk);
441 JoinPlayer(plr);
442 // Transfer contents if active.
443 if (transfer_contents)
444 GetRelaunchRule()->TransferInventory(clonk, new_clonk);
445 // Scenario script callback.
446 GameCall("OnPlayerRespawn", plr, FindRespawnCP(plr));
447 // Log message.
448 Log(RndRespawnMsg(), GetPlayerName(plr));
449 // Respawn actions
450 var cp = FindRespawnCP(plr);
451 UserAction->EvaluateAction(on_respawn, this, clonk, plr);
452 if (cp)
453 cp->OnPlayerRespawn(new_clonk, plr);
454 return;
455 }
456
RndRespawnMsg()457 private func RndRespawnMsg()
458 {
459 return Translate(Format("MsgRespawn%d", Random(4)));
460 }
461
JoinPlayer(int plr)462 protected func JoinPlayer(int plr)
463 {
464 var clonk = GetCrew(plr);
465 clonk->DoEnergy(clonk.MaxEnergy / 1000);
466 var pos = FindRespawnPos(plr);
467 clonk->SetPosition(pos[0], pos[1]);
468 AddEffect("IntDirNextCP", clonk, 100, 1, this);
469 return;
470 }
471
472 // You always respawn at the last completed checkpoint you passed by.
473 // More complicated behavior should be set by the scenario.
FindRespawnCP(int plr)474 private func FindRespawnCP(int plr)
475 {
476 var respawn_cp = respawn_list[plr];
477 if (!respawn_cp)
478 respawn_cp = respawn_list[plr] = cp_list[0];
479 return respawn_cp;
480 }
481
FindRespawnPos(int plr)482 private func FindRespawnPos(int plr)
483 {
484 var cp = FindRespawnCP(plr);
485 if (!cp) cp = this; // Have to start somewhere
486 return [cp->GetX(), cp->GetY()];
487 }
488
RemovePlayer(int plr)489 protected func RemovePlayer(int plr)
490 {
491 respawn_list[plr] = nil;
492 if (!finished)
493 AddEvalData(plr);
494 return;
495 }
496
497
498 /*-- Scenario saving --*/
499
SaveScenarioObject(props)500 public func SaveScenarioObject(props)
501 {
502 if (!inherited(props, ...))
503 return false;
504 props->AddCall("Goal", this, "EnsureRestartRule");
505 if (no_respawn_handling)
506 props->AddCall("Goal", this, "DisableRespawnHandling");
507 if (transfer_contents)
508 props->AddCall("Goal", this, "TransferContentsOnRelaunch", true);
509 return true;
510 }
511
512
513 /*-- Scoreboard --*/
514
515 static const SBRD_Checkpoints = 0;
516 static const SBRD_BestTime = 1;
517
UpdateScoreboardTitle()518 private func UpdateScoreboardTitle()
519 {
520 if (cp_count > 0)
521 var caption = Format("$MsgCaptionX$", cp_count);
522 else
523 var caption = "$MsgCaptionNone$";
524 return Scoreboard->SetTitle(caption);
525 }
526
InitScoreboard()527 private func InitScoreboard()
528 {
529 Scoreboard->Init(
530 [
531 {key = "checkpoints", title = "#", sorted = true, desc = true, default = 0, priority = 80},
532 {key = "besttime", title = GUI_Clock, sorted = true, desc = true, default = 0, priority = 70}
533 ]
534 );
535 UpdateScoreboardTitle();
536 return;
537 }
538
UpdateScoreboard(int plr)539 private func UpdateScoreboard(int plr)
540 {
541 if (finished)
542 return;
543 var plrid = GetPlayerID(plr);
544 Scoreboard->SetPlayerData(plr, "checkpoints", plr_list[plrid]);
545 var bt = GetPlrExtraData(plr, time_store);
546 Scoreboard->SetPlayerData(plr, "besttime", TimeToString(bt), bt);
547 return;
548 }
549
550
551 /*-- Direction indication --*/
552
553 // Effect for direction indication for the clonk.
FxIntDirNextCPStart(object target,effect fx)554 protected func FxIntDirNextCPStart(object target, effect fx)
555 {
556 var arrow = CreateObjectAbove(GUI_GoalArrow, 0, 0, target->GetOwner());
557 arrow->SetAction("Show", target);
558 fx.arrow = arrow;
559 return FX_OK;
560 }
561
FxIntDirNextCPTimer(object target,effect fx)562 protected func FxIntDirNextCPTimer(object target, effect fx)
563 {
564 var plr = target->GetOwner();
565 var team = GetPlayerTeam(plr);
566 var arrow = fx.arrow;
567 // Find nearest CP.
568 var nextcp;
569 for (var cp in FindObjects(Find_ID(ParkourCheckpoint), Find_Func("FindCPMode", PARKOUR_CP_Check | PARKOUR_CP_Finish), Sort_Distance(target->GetX() - GetX(), target->GetY() - GetY())))
570 if (!cp->ClearedByPlayer(plr) && (cp->IsActiveForPlayer(plr) || cp->IsActiveForTeam(team)))
571 {
572 nextcp = cp;
573 break;
574 }
575 if (!nextcp)
576 return arrow->SetClrModulation(RGBa(0, 0, 0, 0));
577 // Calculate parameters.
578 var angle = Angle(target->GetX(), target->GetY(), nextcp->GetX(), nextcp->GetY());
579 var dist = Min(510 * ObjectDistance(GetCrew(plr), nextcp) / 400, 510);
580 var red = BoundBy(dist, 0, 255);
581 var green = BoundBy(510 - dist, 0, 255);
582 var blue = 0;
583 // Arrow is colored a little different for the finish.
584 if (cp->GetCPMode() & PARKOUR_CP_Finish)
585 blue = 128;
586 var color = RGBa(red, green, blue, 128);
587 // Draw arrow.
588 arrow->SetR(angle);
589 arrow->SetClrModulation(color);
590 // Check if clonk is contained in a vehicle, if so attach arrow to vehicle.
591 var container = target->Contained();
592 if (container && container->GetCategory() & C4D_Vehicle)
593 {
594 if (arrow->GetActionTarget() != container)
595 {
596 arrow->SetActionTargets(container);
597 arrow->SetCategory(C4D_Vehicle);
598 }
599 }
600 else
601 {
602 if (arrow->GetActionTarget() != target)
603 {
604 arrow->SetActionTargets(target);
605 arrow->SetCategory(C4D_StaticBack);
606 }
607 }
608 return FX_OK;
609 }
610
FxIntDirNextCPStop(object target,effect fx)611 protected func FxIntDirNextCPStop(object target, effect fx)
612 {
613 fx.arrow->RemoveObject();
614 return;
615 }
616
617
618 /*-- Time tracker --*/
619
620 // Store the best time in the player file, same for teammembers.
DoBestTime(int plr)621 private func DoBestTime(int plr)
622 {
623 var effect = GetEffect("IntBestTime", this);
624 var time = effect.besttime;
625 var winteam = GetPlayerTeam(plr);
626 for (var i = 0; i < GetPlayerCount(); i++)
627 {
628 var check_plr = GetPlayerByIndex(i);
629 if (winteam == 0 && check_plr != plr)
630 continue;
631 if (winteam != GetPlayerTeam(check_plr))
632 continue;
633 // Store best time for all players in the winning team.
634 var rectime = GetPlrExtraData(check_plr, time_store);
635 if (time != 0 && (!rectime || time < rectime))
636 {
637 SetPlrExtraData(check_plr, time_store, time);
638 Log(Format("$MsgBestTime$", GetPlayerName(check_plr), TimeToString(time)));
639 }
640 }
641 return;
642 }
643
644 // Starts at goal initialization, should be equivalent to gamestart.
FxIntBestTimeTimer(object target,effect,time)645 protected func FxIntBestTimeTimer(object target, effect, time)
646 {
647 effect.besttime = time;
648 return FX_OK;
649 }
650
651 // Returns a best time string.
TimeToString(int time)652 private func TimeToString(int time)
653 {
654 if (!time) // No time.
655 return "N/A";
656 if (time > 36 * 60 * 60) // Longer than an hour.
657 return Format("%d:%.2d:%.2d.%.1d", (time / 60 / 60 / 36) % 24, (time / 60 / 36) % 60, (time / 36) % 60, (10 * time / 36) % 10);
658 if (time > 36 * 60) // Longer than a minute.
659 return Format("%d:%.2d.%.1d", (time / 60 / 36) % 60, (time / 36) % 60, (10 * time / 36) % 10);
660 else // Only seconds.
661 return Format("%d.%.1d", (time / 36) % 60, (10 * time / 36) % 10);
662 }
663
664 // Resets the personal best (call from message board).
ResetPersonalBest(int plr)665 public func ResetPersonalBest(int plr)
666 {
667 if (!GetPlayerName(plr))
668 return;
669 // Forward call to actual goal.
670 if (this == Goal_Parkour)
671 {
672 var goal = FindObject(Find_ID(Goal_Parkour));
673 if (goal)
674 goal->ResetPersonalBest(plr);
675 }
676 SetPlrExtraData(plr, time_store, nil);
677 // Also update the scoreboard.
678 UpdateScoreboard(plr);
679 return;
680 }
681
682
683 /*-- Evaluation data --*/
684
SetEvalData(int winner)685 private func SetEvalData(int winner)
686 {
687 var winteam = GetPlayerTeam(winner);
688 var effect = GetEffect("IntBestTime", this);
689 var time = effect.besttime;
690 var msg;
691 // General data.
692 if (winteam)
693 msg = Format("$MsgEvalTeamWon$", GetTeamName(winteam), TimeToString(time));
694 else
695 msg = Format("$MsgEvalPlrWon$", GetPlayerName(winner), TimeToString(time));
696 AddEvaluationData(msg, 0);
697 // Individual data.
698 for (var i = 0; i < GetPlayerCount(); i++)
699 AddEvalData(GetPlayerByIndex(i));
700 // Obviously get rid of settlement score.
701 HideSettlementScoreInEvaluation(true);
702 return;
703 }
704
AddEvalData(int plr)705 private func AddEvalData(int plr)
706 {
707 if (finished)
708 return;
709 var plrid = GetPlayerID(plr);
710 var cps = plr_list[plrid];
711 var msg;
712 if (cps == cp_count)
713 msg = "$MsgEvalPlayerAll$";
714 else
715 msg = Format("$MsgEvalPlayerX$", cps, cp_count);
716 AddEvaluationData(msg, plrid);
717 return;
718 }
719
720
721 /* Editor */
722
723 local on_checkpoint_cleared, on_checkpoint_first_cleared, on_respawn;
724
SetOnCheckpointCleared(v)725 public func SetOnCheckpointCleared(v) { on_checkpoint_cleared=v; return true; }
SetOnCheckpointFirstCleared(v)726 public func SetOnCheckpointFirstCleared(v) { on_checkpoint_first_cleared=v; return true; }
SetOnRespawn(v)727 public func SetOnRespawn(v) { on_respawn=v; return true; }
728
Definition(def)729 public func Definition(def)
730 {
731 _inherited(def);
732 if (!def.EditorProps) def.EditorProps = {};
733 def.EditorProps.on_checkpoint_cleared = new UserAction.Prop { Name="$OnCleared$", EditorHelp="$OnClearedHelp$", Set="SetOnCheckpointCleared", Save="Checkpoint" };
734 def.EditorProps.on_checkpoint_first_cleared = new UserAction.Prop { Name="$OnFirstCleared$", EditorHelp="$OnFirstClearedHelp$", Set="SetOnCheckpointFirstCleared", Save="Checkpoint" };
735 def.EditorProps.on_respawn = new UserAction.Prop { Name="$OnRespawn$", EditorHelp="$OnRespawnHelp$", Set="SetOnRespawn", Save = "Checkpoint" };
736 if (!def.EditorActions) def.EditorActions = {};
737 def.EditorActions.reset_all_cleared = { Name="$ResetAllCleared$", EditorHelp="$ResetAllClearedHelp$", Command="ResetAllClearedCP()" };
738 }
739
740
741 /*-- Proplist --*/
742
743 local Name = "$Name$";
744