source: ps/trunk/binaries/data/mods/public/gui/session/session.js@ 25156

Last change on this file since 25156 was 25156, checked in by wraitii, 3 years ago

Netcode: allow observers to lag behind the live game.

Observers no longer lag the game for players. There is still some time to serialise the game when sending it to a joining observer, and depending on the chosen 'max lag' the game may stop while observers sufficiently catch up, but this impact too is reduced.

  • Make the NetServerTurnManager ignore players marked as 'observers' for the purpose of ending a turn, effectively making it possible for observers to lag without it affecting the players in any way.
  • Add a config option (network.observermaxlag) that specifies how many turns behind the live game observers are allowed to be. Default to 10 turns, or 2 seconds, to keep them 'largely live'.
  • The controller is not treated as an observer.
  • Implement a simple UI to show this delay & allow the game to speed up automatically to try and catch up. This can be deactivated via network.autocatchup.
  • Move network options to the renamed 'Network / Lobby' options page.
  • Do not debug_warn/crash when receiving commands from the past - instead warn and carry on, to avoid DOS and "coop play" issues.

Refs #5903, Refs #4210

Differential Revision: https://code.wildfiregames.com/D3737

  • Property svn:eol-style set to native
File size: 23.4 KB
Line 
1const g_IsReplay = Engine.IsVisualReplay();
2
3const g_CivData = loadCivData(false, true);
4
5const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
6const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
7const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
8const g_WorldPopulationCapacities = prepareForDropdown(g_Settings && g_Settings.WorldPopulationCapacities);
9const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
10const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions;
11
12var g_Ambient;
13var g_AutoFormation;
14var g_Chat;
15var g_Cheats;
16var g_DeveloperOverlay;
17var g_DiplomacyColors;
18var g_DiplomacyDialog;
19var g_GameSpeedControl;
20var g_Menu;
21var g_MiniMapPanel;
22var g_NetworkStatusOverlay;
23var g_NetworkDelayOverlay;
24var g_ObjectivesDialog;
25var g_OutOfSyncNetwork;
26var g_OutOfSyncReplay;
27var g_PanelEntityManager;
28var g_PauseControl;
29var g_PauseOverlay;
30var g_PlayerViewControl;
31var g_QuitConfirmationDefeat;
32var g_QuitConfirmationReplay;
33var g_RangeOverlayManager;
34var g_ResearchProgress;
35var g_TimeNotificationOverlay;
36var g_TopPanel;
37var g_TradeDialog;
38
39/**
40 * Map, player and match settings set in game setup.
41 */
42const g_InitAttributes = deepfreeze(Engine.GuiInterfaceCall("GetInitAttributes"));
43
44/**
45 * True if this is a multiplayer game.
46 */
47const g_IsNetworked = Engine.HasNetClient();
48
49/**
50 * Is this user in control of game settings (i.e. is a network server, or offline player).
51 */
52var g_IsController = !g_IsNetworked || Engine.IsNetController();
53
54/**
55 * Whether we have finished the synchronization and
56 * can start showing simulation related message boxes.
57 */
58var g_IsNetworkedActive = false;
59
60/**
61 * True if the connection to the server has been lost.
62 */
63var g_Disconnected = false;
64
65/**
66 * True if the current user has observer capabilities.
67 */
68var g_IsObserver = false;
69
70/**
71 * True if the current user has rejoined (or joined the game after it started).
72 */
73var g_HasRejoined = false;
74
75/**
76 * The playerID selected in the change perspective tool.
77 */
78var g_ViewedPlayer = Engine.GetPlayerID();
79
80/**
81 * True if the camera should focus on attacks and player commands
82 * and select the affected units.
83 */
84var g_FollowPlayer = false;
85
86/**
87 * Cache the basic player data (name, civ, color).
88 */
89var g_Players = [];
90
91/**
92 * Last time when onTick was called().
93 * Used for animating the main menu.
94 */
95var g_LastTickTime = Date.now();
96
97/**
98 * Recalculate which units have their status bars shown with this frequency in milliseconds.
99 */
100var g_StatusBarUpdate = 200;
101
102/**
103 * For restoring selection, order and filters when returning to the replay menu
104 */
105var g_ReplaySelectionData;
106
107/**
108 * Remembers which clients are assigned to which player slots.
109 * The keys are GUIDs or "local" in single-player.
110 */
111var g_PlayerAssignments;
112
113/**
114 * Whether the entire UI should be hidden (useful for promotional screenshots).
115 * Can be toggled with a hotkey.
116 */
117var g_ShowGUI = true;
118
119/**
120 * Whether status bars should be shown for all of the player's units.
121 */
122var g_ShowAllStatusBars = false;
123
124/**
125 * Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update).
126 */
127var g_SimState;
128var g_EntityStates = {};
129var g_TemplateData = {};
130var g_TechnologyData = {};
131
132var g_ResourceData = new Resources();
133
134/**
135 * These handlers are called each time a new turn was simulated.
136 * Use this as sparely as possible.
137 */
138var g_SimulationUpdateHandlers = new Set();
139
140/**
141 * These handlers are called after the player states have been initialized.
142 */
143var g_PlayersInitHandlers = new Set();
144
145/**
146 * These handlers are called when a player has been defeated or won the game.
147 */
148var g_PlayerFinishedHandlers = new Set();
149
150/**
151 * These events are fired whenever the player added or removed entities from the selection.
152 */
153var g_EntitySelectionChangeHandlers = new Set();
154
155/**
156 * These events are fired when the user has performed a hotkey assignment change.
157 * Currently only fired on init, but to be fired from any hotkey editor dialog.
158 */
159var g_HotkeyChangeHandlers = new Set();
160
161/**
162 * List of additional entities to highlight.
163 */
164var g_ShowGuarding = false;
165var g_ShowGuarded = false;
166var g_AdditionalHighlight = [];
167
168/**
169 * Order in which the panel entities are shown.
170 */
171var g_PanelEntityOrder = ["Hero", "Relic"];
172
173/**
174 * Unit classes to be checked for the idle-worker-hotkey.
175 */
176var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "Citizen"];
177
178/**
179 * Unit classes to be checked for the military-only-selection modifier and for the idle-warrior-hotkey.
180 */
181var g_MilitaryTypes = ["Melee", "Ranged"];
182
183function GetSimState()
184{
185 if (!g_SimState)
186 g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState"));
187
188 return g_SimState;
189}
190
191function GetMultipleEntityStates(ents)
192{
193 if (!ents.length)
194 return null;
195 let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents);
196 for (let item of entityStates)
197 g_EntityStates[item.entId] = item.state && deepfreeze(item.state);
198 return entityStates;
199}
200
201function GetEntityState(entId)
202{
203 if (!g_EntityStates[entId])
204 {
205 let entityState = Engine.GuiInterfaceCall("GetEntityState", entId);
206 g_EntityStates[entId] = entityState && deepfreeze(entityState);
207 }
208
209 return g_EntityStates[entId];
210}
211
212/**
213 * Returns template data calling GetTemplateData defined in GuiInterface.js
214 * and deepfreezing returned object.
215 * @param {string} templateName - Data of this template will be returned.
216 * @param {number|undefined} player - Modifications of this player will be applied to the template.
217 * If undefined, id of player calling this method will be used.
218 */
219function GetTemplateData(templateName, player)
220{
221 if (!(templateName in g_TemplateData))
222 {
223 let template = Engine.GuiInterfaceCall("GetTemplateData", { "templateName": templateName, "player": player });
224 translateObjectKeys(template, ["specific", "generic", "tooltip"]);
225 g_TemplateData[templateName] = deepfreeze(template);
226 }
227 return g_TemplateData[templateName];
228}
229
230function GetTechnologyData(technologyName, civ)
231{
232 if (!g_TechnologyData[civ])
233 g_TechnologyData[civ] = {};
234
235 if (!(technologyName in g_TechnologyData[civ]))
236 {
237 let template = GetTechnologyDataHelper(TechnologyTemplates.Get(technologyName), civ, g_ResourceData);
238 translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
239 g_TechnologyData[civ][technologyName] = deepfreeze(template);
240 }
241
242 return g_TechnologyData[civ][technologyName];
243}
244
245function init(initData, hotloadData)
246{
247 if (!g_Settings)
248 {
249 Engine.EndGame();
250 Engine.SwitchGuiPage("page_pregame.xml");
251 return;
252 }
253
254 // Fallback used by atlas
255 g_PlayerAssignments = initData ? initData.playerAssignments : { "local": { "player": 1 } };
256
257 // Fallback used by atlas and autostart games
258 if (g_PlayerAssignments.local && !g_PlayerAssignments.local.name)
259 g_PlayerAssignments.local.name = singleplayerName();
260
261 if (initData)
262 {
263 g_ReplaySelectionData = initData.replaySelectionData;
264 g_HasRejoined = initData.isRejoining;
265
266 if (initData.savedGUIData)
267 restoreSavedGameData(initData.savedGUIData);
268 }
269
270 if (g_InitAttributes.campaignData)
271 g_CampaignSession = new CampaignSession(g_InitAttributes.campaignData);
272
273 let mapCache = new MapCache();
274 g_Cheats = new Cheats();
275 g_DiplomacyColors = new DiplomacyColors();
276 g_PlayerViewControl = new PlayerViewControl();
277 g_PlayerViewControl.registerViewedPlayerChangeHandler(g_DiplomacyColors.updateDisplayedPlayerColors.bind(g_DiplomacyColors));
278 g_DiplomacyColors.registerDiplomacyColorsChangeHandler(g_PlayerViewControl.rebuild.bind(g_PlayerViewControl));
279 g_DiplomacyColors.registerDiplomacyColorsChangeHandler(updateGUIObjects);
280 g_PauseControl = new PauseControl();
281 g_PlayerViewControl.registerPreViewedPlayerChangeHandler(removeStatusBarDisplay);
282 g_PlayerViewControl.registerViewedPlayerChangeHandler(resetTemplates);
283
284 g_Ambient = new Ambient();
285 g_AutoFormation = new AutoFormation();
286 g_Chat = new Chat(g_PlayerViewControl, g_Cheats);
287 g_DeveloperOverlay = new DeveloperOverlay(g_PlayerViewControl, g_Selection);
288 g_DiplomacyDialog = new DiplomacyDialog(g_PlayerViewControl, g_DiplomacyColors);
289 g_GameSpeedControl = new GameSpeedControl(g_PlayerViewControl);
290 g_Menu = new Menu(g_PauseControl, g_PlayerViewControl, g_Chat);
291 g_MiniMapPanel = new MiniMapPanel(g_PlayerViewControl, g_DiplomacyColors, g_WorkerTypes);
292 g_NetworkStatusOverlay = new NetworkStatusOverlay();
293 g_NetworkDelayOverlay = new NetworkDelayOverlay();
294 g_ObjectivesDialog = new ObjectivesDialog(g_PlayerViewControl, mapCache);
295 g_OutOfSyncNetwork = new OutOfSyncNetwork();
296 g_OutOfSyncReplay = new OutOfSyncReplay();
297 g_PanelEntityManager = new PanelEntityManager(g_PlayerViewControl, g_Selection, g_PanelEntityOrder);
298 g_PauseOverlay = new PauseOverlay(g_PauseControl);
299 g_QuitConfirmationDefeat = new QuitConfirmationDefeat();
300 g_QuitConfirmationReplay = new QuitConfirmationReplay();
301 g_RangeOverlayManager = new RangeOverlayManager(g_Selection);
302 g_ResearchProgress = new ResearchProgress(g_PlayerViewControl, g_Selection);
303 g_TradeDialog = new TradeDialog(g_PlayerViewControl);
304 g_TopPanel = new TopPanel(g_PlayerViewControl, g_DiplomacyDialog, g_TradeDialog, g_ObjectivesDialog, g_GameSpeedControl);
305 g_TimeNotificationOverlay = new TimeNotificationOverlay(g_PlayerViewControl);
306
307 initBatchTrain();
308 initDisplayedNames();
309 initSelectionPanels();
310 LoadModificationTemplates();
311 updatePlayerData();
312 initializeMusic(); // before changing the perspective
313 Engine.SetBoundingBoxDebugOverlay(false);
314
315 for (let handler of g_PlayersInitHandlers)
316 handler();
317
318 for (let handler of g_HotkeyChangeHandlers)
319 handler();
320
321 if (hotloadData)
322 {
323 g_Selection.selected = hotloadData.selection;
324 g_PlayerAssignments = hotloadData.playerAssignments;
325 g_Players = hotloadData.player;
326 }
327
328 // TODO: use event instead
329 onSimulationUpdate();
330
331 setTimeout(displayGamestateNotifications, 1000);
332}
333
334function registerPlayersInitHandler(handler)
335{
336 g_PlayersInitHandlers.add(handler);
337}
338
339function registerPlayersFinishedHandler(handler)
340{
341 g_PlayerFinishedHandlers.add(handler);
342}
343
344function registerSimulationUpdateHandler(handler)
345{
346 g_SimulationUpdateHandlers.add(handler);
347}
348
349function unregisterSimulationUpdateHandler(handler)
350{
351 g_SimulationUpdateHandlers.delete(handler);
352}
353
354function registerEntitySelectionChangeHandler(handler)
355{
356 g_EntitySelectionChangeHandlers.add(handler);
357}
358
359function unregisterEntitySelectionChangeHandler(handler)
360{
361 g_EntitySelectionChangeHandlers.delete(handler);
362}
363
364function registerHotkeyChangeHandler(handler)
365{
366 g_HotkeyChangeHandlers.add(handler);
367}
368
369function updatePlayerData()
370{
371 let simState = GetSimState();
372 if (!simState)
373 return;
374
375 let playerData = [];
376
377 for (let i = 0; i < simState.players.length; ++i)
378 {
379 let playerState = simState.players[i];
380
381 playerData.push({
382 "name": playerState.name,
383 "civ": playerState.civ,
384 "color": {
385 "r": playerState.color.r * 255,
386 "g": playerState.color.g * 255,
387 "b": playerState.color.b * 255,
388 "a": playerState.color.a * 255
389 },
390 "team": playerState.team,
391 "teamsLocked": playerState.teamsLocked,
392 "cheatsEnabled": playerState.cheatsEnabled,
393 "state": playerState.state,
394 "isAlly": playerState.isAlly,
395 "isMutualAlly": playerState.isMutualAlly,
396 "isNeutral": playerState.isNeutral,
397 "isEnemy": playerState.isEnemy,
398 "guid": undefined, // network guid for players controlled by hosts
399 "offline": g_Players[i] && !!g_Players[i].offline
400 });
401 }
402
403 for (let guid in g_PlayerAssignments)
404 {
405 let playerID = g_PlayerAssignments[guid].player;
406
407 if (!playerData[playerID])
408 continue;
409
410 playerData[playerID].guid = guid;
411 playerData[playerID].name = g_PlayerAssignments[guid].name;
412 }
413
414 g_Players = playerData;
415}
416
417/**
418 * @param {number} ent - The entity to get its ID for.
419 * @return {number} - The entity ID of the entity or of its garrisonHolder.
420 */
421function getEntityOrHolder(ent)
422{
423 let entState = GetEntityState(ent);
424 if (entState && !entState.position && entState.garrisonable && entState.garrisonable.holder != INVALID_ENTITY)
425 return getEntityOrHolder(entState.garrisonable.holder);
426
427 return ent;
428}
429
430function initializeMusic()
431{
432 initMusic();
433 if (g_ViewedPlayer != -1 && g_CivData[g_Players[g_ViewedPlayer].civ].Music)
434 global.music.storeTracks(g_CivData[g_Players[g_ViewedPlayer].civ].Music);
435 global.music.setState(global.music.states.PEACE);
436}
437
438function resetTemplates()
439{
440 // Update GUI and clear player-dependent cache
441 g_TemplateData = {};
442 Engine.GuiInterfaceCall("ResetTemplateModified");
443
444 // TODO: do this more selectively
445 onSimulationUpdate();
446}
447
448/**
449 * Returns true if the player with that ID is in observermode.
450 */
451function isPlayerObserver(playerID)
452{
453 let playerStates = GetSimState().players;
454 return !playerStates[playerID] || playerStates[playerID].state != "active";
455}
456
457/**
458 * Returns true if the current user can issue commands for that player.
459 */
460function controlsPlayer(playerID)
461{
462 let playerStates = GetSimState().players;
463
464 return !!playerStates[Engine.GetPlayerID()] &&
465 playerStates[Engine.GetPlayerID()].controlsAll ||
466 Engine.GetPlayerID() == playerID &&
467 !!playerStates[playerID] &&
468 playerStates[playerID].state != "defeated";
469}
470
471/**
472 * Called when one or more players have won or were defeated.
473 *
474 * @param {array} - IDs of the players who have won or were defeated.
475 * @param {Object} - a plural string stating the victory reason.
476 * @param {boolean} - whether these players have won or lost.
477 */
478function playersFinished(players, victoryString, won)
479{
480 addChatMessage({
481 "type": "playerstate",
482 "message": victoryString,
483 "players": players
484 });
485
486 updatePlayerData();
487
488 // TODO: The other calls in this function should move too
489 for (let handler of g_PlayerFinishedHandlers)
490 handler(players, won);
491
492 if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
493 return;
494
495 global.music.setState(
496 won ?
497 global.music.states.VICTORY :
498 global.music.states.DEFEAT
499 );
500}
501
502function resumeGame()
503{
504 g_PauseControl.implicitResume();
505}
506
507function closeOpenDialogs()
508{
509 g_Menu.close();
510 g_Chat.closePage();
511 g_DiplomacyDialog.close();
512 g_ObjectivesDialog.close();
513 g_TradeDialog.close();
514}
515
516function endGame()
517{
518 // Before ending the game
519 let replayDirectory = Engine.GetCurrentReplayDirectory();
520 let simData = Engine.GuiInterfaceCall("GetReplayMetadata");
521 let playerID = Engine.GetPlayerID();
522
523 Engine.EndGame();
524
525 // After the replay file was closed in EndGame
526 // Done here to keep EndGame small
527 if (!g_IsReplay)
528 Engine.AddReplayToCache(replayDirectory);
529
530 if (g_IsController && Engine.HasXmppClient())
531 Engine.SendUnregisterGame();
532
533 let summaryData = {
534 "sim": simData,
535 "gui": {
536 "dialog": false,
537 "assignedPlayer": playerID,
538 "disconnected": g_Disconnected,
539 "isReplay": g_IsReplay,
540 "replayDirectory": !g_HasRejoined && replayDirectory,
541 "replaySelectionData": g_ReplaySelectionData
542 }
543 };
544
545 if (g_InitAttributes.campaignData)
546 {
547 let menu = g_CampaignSession.getMenu();
548 if (g_InitAttributes.campaignData.skipSummary)
549 {
550 Engine.SwitchGuiPage(menu);
551 return;
552 }
553 summaryData.campaignData = { "filename": g_InitAttributes.campaignData.run };
554 summaryData.nextPage = menu;
555 }
556
557 Engine.SwitchGuiPage("page_summary.xml", summaryData);
558}
559
560// Return some data that we'll use when hotloading this file after changes
561function getHotloadData()
562{
563 return {
564 "selection": g_Selection.selected,
565 "playerAssignments": g_PlayerAssignments,
566 "player": g_Players,
567 };
568}
569
570function getSavedGameData()
571{
572 return {
573 "groups": g_Groups.groups
574 };
575}
576
577function restoreSavedGameData(data)
578{
579 // Restore camera if any
580 if (data.camera)
581 Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ,
582 data.camera.RotX, data.camera.RotY, data.camera.Zoom);
583
584 // Clear selection when loading a game
585 g_Selection.reset();
586
587 // Restore control groups
588 for (let groupNumber in data.groups)
589 {
590 g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups;
591 g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents;
592 }
593 updateGroups();
594}
595
596/**
597 * Called every frame.
598 */
599function onTick()
600{
601 if (!g_Settings)
602 return;
603
604 let now = Date.now();
605 let tickLength = now - g_LastTickTime;
606 g_LastTickTime = now;
607
608 handleNetMessages();
609
610 updateCursorAndTooltip();
611
612 if (g_Selection.dirty)
613 {
614 g_Selection.dirty = false;
615 // When selection changed, get the entityStates of new entities
616 GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId]));
617
618 for (let handler of g_EntitySelectionChangeHandlers)
619 handler();
620
621 updateGUIObjects();
622
623 // Display rally points for selected structures.
624 if (Engine.GetPlayerID() != -1)
625 Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
626 }
627 else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength)
628 recalculateStatusBarDisplay();
629
630 updateTimers();
631 Engine.GuiInterfaceCall("ClearRenamedEntities");
632}
633
634function onSimulationUpdate()
635{
636 // Templates change depending on technologies and auras, so they have to be reloaded after such a change.
637 // g_TechnologyData data never changes, so it shouldn't be deleted.
638 g_EntityStates = {};
639 if (Engine.GuiInterfaceCall("IsTemplateModified"))
640 {
641 g_TemplateData = {};
642 Engine.GuiInterfaceCall("ResetTemplateModified");
643 }
644 g_SimState = undefined;
645
646 // Some changes may require re-rendering the selection.
647 if (Engine.GuiInterfaceCall("IsSelectionDirty"))
648 {
649 g_Selection.onChange();
650 Engine.GuiInterfaceCall("ResetSelectionDirty");
651 }
652
653 if (!GetSimState())
654 return;
655
656 GetMultipleEntityStates(g_Selection.toList());
657
658 for (let handler of g_SimulationUpdateHandlers)
659 handler();
660
661 // TODO: Move to handlers
662 updateCinemaPath();
663 handleNotifications();
664 updateGUIObjects();
665}
666
667function toggleGUI()
668{
669 g_ShowGUI = !g_ShowGUI;
670 updateCinemaPath();
671}
672
673function updateCinemaPath()
674{
675 let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
676
677 Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath;
678 Engine.ConfigDB_CreateValue("user", "silhouettes", !isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true" ? "true" : "false");
679}
680
681// TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
682function updateGUIObjects()
683{
684 g_Selection.update();
685
686 if (g_ShowAllStatusBars)
687 recalculateStatusBarDisplay();
688
689 if (g_ShowGuarding || g_ShowGuarded)
690 updateAdditionalHighlight();
691
692 updateGroups();
693 updateSelectionDetails();
694 updateBuildingPlacementPreview();
695
696 if (!g_IsObserver)
697 {
698 // Update music state on basis of battle state.
699 let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer);
700 if (battleState)
701 global.music.setState(global.music.states[battleState]);
702 }
703}
704
705function updateGroups()
706{
707 g_Groups.update();
708
709 // Determine the sum of the costs of a given template
710 let getCostSum = (ent) => {
711 let cost = GetTemplateData(GetEntityState(ent).template).cost;
712 return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0;
713 };
714
715 for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children)
716 {
717 Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i;
718
719 let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]");
720 button.hidden = g_Groups.groups[i].getTotalCount() == 0;
721 button.onPress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); }; })(i);
722 button.onDoublePress = (function(i) { return function() { performGroup("snap", i); }; })(i);
723 button.onPressRight = (function(i) { return function() { performGroup("breakUp", i); }; })(i);
724
725 // Choose the icon of the most common template (or the most costly if it's not unique)
726 if (g_Groups.groups[i].getTotalCount() > 0)
727 {
728 let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => {
729 if (pre.ents.length == cur.ents.length)
730 return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur;
731 return pre.ents.length > cur.ents.length ? pre : cur;
732 }).ents[0]).template).icon;
733
734 Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite =
735 icon ? ("stretched:session/portraits/" + icon) : "groupsIcon";
736 }
737
738 setPanelObjectPosition(button, i, 1);
739 }
740}
741
742/**
743 * Toggles the display of status bars for all of the player's entities.
744 *
745 * @param {boolean} remove - Whether to hide all previously shown status bars.
746 */
747function recalculateStatusBarDisplay(remove = false)
748{
749 let entities;
750 if (g_ShowAllStatusBars && !remove)
751 entities = g_ViewedPlayer == -1 ?
752 Engine.PickNonGaiaEntitiesOnScreen() :
753 Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer);
754 else
755 {
756 let selected = g_Selection.toList();
757 for (let ent in g_Selection.highlighted)
758 selected.push(g_Selection.highlighted[ent]);
759
760 // Remove selected entities from the 'all entities' array,
761 // to avoid disabling their status bars.
762 entities = Engine.GuiInterfaceCall(
763 g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", {
764 "viewedPlayer": g_ViewedPlayer
765 }).filter(idx => selected.indexOf(idx) == -1);
766 }
767
768 Engine.GuiInterfaceCall("SetStatusBars", {
769 "entities": entities,
770 "enabled": g_ShowAllStatusBars && !remove,
771 "showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true",
772 "showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true"
773 });
774}
775
776function removeStatusBarDisplay()
777{
778 if (g_ShowAllStatusBars)
779 recalculateStatusBarDisplay(true);
780}
781
782/**
783 * Updates the primary/secondary names in the simulation and GUI.
784 */
785function updateDisplayedNames()
786{
787 g_SpecificNamesPrimary = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 2;
788 g_ShowSecondaryNames = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 1;
789}
790
791/**
792 * Inverts the given configuration boolean and returns the current state.
793 * For example "silhouettes".
794 */
795function toggleConfigBool(configName)
796{
797 let enabled = Engine.ConfigDB_GetValue("user", configName) != "true";
798 Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg");
799 return enabled;
800}
801
802// Update the additional list of entities to be highlighted.
803function updateAdditionalHighlight()
804{
805 let entsAdd = []; // list of entities units to be highlighted
806 let entsRemove = [];
807 let highlighted = g_Selection.toList();
808 for (let ent in g_Selection.highlighted)
809 highlighted.push(g_Selection.highlighted[ent]);
810
811 if (g_ShowGuarding)
812 // flag the guarding entities to add in this additional highlight
813 for (let sel in g_Selection.selected)
814 {
815 let state = GetEntityState(g_Selection.selected[sel]);
816 if (!state.guard || !state.guard.entities.length)
817 continue;
818
819 for (let ent of state.guard.entities)
820 if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
821 entsAdd.push(ent);
822 }
823
824 if (g_ShowGuarded)
825 // flag the guarded entities to add in this additional highlight
826 for (let sel in g_Selection.selected)
827 {
828 let state = GetEntityState(g_Selection.selected[sel]);
829 if (!state.unitAI || !state.unitAI.isGuarding)
830 continue;
831 let ent = state.unitAI.isGuarding;
832 if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
833 entsAdd.push(ent);
834 }
835
836 // flag the entities to remove (from the previously added) from this additional highlight
837 for (let ent of g_AdditionalHighlight)
838 if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1)
839 entsRemove.push(ent);
840
841 _setHighlight(entsAdd, g_HighlightedAlpha, true);
842 _setHighlight(entsRemove, 0, false);
843 g_AdditionalHighlight = entsAdd;
844}
Note: See TracBrowser for help on using the repository browser.