Create designated tutorial class in the GUI

This keeps messags.js shorter, prevents unnecessary GetGUIObjectByName
calls and, most importantly, makes it a lot more extensible for the
future.
This commit is contained in:
Vantha 2026-02-24 16:07:05 +01:00 committed by Vantha
parent 0e6e0280fc
commit 663f5c5ff6
6 changed files with 79 additions and 76 deletions

View file

@ -9,7 +9,7 @@
</object> </object>
<object hotkey="session.gui.tutorial.toggle"> <object hotkey="session.gui.tutorial.toggle">
<action on="Press">toggleTutorial();</action> <action on="Press">g_Tutorial?.toggle();</action>
</object> </object>
<object hotkey="silhouettes"> <object hotkey="silhouettes">

View file

@ -1,13 +1,3 @@
/**
* All tutorial messages received so far.
*/
var g_TutorialMessages = [];
/**
* GUI tags applied to the most recent tutorial message.
*/
var g_TutorialNewMessageTags = { "color": "255 226 149" };
/** /**
* The number of seconds we monitor to rate limit flares. * The number of seconds we monitor to rate limit flares.
*/ */
@ -174,7 +164,7 @@ var g_NotificationsTypes =
}, },
"tutorial": function(notification, player) "tutorial": function(notification, player)
{ {
updateTutorial(notification); g_Tutorial.update(notification);
}, },
"tribute": function(notification, player) "tribute": function(notification, player)
{ {
@ -335,7 +325,7 @@ function findGuidForPlayerID(playerID)
/** /**
* Processes all pending notifications sent from the GUIInterface simulation component. * Processes all pending notifications sent from the GUIInterface simulation component.
*/ */
function handleNotifications(closePageCallback) function handleNotifications()
{ {
for (const notification of Engine.GuiInterfaceCall("GetNotifications")) for (const notification of Engine.GuiInterfaceCall("GetNotifications"))
{ {
@ -346,7 +336,7 @@ function handleNotifications(closePageCallback)
} }
for (const player of notification.players) for (const player of notification.players)
g_NotificationsTypes[notification.type](notification, player, closePageCallback); g_NotificationsTypes[notification.type](notification, player);
} }
} }
@ -365,57 +355,6 @@ function focusAttack(attack)
} }
} }
function toggleTutorial()
{
const tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel");
tutorialPanel.hidden = !tutorialPanel.hidden || !Engine.GetGUIObjectByName("tutorialText").caption;
}
/**
* Updates the tutorial panel when a new goal.
*/
function updateTutorial(notification, closePageCallback)
{
// Show the tutorial panel if not yet done
Engine.GetGUIObjectByName("tutorialPanel").hidden = false;
if (notification.warning)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = coloredText(translate(notification.warning), "orange");
return;
}
const notificationText =
notification.instructions.reduce((instructions, item) =>
instructions + (typeof item === "string" ? translate(item) : colorizeHotkey(translate(item.text), item.hotkey)),
"");
Engine.GetGUIObjectByName("tutorialText").caption = g_TutorialMessages.concat(setStringTags(notificationText, g_TutorialNewMessageTags)).join("\n");
g_TutorialMessages.push(notificationText);
if (notification.readyButton)
{
Engine.GetGUIObjectByName("tutorialReady").hidden = false;
if (notification.leave)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click to quit this tutorial.");
Engine.GetGUIObjectByName("tutorialReady").caption = translate("Quit");
Engine.GetGUIObjectByName("tutorialReady").onPress = () =>
{
closePageCallback({ [Engine.openRequest]: endGame(true) });
};
}
else
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click when ready.");
}
else
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Follow the instructions.");
Engine.GetGUIObjectByName("tutorialReady").hidden = true;
}
}
/** /**
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer. * Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
* Saves the received object to mainlog.html. * Saves the received object to mainlog.html.

View file

@ -35,6 +35,7 @@ var g_ResearchProgress;
var g_TimeNotificationOverlay; var g_TimeNotificationOverlay;
var g_TopPanel; var g_TopPanel;
var g_TradeDialog; var g_TradeDialog;
var g_Tutorial;
/** /**
* Map, player and match settings set in game setup. * Map, player and match settings set in game setup.
@ -284,6 +285,7 @@ async function init(initData, hotloadData)
g_DiplomacyColors = new DiplomacyColors(); g_DiplomacyColors = new DiplomacyColors();
g_PlayerViewControl = new PlayerViewControl(); g_PlayerViewControl = new PlayerViewControl();
g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors)); g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors));
g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl)); g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects); g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects);
g_PauseControl = new PauseControl(); g_PauseControl = new PauseControl();
@ -339,18 +341,17 @@ async function init(initData, hotloadData)
handleNetMessages().catch(reject); handleNetMessages().catch(reject);
}), new Promise(closePageCallback => }), new Promise(closePageCallback =>
{ {
g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates.bind(undefined,
closePageCallback));
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat, closePageCallback); g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat, closePageCallback);
g_NetworkStatusOverlay = new NetworkStatusOverlay(closePageCallback); g_NetworkStatusOverlay = new NetworkStatusOverlay(closePageCallback);
g_QuitConfirmationDefeat = new QuitConfirmationDefeat(closePageCallback); g_QuitConfirmationDefeat = new QuitConfirmationDefeat(closePageCallback);
g_QuitConfirmationReplay = new QuitConfirmationReplay(closePageCallback); g_QuitConfirmationReplay = new QuitConfirmationReplay(closePageCallback);
// TODO: use event instead g_Tutorial = new Tutorial(closePageCallback);
onSimulationUpdate(closePageCallback);
Engine.GetGUIObjectByName("session").onSimulationUpdate =
onSimulationUpdate.bind(undefined, closePageCallback);
})]); })]);
// TODO: use event instead
onSimulationUpdate();
Engine.GetGUIObjectByName("session").onSimulationUpdate = onSimulationUpdate;
for (const handler of g_PlayersInitHandlers) for (const handler of g_PlayersInitHandlers)
handler(); handler();
@ -472,14 +473,14 @@ function initializeMusic()
global.music.setState(global.music.states.PEACE); global.music.setState(global.music.states.PEACE);
} }
function resetTemplates(closePageCallback) function resetTemplates()
{ {
// Update GUI and clear player-dependent cache // Update GUI and clear player-dependent cache
g_TemplateData = {}; g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified"); Engine.GuiInterfaceCall("ResetTemplateModified");
// TODO: do this more selectively // TODO: do this more selectively
onSimulationUpdate(closePageCallback); onSimulationUpdate();
} }
/** /**
@ -677,7 +678,7 @@ function onTick()
Engine.GuiInterfaceCall("ClearRenamedEntities"); Engine.GuiInterfaceCall("ClearRenamedEntities");
} }
function onSimulationUpdate(closePageCallback) function onSimulationUpdate()
{ {
// Templates change depending on technologies and auras, so they have to be reloaded after such a change. // Templates change depending on technologies and auras, so they have to be reloaded after such a change.
// g_TechnologyData data never changes, so it shouldn't be deleted. // g_TechnologyData data never changes, so it shouldn't be deleted.
@ -705,7 +706,7 @@ function onSimulationUpdate(closePageCallback)
handler(); handler();
// TODO: Move to handlers // TODO: Move to handlers
handleNotifications(closePageCallback); handleNotifications();
updateGUIObjects(); updateGUIObjects();
} }

View file

@ -20,6 +20,7 @@
<script directory="gui/session/top_panel/"/> <script directory="gui/session/top_panel/"/>
<script directory="gui/session/top_panel/IconButtons/"/> <script directory="gui/session/top_panel/IconButtons/"/>
<script directory="gui/session/trade/"/> <script directory="gui/session/trade/"/>
<script directory="gui/session/tutorial/"/>
<object name="session"> <object name="session">
@ -52,7 +53,7 @@
<include file="gui/session/TimeNotificationOverlay.xml"/> <include file="gui/session/TimeNotificationOverlay.xml"/>
<include file="gui/session/TopPanel.xml"/> <include file="gui/session/TopPanel.xml"/>
<include file="gui/session/trade/TradeDialog.xml"/> <include file="gui/session/trade/TradeDialog.xml"/>
<include file="gui/session/tutorial_panel.xml"/> <include file="gui/session/tutorial/Tutorial.xml"/>
<include file="gui/session/Menu.xml"/> <include file="gui/session/Menu.xml"/>
<!-- Contains miscellanious objects s.a.: the technology research --> <!-- Contains miscellanious objects s.a.: the technology research -->

View file

@ -0,0 +1,62 @@
class Tutorial
{
panel = Engine.GetGUIObjectByName("tutorialPanel");
text = Engine.GetGUIObjectByName("tutorialText");
warning = Engine.GetGUIObjectByName("tutorialWarning");
readyButton = Engine.GetGUIObjectByName("tutorialReady");
instructions = [];
closePageCallback;
constructor(closePageCallback)
{
this.closePageCallback = closePageCallback;
}
toggle()
{
this.panel.hidden = !this.panel.hidden || !this.text.caption;
}
update(notification)
{
this.panel.hidden = false;
if (notification.warning)
{
this.warning.caption = coloredText(translate(notification.warning), "orange");
return;
}
const notificationText =
notification.instructions.reduce((instructions, item) =>
instructions + (typeof item === "string" ? translate(item) : colorizeHotkey(translate(item.text), item.hotkey)),
"");
this.text.caption = this.instructions.concat(setStringTags(notificationText, this.NewInstructionTags)).join("\n");
this.instructions.push(notificationText);
if (notification.readyButton)
{
this.readyButton.hidden = false;
if (notification.leave)
{
this.warning.caption = translate("Click to quit this tutorial.");
this.readyButton.caption = translate("Quit");
this.readyButton.onPress = () => { this.closePageCallback({ [Engine.openRequest]: endGame(true) }); };
}
else
this.warning.caption = translate("Click when ready.");
}
else
{
this.warning.caption = translate("Follow the instructions.");
this.readyButton.hidden = true;
}
}
}
/**
* GUI tags applied to the most recent instruction.
*/
Tutorial.prototype.NewInstructionTags = { "color": "255 226 149" };