0ad/binaries/data/mods/public/maps/scripts/Tutorial.js
Vantha 7e77065c56 Add an info panel to the tutorial
This patch adds a new tutorial step type and corresponding tutorial
panel labelled "info". It is intended to display steps whose purpose it
is to explain something to the player and give tips -- rather than
just giving instructions and telling the player what to do, like the
instruction panel does.
Also, it can show several related steps at once. To do that, the first
step has to set the 'appendable' flag to true and can also define a
title; then the succeeding steps can then set the 'appendToPrevious'
flag to true in order to do exactly that.
Note: 'info'-type tutorial steps are still supposed to be able to define
triggers, switch to the next step on their own and hide the continue
button on the info panel, to teach the topic interactively, e.g.
"Select a unit by left-clicking on it."

In order to prevent code duplication between `InfoPanel` and
`InstructionPanel` a generic superclass `TutorialPanel` is introduced,
which manages the text, hint, and button objects.
2026-07-22 23:51:49 +02:00

120 lines
3.2 KiB
JavaScript

// Needs to be kept in sync with the one in gui/session/tutorial/Tutorial.js
var TUTORIAL_STEP_TYPE = deepfreeze({
"INSTRUCTION": 1,
"INFO": 2
});
Engine.RegisterGlobal("TUTORIAL_STEP_TYPE", TUTORIAL_STEP_TYPE);
Trigger.prototype.InitTutorial = function(data)
{
this.stepIndex = 0;
this.tutorialEvents = [];
// Register needed triggers
this.RegisterTrigger("OnDeserialized", "DeserializedAction", { "enabled": true });
this.RegisterTrigger("OnPlayerCommand", "BasePlayerCommandAction", { "enabled": true });
for (const step of this.tutorialSteps)
{
for (const key in step)
{
if (typeof step[key] !== "function" || this.tutorialEvents.indexOf(key) != -1)
continue;
if (key == "Init")
continue;
if (key == "IsDone")
continue;
const action = key.substring(2) + "Action";
this.RegisterTrigger(key, action, { "enabled": false });
this.tutorialEvents.push(key);
}
}
this.NextStep();
};
Trigger.prototype.NextStep = function(deserializing = false)
{
if (this.stepIndex > this.tutorialSteps.length)
return;
const step = this.tutorialSteps[this.stepIndex];
Trigger.prototype.Init = step.Init || null;
if (!deserializing && this.Init)
this.Init();
for (const event of this.tutorialEvents)
{
const action = event.substring(2) + "Action";
if (step[event])
{
Trigger.prototype[action] =
event == "OnPlayerCommand" ?
(msg) =>
{
// Don't forward tutorial continue commands, since the step trigger actions aren't supposed to handle them,
// plus we might have already loaded in the next step.
if (msg.cmd.type != "dialog-answer" || msg.cmd.tutorial != "continue")
step.OnPlayerCommand.call(this, msg);
} :
step[event];
this.EnableTrigger(event, action);
}
else
this.DisableTrigger(event, action);
}
Trigger.prototype.IsDone = step.IsDone || (() => false);
const showContinueButton = this.IsDone() || (step.panelData.showContinueButton === undefined ?
this.tutorialEvents.every(event => !step[event]) :
step.panelData.showContinueButton
);
this.DisplayStep(step, showContinueButton, ++this.stepIndex == this.tutorialSteps.length);
};
Trigger.prototype.DisplayStep = function(step, showContinueButton = false, isLast = false)
{
const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "tutorial",
"players": [1],
"step": {
"type": step.type,
"panelData": {
...step.panelData,
"showContinueButton": showContinueButton,
"isLast": isLast
}
}
});
};
Trigger.prototype.DisplayWarning = function(warning)
{
const cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "tutorial",
"players": [1],
"warning": warning
});
};
Trigger.prototype.BasePlayerCommandAction = function(msg)
{
if (msg.cmd.type == "dialog-answer" && msg.cmd.tutorial == "continue")
this.NextStep();
};
Trigger.prototype.DeserializedAction = function()
{
this.stepIndex = Math.max(0, this.stepIndex - 1);
// Display messages from already processed steps
for (let i = 0; i < this.stepIndex; ++i)
this.DisplayStep(this.tutorialSteps[i], false, false);
this.NextStep(true);
};