Index: binaries/data/mods/public/gui/session/input.js =================================================================== --- binaries/data/mods/public/gui/session/input.js +++ binaries/data/mods/public/gui/session/input.js @@ -72,6 +72,11 @@ */ var g_DragStart; +/** + * Current input states. Input is a non-deterministic state machine. + */ +var g_InputStates = []; + /** * Store the clicked entity on mousedown or mouseup for single/double/triple clicks to select entities. * If any mousedown or mouseup of a sequence of clicks lands on a unit, @@ -481,6 +486,9 @@ && (ev.button == SDL_BUTTON_LEFT || ev.button == SDL_BUTTON_RIGHT)) closeMenu(); + if (g_InputStates.some(e => e.handleInputBeforeGui(ev))) + return true; + // State-machine processing: // // (This is for states which should override the normal GUI processing - events will @@ -489,7 +497,6 @@ // // TODO: it'd probably be nice to have a better state-machine system, with guaranteed // entry/exit functions, since this is a bit broken now - switch (inputState) { case INPUT_BANDBOXING: @@ -767,14 +774,6 @@ } break; - case INPUT_MASSTRIBUTING: - if (ev.type == "hotkeyup" && ev.hotkey == "session.masstribute") - { - g_FlushTributing(); - inputState = INPUT_NORMAL; - } - break; - case INPUT_BATCHTRAINING: if (ev.type == "hotkeyup" && ev.hotkey == "session.batchtrain") { Index: binaries/data/mods/public/gui/session/inputs/input_state.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/inputs/input_state.js @@ -0,0 +1,16 @@ +// InputState is the default template for an input state. +// It implements the FSM logic so child-classes should extend it. + +// TODO: after the migration to SM45, this should be rewritten as a class with a constructor. +// And the _XXX methods could just be called using super() +const InputState = function() { + g_InputStates.push(this); +}; + +InputState.prototype._leave = function() { + g_InputStates.splice(g_InputStates.indexOf(this), 1); +}; + +InputState.prototype.handleInputBeforeGui = function(ev) {} + +InputState.prototype.handleInputAfterGui = function(ev) {} Index: binaries/data/mods/public/gui/session/inputs/tributing.js =================================================================== --- /dev/null +++ binaries/data/mods/public/gui/session/inputs/tributing.js @@ -0,0 +1,131 @@ +// TODO SM45: rewrite this as a class extending InputState +const TributingAction = function(menu) { + let ret = new InputState(); + + ret.menu = menu; + + ret.tributePlayer = null; + ret.currentAmounts = {}; + for (let res of g_ResourceData.GetCodes()) + ret.currentAmounts[res] = 0; + + ret.doTribute = function() { + Engine.PostNetworkCommand({ "type": "tribute", "player": this.tributePlayer, "amounts": this.currentAmounts }); + this.menu.tributingAction = null; + this.menu.render(); + this._leave(); + }; + + ret.handleInputBeforeGui = function(ev) { + if (ev.type == "hotkeyup" && ev.hotkey == "session.masstribute") + this.doTribute(); + }; + + ret.targetPlayer = function(i) { + if (this.tributePlayer == i) + return; + this.tributePlayer = i; + }; + + ret.addAmount = function(resCode, amount) { + this.currentAmounts[resCode] += amount; + // Re-render the menu as tooltips need update. + this.menu.render(); + }; + return ret; +}; + +const TributingMenu = function() { + let ret = new InputState(); + + ret.hidden = {}; + ret.tributingAction = null; + + ret.amountSmall = 100; + ret.amountLarge = 500; + + + ret.setPlayerHidden = function(player, hidden) { + if (player in this.hidden && this.hidden[player] == hidden) + return; + this.hidden[player] = hidden; + this.renderPlayer(player); + } + + ret.handleInputBeforeGui = function(ev) { + if ((ev.type == "hotkeydown" || ev.type == "hotkeyup") && ev.hotkey == "session.masstribute") + this.render(); + }; + + ret.tributeButtonClicked = function(i, resCode) { + if (!this.tributingAction) + this.tributingAction = new TributingAction(this); + + this.tributingAction.targetPlayer(i); + + let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); + this.tributingAction.addAmount(resCode, isBatchTrainPressed ? this.amountLarge : this.amountSmall); + + // Tribute immediately unless we are batch-tributing. + if (!isBatchTrainPressed) + this.tributingAction.doTribute(); + }; + + ret.formatTributeTooltip = function(playerID, resourceCode) + { + if (this.tributingAction) + return sprintf(translate("Release shift to tribute %(resources)s to %(playerName)s. Shift-click to tribute an additional %(amount)s %(resourceType)s."), { + "resources": getLocalizedResourceAmounts(this.tributingAction.currentAmounts), + "playerName": colorizePlayernameByID(playerID), + "amount": this.amountLarge, + "resourceType": resourceNameWithinSentence(resourceCode) + }); + let tributeString = sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s."), { + "resourceAmount": Engine.HotkeyIsPressed("session.masstribute") ? this.amountLarge : this.amountSmall, + "resourceType": resourceNameWithinSentence(resourceCode), + "playerName": colorizePlayernameByID(playerID) + }); + if (!Engine.HotkeyIsPressed("session.masstribute")) + tributeString += sprintf(translate("Shift-click to tribute %(amount)s."), { + "amount": this.amountLarge + }); + return tributeString + } + + ret.getTributeAmount = function(large) { + return large ? 500 : 100; + }; + + ret.renderPlayer = function(i) { + let r = 0; + for (let resCode of g_ResourceData.GetCodes()) + { + let button = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]"); + if (!button) + { + warn("Current GUI limits prevent displaying more than " + r + " tribute buttons!"); + break; + } + + Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]_image").sprite = "stretched:session/icons/resources/" + resCode + ".png"; + button.hidden = this.hidden[i]; + setPanelObjectPosition(button, r, r + 1, 0); + ++r; + if (this.hidden[i]) + continue; + + button.enabled = controlsPlayer(g_ViewedPlayer); + button.tooltip = this.formatTributeTooltip(i, resCode); + button.onPress = ((i, resCode) => () => this.tributeButtonClicked(i, resCode))(i, resCode); + } + } + + ret.render = function() { + for (let i = 1; i < g_Players.length; ++i) + this.renderPlayer(i); + } + + return ret; +} + +var g_TributingMenu; Index: binaries/data/mods/public/gui/session/menu.js =================================================================== --- binaries/data/mods/public/gui/session/menu.js +++ binaries/data/mods/public/gui/session/menu.js @@ -71,9 +71,6 @@ */ var g_SummarySelectedData; -// Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui. -var g_FlushTributing = function() {}; - function initMenu() { Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; @@ -379,6 +376,8 @@ g_IsDiplomacyOpen = true; + g_TributingMenu = new TributingMenu(); + updateDiplomacy(true); Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false; @@ -387,6 +386,9 @@ function closeDiplomacy() { g_IsDiplomacyOpen = false; + + g_TributingMenu = null; + Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = true; } @@ -421,9 +423,7 @@ diplomacySetupTexts(i, rowsize); diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked); - // Tribute buttons do not need to be updated onTick, and should not because of massTributing - if (opening) - diplomacyFormatTributeButtons(i, myself || playerInactive); + g_TributingMenu.setPlayerHidden(i, myself || playerInactive); diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]); diplomacyFormatSpyRequestButton(i, myself || playerInactive || g_Players[i].isMutualAlly[g_ViewedPlayer] && hasSharedLos); } @@ -489,62 +489,6 @@ } } -function diplomacyFormatTributeButtons(i, hidden) -{ - let resCodes = g_ResourceData.GetCodes(); - let r = 0; - for (let resCode of resCodes) - { - let button = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]"); - if (!button) - { - warn("Current GUI limits prevent displaying more than " + r + " tribute buttons!"); - break; - } - - Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]_image").sprite = "stretched:session/icons/resources/" + resCode + ".png"; - button.hidden = hidden; - setPanelObjectPosition(button, r, r + 1, 0); - ++r; - if (hidden) - continue; - - button.enabled = controlsPlayer(g_ViewedPlayer); - button.tooltip = formatTributeTooltip(i, resCode, 100); - button.onPress = (function(i, resCode, button) { - // Shift+click to send 500, shift+click+click to send 1000, etc. - // See INPUT_MASSTRIBUTING in input.js - let multiplier = 1; - return function() { - let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); - if (isBatchTrainPressed) - { - inputState = INPUT_MASSTRIBUTING; - multiplier += multiplier == 1 ? 4 : 5; - } - - let amounts = {}; - for (let res of resCodes) - amounts[res] = 0; - amounts[resCode] = 100 * multiplier; - - button.tooltip = formatTributeTooltip(i, resCode, amounts[resCode]); - - // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some - // evil global variable hackery. - g_FlushTributing = function() { - Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts": amounts }); - multiplier = 1; - button.tooltip = formatTributeTooltip(i, resCode, 100); - }; - - if (!isBatchTrainPressed) - g_FlushTributing(); - }; - })(i, resCode, button); - } -} - function diplomacyFormatAttackRequestButton(i, hidden) { let button = Engine.GetGUIObjectByName("diplomacyAttackRequest[" + (i - 1) + "]"); @@ -1268,13 +1212,3 @@ closeTrade(); closeObjectives(); } - -function formatTributeTooltip(playerID, resourceCode, amount) -{ - return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), { - "resourceAmount": amount, - "resourceType": resourceNameWithinSentence(resourceCode), - "playerName": colorizePlayernameByID(playerID), - "greaterAmount": amount < 500 ? 500 : amount + 500 - }); -} Index: binaries/data/mods/public/gui/session/session.xml =================================================================== --- binaries/data/mods/public/gui/session/session.xml +++ binaries/data/mods/public/gui/session/session.xml @@ -3,6 +3,7 @@