Index: ps/trunk/binaries/data/mods/public/gui/session/session_objects/panel_entities.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session_objects/panel_entities.xml (revision 23088)
+++ ps/trunk/binaries/data/mods/public/gui/session/session_objects/panel_entities.xml (nonexistent)
@@ -1,23 +0,0 @@
-
-
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/session_objects/panel_entities.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PanelEntities.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PanelEntities.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PanelEntities.xml (revision 23089)
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PanelEntities.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js (revision 23089)
@@ -0,0 +1,92 @@
+/**
+ * This class sets up a shortcut to a specific entity in the GUI panel.
+ * The button shows the portrait a tooltip with state information and a health bar.
+ * Clicking the button selects and jumps to to the entity.
+ */
+class PanelEntity
+{
+ constructor(selection, entityID, buttonID, orderKey)
+ {
+ this.selection = selection;
+ this.hitpoints = undefined;
+
+ /**
+ * Public property
+ */
+ this.entityID = entityID;
+
+ /**
+ * Public property
+ */
+ this.orderKey = orderKey;
+
+ this.overlayName = "panelEntityHitOverlay[" + buttonID + "]";
+ this.panelEntityHealthBar = Engine.GetGUIObjectByName("panelEntityHealthBar[" + buttonID + "]");
+ this.panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + buttonID + "]");
+ this.panelEntButton.onPress = this.onPress.bind(this);
+ this.panelEntButton.onDoublePress = this.onDoublePress.bind(this);
+ this.panelEntButton.hidden = false;
+
+ let entityState = GetEntityState(entityID);
+ let template = GetTemplateData(entityState.template);
+ this.nameTooltip = setStringTags(template.name.specific, this.NameTags) + "\n";
+
+ Engine.GetGUIObjectByName("panelEntityImage[" + buttonID + "]").sprite =
+ "stretched:" + this.PortraitDirectory + template.icon;
+ }
+
+ destroy()
+ {
+ this.panelEntButton.hidden = true;
+ stopColorFade(this.overlayName);
+ }
+
+ update(i, reposition)
+ {
+ // TODO: Instead of instant position changes, animate button movement.
+ if (reposition)
+ setPanelObjectPosition(this.panelEntButton, i, Infinity);
+
+ let entityState = GetEntityState(this.entityID);
+
+ if (this.hitpoints != entityState.hitpoints)
+ {
+ let size = this.panelEntityHealthBar.size;
+ size.rright = 100 * entityState.hitpoints / entityState.maxHitpoints;
+ this.panelEntityHealthBar.size = size;
+ }
+
+ this.panelEntButton.tooltip =
+ this.nameTooltip +
+ this.Tooltips.map(tooltip => tooltip(entityState)).filter(tip => tip).join("\n");
+
+ if (this.hitpoints > entityState.hitpoints)
+ startColorFade(this.overlayName, 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
+ this.hitpoints = entityState.hitpoints;
+ }
+
+ onPress()
+ {
+ if (!Engine.HotkeyIsPressed("selection.add"))
+ this.selection.reset();
+
+ this.selection.addList([this.entityID]);
+ }
+
+ onDoublePress()
+ {
+ this.selection.selectAndMoveTo(getEntityOrHolder(this.entityID));
+ }
+}
+
+PanelEntity.prototype.NameTags = { "font": "sans-bold-16" };
+
+PanelEntity.prototype.PortraitDirectory = "session/portraits/";
+
+PanelEntity.prototype.Tooltips = [
+ getCurrentHealthTooltip,
+ getAttackTooltip,
+ getArmorTooltip,
+ getEntityTooltip,
+ getAurasTooltip
+];
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PanelEntity.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/PanelEntityManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/PanelEntityManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/PanelEntityManager.js (revision 23089)
@@ -0,0 +1,92 @@
+/**
+ * This class creates a new buttonhandler per entity owned by the currently viewed player,
+ * or any player if the observer view is selected.
+ * The buttons are animated when the according entities are attacked, hence they have to keep a state.
+ */
+class PanelEntityManager
+{
+ constructor(playerViewControl, selection, entityOrder)
+ {
+ this.panelEntityButtons = Engine.GetGUIObjectByName("panelEntityButtons").children;
+
+ this.selection = selection;
+ this.entityOrder = entityOrder;
+
+ // One handler per panelEntity owned, sorted according to given order, limited to buttoncount
+ this.handlers = [];
+
+ let updater = this.update.bind(this);
+ registerSimulationUpdateHandler(updater);
+ playerViewControl.registerViewedPlayerChangeHandler(updater);
+ }
+
+ update()
+ {
+ // Obtain entity IDs to display
+ let entityIDs =
+ g_ViewedPlayer == -1 ?
+ g_SimState.players.reduce((ents, pState) => ents.concat(pState.panelEntities), []) :
+ g_SimState.players[g_ViewedPlayer].panelEntities;
+
+ let reposition = false;
+
+ // Delete handlers for entities not owned anymore
+ for (let i = 0; i < this.handlers.length;)
+ if (entityIDs.indexOf(this.handlers[i].entityID) == -1)
+ {
+ this.handlers[i].destroy();
+ this.handlers.splice(i, 1);
+ reposition = true;
+ }
+ else
+ ++i;
+
+ // Construct new handlers
+ for (let entityID of entityIDs)
+ if (this.handlers.every(handler => entityID != handler.entityID))
+ if (this.insertIfRelevant(entityID))
+ reposition = true;
+
+ for (let i = 0; i < this.handlers.length; ++i)
+ this.handlers[i].update(i, reposition);
+ }
+
+ insertIfRelevant(entityID)
+ {
+ let entityState = GetEntityState(entityID);
+
+ let orderKey = this.entityOrder.findIndex(entClass =>
+ entityState.identity.classes.indexOf(entClass) != -1);
+
+ // Sort depending on given order
+ let insertPos = this.handlers.reduce(
+ (pos, handler) => {
+ if (handler.orderKey <= orderKey)
+ ++pos;
+ return pos;
+ },
+ 0);
+
+ // Don't insert past the button limit
+ if (insertPos >= this.panelEntityButtons.length)
+ return false;
+
+ // Delete last handler if the limit is reached
+ if (this.handlers.length == this.panelEntityButtons.length)
+ {
+ this.handlers[this.handlers.length - 1].destroy();
+ this.handlers.splice(this.handlers.length - 1);
+ }
+
+ this.handlers.splice(
+ insertPos,
+ 0,
+ new PanelEntity(
+ this.selection,
+ entityID,
+ this.panelEntityButtons.findIndex(button => button.hidden),
+ orderKey));
+
+ return true;
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/PanelEntityManager.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/session/ResearchProgress.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/ResearchProgress.js (revision 23088)
+++ ps/trunk/binaries/data/mods/public/gui/session/ResearchProgress.js (revision 23089)
@@ -1,107 +1,108 @@
/**
* This class is responsible for displaying the currently researched technologies in an overlay.
*/
class ResearchProgress
{
- constructor(playerViewControl)
+ constructor(playerViewControl, selection)
{
this.buttons = Engine.GetGUIObjectByName("researchStartedButtons").children;
- this.buttonHandlers = this.buttons.map((button, i) => new ResearchProgressButton(i));
+ this.buttonHandlers = this.buttons.map((button, i) => new ResearchProgressButton(selection, i));
/**
* Top coordinate of the research list.
* Changes depending on the number of displayed counters.
*/
this.topOffset = 0;
let updater = this.updateResearchProgress.bind(this);
registerSimulationUpdateHandler(updater);
playerViewControl.registerViewedPlayerChangeHandler(updater);
}
setTopOffset(offset)
{
this.topOffset = offset;
}
updateResearchProgress()
{
let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", g_ViewedPlayer);
let i = 0;
for (let techName in researchStarted)
{
if (i == this.buttons.length)
break;
this.buttonHandlers[i++].onResearchedProgress(this.topOffset, techName, researchStarted[techName]);
}
while (i < this.buttons.length)
this.buttons[i++].hidden = true;
}
}
/**
* This is an individual button displaying a tech currently researched by the currently viewed player.
*/
class ResearchProgressButton
{
- constructor(i)
+ constructor(selection, i)
{
+ this.selection = selection;
this.button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]");
this.sprite = Engine.GetGUIObjectByName("researchStartedIcon[" + i + "]");
this.progress = Engine.GetGUIObjectByName("researchStartedProgressSlider[" + i + "]");
this.timeRemaining = Engine.GetGUIObjectByName("researchStartedTimeRemaining[" + i + "]");
this.buttonHeight = this.button.size.bottom - this.button.size.top;
this.buttonTop = this.Margin + (this.Margin + this.buttonHeight) * i;
this.progressHeight = this.progress.size.bottom - this.progress.size.top;
this.progressTop = this.progress.size.top;
this.button.onPress = this.onPress.bind(this);
}
onResearchedProgress(offset, techName, researchStatus)
{
this.researcher = researchStatus.researcher;
let template = GetTechnologyData(techName, g_Players[g_ViewedPlayer].civ);
this.sprite.sprite = "stretched:" + this.PortraitDirectory + template.icon;
let size = this.button.size;
size.top = offset + this.buttonTop;
size.bottom = size.top + this.buttonHeight;
this.button.size = size;
this.button.tooltip = getEntityNames(template);
this.button.hidden = false;
size = this.progress.size;
size.top = this.progressTop + this.progressHeight * researchStatus.progress;
this.progress.size = size;
this.timeRemaining.caption =
Engine.FormatMillisecondsIntoDateStringGMT(
researchStatus.timeRemaining,
translateWithContext("countdown format", this.CountdownFormat));
}
onPress()
{
- selectAndMoveTo(this.researcher);
+ this.selection.selectAndMoveTo(this.researcher);
}
}
/**
* Distance between consecutive buttons.
*/
ResearchProgressButton.prototype.Margin = 4;
/**
* Directory containing all icons.
*/
ResearchProgressButton.prototype.PortraitDirectory = "session/portraits/";
/**
* This format is used when displaying the remaining time of the currently viewed techs in research.
*/
ResearchProgressButton.prototype.CountdownFormat = markForTranslationWithContext("countdown format", "m:ss");
Index: ps/trunk/binaries/data/mods/public/gui/session/selection.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection.js (revision 23088)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection.js (revision 23089)
@@ -1,512 +1,524 @@
// Limits selection size
var g_MaxSelectionSize = 200;
// Alpha value of hovered/mouseover/highlighted selection overlays
// (should probably be greater than always visible alpha value,
// see CCmpSelectable)
var g_HighlightedAlpha = 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)
return;
Engine.GuiInterfaceCall("SetStatusBars", {
"entities": ents,
"enabled": enabled,
"showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true",
"showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true"
});
}
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 (let ent of ents)
{
if (this.ents[ent])
continue;
var 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;
var templateName = entState.template;
var key = GetTemplateData(templateName).selectionGroupName || templateName;
// Group the ents by player and template
if (entState.player !== undefined)
key = "p" + entState.player + "&" + key;
if (this.groups[key])
this.groups[key] += 1;
else
this.groups[key] = 1;
this.ents[ent] = key;
}
};
EntityGroups.prototype.removeEnt = function(ent)
{
var key = this.ents[ent];
// Remove the entity
delete this.ents[ent];
--this.groups[key];
// Remove the entire group
if (this.groups[key] == 0)
delete this.groups[key];
};
EntityGroups.prototype.rebuildGroup = function(renamed)
{
var oldGroup = this.ents;
this.reset();
var toAdd = [];
for (var ent in oldGroup)
toAdd.push(renamed[ent] ? renamed[ent] : +ent);
this.add(toAdd);
};
EntityGroups.prototype.getCount = function(key)
{
return this.groups[key];
};
EntityGroups.prototype.getTotalCount = function()
{
let totalCount = 0;
for (let key in this.groups)
totalCount += this.groups[key];
return totalCount;
};
EntityGroups.prototype.getKeys = function()
{
// Preserve order even when shuffling units around
// Can be optimized by moving the sorting elsewhere
return Object.keys(this.groups).sort();
};
EntityGroups.prototype.getEntsByKey = function(key)
{
var ents = [];
for (var ent in this.ents)
if (this.ents[ent] == key)
ents.push(+ent);
return ents;
};
/**
* get a list of entities grouped by a key
*/
EntityGroups.prototype.getEntsGrouped = function()
{
return this.getKeys().map(key => ({
"ents": this.getEntsByKey(key),
"key": key
}));
};
/**
* Gets all ents in every group except ones of the specified group
*/
EntityGroups.prototype.getEntsByKeyInverse = function(key)
{
var ents = [];
for (var ent in this.ents)
if (this.ents[ent] != key)
ents.push(+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 inverse is true otherwise deselect just the chosen entity
*/
EntitySelection.prototype.makePrimarySelection = function(key, inverse)
{
let ents = inverse ?
this.groups.getEntsByKeyInverse(key) :
this.groups.getEntsByKey(key);
this.reset();
this.addList(ents);
};
/**
* Get a list of the template names
*/
EntitySelection.prototype.getTemplateNames = function()
{
var templateNames = [];
for (let ent in this.selected)
{
let 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();
let controlsAll = g_SimState.players[g_ViewedPlayer] && g_SimState.players[g_ViewedPlayer].controlsAll;
let removeOwnerChanges = !g_IsObserver && !controlsAll && this.toList().length > 1;
let changed = false;
for (let ent in this.selected)
{
let entState = GetEntityState(+ent);
// Remove deleted units
if (!entState)
{
delete this.selected[ent];
this.groups.removeEnt(+ent);
changed = true;
continue;
}
// Remove non-visible units (e.g. moved back into fog-of-war)
// At the next update, mirages will be renamed to the real
// entity they replace, so just ignore them now
// Futhermore, when multiple selection, remove units which have changed ownership
if (entState.visibility == "hidden" && !entState.mirage ||
removeOwnerChanges && entState.player != g_ViewedPlayer)
{
// Disable any highlighting of the disappeared unit
_setHighlight([+ent], 0, false);
_setStatusBars([+ent], false);
_setMotionOverlay([+ent], false);
delete this.selected[ent];
this.groups.removeEnt(+ent);
changed = true;
continue;
}
}
if (changed)
this.onChange();
};
/**
* 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 (let renamedEntity of renamedEntities)
renamedLookup[renamedEntity.entity] = renamedEntity.newentity;
// Reconstruct the selection if at least one entity has been renamed.
for (let renamedEntity of renamedEntities)
if (this.selected[renamedEntity.entity])
{
this.rebuildSelection(renamedLookup);
return;
}
}
};
/**
* Add entities to selection. Play selection sound unless quiet is true
*/
EntitySelection.prototype.addList = function(ents, quiet, force = false)
{
let selection = this.toList();
// If someone else's player is the sole selected unit, don't allow adding to the selection
let firstEntState = selection.length == 1 && GetEntityState(selection[0]);
if (firstEntState && firstEntState.player != g_ViewedPlayer && !force)
return;
let i = 1;
let added = [];
for (let ent of ents)
{
if (selection.length + i > g_MaxSelectionSize)
break;
if (this.selected[ent])
continue;
var entState = GetEntityState(ent);
if (!entState)
continue;
let isUnowned = g_ViewedPlayer != -1 && entState.player != g_ViewedPlayer ||
g_ViewedPlayer == -1 && entState.player == 0;
// Don't add unowned entities to the list, unless a single entity was selected
if (isUnowned && (ents.length > 1 || selection.length) && !force)
continue;
added.push(ent);
this.selected[ent] = ent;
++i;
}
_setHighlight(added, 1, true);
_setStatusBars(added, true);
_setMotionOverlay(added, this.motionDebugOverlay);
if (added.length)
{
// Play the sound if the entity is controllable by us or Gaia-owned.
var owner = GetEntityState(added[0]).player;
if (!quiet && (controlsPlayer(owner) || g_IsObserver || owner == 0))
_playSound(added[0]);
}
this.groups.add(this.toList()); // Create Selection Groups
this.onChange();
};
EntitySelection.prototype.removeList = function(ents)
{
var removed = [];
for (let ent of 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.onChange();
};
EntitySelection.prototype.reset = function()
{
_setHighlight(this.toList(), 0, false);
_setStatusBars(this.toList(), false);
_setMotionOverlay(this.toList(), false);
this.selected = {};
this.groups.reset();
this.onChange();
};
EntitySelection.prototype.rebuildSelection = function(renamed)
{
var oldSelection = this.selected;
this.reset();
var toAdd = [];
for (let ent in oldSelection)
toAdd.push(renamed[ent] || +ent);
this.addList(toAdd, true); // don't play selection sounds
};
EntitySelection.prototype.getFirstSelected = function()
{
for (let ent in this.selected)
return +ent;
return undefined;
};
/**
* TODO: This array should not be recreated every call
*/
EntitySelection.prototype.toList = function()
{
let ents = [];
for (let ent in this.selected)
ents.push(+ent);
return ents;
};
EntitySelection.prototype.setHighlightList = function(ents)
{
var highlighted = {};
for (let ent of 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 (let ent in this.highlighted)
if (!highlighted[ent] && !this.selected[ent])
removed.push(+ent);
// Add new highlighting for units that aren't already highlighted
for (let ent of ents)
if (!this.highlighted[ent] && !this.selected[ent])
added.push(+ent);
_setHighlight(removed, 0, false);
_setStatusBars(removed, false);
_setHighlight(added, g_HighlightedAlpha, true);
_setStatusBars(added, true);
// Store the new highlight list
this.highlighted = highlighted;
};
EntitySelection.prototype.SetMotionDebugOverlay = function(enabled)
{
this.motionDebugOverlay = enabled;
_setMotionOverlay(this.toList(), enabled);
};
EntitySelection.prototype.onChange = function()
{
this.dirty = true;
if (this.isSelection)
onSelectionChange();
};
+EntitySelection.prototype.selectAndMoveTo = function(entityID)
+{
+ let entState = GetEntityState(entityID);
+ if (!entState || !entState.position)
+ return;
+
+ this.reset();
+ this.addList([entityID]);
+
+ Engine.CameraMoveTo(entState.position.x, entState.position.z);
+}
+
/**
* Cache some quantities which depends only on selection
*/
var g_Selection = new EntitySelection();
g_Selection.isSelection = true;
var g_canMoveIntoFormation = {};
var g_allBuildableEntities;
var g_allTrainableEntities;
// Reset cached quantities
function onSelectionChange()
{
g_canMoveIntoFormation = {};
g_allBuildableEntities = undefined;
g_allTrainableEntities = undefined;
}
/**
* 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 (let ent of ents)
for (let group of this.groups)
if (ent in group.ents)
group.removeEnt(ent);
this.groups[groupName].add(ents);
};
EntityGroupsContainer.prototype.update = function()
{
this.checkRenamedEntities();
for (let group of 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 (let renamedEntity of renamedEntities)
renamedLookup[renamedEntity.entity] = renamedEntity.newentity;
for (let group of this.groups)
for (let renamedEntity of 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();
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23088)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 23089)
@@ -1,1102 +1,918 @@
const g_IsReplay = Engine.IsVisualReplay();
const g_CivData = loadCivData(false, true);
const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire);
const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDurations);
const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions;
var g_Chat;
var g_DeveloperOverlay;
var g_DiplomacyColors;
var g_DiplomacyDialog;
var g_GameSpeedControl;
var g_Menu;
var g_MiniMapPanel;
var g_ObjectivesDialog;
+var g_PanelEntityManager;
var g_PauseControl;
var g_PauseOverlay;
var g_PlayerViewControl;
var g_ResearchProgress;
var g_TradeDialog;
var g_TopPanel;
/**
* A random file will be played. TODO: more variety
*/
var g_Ambient = ["audio/ambient/dayscape/day_temperate_gen_03.ogg"];
/**
* Map, player and match settings set in gamesetup.
*/
const g_GameAttributes = deepfreeze(Engine.GuiInterfaceCall("GetInitAttributes"));
/**
* True if this is a multiplayer game.
*/
const g_IsNetworked = Engine.HasNetClient();
/**
* Is this user in control of game settings (i.e. is a network server, or offline player).
*/
var g_IsController = !g_IsNetworked || Engine.HasNetServer();
/**
* Whether we have finished the synchronization and
* can start showing simulation related message boxes.
*/
var g_IsNetworkedActive = false;
/**
* True if the connection to the server has been lost.
*/
var g_Disconnected = false;
/**
* True if the current user has observer capabilities.
*/
var g_IsObserver = false;
/**
* True if the current user has rejoined (or joined the game after it started).
*/
var g_HasRejoined = false;
/**
* Shows a message box asking the user to leave if "won" or "defeated".
*/
var g_ConfirmExit = false;
/**
* The playerID selected in the change perspective tool.
*/
var g_ViewedPlayer = Engine.GetPlayerID();
/**
* True if the camera should focus on attacks and player commands
* and select the affected units.
*/
var g_FollowPlayer = false;
/**
* Cache the basic player data (name, civ, color).
*/
var g_Players = [];
/**
* Last time when onTick was called().
* Used for animating the main menu.
*/
var g_LastTickTime = Date.now();
/**
* Recalculate which units have their status bars shown with this frequency in milliseconds.
*/
var g_StatusBarUpdate = 200;
/**
* For restoring selection, order and filters when returning to the replay menu
*/
var g_ReplaySelectionData;
/**
* Remembers which clients are assigned to which player slots.
* The keys are guids or "local" in Singleplayer.
*/
var g_PlayerAssignments;
/**
* Whether the entire UI should be hidden (useful for promotional screenshots).
* Can be toggled with a hotkey.
*/
var g_ShowGUI = true;
/**
* Whether status bars should be shown for all of the player's units.
*/
var g_ShowAllStatusBars = false;
/**
* Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update).
*/
var g_SimState;
var g_EntityStates = {};
var g_TemplateData = {};
var g_TechnologyData = {};
var g_ResourceData = new Resources();
/**
* These handlers are called each time a new turn was simulated.
* Use this as sparely as possible.
*/
var g_SimulationUpdateHandlers = new Set();
/**
* These handlers are called after the player states have been initialized.
*/
var g_PlayersInitHandlers = new Set();
/**
* These handlers are called when a player has been defeated or won the game.
*/
var g_PlayerFinishedHandlers = new Set();
/**
* These events are fired whenever the player added or removed entities from the selection.
*/
var g_EntitySelectionChangeHandlers = new Set();
/**
* These events are fired when the user has performed a hotkey assignment change.
* Currently only fired on init, but to be fired from any hotkey editor dialog.
*/
var g_HotkeyChangeHandlers = new Set();
/**
* List of additional entities to highlight.
*/
var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
/**
- * Display data of the current players entities shown in the top panel.
- */
-var g_PanelEntities = [];
-
-/**
* Order in which the panel entities are shown.
*/
var g_PanelEntityOrder = ["Hero", "Relic"];
/**
* Unit classes to be checked for the idle-worker-hotkey.
*/
var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "Citizen"];
/**
* Unit classes to be checked for the military-only-selection modifier and for the idle-warrior-hotkey.
*/
var g_MilitaryTypes = ["Melee", "Ranged"];
function GetSimState()
{
if (!g_SimState)
g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState"));
return g_SimState;
}
function GetMultipleEntityStates(ents)
{
if (!ents.length)
return null;
let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents);
for (let item of entityStates)
g_EntityStates[item.entId] = item.state && deepfreeze(item.state);
return entityStates;
}
function GetEntityState(entId)
{
if (!g_EntityStates[entId])
{
let entityState = Engine.GuiInterfaceCall("GetEntityState", entId);
g_EntityStates[entId] = entityState && deepfreeze(entityState);
}
return g_EntityStates[entId];
}
function GetTemplateData(templateName)
{
if (!(templateName in g_TemplateData))
{
let template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
translateObjectKeys(template, ["specific", "generic", "tooltip"]);
g_TemplateData[templateName] = deepfreeze(template);
}
return g_TemplateData[templateName];
}
function GetTechnologyData(technologyName, civ)
{
if (!g_TechnologyData[civ])
g_TechnologyData[civ] = {};
if (!(technologyName in g_TechnologyData[civ]))
{
let template = GetTechnologyDataHelper(TechnologyTemplates.Get(technologyName), civ, g_ResourceData);
translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
g_TechnologyData[civ][technologyName] = deepfreeze(template);
}
return g_TechnologyData[civ][technologyName];
}
function init(initData, hotloadData)
{
if (!g_Settings)
{
Engine.EndGame();
Engine.SwitchGuiPage("page_pregame.xml");
return;
}
// Fallback used by atlas
g_PlayerAssignments = initData ? initData.playerAssignments : { "local": { "player": 1 } };
// Fallback used by atlas and autostart games
if (g_PlayerAssignments.local && !g_PlayerAssignments.local.name)
g_PlayerAssignments.local.name = singleplayerName();
if (initData)
{
g_ReplaySelectionData = initData.replaySelectionData;
g_HasRejoined = initData.isRejoining;
if (initData.savedGUIData)
restoreSavedGameData(initData.savedGUIData);
}
g_DiplomacyColors = new DiplomacyColors();
g_PlayerViewControl = new PlayerViewControl();
g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl));
g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects);
g_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay);
g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
g_Chat = new Chat(g_PlayerViewControl);
g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection);
g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl);
+ g_PanelEntityManager = new PanelEntityManager(g_PlayerViewControl, g_Selection, g_PanelEntityOrder);
g_PauseControl = new PauseControl();
g_PauseOverlay = new PauseOverlay(g_PauseControl);
g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
- g_ResearchProgress = new ResearchProgress(g_PlayerViewControl);
+ g_ResearchProgress = new ResearchProgress(g_PlayerViewControl, g_Selection);
g_TradeDialog = new TradeDialog(g_PlayerViewControl);
g_TopPanel = new TopPanel(g_PlayerViewControl, g_DiplomacyDialog, g_TradeDialog, g_ObjectivesDialog, g_GameSpeedControl);
initSelectionPanels();
LoadModificationTemplates();
updatePlayerData();
initializeMusic(); // before changing the perspective
- initPanelEntities();
Engine.SetBoundingBoxDebugOverlay(false);
updateEnabledRangeOverlayTypes();
for (let handler of g_PlayersInitHandlers)
handler();
for (let handler of g_HotkeyChangeHandlers)
handler();
if (hotloadData)
{
g_Selection.selected = hotloadData.selection;
g_PlayerAssignments = hotloadData.playerAssignments;
g_Players = hotloadData.player;
}
// TODO: use event instead
onSimulationUpdate();
setTimeout(displayGamestateNotifications, 1000);
}
function registerPlayersInitHandler(handler)
{
g_PlayersInitHandlers.add(handler);
}
function registerPlayersFinishedHandler(handler)
{
g_PlayerFinishedHandlers.add(handler);
}
function registerSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.add(handler);
}
function unregisterSimulationUpdateHandler(handler)
{
g_SimulationUpdateHandlers.delete(handler);
}
function registerEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.add(handler);
}
function unregisterEntitySelectionChangeHandler(handler)
{
g_EntitySelectionChangeHandlers.delete(handler);
}
function registerHotkeyChangeHandler(handler)
{
g_HotkeyChangeHandlers.add(handler);
}
function updatePlayerData()
{
let simState = GetSimState();
if (!simState)
return;
let playerData = [];
for (let i = 0; i < simState.players.length; ++i)
{
let playerState = simState.players[i];
playerData.push({
"name": playerState.name,
"civ": playerState.civ,
"color": {
"r": playerState.color.r * 255,
"g": playerState.color.g * 255,
"b": playerState.color.b * 255,
"a": playerState.color.a * 255
},
"team": playerState.team,
"teamsLocked": playerState.teamsLocked,
"cheatsEnabled": playerState.cheatsEnabled,
"state": playerState.state,
"isAlly": playerState.isAlly,
"isMutualAlly": playerState.isMutualAlly,
"isNeutral": playerState.isNeutral,
"isEnemy": playerState.isEnemy,
"guid": undefined, // network guid for players controlled by hosts
"offline": g_Players[i] && !!g_Players[i].offline
});
}
for (let guid in g_PlayerAssignments)
{
let playerID = g_PlayerAssignments[guid].player;
if (!playerData[playerID])
continue;
playerData[playerID].guid = guid;
playerData[playerID].name = g_PlayerAssignments[guid].name;
}
g_Players = playerData;
}
/**
* Called when the user changed the diplomacy colors in the options.
* TODO: Remove this proxy and make the options page agnostic of the session page.
*/
function updateDisplayedPlayerColors()
{
g_DiplomacyColors.updateDisplayedPlayerColors();
}
-function initPanelEntities()
-{
- Engine.GetGUIObjectByName("panelEntityPanel").children.forEach((button, slot) => {
-
- button.onPress = function() {
- let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot);
- if (!panelEnt)
- return;
-
- if (!Engine.HotkeyIsPressed("selection.add"))
- g_Selection.reset();
-
- g_Selection.addList([panelEnt.ent]);
- };
-
- button.onDoublePress = function() {
- let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot);
- if (panelEnt)
- selectAndMoveTo(getEntityOrHolder(panelEnt.ent));
- };
- });
-}
-
/**
* Returns the entity itself except when garrisoned where it returns its garrisonHolder
*/
function getEntityOrHolder(ent)
{
let entState = GetEntityState(ent);
if (entState && !entState.position && entState.unitAI && entState.unitAI.orders.length &&
entState.unitAI.orders[0].type == "Garrison")
return getEntityOrHolder(entState.unitAI.orders[0].data.target);
return ent;
}
function initializeMusic()
{
initMusic();
if (g_ViewedPlayer != -1 && g_CivData[g_Players[g_ViewedPlayer].civ].Music)
global.music.storeTracks(g_CivData[g_Players[g_ViewedPlayer].civ].Music);
global.music.setState(global.music.states.PEACE);
playAmbient();
}
function resetTemplates()
{
// Update GUI and clear player-dependent cache
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
// TODO: do this more selectively
onSimulationUpdate();
}
/**
* Returns true if the player with that ID is in observermode.
*/
function isPlayerObserver(playerID)
{
let playerStates = GetSimState().players;
return !playerStates[playerID] || playerStates[playerID].state != "active";
}
/**
* Returns true if the current user can issue commands for that player.
*/
function controlsPlayer(playerID)
{
let playerStates = GetSimState().players;
return !!playerStates[Engine.GetPlayerID()] &&
playerStates[Engine.GetPlayerID()].controlsAll ||
Engine.GetPlayerID() == playerID &&
!!playerStates[playerID] &&
playerStates[playerID].state != "defeated";
}
/**
* Called when one or more players have won or were defeated.
*
* @param {array} - IDs of the players who have won or were defeated.
* @param {object} - a plural string stating the victory reason.
* @param {boolean} - whether these players have won or lost.
*/
function playersFinished(players, victoryString, won)
{
addChatMessage({
"type": "playerstate",
"message": victoryString,
"players": players
});
updatePlayerData();
// TODO: The other calls in this function should move too
for (let handler of g_PlayerFinishedHandlers)
handler(players, won);
if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
return;
global.music.setState(
won ?
global.music.states.VICTORY :
global.music.states.DEFEAT
);
g_ConfirmExit = won ? "won" : "defeated";
}
function resumeGame()
{
g_PauseControl.implicitResume();
}
function closeOpenDialogs()
{
g_Menu.close();
g_Chat.closePage();
g_DiplomacyDialog.close();
g_ObjectivesDialog.close();
g_TradeDialog.close();
}
function endGame()
{
// Before ending the game
let replayDirectory = Engine.GetCurrentReplayDirectory();
let simData = Engine.GuiInterfaceCall("GetReplayMetadata");
let playerID = Engine.GetPlayerID();
Engine.EndGame();
// After the replay file was closed in EndGame
// Done here to keep EndGame small
if (!g_IsReplay)
Engine.AddReplayToCache(replayDirectory);
if (g_IsController && Engine.HasXmppClient())
Engine.SendUnregisterGame();
Engine.SwitchGuiPage("page_summary.xml", {
"sim": simData,
"gui": {
"dialog": false,
"assignedPlayer": playerID,
"disconnected": g_Disconnected,
"isReplay": g_IsReplay,
"replayDirectory": !g_HasRejoined && replayDirectory,
"replaySelectionData": g_ReplaySelectionData
}
});
}
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
return {
"selection": g_Selection.selected,
"playerAssignments": g_PlayerAssignments,
"player": g_Players,
};
}
function getSavedGameData()
{
return {
"groups": g_Groups.groups
};
}
function restoreSavedGameData(data)
{
// Restore camera if any
if (data.camera)
Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ,
data.camera.RotX, data.camera.RotY, data.camera.Zoom);
// Clear selection when loading a game
g_Selection.reset();
// Restore control groups
for (let groupNumber in data.groups)
{
g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups;
g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents;
}
updateGroups();
}
/**
* Called every frame.
*/
function onTick()
{
if (!g_Settings)
return;
let now = Date.now();
let tickLength = now - g_LastTickTime;
g_LastTickTime = now;
handleNetMessages();
updateCursorAndTooltip();
if (g_Selection.dirty)
{
g_Selection.dirty = false;
// When selection changed, get the entityStates of new entities
GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId]));
for (let handler of g_EntitySelectionChangeHandlers)
handler();
updateGUIObjects();
// Display rally points for selected buildings
if (Engine.GetPlayerID() != -1)
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
}
else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength)
recalculateStatusBarDisplay();
updateTimers();
Engine.GuiInterfaceCall("ClearRenamedEntities");
}
function onSimulationUpdate()
{
// Templates change depending on technologies and auras, so they have to be reloaded after such a change.
// g_TechnologyData data never changes, so it shouldn't be deleted.
g_EntityStates = {};
if (Engine.GuiInterfaceCall("IsTemplateModified"))
{
g_TemplateData = {};
Engine.GuiInterfaceCall("ResetTemplateModified");
}
g_SimState = undefined;
if (!GetSimState())
return;
GetMultipleEntityStates(g_Selection.toList());
for (let handler of g_SimulationUpdateHandlers)
handler();
// TODO: Move to handlers
updateCinemaPath();
handleNotifications();
updateGUIObjects();
if (g_ConfirmExit)
confirmExit();
}
/**
* Don't show the message box before all playerstate changes are processed.
*/
function confirmExit()
{
if (g_IsNetworked && !g_IsNetworkedActive)
return;
closeOpenDialogs();
g_PauseControl.implicitPause();
// Don't ask for exit if other humans are still playing
let askExit = !Engine.HasNetServer() || g_Players.every((player, i) =>
i == 0 ||
player.state != "active" ||
g_GameAttributes.settings.PlayerData[i].AI != "");
let subject = g_PlayerStateMessages[g_ConfirmExit];
if (askExit)
subject += "\n" + translate("Do you want to quit?");
messageBox(
400, 200,
subject,
g_ConfirmExit == "won" ?
translate("VICTORIOUS!") :
translate("DEFEATED!"),
askExit ? [translate("No"), translate("Yes")] : [translate("OK")],
askExit ? [resumeGame, endGame] : [resumeGame]);
g_ConfirmExit = false;
}
function toggleGUI()
{
g_ShowGUI = !g_ShowGUI;
updateCinemaPath();
}
function updateCinemaPath()
{
let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath;
Engine.Renderer_SetSilhouettesEnabled(!isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true");
}
// TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
function updateGUIObjects()
{
g_Selection.update();
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay();
if (g_ShowGuarding || g_ShowGuarded)
updateAdditionalHighlight();
- updatePanelEntities();
- displayPanelEntities();
-
updateGroups();
updateSelectionDetails();
updateBuildingPlacementPreview();
updateTimeNotifications();
if (!g_IsObserver)
{
// Update music state on basis of battle state.
let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer);
if (battleState)
global.music.setState(global.music.states[battleState]);
}
}
function onReplayFinished()
{
closeOpenDialogs();
g_PauseControl.implicitPause();
messageBox(400, 200,
translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"),
translateWithContext("replayFinished", "Confirmation"),
[translateWithContext("replayFinished", "No"), translateWithContext("replayFinished", "Yes")],
[resumeGame, endGame]);
}
-/**
-* updates a status bar on the GUI
-* nameOfBar: name of the bar
-* points: points to show
-* maxPoints: max points
-* direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3;
-*/
-function updateGUIStatusBar(nameOfBar, points, maxPoints, direction)
-{
- // check, if optional direction parameter is valid.
- if (!direction || !(direction >= 0 && direction < 4))
- direction = 0;
-
- // get the bar and update it
- let statusBar = Engine.GetGUIObjectByName(nameOfBar);
- if (!statusBar)
- return;
-
- let healthSize = statusBar.size;
- let value = 100 * Math.max(0, Math.min(1, points / maxPoints));
-
- // inverse bar
- if (direction == 2 || direction == 3)
- value = 100 - value;
-
- if (direction == 0)
- healthSize.rright = value;
- else if (direction == 1)
- healthSize.rbottom = value;
- else if (direction == 2)
- healthSize.rleft = value;
- else if (direction == 3)
- healthSize.rtop = value;
-
- statusBar.size = healthSize;
-}
-
-function updatePanelEntities()
-{
- let panelEnts =
- g_ViewedPlayer == -1 ?
- GetSimState().players.reduce((ents, pState) => ents.concat(pState.panelEntities), []) :
- GetSimState().players[g_ViewedPlayer].panelEntities;
-
- g_PanelEntities = g_PanelEntities.filter(panelEnt => panelEnts.find(ent => ent == panelEnt.ent));
-
- for (let ent of panelEnts)
- {
- let panelEntState = GetEntityState(ent);
- let template = GetTemplateData(panelEntState.template);
-
- let panelEnt = g_PanelEntities.find(pEnt => ent == pEnt.ent);
-
- if (!panelEnt)
- {
- panelEnt = {
- "ent": ent,
- "tooltip": undefined,
- "sprite": "stretched:session/portraits/" + template.icon,
- "maxHitpoints": undefined,
- "currentHitpoints": panelEntState.hitpoints,
- "previousHitpoints": undefined
- };
- g_PanelEntities.push(panelEnt);
- }
-
- panelEnt.tooltip = createPanelEntityTooltip(panelEntState, template);
- panelEnt.previousHitpoints = panelEnt.currentHitpoints;
- panelEnt.currentHitpoints = panelEntState.hitpoints;
- panelEnt.maxHitpoints = panelEntState.maxHitpoints;
- }
-
- let panelEntIndex = ent => g_PanelEntityOrder.findIndex(entClass =>
- GetEntityState(ent).identity.classes.indexOf(entClass) != -1);
-
- g_PanelEntities.sort((panelEntA, panelEntB) =>
- panelEntIndex(panelEntA.ent) - panelEntIndex(panelEntB.ent)
- ).splice(Engine.GetGUIObjectByName("panelEntityPanel").children.length);
-}
-
-function createPanelEntityTooltip(panelEntState, template)
-{
- let getPanelEntNameTooltip = panelEntState => "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]";
-
- return [
- getPanelEntNameTooltip,
- getCurrentHealthTooltip,
- getAttackTooltip,
- getArmorTooltip,
- getEntityTooltip,
- getAurasTooltip
- ].map(tooltip => tooltip(panelEntState)).filter(tip => tip).join("\n");
-}
-
-function displayPanelEntities()
-{
- let buttons = Engine.GetGUIObjectByName("panelEntityPanel").children;
-
- buttons.forEach((button, slot) => {
-
- if (button.hidden || g_PanelEntities.some(ent => ent.slot !== undefined && ent.slot == slot))
- return;
-
- button.hidden = true;
- stopColorFade("panelEntityHitOverlay[" + slot + "]");
- });
-
- // The slot identifies the button, displayIndex determines its position.
- for (let displayIndex = 0; displayIndex < Math.min(g_PanelEntities.length, buttons.length); ++displayIndex)
- {
- let panelEnt = g_PanelEntities[displayIndex];
-
- // Find the first unused slot if new, otherwise reuse previous.
- let slot = panelEnt.slot === undefined ?
- buttons.findIndex(button => button.hidden) :
- panelEnt.slot;
-
- let panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + slot + "]");
- panelEntButton.tooltip = panelEnt.tooltip;
-
- updateGUIStatusBar("panelEntityHealthBar[" + slot + "]", panelEnt.currentHitpoints, panelEnt.maxHitpoints);
-
- if (panelEnt.slot === undefined)
- {
- let panelEntImage = Engine.GetGUIObjectByName("panelEntityImage[" + slot + "]");
- panelEntImage.sprite = panelEnt.sprite;
-
- panelEntButton.hidden = false;
- panelEnt.slot = slot;
- }
-
- // If the health of the panelEnt changed since the last update, trigger the animation.
- if (panelEnt.previousHitpoints > panelEnt.currentHitpoints)
- startColorFade("panelEntityHitOverlay[" + slot + "]", 100, 0,
- colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
-
- // TODO: Instead of instant position changes, animate button movement.
- setPanelObjectPosition(panelEntButton, displayIndex, buttons.length);
- }
-}
-
function updateGroups()
{
g_Groups.update();
// Determine the sum of the costs of a given template
let getCostSum = (ent) => {
let cost = GetTemplateData(GetEntityState(ent).template).cost;
return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0;
};
for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children)
{
Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i;
let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]");
button.hidden = g_Groups.groups[i].getTotalCount() == 0;
button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); }; })(i);
button.ondoublepress = (function(i) { return function() { performGroup("snap", i); }; })(i);
button.onpressright = (function(i) { return function() { performGroup("breakUp", i); }; })(i);
// Choose the icon of the most common template (or the most costly if it's not unique)
if (g_Groups.groups[i].getTotalCount() > 0)
{
let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => {
if (pre.ents.length == cur.ents.length)
return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur;
return pre.ents.length > cur.ents.length ? pre : cur;
}).ents[0]).template).icon;
Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite =
icon ? ("stretched:session/portraits/" + icon) : "groupsIcon";
}
setPanelObjectPosition(button, i, 1);
}
}
-function selectAndMoveTo(ent)
-{
- let entState = GetEntityState(ent);
- if (!entState || !entState.position)
- return;
-
- g_Selection.reset();
- g_Selection.addList([ent]);
-
- let position = entState.position;
- Engine.CameraMoveTo(position.x, position.z);
-}
-
/**
* Toggles the display of status bars for all of the player's entities.
*
* @param {Boolean} remove - Whether to hide all previously shown status bars.
*/
function recalculateStatusBarDisplay(remove = false)
{
let entities;
if (g_ShowAllStatusBars && !remove)
entities = g_ViewedPlayer == -1 ?
Engine.PickNonGaiaEntitiesOnScreen() :
Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer);
else
{
let selected = g_Selection.toList();
for (let ent in g_Selection.highlighted)
selected.push(g_Selection.highlighted[ent]);
// Remove selected entities from the 'all entities' array,
// to avoid disabling their status bars.
entities = Engine.GuiInterfaceCall(
g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", {
"viewedPlayer": g_ViewedPlayer
}).filter(idx => selected.indexOf(idx) == -1);
}
Engine.GuiInterfaceCall("SetStatusBars", {
"entities": entities,
"enabled": g_ShowAllStatusBars && !remove,
"showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true",
"showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true"
});
}
function removeStatusBarDisplay()
{
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay(true);
}
/**
* Inverts the given configuration boolean and returns the current state.
* For example "silhouettes".
*/
function toggleConfigBool(configName)
{
let enabled = Engine.ConfigDB_GetValue("user", configName) != "true";
Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg");
return enabled;
}
/**
* Toggles the display of range overlays of selected entities for the given range type.
* @param {string} type - for example "Auras"
*/
function toggleRangeOverlay(type)
{
let enabled = toggleConfigBool("gui.session." + type.toLowerCase() + "range");
Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", {
"type": type,
"enabled": enabled
});
let selected = g_Selection.toList();
for (let ent in g_Selection.highlighted)
selected.push(g_Selection.highlighted[ent]);
Engine.GuiInterfaceCall("SetRangeOverlays", {
"entities": selected,
"enabled": enabled
});
}
function updateEnabledRangeOverlayTypes()
{
for (let type of ["Attack", "Auras", "Heal"])
Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", {
"type": type,
"enabled": Engine.ConfigDB_GetValue("user", "gui.session." + type.toLowerCase() + "range") == "true"
});
}
// Update the additional list of entities to be highlighted.
function updateAdditionalHighlight()
{
let entsAdd = []; // list of entities units to be highlighted
let entsRemove = [];
let highlighted = g_Selection.toList();
for (let ent in g_Selection.highlighted)
highlighted.push(g_Selection.highlighted[ent]);
if (g_ShowGuarding)
// flag the guarding entities to add in this additional highlight
for (let sel in g_Selection.selected)
{
let state = GetEntityState(g_Selection.selected[sel]);
if (!state.guard || !state.guard.entities.length)
continue;
for (let ent of state.guard.entities)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
if (g_ShowGuarded)
// flag the guarded entities to add in this additional highlight
for (let sel in g_Selection.selected)
{
let state = GetEntityState(g_Selection.selected[sel]);
if (!state.unitAI || !state.unitAI.isGuarding)
continue;
let ent = state.unitAI.isGuarding;
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
// flag the entities to remove (from the previously added) from this additional highlight
for (let ent of g_AdditionalHighlight)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1)
entsRemove.push(ent);
_setHighlight(entsAdd, g_HighlightedAlpha, true);
_setHighlight(entsRemove, 0, false);
g_AdditionalHighlight = entsAdd;
}
function playAmbient()
{
Engine.PlayAmbientSound(pickRandom(g_Ambient), true);
}
/**
* Adds the ingame time and ceasefire counter to the global FPS and
* realtime counters shown in the top right corner.
*/
function appendSessionCounters(counters)
{
let simState = GetSimState();
if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true")
{
let currentSpeed = Engine.GetSimRate();
if (currentSpeed != 1.0)
// Translation: The "x" means "times", with the mathematical meaning of multiplication.
counters.push(sprintf(translate("%(time)s (%(speed)sx)"), {
"time": timeToString(simState.timeElapsed),
"speed": Engine.FormatDecimalNumberIntoString(currentSpeed)
}));
else
counters.push(timeToString(simState.timeElapsed));
}
if (simState.ceasefireActive && Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") === "true")
counters.push(timeToString(simState.ceasefireTimeRemaining));
g_ResearchProgress.setTopOffset(14 * counters.length);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23088)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 23089)
@@ -1,148 +1,149 @@
onTick();
restoreSavedGameData(arguments[0]);
onSimulationUpdate();
onReplayFinished();
onReplayOutOfSync(arguments[0], arguments[1], arguments[2]);
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", String(Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") != "true"));
Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", String(Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") != "true"));
ExitendGame();
+