From 27fbf02ff4ca16247f96c3e8302cc569c675f395 Mon Sep 17 00:00:00 2001 From: Vantha Date: Sun, 22 Mar 2026 20:53:02 +0100 Subject: [PATCH] Improve the ending of tutorials This patch introduces two functions `CompleteTutorial` and `FailTutorial` for the tutorial steps to call. They can pass a message to it to be shown on the victory/defeat screen. `CompleteTutorial` is also called automatically when all steps are finished, which resolves #8583 and prevents it from happening in the future. `FailTutorial` isn't used anywhere at the moment, but it'll be useful for the future. Fixes #8583 --- .../data/mods/public/maps/scripts/Tutorial.js | 37 ++++++++++++++++--- .../maps/tutorials/introductory_tutorial.js | 13 +------ .../tutorials/starting_economy_walkthrough.js | 8 +--- .../public/simulation/components/Trigger.js | 7 ++++ 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/binaries/data/mods/public/maps/scripts/Tutorial.js b/binaries/data/mods/public/maps/scripts/Tutorial.js index c16dd8f451..870115fba2 100644 --- a/binaries/data/mods/public/maps/scripts/Tutorial.js +++ b/binaries/data/mods/public/maps/scripts/Tutorial.js @@ -37,7 +37,10 @@ Trigger.prototype.InitTutorial = function(data) Trigger.prototype.NextStep = function(deserializing = false) { if (++this.stepIndex >= this.tutorialSteps.length) + { + this.CompleteTutorial(); return; + } const step = this.tutorialSteps[this.stepIndex]; Trigger.prototype.Init = step.Init || null; @@ -68,16 +71,15 @@ Trigger.prototype.NextStep = function(deserializing = false) Trigger.prototype.IsDone = step.IsDone || (() => false); const isDone = this.IsDone(); - const isLast = this.stepIndex === this.tutorialSteps.length - 1; - const showContinueButton = isDone || isLast || (step.panelData.showContinueButton === undefined ? + const showContinueButton = isDone || (step.panelData.showContinueButton === undefined ? this.tutorialEvents.every(event => !step[event]) : step.panelData.showContinueButton ); - this.DisplayStep(step, showContinueButton, isDone, isLast); + this.DisplayStep(step, showContinueButton, isDone); }; -Trigger.prototype.DisplayStep = function(step, showContinueButton = false, isDone = false, isLast = false) +Trigger.prototype.DisplayStep = function(step, showContinueButton = false, isDone = false) { const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ @@ -86,7 +88,7 @@ Trigger.prototype.DisplayStep = function(step, showContinueButton = false, isDon "step": { "type": step.type, "panelData": { - ...step.panelData, showContinueButton, isDone, isLast + ...step.panelData, showContinueButton, isDone } } }); @@ -102,6 +104,31 @@ Trigger.prototype.DisplayWarning = function(warning) }); }; +Trigger.prototype.CompleteTutorial = function(message = markForTranslation("You have successfully completed the tutorial.")) +{ + // End the tutorial first, before marking the player won, since otherwise the triggers would be invoked + // once more when doing so. + this.EndTutorial(message); + TriggerHelper.SetPlayerWon(this.playerID, () => markForTranslation("%(player)s has completed the tutorial."), () => "", message); +}; + +Trigger.prototype.FailTutorial = function(message = markForTranslation("You have failed to complete the tutorial.")) +{ + // End the tutorial first, before marking the player defeated, since otherwise the triggers would be invoked + // once more when doing so. + this.EndTutorial(message); + TriggerHelper.DefeatPlayer(this.playerID, markForTranslation("%(player)s has failed the tutorial."), message); +}; + +Trigger.prototype.EndTutorial = function(message) +{ + this.DisableAllTriggers(); + this.DisplayStep({ + "type": TUTORIAL_STEP_TYPE.INSTRUCTION, + "panelData": { "isLast": true, "text": message } + }, true, false); +}; + Trigger.prototype.BasePlayerCommandAction = function(msg) { if (msg.cmd.type == "dialog-answer" && msg.cmd.tutorial == "continue") diff --git a/binaries/data/mods/public/maps/tutorials/introductory_tutorial.js b/binaries/data/mods/public/maps/tutorials/introductory_tutorial.js index d612f5f9ce..7128d2d94f 100644 --- a/binaries/data/mods/public/maps/tutorials/introductory_tutorial.js +++ b/binaries/data/mods/public/maps/tutorials/introductory_tutorial.js @@ -446,18 +446,7 @@ Trigger.prototype.tutorialSteps = [ if (msg.from != this.enemyID) return; if (TriggerHelper.EntityMatchesClassList(msg.entity, "CivilCentre")) - this.NextStep(); - } - }, - { - "type": TUTORIAL_STEP_TYPE.INSTRUCTION, - "panelData": { - "text": markForTranslation("The enemy has been defeated. These tutorial tasks are now completed.") - }, - "Init": function() - { - const cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager); - cmpEndGameManager.MarkPlayerAndAlliesAsWon(1, () => "Tutorial completed", () => ""); + this.CompleteTutorial(markForTranslation("You have defeated the enemy by destroying or capturing the Civic Center. These tutorial tasks are now completed.")); } } ]; diff --git a/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js b/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js index b05e684d9d..494789132e 100644 --- a/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js +++ b/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js @@ -572,13 +572,7 @@ Trigger.prototype.tutorialSteps = [ "OnResearchFinished": function(msg) { if (this.IsDone()) - this.NextStep(); - } - }, - { - "type": TUTORIAL_STEP_TYPE.INSTRUCTION, - "panelData": { - "text": markForTranslation("This is the end of the walkthrough. This should give you a good idea of the basics of setting up your economy.") + this.CompleteTutorial(markForTranslation("This is the end of the walkthrough. This should give you a good idea of the basics of setting up your economy.")); } } ]; diff --git a/binaries/data/mods/public/simulation/components/Trigger.js b/binaries/data/mods/public/simulation/components/Trigger.js index 3d8b4c8e34..51b472e16b 100644 --- a/binaries/data/mods/public/simulation/components/Trigger.js +++ b/binaries/data/mods/public/simulation/components/Trigger.js @@ -173,6 +173,13 @@ Trigger.prototype.DisableTrigger = function(event, name) triggerData.enabled = false; }; +Trigger.prototype.DisableAllTriggers = function() +{ + for (const event of this.eventNames) + for (const name in this.triggers[event]) + this.DisableTrigger(event, name); +}; + Trigger.prototype.EnableTrigger = function(event, name) { if (!this.triggers[event][name])