1 | const g_IsReplay = Engine.IsVisualReplay();
|
---|
2 |
|
---|
3 | const g_CivData = loadCivData(false, true);
|
---|
4 |
|
---|
5 | const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
|
---|
6 | const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
|
---|
7 | const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
|
---|
8 | const g_WorldPopulationCapacities = prepareForDropdown(g_Settings && g_Settings.WorldPopulationCapacities);
|
---|
9 | const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
|
---|
10 | const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions;
|
---|
11 |
|
---|
12 | var g_Ambient;
|
---|
13 | var g_AutoFormation;
|
---|
14 | var g_Chat;
|
---|
15 | var g_Cheats;
|
---|
16 | var g_DeveloperOverlay;
|
---|
17 | var g_DiplomacyColors;
|
---|
18 | var g_DiplomacyDialog;
|
---|
19 | var g_GameSpeedControl;
|
---|
20 | var g_Menu;
|
---|
21 | var g_MiniMapPanel;
|
---|
22 | var g_NetworkStatusOverlay;
|
---|
23 | var g_NetworkDelayOverlay;
|
---|
24 | var g_ObjectivesDialog;
|
---|
25 | var g_OutOfSyncNetwork;
|
---|
26 | var g_OutOfSyncReplay;
|
---|
27 | var g_PanelEntityManager;
|
---|
28 | var g_PauseControl;
|
---|
29 | var g_PauseOverlay;
|
---|
30 | var g_PlayerViewControl;
|
---|
31 | var g_QuitConfirmationDefeat;
|
---|
32 | var g_QuitConfirmationReplay;
|
---|
33 | var g_RangeOverlayManager;
|
---|
34 | var g_ResearchProgress;
|
---|
35 | var g_TimeNotificationOverlay;
|
---|
36 | var g_TopPanel;
|
---|
37 | var g_TradeDialog;
|
---|
38 |
|
---|
39 | /**
|
---|
40 | * Map, player and match settings set in game setup.
|
---|
41 | */
|
---|
42 | const g_InitAttributes = deepfreeze(Engine.GuiInterfaceCall("GetInitAttributes"));
|
---|
43 |
|
---|
44 | /**
|
---|
45 | * True if this is a multiplayer game.
|
---|
46 | */
|
---|
47 | const 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 | */
|
---|
52 | var 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 | */
|
---|
58 | var g_IsNetworkedActive = false;
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * True if the connection to the server has been lost.
|
---|
62 | */
|
---|
63 | var g_Disconnected = false;
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * True if the current user has observer capabilities.
|
---|
67 | */
|
---|
68 | var g_IsObserver = false;
|
---|
69 |
|
---|
70 | /**
|
---|
71 | * True if the current user has rejoined (or joined the game after it started).
|
---|
72 | */
|
---|
73 | var g_HasRejoined = false;
|
---|
74 |
|
---|
75 | /**
|
---|
76 | * The playerID selected in the change perspective tool.
|
---|
77 | */
|
---|
78 | var 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 | */
|
---|
84 | var g_FollowPlayer = false;
|
---|
85 |
|
---|
86 | /**
|
---|
87 | * Cache the basic player data (name, civ, color).
|
---|
88 | */
|
---|
89 | var g_Players = [];
|
---|
90 |
|
---|
91 | /**
|
---|
92 | * Last time when onTick was called().
|
---|
93 | * Used for animating the main menu.
|
---|
94 | */
|
---|
95 | var g_LastTickTime = Date.now();
|
---|
96 |
|
---|
97 | /**
|
---|
98 | * Recalculate which units have their status bars shown with this frequency in milliseconds.
|
---|
99 | */
|
---|
100 | var g_StatusBarUpdate = 200;
|
---|
101 |
|
---|
102 | /**
|
---|
103 | * For restoring selection, order and filters when returning to the replay menu
|
---|
104 | */
|
---|
105 | var 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 | */
|
---|
111 | var g_PlayerAssignments;
|
---|
112 |
|
---|
113 | /**
|
---|
114 | * Whether the entire UI should be hidden (useful for promotional screenshots).
|
---|
115 | * Can be toggled with a hotkey.
|
---|
116 | */
|
---|
117 | var g_ShowGUI = true;
|
---|
118 |
|
---|
119 | /**
|
---|
120 | * Whether status bars should be shown for all of the player's units.
|
---|
121 | */
|
---|
122 | var g_ShowAllStatusBars = false;
|
---|
123 |
|
---|
124 | /**
|
---|
125 | * Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update).
|
---|
126 | */
|
---|
127 | var g_SimState;
|
---|
128 | var g_EntityStates = {};
|
---|
129 | var g_TemplateData = {};
|
---|
130 | var g_TechnologyData = {};
|
---|
131 |
|
---|
132 | var 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 | */
|
---|
138 | var g_SimulationUpdateHandlers = new Set();
|
---|
139 |
|
---|
140 | /**
|
---|
141 | * These handlers are called after the player states have been initialized.
|
---|
142 | */
|
---|
143 | var g_PlayersInitHandlers = new Set();
|
---|
144 |
|
---|
145 | /**
|
---|
146 | * These handlers are called when a player has been defeated or won the game.
|
---|
147 | */
|
---|
148 | var g_PlayerFinishedHandlers = new Set();
|
---|
149 |
|
---|
150 | /**
|
---|
151 | * These events are fired whenever the player added or removed entities from the selection.
|
---|
152 | */
|
---|
153 | var 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 | */
|
---|
159 | var g_HotkeyChangeHandlers = new Set();
|
---|
160 |
|
---|
161 | /**
|
---|
162 | * List of additional entities to highlight.
|
---|
163 | */
|
---|
164 | var g_ShowGuarding = false;
|
---|
165 | var g_ShowGuarded = false;
|
---|
166 | var g_AdditionalHighlight = [];
|
---|
167 |
|
---|
168 | /**
|
---|
169 | * Order in which the panel entities are shown.
|
---|
170 | */
|
---|
171 | var g_PanelEntityOrder = ["Hero", "Relic"];
|
---|
172 |
|
---|
173 | /**
|
---|
174 | * Unit classes to be checked for the idle-worker-hotkey.
|
---|
175 | */
|
---|
176 | var 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 | */
|
---|
181 | var g_MilitaryTypes = ["Melee", "Ranged"];
|
---|
182 |
|
---|
183 | function GetSimState()
|
---|
184 | {
|
---|
185 | if (!g_SimState)
|
---|
186 | g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState"));
|
---|
187 |
|
---|
188 | return g_SimState;
|
---|
189 | }
|
---|
190 |
|
---|
191 | function 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 |
|
---|
201 | function 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 | */
|
---|
219 | function 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 |
|
---|
230 | function 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 |
|
---|
245 | function 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 |
|
---|
334 | function registerPlayersInitHandler(handler)
|
---|
335 | {
|
---|
336 | g_PlayersInitHandlers.add(handler);
|
---|
337 | }
|
---|
338 |
|
---|
339 | function registerPlayersFinishedHandler(handler)
|
---|
340 | {
|
---|
341 | g_PlayerFinishedHandlers.add(handler);
|
---|
342 | }
|
---|
343 |
|
---|
344 | function registerSimulationUpdateHandler(handler)
|
---|
345 | {
|
---|
346 | g_SimulationUpdateHandlers.add(handler);
|
---|
347 | }
|
---|
348 |
|
---|
349 | function unregisterSimulationUpdateHandler(handler)
|
---|
350 | {
|
---|
351 | g_SimulationUpdateHandlers.delete(handler);
|
---|
352 | }
|
---|
353 |
|
---|
354 | function registerEntitySelectionChangeHandler(handler)
|
---|
355 | {
|
---|
356 | g_EntitySelectionChangeHandlers.add(handler);
|
---|
357 | }
|
---|
358 |
|
---|
359 | function unregisterEntitySelectionChangeHandler(handler)
|
---|
360 | {
|
---|
361 | g_EntitySelectionChangeHandlers.delete(handler);
|
---|
362 | }
|
---|
363 |
|
---|
364 | function registerHotkeyChangeHandler(handler)
|
---|
365 | {
|
---|
366 | g_HotkeyChangeHandlers.add(handler);
|
---|
367 | }
|
---|
368 |
|
---|
369 | function 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 | */
|
---|
421 | function 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 |
|
---|
430 | function 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 |
|
---|
438 | function 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 | */
|
---|
451 | function 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 | */
|
---|
460 | function 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 | */
|
---|
478 | function 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 |
|
---|
502 | function resumeGame()
|
---|
503 | {
|
---|
504 | g_PauseControl.implicitResume();
|
---|
505 | }
|
---|
506 |
|
---|
507 | function closeOpenDialogs()
|
---|
508 | {
|
---|
509 | g_Menu.close();
|
---|
510 | g_Chat.closePage();
|
---|
511 | g_DiplomacyDialog.close();
|
---|
512 | g_ObjectivesDialog.close();
|
---|
513 | g_TradeDialog.close();
|
---|
514 | }
|
---|
515 |
|
---|
516 | function 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
|
---|
561 | function getHotloadData()
|
---|
562 | {
|
---|
563 | return {
|
---|
564 | "selection": g_Selection.selected,
|
---|
565 | "playerAssignments": g_PlayerAssignments,
|
---|
566 | "player": g_Players,
|
---|
567 | };
|
---|
568 | }
|
---|
569 |
|
---|
570 | function getSavedGameData()
|
---|
571 | {
|
---|
572 | return {
|
---|
573 | "groups": g_Groups.groups
|
---|
574 | };
|
---|
575 | }
|
---|
576 |
|
---|
577 | function 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 | */
|
---|
599 | function 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 | let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
|
---|
634 | if (isPlayingCinemaPath)
|
---|
635 | updateCinemaOverlay();
|
---|
636 | }
|
---|
637 |
|
---|
638 | function onSimulationUpdate()
|
---|
639 | {
|
---|
640 | // Templates change depending on technologies and auras, so they have to be reloaded after such a change.
|
---|
641 | // g_TechnologyData data never changes, so it shouldn't be deleted.
|
---|
642 | g_EntityStates = {};
|
---|
643 | if (Engine.GuiInterfaceCall("IsTemplateModified"))
|
---|
644 | {
|
---|
645 | g_TemplateData = {};
|
---|
646 | Engine.GuiInterfaceCall("ResetTemplateModified");
|
---|
647 | }
|
---|
648 | g_SimState = undefined;
|
---|
649 |
|
---|
650 | // Some changes may require re-rendering the selection.
|
---|
651 | if (Engine.GuiInterfaceCall("IsSelectionDirty"))
|
---|
652 | {
|
---|
653 | g_Selection.onChange();
|
---|
654 | Engine.GuiInterfaceCall("ResetSelectionDirty");
|
---|
655 | }
|
---|
656 |
|
---|
657 | if (!GetSimState())
|
---|
658 | return;
|
---|
659 |
|
---|
660 | GetMultipleEntityStates(g_Selection.toList());
|
---|
661 |
|
---|
662 | for (let handler of g_SimulationUpdateHandlers)
|
---|
663 | handler();
|
---|
664 |
|
---|
665 | // TODO: Move to handlers
|
---|
666 | updateCinemaPath();
|
---|
667 | handleNotifications();
|
---|
668 | updateGUIObjects();
|
---|
669 | }
|
---|
670 |
|
---|
671 | function toggleGUI()
|
---|
672 | {
|
---|
673 | g_ShowGUI = !g_ShowGUI;
|
---|
674 | updateCinemaPath();
|
---|
675 | }
|
---|
676 |
|
---|
677 | function updateCinemaPath()
|
---|
678 | {
|
---|
679 | let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected;
|
---|
680 |
|
---|
681 | Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath;
|
---|
682 | Engine.GetGUIObjectByName("cinemaOverlay").hidden = !isPlayingCinemaPath;
|
---|
683 | Engine.ConfigDB_CreateValue("user", "silhouettes", !isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true" ? "true" : "false");
|
---|
684 | }
|
---|
685 |
|
---|
686 | function updateCinemaOverlay()
|
---|
687 | {
|
---|
688 | let cinemaOverlay = Engine.GetGUIObjectByName("cinemaOverlay");
|
---|
689 | let width = cinemaOverlay.getComputedSize().right;
|
---|
690 | let height = cinemaOverlay.getComputedSize().bottom;
|
---|
691 | let barHeight = (height - width / 2.39) / 2;
|
---|
692 | if (barHeight < 0)
|
---|
693 | barHeight = 0;
|
---|
694 |
|
---|
695 | let cinemaBarTop = Engine.GetGUIObjectByName("cinemaBarTop");
|
---|
696 | let cinemaBarTopSize = cinemaBarTop.size;
|
---|
697 | cinemaBarTopSize.bottom = barHeight;
|
---|
698 | cinemaBarTop.size = cinemaBarTopSize;
|
---|
699 |
|
---|
700 | let cinemaBarBottom = Engine.GetGUIObjectByName("cinemaBarBottom");
|
---|
701 | let cinemaBarBottomSize = cinemaBarBottom.size;
|
---|
702 | cinemaBarBottomSize.top = -barHeight;
|
---|
703 | cinemaBarBottom.size = cinemaBarBottomSize;
|
---|
704 | }
|
---|
705 |
|
---|
706 | // TODO: Use event subscription onSimulationUpdate, onEntitySelectionChange, onPlayerViewChange, ... instead
|
---|
707 | function updateGUIObjects()
|
---|
708 | {
|
---|
709 | g_Selection.update();
|
---|
710 |
|
---|
711 | if (g_ShowAllStatusBars)
|
---|
712 | recalculateStatusBarDisplay();
|
---|
713 |
|
---|
714 | if (g_ShowGuarding || g_ShowGuarded)
|
---|
715 | updateAdditionalHighlight();
|
---|
716 |
|
---|
717 | updateGroups();
|
---|
718 | updateSelectionDetails();
|
---|
719 | updateBuildingPlacementPreview();
|
---|
720 |
|
---|
721 | if (!g_IsObserver)
|
---|
722 | {
|
---|
723 | // Update music state on basis of battle state.
|
---|
724 | let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer);
|
---|
725 | if (battleState)
|
---|
726 | global.music.setState(global.music.states[battleState]);
|
---|
727 | }
|
---|
728 | }
|
---|
729 |
|
---|
730 | function updateGroups()
|
---|
731 | {
|
---|
732 | g_Groups.update();
|
---|
733 |
|
---|
734 | // Determine the sum of the costs of a given template
|
---|
735 | let getCostSum = (ent) => {
|
---|
736 | let cost = GetTemplateData(GetEntityState(ent).template).cost;
|
---|
737 | return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0;
|
---|
738 | };
|
---|
739 |
|
---|
740 | for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children)
|
---|
741 | {
|
---|
742 | Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i;
|
---|
743 |
|
---|
744 | let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]");
|
---|
745 | button.hidden = g_Groups.groups[i].getTotalCount() == 0;
|
---|
746 | button.onPress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); }; })(i);
|
---|
747 | button.onDoublePress = (function(i) { return function() { performGroup("snap", i); }; })(i);
|
---|
748 | button.onPressRight = (function(i) { return function() { performGroup("breakUp", i); }; })(i);
|
---|
749 |
|
---|
750 | // Choose the icon of the most common template (or the most costly if it's not unique)
|
---|
751 | if (g_Groups.groups[i].getTotalCount() > 0)
|
---|
752 | {
|
---|
753 | let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => {
|
---|
754 | if (pre.ents.length == cur.ents.length)
|
---|
755 | return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur;
|
---|
756 | return pre.ents.length > cur.ents.length ? pre : cur;
|
---|
757 | }).ents[0]).template).icon;
|
---|
758 |
|
---|
759 | Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite =
|
---|
760 | icon ? ("stretched:session/portraits/" + icon) : "groupsIcon";
|
---|
761 | }
|
---|
762 |
|
---|
763 | setPanelObjectPosition(button, i, 1);
|
---|
764 | }
|
---|
765 | }
|
---|
766 |
|
---|
767 | /**
|
---|
768 | * Toggles the display of status bars for all of the player's entities.
|
---|
769 | *
|
---|
770 | * @param {boolean} remove - Whether to hide all previously shown status bars.
|
---|
771 | */
|
---|
772 | function recalculateStatusBarDisplay(remove = false)
|
---|
773 | {
|
---|
774 | let entities;
|
---|
775 | if (g_ShowAllStatusBars && !remove)
|
---|
776 | entities = g_ViewedPlayer == -1 ?
|
---|
777 | Engine.PickNonGaiaEntitiesOnScreen() :
|
---|
778 | Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer);
|
---|
779 | else
|
---|
780 | {
|
---|
781 | let selected = g_Selection.toList();
|
---|
782 | for (let ent in g_Selection.highlighted)
|
---|
783 | selected.push(g_Selection.highlighted[ent]);
|
---|
784 |
|
---|
785 | // Remove selected entities from the 'all entities' array,
|
---|
786 | // to avoid disabling their status bars.
|
---|
787 | entities = Engine.GuiInterfaceCall(
|
---|
788 | g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", {
|
---|
789 | "viewedPlayer": g_ViewedPlayer
|
---|
790 | }).filter(idx => selected.indexOf(idx) == -1);
|
---|
791 | }
|
---|
792 |
|
---|
793 | Engine.GuiInterfaceCall("SetStatusBars", {
|
---|
794 | "entities": entities,
|
---|
795 | "enabled": g_ShowAllStatusBars && !remove,
|
---|
796 | "showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true",
|
---|
797 | "showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true"
|
---|
798 | });
|
---|
799 | }
|
---|
800 |
|
---|
801 | function removeStatusBarDisplay()
|
---|
802 | {
|
---|
803 | if (g_ShowAllStatusBars)
|
---|
804 | recalculateStatusBarDisplay(true);
|
---|
805 | }
|
---|
806 |
|
---|
807 | /**
|
---|
808 | * Updates the primary/secondary names in the simulation and GUI.
|
---|
809 | */
|
---|
810 | function updateDisplayedNames()
|
---|
811 | {
|
---|
812 | g_SpecificNamesPrimary = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 2;
|
---|
813 | g_ShowSecondaryNames = Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 0 || Engine.ConfigDB_GetValue("user", "gui.session.howtoshownames") == 1;
|
---|
814 | }
|
---|
815 |
|
---|
816 | /**
|
---|
817 | * Inverts the given configuration boolean and returns the current state.
|
---|
818 | * For example "silhouettes".
|
---|
819 | */
|
---|
820 | function toggleConfigBool(configName)
|
---|
821 | {
|
---|
822 | let enabled = Engine.ConfigDB_GetValue("user", configName) != "true";
|
---|
823 | Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg");
|
---|
824 | return enabled;
|
---|
825 | }
|
---|
826 |
|
---|
827 | // Update the additional list of entities to be highlighted.
|
---|
828 | function updateAdditionalHighlight()
|
---|
829 | {
|
---|
830 | let entsAdd = []; // list of entities units to be highlighted
|
---|
831 | let entsRemove = [];
|
---|
832 | let highlighted = g_Selection.toList();
|
---|
833 | for (let ent in g_Selection.highlighted)
|
---|
834 | highlighted.push(g_Selection.highlighted[ent]);
|
---|
835 |
|
---|
836 | if (g_ShowGuarding)
|
---|
837 | // flag the guarding entities to add in this additional highlight
|
---|
838 | for (let sel in g_Selection.selected)
|
---|
839 | {
|
---|
840 | let state = GetEntityState(g_Selection.selected[sel]);
|
---|
841 | if (!state.guard || !state.guard.entities.length)
|
---|
842 | continue;
|
---|
843 |
|
---|
844 | for (let ent of state.guard.entities)
|
---|
845 | if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
|
---|
846 | entsAdd.push(ent);
|
---|
847 | }
|
---|
848 |
|
---|
849 | if (g_ShowGuarded)
|
---|
850 | // flag the guarded entities to add in this additional highlight
|
---|
851 | for (let sel in g_Selection.selected)
|
---|
852 | {
|
---|
853 | let state = GetEntityState(g_Selection.selected[sel]);
|
---|
854 | if (!state.unitAI || !state.unitAI.isGuarding)
|
---|
855 | continue;
|
---|
856 | let ent = state.unitAI.isGuarding;
|
---|
857 | if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
|
---|
858 | entsAdd.push(ent);
|
---|
859 | }
|
---|
860 |
|
---|
861 | // flag the entities to remove (from the previously added) from this additional highlight
|
---|
862 | for (let ent of g_AdditionalHighlight)
|
---|
863 | if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1)
|
---|
864 | entsRemove.push(ent);
|
---|
865 |
|
---|
866 | _setHighlight(entsAdd, g_HighlightedAlpha, true);
|
---|
867 | _setHighlight(entsRemove, 0, false);
|
---|
868 | g_AdditionalHighlight = entsAdd;
|
---|
869 | }
|
---|