Index: binaries/data/mods/public/gui/session/selection.js =================================================================== --- binaries/data/mods/public/gui/session/selection.js +++ binaries/data/mods/public/gui/session/selection.js @@ -289,6 +289,7 @@ let i = 1; let added = []; + ents = this.addBattalionMembers(ents); for (let ent of ents) { @@ -332,6 +333,7 @@ EntitySelection.prototype.removeList = function(ents) { var removed = []; + ents = this.addBattalionMembers(ents); for (let ent of ents) if (this.selected[ent]) @@ -388,6 +390,7 @@ EntitySelection.prototype.setHighlightList = function(ents) { var highlighted = {}; + ents = this.addBattalionMembers(ents); for (let ent of ents) highlighted[ent] = ent; @@ -429,6 +432,20 @@ }; /** + * Adds the battalion members of a selected entity to the selection. + * @param {number[]} ents - The entity IDs of selected entities. + * @return {number[]} - Some more entity IDs if part of a battalion was selected. + */ +EntitySelection.prototype.addBattalionMembers = function(ents) +{ + for (let battalion of g_Battalions.battalions) + if (ents.some(ent => battalion.ents.indexOf(ent) != -1)) + ents = ents.concat(battalion.ents.filter(ent => ents.indexOf(ent) == -1)); + + return ents; +}; + +/** * Cache some quantities which depends only on selection */ @@ -505,3 +522,182 @@ }; var g_Groups = new EntityGroupsContainer(); +var g_Battalions = new BattalionsContainer(); + +/** + * EntityBattalions class for managing entities in battalions (i.e. select one, select all). + */ +function BattalionsContainer() +{ + this.battalions = []; +} + +/** + * Adds an entity/entities to the battalion. + * @param {number[]} ents - The creatures from LOTR. + */ +BattalionsContainer.prototype.formBattalion = function(ents) +{ +warn("Selection form-function called! " + uneval(ents)); + // Check whether there are free battalion spots. + for (let id = 0; true; id++) + { + if (!this.battalions.some(battalion => battalion.id == id)) + { +warn(id); + let index = this.battalions.length; +warn(index); + this.battalions[index] = {"id": id}; + this.addEntities(id, ents); + break; + } + } +warn("Selection form-function finished! " + uneval(this.battalions)); +}; + +/** + * Removes battalion(s). + * @param {number[]} battalionIDs - The unique IDs of the battalions. + */ +BattalionsContainer.prototype.disbandBattalion = function(battalionIDs) +{ +warn("Selection disband-function called! " + uneval(battalionIDs)); +warn("Selection disband-function battalions: " + uneval(this.battalions)); + + this.battalions = this.battalions.filter(battalion => battalionIDs.indexOf(battalion.id) == -1); + +warn("Selection disband-function finished! " + uneval(this.battalions)); +}; + +/** + * Adds an entity/entities to the battalion. + * @param {number} battalionID - The unique ID of the battalion, passed by "formBattalion". + * @param {number[]} ents - The creatures from LOTR. + */ +BattalionsContainer.prototype.addEntities = function(battalionID, ents) +{ +warn("Selection add-function called! " + uneval(battalionID) + ": " + uneval(ents)); + let index = +this.battalions.length - 1; + this.battalions[index].ents = []; + for (let ent of ents) + { + // Entities cannot be in more than one battalion at the same time. + let bat = this.isPartOfBattalion(ent); + if (bat !== false) + this.removeEntity(bat, ent); + + if (this.battalions[index].ents.indexOf(ent) != -1) + continue; + let entState = GetEntityState(ent); + + // When this function is called during group rebuild, deleted + // entities will not yet have been removed, so entities might + // still be present in the group despite not existing. + if (!entState) + continue; + + let templateName = entState.template; + let key = GetTemplateData(templateName).selectionGroupName || templateName; + + this.battalions[index].ents.push(ent); + if (!this.battalions[index].template) + this.battalions[index].template = key; + } +}; + +/** + * Removes an entity/entities from the battalion. + * @param {number} battalionID - The unique ID of the battalion. + * @param {number} ent - The entity ID of the entity to remove. + */ +BattalionsContainer.prototype.removeEntity = function(battalionID, ent) +{ +warn("Selection remove-function called! " + uneval(battalionID) + ": " + uneval(ent)); + for (let battalion of this.battalions) + { + if (battalion.id != battalionID) + continue; + let entIndex = battalion.ents.indexOf(ent); + if (entIndex != -1) + { + let battalionIndex = this.battalions.indexOf(battalion); + this.battalions[battalionIndex].ents.splice(entIndex, 1); + } + } +warn("Selection remove-function finished! " + uneval(this.battalions)); +}; + +/** + * Check wheter an entity is part of an battalion. + * @param {number} ent - The entity IDs to check. + * @return {number|boolean} - Either a number when the entity is part of a battalion + * or false when it is not. + */ +BattalionsContainer.prototype.isPartOfBattalion = function(ent) +{ +//warn("Selection isPartOfBattalion-function called! " + uneval(ent)); +//warn("Selection isPartOfBattalion-function battalions: " + uneval(this.battalions)); + + for (let battalion of this.battalions) + if (battalion.ents.indexOf(ent) != -1) + return battalion.id; + +//warn("Selection isPartOfBattalion-function? Nope!"); + return false; +}; + +/** + * Update the battalions. E.g when units die or change ownership and such. + */ +BattalionsContainer.prototype.update = function() +{ +warn("Selection update-function called! " + uneval(this.battalions)); + this.checkRenamedEntities(); + let battalionsToDisband = []; + let battalions = this.battalions; + for (let battalion of battalions) + { +warn("Selection update-function iteration: " + uneval(battalions)); + for (let ent of battalion.ents) + { + let entState = GetEntityState(+ent); + // Remove deleted units + if (!entState) + this.removeEntity(battalion.id, ent); + } +warn("Selection update-function iteration: " + uneval(battalion)); + if (!battalion.ents.length) + battalionsToDisband.push(battalion.id); + } + if (battalionsToDisband.length) + this.disbandBattalion(battalionsToDisband); +warn("Selection update-function finished! " + uneval(this.battalions)); + +}; + +/** + * Update battalions if some entities in the battalion were renamed + * (in case of unit promotion or finishing building structure). + */ +BattalionsContainer.prototype.checkRenamedEntities = function() +{ + let renamedEntities = Engine.GuiInterfaceCall("GetRenamedEntities"); + if (renamedEntities.length > 0) + { + let renamedLookup = {}; + for (let renamedEntity of renamedEntities) + renamedLookup[renamedEntity.entity] = renamedEntity.newentity; + + for (let battalion of this.battalions) + for (let renamedEntity of renamedEntities) + // Reconstruct the group if at least one entity has been renamed. + if (renamedEntity.entity in battalion.ents) + { + let oldEnts = battalion.ents; + // We probably want to keep the old battalion ID? Just "add entities" might do the job. + this.disbandBattalion(battalion.id); + this.formBattalion(oldEnts); + break; + } + } +}; Index: binaries/data/mods/public/gui/session/selection_panels_helpers.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -278,6 +278,30 @@ }); } +function formBattalion(entities) +{ + if (!entities) + return; +warn("Selection panel helper form-function called!"); + g_Battalions.formBattalion(entities); + g_Battalions.update(); + + Engine.PostNetworkCommand({ + "type": "formation", + "entities": entities, + "name": "special/formations/battle_line" + }); +} + +function disbandBattalion(battalions) +{ + if (!battalions) + return; +warn("Selection panel helper disband-function called!"); + g_Battalions.disbandBattalion(battalions); + g_Battalions.update(); +} + function performStance(entities, stanceName) { if (!entities) Index: binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- binaries/data/mods/public/gui/session/unit_actions.js +++ binaries/data/mods/public/gui/session/unit_actions.js @@ -1095,6 +1095,58 @@ }, }, + "formBattalion": { + "getInfo": function(entStates) + { + // Return false when multiple types of units are selected. + let refTemplateName = entStates[0].template.selectionGroupName || entStates[0].template; + if (entStates.length < 2 || + entStates.some(entState => + entState.template.selectionGroupName || entState.template != refTemplateName + ) + ) + return false; + + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.formbattalion") + + translate("Form a battalion with the currently selected units."), + "icon": "training.png" + }; + }, + "execute": function(entStates) + { + if (entStates.length) + formBattalion(entStates.map(entState => entState.id)); + }, + }, + + "disbandBattalion": { + "getInfo": function(entStates) + { + // Only return false when no battalions are selected. + if (entStates.every(entState => + g_Battalions.isPartOfBattalion(entState.id) === false + )) + return false; + + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.disbandbattalion") + + translate("Disband all currently selected battalion(s)."), + "icon": "cancel.png" + }; + }, + "execute": function(entStates) + { + // Pass the battalion IDs to the function. ToDo. + if (entStates.length) + disbandBattalion(entStates.map(entState => { + let bat = g_Battalions.isPartOfBattalion(entState.id); + if (bat !== false) + return bat; + })); + }, + }, + "garrison": { "getInfo": function(entStates) {