forked from 0ad/0ad
quantumstate
bfcbcc7ba8
e.g. If you have a long wall as primary selection and CC as secondary, after the gate upgrade the wall (now gate) remains the primary selection. This was SVN commit r12192.
475 lines
11 KiB
JavaScript
475 lines
11 KiB
JavaScript
// 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();
|