// Limits selection size const MAX_SELECTION_SIZE = 200; // Alpha value of hovered/mouseover/highlighted selection overlays // (should probably be greater than always visible alpha value, // see CCmpSelectable) const HIGHLIGHTED_ALPHA = 0.75; function _setHighlight(ents, alpha, selected) { if (ents.length) Engine.GuiInterfaceCall("SetSelectionHighlight", { "entities":ents, "alpha":alpha, "selected":selected }); } function _setStatusBars(ents, enabled) { if (ents.length) Engine.GuiInterfaceCall("SetStatusBars", { "entities":ents, "enabled":enabled }); } function _setMotionOverlay(ents, enabled) { if (ents.length) Engine.GuiInterfaceCall("SetMotionDebugOverlay", { "entities":ents, "enabled":enabled }); } function _playSound(ent) { Engine.GuiInterfaceCall("PlaySound", { "name":"select", "entity":ent }); } //-------------------------------- -------------------------------- -------------------------------- // EntityGroups class for managing grouped entities //-------------------------------- -------------------------------- -------------------------------- function EntityGroups() { this.groups = {}; this.ents = {}; } EntityGroups.prototype.reset = function() { this.groups = {}; this.ents = {}; }; EntityGroups.prototype.add = function(ents) { for each (var ent in ents) { if (!this.ents[ent]) { var entState = GetEntityState(ent); var templateName = entState.template; var template = GetTemplateData(templateName); var key = template.selectionGroupName || templateName; if (this.groups[key]) this.groups[key] += 1; else this.groups[key] = 1; this.ents[ent] = key; } } }; EntityGroups.prototype.removeEnt = function(ent) { var templateName = this.ents[ent]; // Remove the entity delete this.ents[ent]; this.groups[templateName]--; // Remove the entire group if (this.groups[templateName] == 0) delete this.groups[templateName]; }; EntityGroups.prototype.rebuildGroup = function(renamed) { var oldGroup = this.ents; this.reset(); var toAdd = []; for (var ent in oldGroup) toAdd.push(renamed[ent] ? renamed[ent] : parseInt(ent)); this.add(toAdd); } EntityGroups.prototype.getCount = function(templateName) { return this.groups[templateName]; }; EntityGroups.prototype.getTotalCount = function() { var totalCount = 0; for each (var group in this.groups) { totalCount += group; } return totalCount; }; EntityGroups.prototype.getTemplateNames = function() { var templateNames = []; for (var templateName in this.groups) templateNames.push(templateName); //Preserve order even when shuffling units around //Can be optimized by moving the sorting elsewhere templateNames.sort(); return templateNames; }; EntityGroups.prototype.getEntsByName = function(templateName) { var ents = []; for (var ent in this.ents) { if (this.ents[ent] == templateName) ents.push(parseInt(ent)); } return ents; }; // Gets all ents in every group except ones of the specified group EntityGroups.prototype.getEntsByNameInverse = function(templateName) { var ents = []; for (var ent in this.ents) { if (this.ents[ent] != templateName) ents.push(parseInt(ent)); } return ents; }; //-------------------------------- -------------------------------- -------------------------------- // EntitySelection class for managing the entity selection list and the primary selection //-------------------------------- -------------------------------- -------------------------------- function EntitySelection() { // Private properties: //-------------------------------- this.selected = {}; // { id:id, id:id, ... } for each selected entity ID 'id' // { id:id, ... } for mouseover-highlighted entity IDs in these, the key is a string and the value is an int; // we want to use the int form wherever possible since it's more efficient to send to the simulation code) this.highlighted = {}; this.motionDebugOverlay = false; // Public properties: //-------------------------------- this.dirty = false; // set whenever the selection has changed this.groups = new EntityGroups(); } // Deselect everything but entities of the chosen type if the modifier is true otherwise deselect just the chosen entity EntitySelection.prototype.makePrimarySelection = function(templateName, modifierKey) { var selection = this.toList(); // This doesn't seem to do anything // var ent; // // // Find an ent of a unit of the same type // for (var i = 0; i < selection.length; i++) // { // var entState = GetEntityState(selection[i]); // if (!entState) // continue; // if (entState.template == templateName) // ent = selection[i]; // } var template = GetTemplateData(templateName); var key = template.selectionGroupName || templateName; var ents = []; if (modifierKey) ents = this.groups.getEntsByNameInverse(key); else ents = this.groups.getEntsByName(key); this.reset(); this.addList(ents); } // Get a list of the template names EntitySelection.prototype.getTemplateNames = function() { var templateNames = []; var ents = this.toList(); for each (var ent in ents) { var entState = GetEntityState(ent); if (entState) templateNames.push(entState.template); } return templateNames; } // Update the selection to take care of changes (like units that have been killed) EntitySelection.prototype.update = function() { this.checkRenamedEntities(); for each (var ent in this.selected) { var entState = GetEntityState(ent); // Remove deleted units if (!entState) { delete this.selected[ent]; this.groups.removeEnt(ent); this.dirty = true; continue; } // Remove non-visible units (e.g. moved back into fog-of-war) if (entState.visibility == "hidden") { // Disable any highlighting of the disappeared unit _setHighlight([ent], 0, false); _setStatusBars([ent], false); _setMotionOverlay([ent], false); delete this.selected[ent]; this.dirty = true; continue; } } }; /** * Update selection if some selected entities were renamed * (in case of unit promotion or finishing building structure) */ EntitySelection.prototype.checkRenamedEntities = function() { var renamedEntities = Engine.GuiInterfaceCall("GetRenamedEntities"); if (renamedEntities.length > 0) { var renamedLookup = {}; for each (var renamedEntity in renamedEntities) renamedLookup[renamedEntity.entity] = renamedEntity.newentity; // Reconstruct the selection if at least one entity has been renamed. for each (var renamedEntity in renamedEntities) { if (this.selected[renamedEntity.entity]) { this.rebuildSelection(renamedLookup); break; } } } } EntitySelection.prototype.addList = function(ents) { var selection = this.toList(); var playerID = Engine.GetPlayerID(); // If someone else's player is the sole selected unit, don't allow adding to the selection if (!g_DevSettings.controlAll && selection.length == 1) { var firstEntState = GetEntityState(selection[0]); if (firstEntState && firstEntState.player != playerID) return; } // Allow selecting things not belong to this player (enemy, ally, gaia) var allowUnownedSelect = g_DevSettings.controlAll || (ents.length == 1 && selection.length == 0); var i = 1; var added = []; for each (var ent in ents) { // Only add entities we own to our selection var entState = GetEntityState(ent); if (!this.selected[ent] && (selection.length + i) <= MAX_SELECTION_SIZE && (allowUnownedSelect || (entState && entState.player == playerID))) { added.push(ent); this.selected[ent] = ent; i++; } } _setHighlight(added, 1, true); _setStatusBars(added, true); _setMotionOverlay(added, this.motionDebugOverlay); if (added.length) _playSound(added[0]); this.groups.add(this.toList()); // Create Selection Groups this.dirty = true; }; EntitySelection.prototype.removeList = function(ents) { var removed = []; for each (var ent in ents) { if (this.selected[ent]) { this.groups.removeEnt(ent); removed.push(ent); delete this.selected[ent]; } } _setHighlight(removed, 0, false); _setStatusBars(removed, false); _setMotionOverlay(removed, false); this.dirty = true; }; EntitySelection.prototype.reset = function() { _setHighlight(this.toList(), 0, false); _setStatusBars(this.toList(), false); _setMotionOverlay(this.toList(), false); this.selected = {}; this.groups.reset(); this.dirty = true; }; EntitySelection.prototype.rebuildSelection = function(renamed) { var oldSelection = this.selected; g_Selection.reset(); var toAdd = []; for each (var ent in oldSelection) toAdd.push(renamed[ent] ? renamed[ent] : ent); this.addList(toAdd); } EntitySelection.prototype.toList = function() { var ents = []; for each (var ent in this.selected) ents.push(ent); return ents; }; EntitySelection.prototype.setHighlightList = function(ents) { var highlighted = {}; for each (var ent in ents) highlighted[ent] = ent; var removed = []; var added = []; // Remove highlighting for the old units that are no longer highlighted // (excluding ones that are actively selected too) for each (var ent in this.highlighted) if (!highlighted[ent] && !this.selected[ent]) removed.push(+ent); // Add new highlighting for units that aren't already highlighted for each (var ent in ents) if (!this.highlighted[ent] && !this.selected[ent]) added.push(+ent); _setHighlight(removed, 0, false); _setStatusBars(removed, false); _setHighlight(added, HIGHLIGHTED_ALPHA, true); _setStatusBars(added, true); // Store the new highlight list this.highlighted = highlighted; }; EntitySelection.prototype.SetMotionDebugOverlay = function(enabled) { this.motionDebugOverlay = enabled; _setMotionOverlay(this.toList(), enabled); }; var g_Selection = new EntitySelection(); //-------------------------------- -------------------------------- -------------------------------- // EntityGroupsContainer class for managing grouped entities //-------------------------------- -------------------------------- -------------------------------- function EntityGroupsContainer() { this.groups = []; for (var i = 0; i < 10; ++i) { this.groups[i] = new EntityGroups(); } } EntityGroupsContainer.prototype.addEntities = function(groupName, ents) { for each (var ent in ents) { for each (var group in this.groups) { if (ent in group.ents) { group.removeEnt(ent); } } } this.groups[groupName].add(ents); } EntityGroupsContainer.prototype.update = function() { this.checkRenamedEntities(); for each (var group in this.groups) { for (var ent in group.ents) { var entState = GetEntityState(+ent); // Remove deleted units if (!entState) { group.removeEnt(ent); } } } } /** * Update control group if some entities in the group were renamed * (in case of unit promotion or finishing building structure) */ EntityGroupsContainer.prototype.checkRenamedEntities = function() { var renamedEntities = Engine.GuiInterfaceCall("GetRenamedEntities"); if (renamedEntities.length > 0) { var renamedLookup = {}; for each (var renamedEntity in renamedEntities) renamedLookup[renamedEntity.entity] = renamedEntity.newentity; for each (var group in this.groups) { for each (var renamedEntity in renamedEntities) { // Reconstruct the group if at least one entity has been renamed. if (renamedEntity.entity in group.ents) { group.rebuildGroup(renamedLookup); break; } } } } } var g_Groups = new EntityGroupsContainer();