From 59d0885d681467bb387a3220cb086179848d86b3 Mon Sep 17 00:00:00 2001 From: wraitii Date: Thu, 31 Dec 2020 10:04:58 +0000 Subject: [PATCH] Allow picking a default formation for walk (and walk-like) orders. This allows choosing a "default formation", which is activated automatically for units given walk orders (and attack-walk etc.). Conversely, units in formation that are given a gather/build/... order are taken out of formation and given the order individually. The default formation can be selected by right-clicking on any formation icon. This leverages formations for walking, where they are quite efficient (in fact, perhaps too efficient), while circumventing issues with various orders. Choosing the "null formation" as the default formation de-activates the behaviour entirely, and plays out exactly like SVN. This makes it possible to queue a formation-order then a noformation-order (i.e. walk then repair), though the behaviour isn't very flexible. For modders, it should be relatively easy to change the setup for each order, and/or to force deactivating/activating formations in general. Tested by: Freagarach, Angen Refs #3479, #1791. Makes #3478 mostly invalid. Differential Revision: https://code.wildfiregames.com/D2764 This was SVN commit r24480. --- binaries/data/config/default.cfg | 1 + .../mods/public/globalscripts/Formation.js | 4 + .../mods/public/gui/session/AutoFormation.js | 59 +++++ .../data/mods/public/gui/session/input.js | 7 +- .../public/gui/session/selection_panels.js | 16 +- .../gui/session/selection_panels_helpers.js | 4 +- .../selection_panels_left/formation_panel.xml | 1 + .../data/mods/public/gui/session/session.js | 2 + .../mods/public/gui/session/unit_actions.js | 36 ++- .../public/simulation/components/Formation.js | 2 + .../public/simulation/components/UnitAI.js | 37 +++- .../public/simulation/helpers/Commands.js | 206 +++++++++--------- 12 files changed, 257 insertions(+), 118 deletions(-) create mode 100644 binaries/data/mods/public/globalscripts/Formation.js create mode 100644 binaries/data/mods/public/gui/session/AutoFormation.js diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 06f1b2862f..2afc50b027 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -393,6 +393,7 @@ respoptooltipsort = 0 ; Sorting players in the resources and populat snaptoedges = "disabled" ; Possible values: disabled, enabled. snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges disjointcontrolgroups = "true" ; Whether control groups are disjoint sets or entities can be in multiple control groups at the same time. +defaultformation = "special/formations/box" ; For walking orders, automatically put units into this formation if they don't have one already. [gui.session.minimap] blinkduration = 1.7 ; The blink duration while pinging diff --git a/binaries/data/mods/public/globalscripts/Formation.js b/binaries/data/mods/public/globalscripts/Formation.js new file mode 100644 index 0000000000..030844ccd1 --- /dev/null +++ b/binaries/data/mods/public/globalscripts/Formation.js @@ -0,0 +1,4 @@ +/** + * The 'null' formation means that units are individuals. + */ +const NULL_FORMATION = "special/formations/null"; diff --git a/binaries/data/mods/public/gui/session/AutoFormation.js b/binaries/data/mods/public/gui/session/AutoFormation.js new file mode 100644 index 0000000000..babfff0202 --- /dev/null +++ b/binaries/data/mods/public/gui/session/AutoFormation.js @@ -0,0 +1,59 @@ +/** + * Handles the logic related to the 'default formation' feature. + * When given a walking order, units that aren't in formation will be put + * in the default formation, to improve pathfinding and reactivity. + * However, when given other tasks (such as e.g. gather), they will be removed + * from any formation they are in, as those orders don't work very well with formations. + * + * Set the default formation to the null formation to disable this entirely. + * + * TODO: it would be nice to let players choose default formations for different orders, + * but that would be neater if orders where defined somewhere unique, + * instead of mostly in unit_actions.js + */ +class AutoFormation +{ + constructor() + { + this.defaultFormation = Engine.ConfigDB_GetValue("user", "gui.session.defaultformation"); + if (!this.defaultFormation) + this.setDefault(NULL_FORMATION); + } + + /** + * Set the default formation to @param formation. + * TODO: would be good to validate, particularly since some formations aren't + * usable with any arbitrary unit type, we may want to warn then. + */ + setDefault(formation) + { + this.defaultFormation = formation; + Engine.ConfigDB_CreateValue("user", "gui.session.defaultformation", this.defaultFormation); + // TODO: It's extremely terrible that we have to explicitly flush the config... + Engine.ConfigDB_SetChanges("user", true); + Engine.ConfigDB_WriteFile("user", "config/user.cfg"); + return true; + } + + isDefault(formation) + { + return formation == this.defaultFormation; + } + + /** + * @return the default formation, or "undefined" if the null formation was chosen, + * otherwise units in formation would disband on any order, which isn't desirable. + */ + getDefault() + { + return this.defaultFormation == NULL_FORMATION ? undefined : this.defaultFormation; + } + + /** + * @return the null formation, or "undefined" if the null formation is the default. + */ + getNull() + { + return this.defaultFormation == NULL_FORMATION ? undefined : NULL_FORMATION; + } +} diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 7ac2b88b35..4a49352a4b 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -315,7 +315,8 @@ function tryPlaceBuilding(queued) "entities": selection, "autorepair": true, "autocontinue": true, - "queued": queued + "queued": queued, + "formation": g_AutoFormation.getNull() }); Engine.GuiInterfaceCall("PlaySound", { "name": "order_build", "entity": selection[0] }); @@ -358,6 +359,7 @@ function tryPlaceWall(queued) "pieces": wallPlacementInfo.pieces, "startSnappedEntity": wallPlacementInfo.startSnappedEnt, "endSnappedEntity": wallPlacementInfo.endSnappedEnt, + "formation": g_AutoFormation.getNull() }; // make sure that there's at least one non-tower entity getting built, to prevent silly edge cases where the start and end @@ -1241,7 +1243,8 @@ function positionUnitsFreehandSelectionMouseUp(ev) "entities": selection, "targetPositions": entityDistribution.map(pos => pos.toFixed(2)), "targetClasses": Engine.HotkeyIsPressed("session.attackmoveUnit") ? { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] }, - "queued": Engine.HotkeyIsPressed("session.queue") + "queued": Engine.HotkeyIsPressed("session.queue"), + "formation": NULL_FORMATION, }); // Add target markers with a minimum distance of 5 to each other. diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js index a471002e0d..338f5fb0bb 100644 --- a/binaries/data/mods/public/gui/session/selection_panels.js +++ b/binaries/data/mods/public/gui/session/selection_panels.js @@ -310,7 +310,7 @@ g_SelectionPanels.Formation = { if (!g_FormationsInfo.has(data.item)) g_FormationsInfo.set(data.item, Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", { "templateName": data.item })); - let formationOk = data.item == "special/formations/null" || canMoveSelectionIntoFormation(data.item); + let formationOk = canMoveSelectionIntoFormation(data.item); let unitIds = data.unitEntStates.map(state => state.id); let formationSelected = Engine.GuiInterfaceCall("IsFormationSelected", { "ents": unitIds, @@ -321,8 +321,21 @@ g_SelectionPanels.Formation = { performFormation(unitIds, data.item); }; + data.button.onMouseRightPress = () => g_AutoFormation.setDefault(data.item); + let formationInfo = g_FormationsInfo.get(data.item); let tooltip = translate(formationInfo.name); + + let isDefaultFormation = g_AutoFormation.isDefault(data.item); + if (data.item === NULL_FORMATION) + tooltip += "\n" + (isDefaultFormation ? + translate("Default formation is disabled.") : + translate("Right-click to disable the default formation feature.")); + else + tooltip += "\n" + (isDefaultFormation ? + translate("This is the default formation, used for movement orders.") : + translate("Right-click to set this as the default formation.")); + if (!formationOk && formationInfo.tooltip) tooltip += "\n" + coloredText(translate(formationInfo.tooltip), "red"); data.button.tooltip = tooltip; @@ -330,6 +343,7 @@ g_SelectionPanels.Formation = { data.button.enabled = formationOk && controlsPlayer(data.player); let grayscale = formationOk ? "" : "grayscale:"; data.guiSelection.hidden = !formationSelected; + data.countDisplay.hidden = !isDefaultFormation; data.icon.sprite = "stretched:" + grayscale + "session/icons/" + formationInfo.icon; setPanelObjectPosition(data.button, data.i, data.rowLength); diff --git a/binaries/data/mods/public/gui/session/selection_panels_helpers.js b/binaries/data/mods/public/gui/session/selection_panels_helpers.js index 55bec15696..427cb11e0e 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ b/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -8,6 +8,8 @@ const UPGRADING_CHOSEN_OTHER = -1; function canMoveSelectionIntoFormation(formationTemplate) { + if (formationTemplate == NULL_FORMATION) + return true; if (!(formationTemplate in g_canMoveIntoFormation)) g_canMoveIntoFormation[formationTemplate] = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", { "ents": g_Selection.toList(), @@ -332,7 +334,7 @@ function performFormation(entities, formationTemplate) Engine.PostNetworkCommand({ "type": "formation", "entities": entities, - "name": formationTemplate + "formation": formationTemplate }); } diff --git a/binaries/data/mods/public/gui/session/selection_panels_left/formation_panel.xml b/binaries/data/mods/public/gui/session/selection_panels_left/formation_panel.xml index a6069935f4..c860ecf862 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_left/formation_panel.xml +++ b/binaries/data/mods/public/gui/session/selection_panels_left/formation_panel.xml @@ -7,6 +7,7 @@