Ticket #9: visualreplay-WIP-r13278.patch

File visualreplay-WIP-r13278.patch, 101.2 KB (added by alpha123, 11 years ago)

Update for a newer SVN version.

Line 
1Index: binaries/data/mods/public/gui/page_replay.xml
2
3===================================================================
4
5--- binaries/data/mods/public/gui/page_replay.xml (revision 0)
6
7+++ binaries/data/mods/public/gui/page_replay.xml (working copy)
8
9@@ -0,0 +1,15 @@
10
11+<?xml version="1.0" encoding="utf-8"?>
12
13+<page>
14
15+ <include>common/setup.xml</include>
16
17+ <include>common/styles.xml</include>
18
19+ <include>common/sprite1.xml</include>
20
21+ <include>common/icon_sprites.xml</include>
22
23+
24
25+ <include>common/common_sprites.xml</include>
26
27+ <include>common/common_styles.xml</include>
28
29+
30
31+ <include>session/sprites.xml</include>
32
33+ <include>session/styles.xml</include>
34
35+ <include>replay/replay.xml</include>
36
37+ <include>common/global.xml</include>
38
39+</page>
40
41Index: binaries/data/mods/public/gui/replay/input.js
42
43===================================================================
44
45--- binaries/data/mods/public/gui/replay/input.js (revision 0)
46
47+++ binaries/data/mods/public/gui/replay/input.js (working copy)
48
49@@ -0,0 +1,457 @@
50
51+const SDL_BUTTON_LEFT = 1;
52+const SDL_BUTTON_MIDDLE = 2;
53+const SDL_BUTTON_RIGHT = 3;
54+const SDLK_LEFTBRACKET = 91;
55+const SDLK_RIGHTBRACKET = 93;
56+const SDLK_RSHIFT = 303;
57+const SDLK_LSHIFT = 304;
58+const SDLK_RCTRL = 305;
59+const SDLK_LCTRL = 306;
60+const SDLK_RALT = 307;
61+const SDLK_LALT = 308;
62+// TODO: these constants should be defined somewhere else instead, in
63+// case any other code wants to use them too
64+
65+const ACTION_NONE = 0;
66+const ACTION_GARRISON = 1;
67+const ACTION_REPAIR = 2;
68+var preSelectedAction = ACTION_NONE;
69+
70+const INPUT_NORMAL = 0;
71+const INPUT_SELECTING = 1;
72+const INPUT_BANDBOXING = 2;
73+
74+var inputState = INPUT_NORMAL;
75+
76+var mouseX = 0;
77+var mouseY = 0;
78+var mouseIsOverObject = false;
79+
80+// Number of pixels the mouse can move before the action is considered a drag
81+var maxDragDelta = 4;
82+
83+// Time in milliseconds in which a double click is recognized
84+const doubleClickTime = 500;
85+var doubleClickTimer = 0;
86+var doubleClicked = false;
87+// Store the previously clicked entity - ensure a double/triple click happens on the same entity
88+var prevClickedEntity = 0;
89+
90+// Same double-click behaviour for hotkey presses
91+const doublePressTime = 500;
92+var doublePressTimer = 0;
93+var prevHotkey = 0;
94+
95+
96+var dragStart; // used for remembering mouse coordinates at start of drag operations
97+
98+// Limits bandboxed selections to certain types of entities based on priority
99+function getPreferredEntities(ents)
100+{
101+ var entStateList = [];
102+ var preferredEnts = [];
103+
104+ // Check if there are units in the selection and get a list of entity states
105+ for each (var ent in ents)
106+ {
107+ var entState = GetEntityState(ent);
108+ if (!entState)
109+ continue;
110+ if (hasClass(entState, "Unit"))
111+ preferredEnts.push(ent);
112+
113+ entStateList.push(entState);
114+ }
115+
116+ // If there are no units, check if there are defensive entities in the selection
117+ if (!preferredEnts.length)
118+ for (var i = 0; i < ents.length; i++)
119+ if (hasClass(entStateList[i], "Defensive"))
120+ preferredEnts.push(ents[i]);
121+
122+ return preferredEnts;
123+}
124+
125+// Removes any support units from the passed list of entities
126+function getMilitaryEntities(ents)
127+{
128+ var militaryEnts = [];
129+ for each (var ent in ents)
130+ {
131+ var entState = GetEntityState(ent);
132+ if (!hasClass(entState, "Support"))
133+ militaryEnts.push(ent);
134+ }
135+ return militaryEnts;
136+}
137+
138+function handleInputBeforeGui(ev, hoveredObject)
139+{
140+ // Capture mouse position so we can use it for displaying cursors,
141+ // and key states
142+ switch (ev.type)
143+ {
144+ case "mousebuttonup":
145+ case "mousebuttondown":
146+ case "mousemotion":
147+ mouseX = ev.x;
148+ mouseY = ev.y;
149+ break;
150+ }
151+
152+ // Remember whether the mouse is over a GUI object or not
153+ mouseIsOverObject = (hoveredObject != null);
154+
155+ // Close the menu when interacting with the game world
156+ if (!mouseIsOverObject && (ev.type =="mousebuttonup" || ev.type == "mousebuttondown")
157+ && (ev.button == SDL_BUTTON_LEFT || ev.button == SDL_BUTTON_RIGHT))
158+ closeMenu();
159+
160+ // State-machine processing:
161+ //
162+ // (This is for states which should override the normal GUI processing - events will
163+ // be processed here before being passed on, and propagation will stop if this function
164+ // returns true)
165+ //
166+ // TODO: it'd probably be nice to have a better state-machine system, with guaranteed
167+ // entry/exit functions, since this is a bit broken now
168+
169+ switch (inputState)
170+ {
171+ case INPUT_BANDBOXING:
172+ switch (ev.type)
173+ {
174+ case "mousemotion":
175+ var x0 = dragStart[0];
176+ var y0 = dragStart[1];
177+ var x1 = ev.x;
178+ var y1 = ev.y;
179+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
180+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
181+
182+ var bandbox = getGUIObjectByName("bandbox");
183+ bandbox.size = [x0, y0, x1, y1].join(" ");
184+ bandbox.hidden = false;
185+
186+ // TODO: Should we handle "control all units" here as well?
187+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
188+ g_Selection.setHighlightList(ents);
189+
190+ return false;
191+
192+ case "mousebuttonup":
193+ if (ev.button == SDL_BUTTON_LEFT)
194+ {
195+ var x0 = dragStart[0];
196+ var y0 = dragStart[1];
197+ var x1 = ev.x;
198+ var y1 = ev.y;
199+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
200+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
201+
202+ var bandbox = getGUIObjectByName("bandbox");
203+ bandbox.hidden = true;
204+
205+ // Get list of entities limited to preferred entities
206+ // TODO: Should we handle "control all units" here as well?
207+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
208+ var preferredEntities = getPreferredEntities(ents)
209+
210+ if (preferredEntities.length)
211+ {
212+ ents = preferredEntities;
213+
214+ if (Engine.HotkeyIsPressed("selection.milonly"))
215+ {
216+ var militaryEntities = getMilitaryEntities(ents);
217+ if (militaryEntities.length)
218+ ents = militaryEntities;
219+ }
220+ }
221+
222+ // Remove the bandbox hover highlighting
223+ g_Selection.setHighlightList([]);
224+
225+ // Update the list of selected units
226+ if (Engine.HotkeyIsPressed("selection.add"))
227+ {
228+ g_Selection.addList(ents);
229+ }
230+ else if (Engine.HotkeyIsPressed("selection.remove"))
231+ {
232+ g_Selection.removeList(ents);
233+ }
234+ else
235+ {
236+ g_Selection.reset();
237+ g_Selection.addList(ents);
238+ }
239+
240+ inputState = INPUT_NORMAL;
241+ return true;
242+ }
243+ else if (ev.button == SDL_BUTTON_RIGHT)
244+ {
245+ // Cancel selection
246+ var bandbox = getGUIObjectByName("bandbox");
247+ bandbox.hidden = true;
248+
249+ g_Selection.setHighlightList([]);
250+
251+ inputState = INPUT_NORMAL;
252+ return true;
253+ }
254+ break;
255+ }
256+ break;
257+ }
258+
259+ return false;
260+}
261+
262+function handleInputAfterGui(ev)
263+{
264+ if (ev.hotkey == "session.showstatusbars")
265+ {
266+ g_ShowAllStatusBars = (ev.type == "hotkeydown");
267+ recalculateStatusBarDisplay();
268+ }
269+
270+ // State-machine processing:
271+
272+ switch (inputState)
273+ {
274+ case INPUT_NORMAL:
275+ switch (ev.type)
276+ {
277+ case "mousemotion":
278+ // Highlight the first hovered entity (if any)
279+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
280+ if (ents.length)
281+ g_Selection.setHighlightList([ents[0]]);
282+ else
283+ g_Selection.setHighlightList([]);
284+
285+ return false;
286+
287+ case "mousebuttondown":
288+ if (ev.button == SDL_BUTTON_LEFT)
289+ {
290+ dragStart = [ ev.x, ev.y ];
291+ inputState = INPUT_SELECTING;
292+ return true;
293+ }
294+ break;
295+ }
296+ break;
297+
298+ case INPUT_SELECTING:
299+ switch (ev.type)
300+ {
301+ case "mousemotion":
302+ // If the mouse moved further than a limit, switch to bandbox mode
303+ var dragDeltaX = ev.x - dragStart[0];
304+ var dragDeltaY = ev.y - dragStart[1];
305+
306+ if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
307+ {
308+ inputState = INPUT_BANDBOXING;
309+ return false;
310+ }
311+
312+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
313+ g_Selection.setHighlightList(ents);
314+ return false;
315+
316+ case "mousebuttonup":
317+ if (ev.button == SDL_BUTTON_LEFT)
318+ {
319+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
320+ if (!ents.length)
321+ {
322+ if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove"))
323+ {
324+ g_Selection.reset();
325+ resetIdleUnit();
326+ }
327+ inputState = INPUT_NORMAL;
328+ return true;
329+ }
330+
331+ var selectedEntity = ents[0];
332+ var now = new Date();
333+
334+ // If camera following and we select different unit, stop
335+ if (Engine.GetFollowedEntity() != selectedEntity)
336+ {
337+ Engine.CameraFollow(0);
338+ }
339+
340+ if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity))
341+ {
342+ // Double click or triple click has occurred
343+ var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen");
344+ var matchRank = true;
345+ var templateToMatch;
346+
347+ // Check for double click or triple click
348+ if (!doubleClicked)
349+ {
350+ // If double click hasn't already occurred, this is a double click.
351+ // Select similar units regardless of rank
352+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).identity.selectionGroupName;
353+ if (templateToMatch)
354+ {
355+ matchRank = false;
356+ }
357+ else
358+ { // No selection group name defined, so fall back to exact match
359+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
360+ }
361+
362+ doubleClicked = true;
363+ // Reset the timer so the user has an extra period 'doubleClickTimer' to do a triple-click
364+ doubleClickTimer = now.getTime();
365+ }
366+ else
367+ {
368+ // Double click has already occurred, so this is a triple click.
369+ // Select units matching exact template name (same rank)
370+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
371+ }
372+
373+ // TODO: Should we handle "control all units" here as well?
374+ ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
375+ }
376+ else
377+ {
378+ // It's single click right now but it may become double or triple click
379+ doubleClicked = false;
380+ doubleClickTimer = now.getTime();
381+ prevClickedEntity = selectedEntity;
382+
383+ // We only want to include the first picked unit in the selection
384+ ents = [ents[0]];
385+ }
386+
387+ // Update the list of selected units
388+ if (Engine.HotkeyIsPressed("selection.add"))
389+ {
390+ g_Selection.addList(ents);
391+ }
392+ else if (Engine.HotkeyIsPressed("selection.remove"))
393+ {
394+ g_Selection.removeList(ents);
395+ }
396+ else
397+ {
398+ g_Selection.reset();
399+ g_Selection.addList(ents);
400+ }
401+
402+ inputState = INPUT_NORMAL;
403+ return true;
404+ }
405+ break;
406+ }
407+ break;
408+ }
409+ return false;
410+}
411+
412+// Called by unit selection buttons
413+function changePrimarySelectionGroup(templateName)
414+{
415+ if (Engine.HotkeyIsPressed("session.deselectgroup"))
416+ g_Selection.makePrimarySelection(templateName, true);
417+ else
418+ g_Selection.makePrimarySelection(templateName, false);
419+}
420+
421+
422+// Set the camera to follow the given unit
423+function setCameraFollow(entity)
424+{
425+ // Follow the given entity if it's a unit
426+ if (entity)
427+ {
428+ var entState = GetEntityState(entity);
429+ if (entState && hasClass(entState, "Unit"))
430+ {
431+ Engine.CameraFollow(entity);
432+ return;
433+ }
434+ }
435+
436+ // Otherwise stop following
437+ Engine.CameraFollow(0);
438+}
439+
440+var lastIdleUnit = 0;
441+var currIdleClass = 0;
442+var lastIdleType = undefined;
443+
444+function resetIdleUnit()
445+{
446+ lastIdleUnit = 0;
447+ currIdleClass = 0;
448+ lastIdleType = undefined;
449+}
450+
451+function findIdleUnit(classes)
452+{
453+ var append = Engine.HotkeyIsPressed("selection.add");
454+ var selectall = Engine.HotkeyIsPressed("selection.offscreen");
455+
456+ // Reset the last idle unit, etc., if the selection type has changed.
457+ var type = classes.join();
458+ if (selectall || type != lastIdleType)
459+ resetIdleUnit();
460+ lastIdleType = type;
461+
462+ // If selectall is true, there is no limit and it's necessary to iterate
463+ // over all of the classes, resetting only when the first match is found.
464+ var matched = false;
465+
466+ for (var i = 0; i < classes.length; ++i)
467+ {
468+ var data = { idleClass: classes[currIdleClass], prevUnit: lastIdleUnit, limit: 1 };
469+ if (append)
470+ data.excludeUnits = g_Selection.toList();
471+
472+ if (selectall)
473+ data = { idleClass: classes[currIdleClass] };
474+
475+ // Check if we have new valid entity
476+ var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
477+ if (idleUnits.length && idleUnits[0] != lastIdleUnit)
478+ {
479+ lastIdleUnit = idleUnits[0];
480+ if (!append && (!selectall || selectall && !matched))
481+ g_Selection.reset()
482+
483+ if (selectall)
484+ g_Selection.addList(idleUnits);
485+ else
486+ {
487+ g_Selection.addList([lastIdleUnit]);
488+ Engine.CameraFollow(lastIdleUnit);
489+ return;
490+ }
491+
492+ matched = true;
493+ }
494+
495+ lastIdleUnit = 0;
496+ currIdleClass = (currIdleClass + 1) % classes.length;
497+ }
498+
499+ // TODO: display a message or play a sound to indicate no more idle units, or something
500+ // Reset for next cycle
501+ resetIdleUnit();
502+}
503+
504+// Ignored: we ignore these callbacks in replay mode
505+function removeFromProductionQueue() {};
506+function unloadTemplate() {};
507+function unloadAll() {};
508Index: binaries/data/mods/public/gui/replay/input.js
509
510===================================================================
511
512--- binaries/data/mods/public/gui/replay/input.js (revision 0)
513
514+++ binaries/data/mods/public/gui/replay/input.js (working copy)
515
516
517
518Property changes on: binaries/data/mods/public/gui/replay/input.js
519
520___________________________________________________________________
521
522Added: svn:eol-style
523
524## -0,0 +1 ##
525
526+native
527
528\ No newline at end of property
529
530Index: binaries/data/mods/public/gui/replay/menu.js
531
532===================================================================
533
534--- binaries/data/mods/public/gui/replay/menu.js (revision 0)
535
536+++ binaries/data/mods/public/gui/replay/menu.js (working copy)
537
538@@ -0,0 +1,211 @@
539
540+const PAUSE = "Pause";
541+const RESUME = "Resume";
542+
543+/*
544+ * MENU POSITION CONSTANTS
545+*/
546+
547+// Menu / panel border size
548+const MARGIN = 4;
549+
550+// Includes the main menu button
551+const NUM_BUTTONS = 4;
552+
553+// Regular menu buttons
554+const BUTTON_HEIGHT = 32;
555+
556+// The position where the bottom of the menu will end up (currently 228)
557+const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN;
558+
559+// Menu starting position: bottom
560+const MENU_BOTTOM = 0;
561+
562+// Menu starting position: top
563+const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
564+
565+// Menu starting position: overall
566+const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
567+
568+// Number of pixels per millisecond to move
569+const MENU_SPEED = 1.2;
570+
571+var isMenuOpen = false;
572+var menu;
573+
574+// Ignore size defined in XML and set the actual menu size here
575+function initMenuPosition()
576+{
577+ menu = getGUIObjectByName("menu");
578+ menu.size = INITIAL_MENU_POSITION;
579+}
580+
581+
582+// =============================================================================
583+// Overall Menu
584+// =============================================================================
585+//
586+// Slide menu
587+function updateMenuPosition(dt)
588+{
589+ if (isMenuOpen)
590+ {
591+ var maxOffset = END_MENU_POSITION - menu.size.bottom;
592+ if (maxOffset > 0)
593+ {
594+ var offset = Math.min(MENU_SPEED * dt, maxOffset);
595+ var size = menu.size;
596+ size.top += offset;
597+ size.bottom += offset;
598+ menu.size = size;
599+ }
600+ }
601+ else
602+ {
603+ var maxOffset = menu.size.top - MENU_TOP;
604+ if (maxOffset > 0)
605+ {
606+ var offset = Math.min(MENU_SPEED * dt, maxOffset);
607+ var size = menu.size;
608+ size.top -= offset;
609+ size.bottom -= offset;
610+ menu.size = size;
611+ }
612+ }
613+}
614+
615+// Opens the menu by revealing the screen which contains the menu
616+function openMenu()
617+{
618+// playButtonSound();
619+ isMenuOpen = true;
620+}
621+
622+// Closes the menu and resets position
623+function closeMenu()
624+{
625+// playButtonSound();
626+ isMenuOpen = false;
627+}
628+
629+function toggleMenu()
630+{
631+ if (isMenuOpen == true)
632+ closeMenu();
633+ else
634+ openMenu();
635+}
636+
637+// Menu buttons
638+// =============================================================================
639+function settingsMenuButton()
640+{
641+ closeMenu();
642+ closeOpenDialogs();
643+ openSettings(true);
644+}
645+
646+function pauseMenuButton()
647+{
648+ togglePause();
649+}
650+
651+function resignMenuButton()
652+{
653+ closeMenu();
654+ closeOpenDialogs();
655+ pauseGame();
656+ var btCaptions = ["Yes", "No"];
657+ var btCode = [resignGame, resumeGame];
658+ messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode);
659+}
660+
661+function exitMenuButton()
662+{
663+ closeMenu();
664+ closeOpenDialogs();
665+ pauseGame();
666+ var btCaptions = ["Yes", "No"];
667+ var btCode = [leaveGame, resumeGame];
668+ messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode);
669+}
670+
671+function openDeleteDialog(selection)
672+{
673+ closeMenu();
674+ closeOpenDialogs();
675+
676+ var deleteSelectedEntities = function ()
677+ {
678+ Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection});
679+ };
680+
681+ var btCaptions = ["Yes", "No"];
682+ var btCode = [deleteSelectedEntities, resumeGame];
683+
684+ messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode);
685+}
686+
687+// Menu functions
688+// =============================================================================
689+
690+function openSettings(pause)
691+{
692+ getGUIObjectByName("settingsDialogPanel").hidden = false;
693+ if (pause)
694+ pauseGame();
695+}
696+
697+function closeSettings(resume)
698+{
699+ getGUIObjectByName("settingsDialogPanel").hidden = true;
700+ if (resume)
701+ resumeGame();
702+}
703+
704+function pauseGame()
705+{
706+ getGUIObjectByName("pauseButtonText").caption = RESUME;
707+ getGUIObjectByName("pauseOverlay").hidden = false;
708+ setPaused(true);
709+}
710+
711+function resumeGame()
712+{
713+ getGUIObjectByName("pauseButtonText").caption = PAUSE;
714+ getGUIObjectByName("pauseOverlay").hidden = true;
715+ setPaused(false);
716+}
717+
718+function togglePause()
719+{
720+ closeMenu();
721+ closeOpenDialogs();
722+
723+ var pauseOverlay = getGUIObjectByName("pauseOverlay");
724+
725+ if (pauseOverlay.hidden)
726+ {
727+ getGUIObjectByName("pauseButtonText").caption = RESUME;
728+ setPaused(true);
729+
730+ }
731+ else
732+ {
733+ setPaused(false);
734+ getGUIObjectByName("pauseButtonText").caption = PAUSE;
735+ }
736+
737+ pauseOverlay.hidden = !pauseOverlay.hidden;
738+}
739+
740+function toggleDeveloperOverlay()
741+{
742+ var devCommands = getGUIObjectByName("devCommands");
743+ devCommands.hidden = !devCommands.hidden;
744+}
745+
746+function closeOpenDialogs()
747+{
748+ closeMenu();
749+ closeSettings(false);
750+}
751Index: binaries/data/mods/public/gui/replay/menu.js
752
753===================================================================
754
755--- binaries/data/mods/public/gui/replay/menu.js (revision 0)
756
757+++ binaries/data/mods/public/gui/replay/menu.js (working copy)
758
759
760
761Property changes on: binaries/data/mods/public/gui/replay/menu.js
762
763___________________________________________________________________
764
765Added: svn:eol-style
766
767## -0,0 +1 ##
768
769+native
770
771\ No newline at end of property
772
773Index: binaries/data/mods/public/gui/replay/replay.js
774
775===================================================================
776
777--- binaries/data/mods/public/gui/replay/replay.js (revision 0)
778
779+++ binaries/data/mods/public/gui/replay/replay.js (working copy)
780
781@@ -0,0 +1,429 @@
782
783+// Network Mode
784+var g_IsNetworked = false;
785+
786+// Cache the basic player data (name, civ, color)
787+var g_Players = [];
788+// Cache the useful civ data
789+var g_CivData = {};
790+
791+var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
792+
793+// Cache dev-mode settings that are frequently or widely used
794+var g_DevSettings = {
795+ controlAll: false
796+};
797+
798+// Whether status bars should be shown for all of the player's units.
799+var g_ShowAllStatusBars = false;
800+
801+// Indicate when one of the current player's training queues is blocked
802+// (this is used to support population counter blinking)
803+var g_IsTrainingBlocked = false;
804+
805+// Cache EntityStates
806+var g_EntityStates = {}; // {id:entState}
807+
808+// Whether the player has lost/won and reached the end of their game
809+var g_GameEnded = false;
810+
811+// Colors to flash when pop limit reached
812+const DEFAULT_POPULATION_COLOR = "white";
813+const POPULATION_ALERT_COLOR = "orange";
814+
815+function GetEntityState(entId)
816+{
817+ if (!(entId in g_EntityStates))
818+ {
819+ var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
820+ g_EntityStates[entId] = entState;
821+ }
822+
823+ return g_EntityStates[entId];
824+}
825+
826+// Cache TemplateData
827+var g_TemplateData = {}; // {id:template}
828+
829+
830+function GetTemplateData(templateName)
831+{
832+ if (!(templateName in g_TemplateData))
833+ {
834+ var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
835+ g_TemplateData[templateName] = template;
836+ }
837+
838+ return g_TemplateData[templateName];
839+}
840+
841+// Cache TechnologyData
842+var g_TechnologyData = {}; // {id:template}
843+
844+function GetTechnologyData(technologyName)
845+{
846+ if (!(technologyName in g_TechnologyData))
847+ {
848+ var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
849+ g_TechnologyData[technologyName] = template;
850+ }
851+
852+ return g_TechnologyData[technologyName];
853+}
854+
855+// Init
856+function init(initData, hotloadData)
857+{
858+ if (initData)
859+ {
860+ g_IsNetworked = initData.isNetworked; // Set network mode
861+ g_PlayerAssignments = initData.playerAssignments;
862+
863+ // Cache the player data
864+ // (This may be updated at runtime by handleNetMessage)
865+ g_Players = getPlayerData(g_PlayerAssignments);
866+ }
867+ else // Needed for autostart loading option
868+ {
869+ g_Players = getPlayerData(null);
870+ }
871+
872+ // Cache civ data
873+ g_CivData = loadCivData();
874+ g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia", "Emblem": "session/icons/groups.png" };
875+
876+ initMenuPosition(); // set initial position
877+
878+ // Populate player selection dropdown
879+ var playerNames = [];
880+ var playerIDs = [];
881+ for (var player in g_Players)
882+ {
883+ playerNames.push(g_Players[player].name);
884+ playerIDs.push(player);
885+ }
886+
887+ var viewPlayerDropdown = getGUIObjectByName("viewPlayer");
888+ viewPlayerDropdown.list = playerNames;
889+ viewPlayerDropdown.list_data = playerIDs;
890+ viewPlayerDropdown.selected = 1;
891+
892+ // If in Atlas editor, disable the exit button
893+ if (Engine.IsAtlasRunning())
894+ getGUIObjectByName("menuExitButton").enabled = false;
895+
896+ if (hotloadData)
897+ {
898+ g_Selection.selected = hotloadData.selection;
899+ }
900+
901+ onSimulationUpdate();
902+
903+ // Report the performance after 5 seconds (when we're still near
904+ // the initial camera view) and a minute (when the profiler will
905+ // have settled down if framerates as very low), to give some
906+ // extremely rough indications of performance
907+ setTimeout(function() { reportPerformance(5); }, 5000);
908+ setTimeout(function() { reportPerformance(60); }, 60000);
909+}
910+
911+function selectViewPlayer(playerID)
912+{
913+ Engine.SetPlayerID(playerID);
914+ getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
915+ getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name;
916+}
917+
918+function reportPerformance(time)
919+{
920+ var settings = Engine.GetMapSettings();
921+ var data = {
922+ time: time,
923+ map: settings.Name,
924+ seed: settings.Seed, // only defined for random maps
925+ size: settings.Size, // only defined for random maps
926+ profiler: Engine.GetProfilerState()
927+ };
928+
929+ Engine.SubmitUserReport("profile", 3, JSON.stringify(data));
930+}
931+
932+function leaveGame()
933+{
934+ var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
935+
936+ var mapSettings = Engine.GetMapSettings();
937+
938+ endGame();
939+
940+ Engine.SwitchGuiPage("page_summary.xml", {
941+ "gameResult" : "Replay mode ended",
942+ "timeElapsed" : extendedSimState.timeElapsed,
943+ "playerStates": extendedSimState.players,
944+ "players": g_Players,
945+ "mapSettings": mapSettings
946+ });
947+}
948+
949+// Return some data that we'll use when hotloading this file after changes
950+function getHotloadData()
951+{
952+ return { selection: g_Selection.selected };
953+}
954+
955+var lastTickTime = new Date;
956+
957+/**
958+ * Called every frame.
959+ */
960+function onTick()
961+{
962+ var now = new Date;
963+ var tickLength = new Date - lastTickTime;
964+ lastTickTime = now;
965+
966+ while (true)
967+ {
968+ var message = Engine.PollNetworkClient();
969+ if (!message)
970+ break;
971+ handleNetMessage(message);
972+ }
973+
974+ // If the selection changed, we need to regenerate the sim display (the display depends on both the
975+ // simulation state and the current selection).
976+ if (g_Selection.dirty)
977+ {
978+ onSimulationUpdate();
979+
980+ // Display rally points for selected buildings
981+ Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
982+ }
983+
984+ // Run timers
985+ updateTimers();
986+
987+ // Animate menu
988+ updateMenuPosition(tickLength);
989+
990+ // When training is blocked, flash population (alternates colour every 500msec)
991+ if (g_IsTrainingBlocked && (Date.now() % 1000) < 500)
992+ getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR;
993+ else
994+ getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR;
995+
996+ // Clear renamed entities list
997+ Engine.GuiInterfaceCall("ClearRenamedEntities");
998+}
999+
1000+function onReplayFinished()
1001+{
1002+ closeMenu();
1003+ closeOpenDialogs();
1004+ pauseGame();
1005+ var btCaptions = ["Yes", "No"];
1006+ var btCode = [leaveGame, resumeGame];
1007+ messageBox(400, 200, "The replay has finished. Do you want to quit?", "Confirmation", 0, btCaptions, btCode);
1008+}
1009+
1010+/**
1011+ * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
1012+ * update (see session.xml), or from onTick when the selection has changed.
1013+ */
1014+function onSimulationUpdate()
1015+{
1016+ g_Selection.dirty = false;
1017+ g_EntityStates = {};
1018+ g_TemplateData = {};
1019+ g_TechnologyData = {};
1020+
1021+ var simState = Engine.GuiInterfaceCall("GetSimulationState");
1022+
1023+ // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing
1024+ if (!simState)
1025+ return;
1026+
1027+ handleNotifications();
1028+
1029+ if (g_ShowAllStatusBars)
1030+ recalculateStatusBarDisplay();
1031+
1032+ updateDebug(simState);
1033+ updatePlayerDisplay(simState);
1034+ updateSelectionDetails(true);
1035+ updateResearchDisplay();
1036+ updateTimeElapsedCounter(simState);
1037+}
1038+
1039+function updateDebug(simState)
1040+{
1041+ var debug = getGUIObjectByName("debug");
1042+
1043+ if (getGUIObjectByName("devDisplayState").checked)
1044+ {
1045+ debug.hidden = false;
1046+ }
1047+ else
1048+ {
1049+ debug.hidden = true;
1050+ return;
1051+ }
1052+
1053+ var conciseSimState = deepcopy(simState);
1054+ conciseSimState.players = "<<<omitted>>>";
1055+ var text = "simulation: " + uneval(conciseSimState);
1056+
1057+ var selection = g_Selection.toList();
1058+ if (selection.length)
1059+ {
1060+ var entState = GetEntityState(selection[0]);
1061+ if (entState)
1062+ {
1063+ var template = GetTemplateData(entState.template);
1064+ text += "\n\nentity: {\n";
1065+ for (var k in entState)
1066+ text += " "+k+":"+uneval(entState[k])+"\n";
1067+ text += "}\n\ntemplate: " + uneval(template);
1068+ }
1069+ }
1070+
1071+ debug.caption = text;
1072+}
1073+
1074+function updatePlayerDisplay(simState)
1075+{
1076+ var playerState = simState.players[Engine.GetPlayerID()];
1077+ if (!playerState)
1078+ return;
1079+
1080+ getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food;
1081+ getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood;
1082+ getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone;
1083+ getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal;
1084+ getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
1085+
1086+ g_IsTrainingBlocked = playerState.trainingBlocked;
1087+}
1088+
1089+function selectAndMoveTo(ent)
1090+{
1091+ var entState = GetEntityState(ent);
1092+ if (!entState)
1093+ return;
1094+
1095+ g_Selection.reset();
1096+ g_Selection.addList([ent]);
1097+
1098+ var position = entState.position;
1099+ Engine.CameraMoveTo(position.x, position.z);
1100+}
1101+
1102+function updateResearchDisplay()
1103+{
1104+ var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID());
1105+ if (!researchStarted)
1106+ return;
1107+
1108+ // Set up initial positioning.
1109+ var buttonSideLength = getGUIObjectByName("researchStartedButton[0]").size.right;
1110+ for (var i = 0; i < 10; ++i)
1111+ {
1112+ var button = getGUIObjectByName("researchStartedButton[" + i + "]");
1113+ var size = button.size;
1114+ size.top = (4 + buttonSideLength) * i;
1115+ size.bottom = size.top + buttonSideLength;
1116+ button.size = size;
1117+ }
1118+
1119+ var numButtons = 0;
1120+ for (var tech in researchStarted)
1121+ {
1122+ // Show at most 10 in-progress techs.
1123+ if (numButtons >= 10)
1124+ break;
1125+
1126+ var template = GetTechnologyData(tech);
1127+ var button = getGUIObjectByName("researchStartedButton[" + numButtons + "]");
1128+ button.hidden = false;
1129+ button.tooltip = getEntityNames(template);
1130+ button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher);
1131+
1132+ var icon = "stretched:session/portraits/" + template.icon;
1133+ getGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon;
1134+
1135+ // Scale the progress indicator.
1136+ var size = getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size;
1137+
1138+ // Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
1139+ size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left));
1140+ getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size;
1141+
1142+ ++numButtons;
1143+ }
1144+
1145+ // Hide unused buttons.
1146+ for (var i = numButtons; i < 10; ++i)
1147+ getGUIObjectByName("researchStartedButton[" + i + "]").hidden = true;
1148+}
1149+
1150+function updateTimeElapsedCounter(simState)
1151+{
1152+ var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
1153+ timeElapsedCounter.caption = timeToString(simState.timeElapsed);
1154+}
1155+
1156+// Toggles the display of status bars for all of the player's entities.
1157+function recalculateStatusBarDisplay()
1158+{
1159+ if (g_ShowAllStatusBars)
1160+ var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID());
1161+ else
1162+ {
1163+ var selected = g_Selection.toList();
1164+ for each (var ent in g_Selection.highlighted)
1165+ selected.push(ent);
1166+
1167+ // Remove selected entities from the 'all entities' array, to avoid disabling their status bars.
1168+ var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter(
1169+ function(idx) { return (selected.indexOf(idx) == -1); }
1170+ );
1171+ }
1172+
1173+ Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars });
1174+}
1175+
1176+// Temporarily adding this here
1177+const AMBIENT_TEMPERATE = "temperate";
1178+var currentAmbient;
1179+function playRandomAmbient(type)
1180+{
1181+ switch (type)
1182+ {
1183+ case AMBIENT_TEMPERATE:
1184+ // Seem to need the underscore at the end of "temperate" to avoid crash
1185+ // (Might be caused by trying to randomly load day_temperate.xml)
1186+// currentAmbient = newRandomSound("ambient", "temperate_", "dayscape");
1187+
1188+ const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
1189+ currentAmbient = new AmbientSound(AMBIENT);
1190+
1191+ if (currentAmbient)
1192+ {
1193+ currentAmbient.loop();
1194+ }
1195+ break;
1196+
1197+ default:
1198+ console.write("Unrecognized ambient type: " + type);
1199+ break;
1200+ }
1201+}
1202+
1203+// Temporarily adding this here
1204+function stopAmbient()
1205+{
1206+ if (currentAmbient)
1207+ {
1208+ currentAmbient.free();
1209+ currentAmbient = null;
1210+ }
1211+}
1212Index: binaries/data/mods/public/gui/replay/replay.js
1213
1214===================================================================
1215
1216--- binaries/data/mods/public/gui/replay/replay.js (revision 0)
1217
1218+++ binaries/data/mods/public/gui/replay/replay.js (working copy)
1219
1220
1221
1222Property changes on: binaries/data/mods/public/gui/replay/replay.js
1223
1224___________________________________________________________________
1225
1226Added: svn:eol-style
1227
1228## -0,0 +1 ##
1229
1230+native
1231
1232\ No newline at end of property
1233
1234Index: binaries/data/mods/public/gui/replay/replay.xml
1235
1236===================================================================
1237
1238--- binaries/data/mods/public/gui/replay/replay.xml (revision 0)
1239
1240+++ binaries/data/mods/public/gui/replay/replay.xml (working copy)
1241
1242@@ -0,0 +1,849 @@
1243
1244+<?xml version="1.0" encoding="utf-8"?>
1245
1246+
1247
1248+<objects>
1249
1250+
1251
1252+ <script file="gui/common/functions_civinfo.js"/>
1253
1254+ <script file="gui/common/functions_utility.js" />
1255
1256+ <script file="gui/common/functions_global_object.js" />
1257
1258+ <script file="gui/common/timer.js"/>
1259
1260+ <script file="gui/replay/input.js"/>
1261
1262+ <script file="gui/replay/menu.js"/>
1263
1264+ <script file="gui/replay/replay.js"/>
1265
1266+ <script file="gui/session/messages.js"/>
1267
1268+ <script file="gui/session/selection.js"/>
1269
1270+ <script file="gui/session/selection_details.js"/>
1271
1272+ <script file="gui/session/unit_commands.js"/>
1273
1274+ <script file="gui/session/utility_functions.js"/>
1275
1276+
1277
1278+ <object name="sn" hotkey="session.gui.toggle">
1279
1280+ <action on="Tick">
1281
1282+ onTick();
1283
1284+ </action>
1285
1286+
1287
1288+ <action on="SimulationUpdate">
1289
1290+ onSimulationUpdate();
1291
1292+ </action>
1293
1294+
1295
1296+ <action on="ReplayFinished">
1297
1298+ onReplayFinished();
1299
1300+ </action>
1301
1302+
1303
1304+ <action on="Press">
1305
1306+ this.hidden = !this.hidden;
1307
1308+ </action>
1309
1310+
1311
1312+ <!-- ================================ ================================ -->
1313
1314+ <!-- HOTKEYS (For some reason, they won't work properly unless outside menu) -->
1315
1316+ <!-- ================================ ================================ -->
1317
1318+
1319
1320+ <!-- Exit button Hotkey -->
1321
1322+ <!--
1323
1324+ <action on="Press"><![CDATA[
1325
1326+ messageBox(400, 200, "Do you really want to quit?", "Confirmation", 0,
1327
1328+ ["Yes", "No!"], [leaveGame, null]);
1329
1330+ ]]></action>
1331
1332+ -->
1333
1334+
1335
1336+ <object hotkey="leave">
1337
1338+ <action on="Press">closeOpenDialogs();</action>
1339
1340+ </object>
1341
1342+
1343
1344+ <!-- Menu -->
1345
1346+ <object hotkey="menu.toggle">
1347
1348+ <action on="Press">openMenu();</action>
1349
1350+ </object>
1351
1352+
1353
1354+ <!-- Unit silhouettes -->
1355
1356+ <object hotkey="silhouettes">
1357
1358+ <action on="Press">renderer.silhouettes = !renderer.silhouettes;</action>
1359
1360+ </object>
1361
1362+
1363
1364+ <!-- Sky -->
1365
1366+ <object hotkey="showsky">
1367
1368+ <action on="Press">renderer.showsky = !renderer.showsky;</action>
1369
1370+ </object>
1371
1372+
1373
1374+ <!-- Pause -->
1375
1376+ <object hotkey="pause">
1377
1378+ <action on="Press">togglePause();</action>
1379
1380+ </object>
1381
1382+
1383
1384+ <!-- camera.follow mode - follow the first unit in the selection -->
1385
1386+ <object hotkey="camera.follow">
1387
1388+ <action on="Press">setCameraFollow(g_Selection.toList()[0]);</action>
1389
1390+ </object>
1391
1392+
1393
1394+ <!-- Find idle warrior - TODO: Potentially move this to own UI button? -->
1395
1396+ <object hotkey="selection.idlewarrior">
1397
1398+ <action on="Press">findIdleUnit(["Hero", "Champion", "CitizenSoldier", "Siege", "Warship"]);</action>
1399
1400+ </object>
1401
1402+
1403
1404+ <!-- ================================ ================================ -->
1405
1406+ <!-- Developer / Debug items -->
1407
1408+ <!-- ================================ ================================ -->
1409
1410+
1411
1412+ <!-- Debug text -->
1413
1414+ <object name="debug"
1415
1416+ type="text"
1417
1418+ size="0 70 80% 100%"
1419
1420+ ghost="true"
1421
1422+ textcolor="yellow"
1423
1424+ font="mono-stroke-10"
1425
1426+ />
1427
1428+
1429
1430+ <!-- Dev/cheat commands -->
1431
1432+ <object name="devCommands" size="100%-156 50%-88 100%-8 50%+88" type="image" sprite="devCommandsBackground" z="40"
1433
1434+ hidden="true" hotkey="session.devcommands.toggle">
1435
1436+ <action on="Press">
1437
1438+ toggleDeveloperOverlay();
1439
1440+ </action>
1441
1442+
1443
1444+ <object size="0 16 100%-18 32" type="text" style="devCommandsText">Display selection state</object>
1445
1446+ <object size="100%-16 16 100% 32" type="checkbox" name="devDisplayState" style="StoneCrossBox"/>
1447
1448+
1449
1450+ <object size="0 32 100%-18 48" type="text" style="devCommandsText">Pathfinder overlay</object>
1451
1452+ <object size="100%-16 32 100% 48" type="checkbox" style="StoneCrossBox">
1453
1454+ <action on="Press">Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", this.checked);</action>
1455
1456+ </object>
1457
1458+
1459
1460+ <object size="0 48 100%-18 64" type="text" style="devCommandsText">Obstruction overlay</object>
1461
1462+ <object size="100%-16 48 100% 64" type="checkbox" style="StoneCrossBox">
1463
1464+ <action on="Press">Engine.GuiInterfaceCall("SetObstructionDebugOverlay", this.checked);</action>
1465
1466+ </object>
1467
1468+
1469
1470+ <object size="0 64 100%-18 80" type="text" style="devCommandsText">Unit motion overlay</object>
1471
1472+ <object size="100%-16 64 100% 80" type="checkbox" style="StoneCrossBox">
1473
1474+ <action on="Press">g_Selection.SetMotionDebugOverlay(this.checked);</action>
1475
1476+ </object>
1477
1478+
1479
1480+ <object size="0 80 100%-18 96" type="text" style="devCommandsText">Range overlay</object>
1481
1482+ <object size="100%-16 80 100% 96" type="checkbox" style="StoneCrossBox">
1483
1484+ <action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
1485
1486+ </object>
1487
1488+
1489
1490+ <object size="0 96 100%-18 112" type="text" style="devCommandsText">Bounding box overlay</object>
1491
1492+ <object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
1493
1494+ <action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
1495
1496+ </object>
1497
1498+
1499
1500+ <object size="0 112 100%-18 128" type="text" style="devCommandsText">Restrict camera</object>
1501
1502+ <object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox" checked="true">
1503
1504+ <action on="Press">gameView.constrainCamera = this.checked;</action>
1505
1506+ </object>
1507
1508+
1509
1510+ <object size="0 128 100%-18 144" type="text" style="devCommandsText">Reveal map</object>
1511
1512+ <object size="100%-16 128 100% 144" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
1513
1514+ <action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
1515
1516+ </object>
1517
1518+
1519
1520+ </object>
1521
1522+
1523
1524+ <!-- ================================ ================================ -->
1525
1526+ <!-- Time elapsed counter -->
1527
1528+ <!-- ================================ ================================ -->
1529
1530+ <object size="100%-100 50 100%-10 70" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" ghost="true">
1531
1532+ <action on="Press"><![CDATA[
1533
1534+ this.hidden = !this.hidden;
1535
1536+ ]]>
1537
1538+ </action>
1539
1540+ </object>
1541
1542+
1543
1544+ <object size="50%-400 50 50%-50 100" type="text" font="serif-bold-24" textcolor="white" ghost="true">Non-interactive Replay Mode</object>
1545
1546+
1547
1548+ <!-- ================================ ================================ -->
1549
1550+ <!-- Pause Overlay -->
1551
1552+ <!-- ================================ ================================ -->
1553
1554+ <object type="button"
1555
1556+ name="pauseOverlay"
1557
1558+ size="0 0 100% 100%"
1559
1560+ tooltip_style="sessionToolTip"
1561
1562+ hidden="true"
1563
1564+ z="0"
1565
1566+ >
1567
1568+ <object size="0 0 100% 100%" type="image" sprite="devCommandsBackground" ghost="true" z="0"/>
1569
1570+ <object size="50%-128 50%-20 50%+128 50%+20" type="text" style="PauseText" ghost="true" z="0">Replay Paused</object>
1571
1572+ <object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">Click to Resume Replay</object>
1573
1574+ <action on="Press">togglePause();</action>
1575
1576+ </object>
1577
1578+
1579
1580+ <!-- ================================ ================================ -->
1581
1582+ <!-- Notification Area -->
1583
1584+ <!-- ================================ ================================ -->
1585
1586+ <object name="notificationPanel" type="image" size="50%-300 60 50%+300 120" ghost="true">
1587
1588+ <object name="notificationText" size="0 0 100% 100%" type="text" style="notificationPanel" ghost="true"/>
1589
1590+ </object>
1591
1592+
1593
1594+ <!-- ================================ ================================ -->
1595
1596+ <!-- Chat -->
1597
1598+ <!-- ================================ ================================ -->
1599
1600+
1601
1602+ <!-- Chat panel -->
1603
1604+ <object name="chatPanel" size="0 130 100% 100%-240" type="image" ghost="true">
1605
1606+ <object name="chatText" size="3 1 100%-1 100%-1" type="text" style="chatPanel" ghost="true"/>
1607
1608+ </object>
1609
1610+
1611
1612+ <!-- ================================ ================================ -->
1613
1614+ <!-- Settings Window -->
1615
1616+ <!-- ================================ ================================ -->
1617
1618+ <object name="settingsDialogPanel"
1619
1620+ style="StoneDialog"
1621
1622+ type="image"
1623
1624+ size="50%-180 50%-200 50%+180 50%+100"
1625
1626+ hidden="true"
1627
1628+ >
1629
1630+ <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Settings</object>
1631
1632+
1633
1634+ <object style="TranslucentPanelThinBorder"
1635
1636+ type="image"
1637
1638+ size="32 32 100%-32 100%-70"
1639
1640+ >
1641
1642+ <!-- Settings / shadows -->
1643
1644+ <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
1645
1646+ <object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
1647
1648+ <action on="Load">if (renderer.shadows) this.checked = true; else this.checked = false;</action>
1649
1650+ <action on="Press">renderer.shadows = this.checked;</action>
1651
1652+ </object>
1653
1654+
1655
1656+ <!-- Settings / Shadow PCF -->
1657
1658+ <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
1659
1660+ <object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
1661
1662+ <action on="Load">if (renderer.shadowPCF) this.checked = true; else this.checked = false;</action>
1663
1664+ <action on="Press">renderer.shadowPCF = this.checked;</action>
1665
1666+ </object>
1667
1668+
1669
1670+ <!-- Settings / Water -->
1671
1672+ <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Water Reflections</object>
1673
1674+ <object name="fancyWaterCheckbox" size="100%-56 65 100%-30 90" type="checkbox" style="StoneCrossBox" checked="true">
1675
1676+ <action on="Load">if (renderer.fancyWater) this.checked = true; else this.checked = false;</action>
1677
1678+ <action on="Press">renderer.fancyWater = this.checked;</action>
1679
1680+ </object>
1681
1682+
1683
1684+ <!-- Settings / Particles -->
1685
1686+ <object size="0 85 100%-80 110" type="text" style="RightLabelText" ghost="true">Enable Particles</object>
1687
1688+ <object name="particlesCheckbox" size="100%-56 90 100%-30 115" type="checkbox" style="StoneCrossBox" checked="true">
1689
1690+ <action on="Load">if (renderer.particles) this.checked = true; else this.checked = false;</action>
1691
1692+ <action on="Press">renderer.particles = this.checked;</action>
1693
1694+ </object>
1695
1696+
1697
1698+ <!-- Settings / Unit Silhouettes -->
1699
1700+ <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Enable Unit Silhouettes</object>
1701
1702+ <object name="silhouettesCheckbox" size="100%-56 115 100%-30 140" type="checkbox" style="StoneCrossBox" checked="true">
1703
1704+ <action on="Load">if (renderer.silhouettes) this.checked = true; else this.checked = false;</action>
1705
1706+ <action on="Press">renderer.silhouettes = this.checked;</action>
1707
1708+ </object>
1709
1710+
1711
1712+ <!-- Settings / Dev Overlay -->
1713
1714+ <object size="0 160 100%-80 185" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
1715
1716+ <object size="100%-56 165 100%-30 190" type="checkbox" style="StoneCrossBox" checked="false">
1717
1718+ <action on="Press">toggleDeveloperOverlay();</action>
1719
1720+ </object>
1721
1722+ </object>
1723
1724+
1725
1726+ <!-- Close button -->
1727
1728+ <object type="button"
1729
1730+ style="StoneButton"
1731
1732+ size="50%-64 100%-52 50%+64 100%-24"
1733
1734+ tooltip_style="sessionToolTip"
1735
1736+ >
1737
1738+ Close
1739
1740+ <action on="Press">closeSettings(true);</action>
1741
1742+ </object>
1743
1744+ </object>
1745
1746+
1747
1748+ <!-- ================================ ================================ -->
1749
1750+ <!-- Top Panel -->
1751
1752+ <!-- ================================ ================================ -->
1753
1754+ <object name="topPanel"
1755
1756+ type="image"
1757
1758+ sprite="topPanel"
1759
1760+ size="-3 0 100%+3 36"
1761
1762+ >
1763
1764+ <!-- ================================ ================================ -->
1765
1766+ <!-- Player resource bar -->
1767
1768+ <!-- ================================ ================================ -->
1769
1770+ <object
1771
1772+ size="10 0 45% 100%"
1773
1774+ >
1775
1776+ <!-- Food -->
1777
1778+ <object size="0 0 90 100%" type="image" style="resourceCounter" tooltip="Food" tooltip_style="sessionToolTipBold">
1779
1780+ <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
1781
1782+ <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/>
1783
1784+ </object>
1785
1786+
1787
1788+ <!-- Wood -->
1789
1790+ <object size="90 0 180 100%" type="image" style="resourceCounter" tooltip="Wood" tooltip_style="sessionToolTipBold">
1791
1792+ <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
1793
1794+ <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/>
1795
1796+ </object>
1797
1798+
1799
1800+ <!-- Stone -->
1801
1802+ <object size="180 0 270 100%" type="image" style="resourceCounter" tooltip="Stone" tooltip_style="sessionToolTipBold">
1803
1804+ <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
1805
1806+ <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/>
1807
1808+ </object>
1809
1810+
1811
1812+ <!-- Metal -->
1813
1814+ <object size="270 0 360 100%" type="image" style="resourceCounter" tooltip="Metal" tooltip_style="sessionToolTipBold">
1815
1816+ <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
1817
1818+ <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/>
1819
1820+ </object>
1821
1822+
1823
1824+ <!-- Population -->
1825
1826+ <object size="360 0 450 100%" type="image" style="resourceCounter" tooltip="Population (current / limit)" tooltip_style="sessionToolTipBold">
1827
1828+ <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
1829
1830+ <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
1831
1832+ </object>
1833
1834+ </object>
1835
1836+
1837
1838+ <!-- ================================ ================================ -->
1839
1840+ <!-- Civ Icon -->
1841
1842+ <!-- ================================ ================================ -->
1843
1844+ <object size="50%-48 -26 50%+48 70" name="civIcon" type="image" tooltip_style="sessionToolTipBold"/>
1845
1846+
1847
1848+ <object size="50%+50 5 50%+150 100%-5" name="viewPlayer" type="dropdown" style="StoneDropDown" tooltip_style="sessionToolTipBold" tooltip="Choose player to view">
1849
1850+ <action on="SelectionChange">selectViewPlayer(this.selected);</action>
1851
1852+ </object>
1853
1854+
1855
1856+ <!-- ================================ ================================ -->
1857
1858+ <!-- Phase -->
1859
1860+ <!-- ================================ ================================ -->
1861
1862+ <!--<object size="50%+50 4 50%+300 100%-2" name="PhaseTitleBar" type="text" font="serif-bold-stroke-14" textcolor="white"> Death Match :: Village Phase</object>-->
1863
1864+
1865
1866+
1867
1868+ <!-- ================================ ================================ -->
1869
1870+ <!-- ALPHA LABELS (alpha, build time, revision) -->
1871
1872+ <!-- ================================ ================================ -->
1873
1874+
1875
1876+ <!-- Displays Alpha name and number -->
1877
1878+ <object size="70%-50 0 70%+128 100%" name="alphaLabel" type="text" style="CenteredLabelText" text_valign="top" ghost="true">
1879
1880+ ALPHA XI : Kronos<!-- IMPORTANT: remember to update pregame/mainmenu.xml in sync with this -->
1881
1882+
1883
1884+ <!-- Displays build date and revision number-->
1885
1886+ <object size="50%-128 0 50%+128 100%-2" name="buildTimeLabel" type="text" style="BuildNameText" ghost="true">
1887
1888+ <action on="Load"><![CDATA[this.caption = buildTime(0) + " (" + buildTime(2) + ")";]]>
1889
1890+ </action>
1891
1892+ </object>
1893
1894+ </object>
1895
1896+
1897
1898+ <!-- ================================ ================================ -->
1899
1900+ <!-- Menu Button -->
1901
1902+ <!-- ================================ ================================ -->
1903
1904+ <object type="button"
1905
1906+ name="menuButton"
1907
1908+ size="100%-164 4 100%-8 32"
1909
1910+ style="StoneButtonFancy"
1911
1912+ tooltip_style="sessionToolTip"
1913
1914+ z="70"
1915
1916+ >
1917
1918+ <!-- This object covers up the text on the menu
1919
1920+ buttons as they slide by so that you don't see
1921
1922+ them on top of the main menu button -->
1923
1924+ <object size="0 -4 100% 0" type="image" sprite="horizontalThinBorder" ghost="true"/>
1925
1926+
1927
1928+ <object size="50%-32 50%-16 50%+32 50%+16" type="image" sprite="menuButton" ghost="true">MENU</object>
1929
1930+ <action on="Press">
1931
1932+ toggleMenu();
1933
1934+ </action>
1935
1936+ </object> <!-- END OF MENU BUTTON -->
1937
1938+ </object> <!-- END OF TOP PANEL -->
1939
1940+
1941
1942+ <!-- ================================ ================================ -->
1943
1944+ <!-- Menu -->
1945
1946+ <!-- ================================ ================================ -->
1947
1948+ <object name="menu"
1949
1950+ style="StonePanelThinBorder"
1951
1952+ type="image"
1953
1954+ hidden="false"
1955
1956+ z="40"
1957
1958+ >
1959
1960+ <object size="4 36 100%-4 50%+20">
1961
1962+
1963
1964+ <!-- Settings button -->
1965
1966+ <object type="button"
1967
1968+ name="settingsButton"
1969
1970+ style="StoneButtonFancy"
1971
1972+ size="0 0 100% 28"
1973
1974+ tooltip_style="sessionToolTip"
1975
1976+ >
1977
1978+ Settings
1979
1980+ <action on="Press">settingsMenuButton();</action>
1981
1982+ </object>
1983
1984+
1985
1986+ <!-- Exit button -->
1987
1988+ <object type="button"
1989
1990+ name="menuExitButton"
1991
1992+ style="StoneButtonFancy"
1993
1994+ size="0 32 100% 60"
1995
1996+ tooltip_style="sessionToolTip"
1997
1998+ >
1999
2000+ Exit
2001
2002+ <action on="Press">exitMenuButton();</action>
2003
2004+ </object>
2005
2006+
2007
2008+ <!-- Pause / Resume Button -->
2009
2010+ <object type="button"
2011
2012+ name="pauseButton"
2013
2014+ style="StoneButtonFancy"
2015
2016+ size="0 64 100% 92"
2017
2018+ tooltip_style="sessionToolTip"
2019
2020+ >
2021
2022+ <object name="pauseButtonText" type="text" style="CenteredButtonText" ghost="true">Pause</object>
2023
2024+ <action on="Press">togglePause();</action>
2025
2026+ </object>
2027
2028+ </object>
2029
2030+ </object>
2031
2032+
2033
2034+ <!-- In-progress research -->
2035
2036+ <object size="100%-50 85 100%-10 100%-200">
2037
2038+ <repeat count="10">
2039
2040+ <object name="researchStartedButton[n]" hidden="true" style="iconButton" type="button" size="0 0 40 40" tooltip_style="sessionToolTipBottom">
2041
2042+ <object name="researchStartedIcon[n]" ghost="true" type="image" size="3 3 37 37"/>
2043
2044+ <object name="researchStartedProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 37 37"/>
2045
2046+ </object>
2047
2048+ </repeat>
2049
2050+ </object>
2051
2052+
2053
2054+ <!-- ================================ ================================ -->
2055
2056+ <!-- Unit Selection Groups -->
2057
2058+ <!-- ================================ ================================ -->
2059
2060+ <!--<object
2061
2062+ name="unitGroupPanel"
2063
2064+ size="0% 50%-216 0%+36 50%+144"
2065
2066+ >
2067
2068+ <repeat count="10">
2069
2070+ <object name="unitGroupButton[n]" size="0 0 36 36" type="button" hidden="false" style="iconButton" tooltip_style="sessionToolTipBottomBold"
2071
2072+ tooltip="Click to select grouped units.">
2073
2074+ <object name="unitGroupIcon[n]" size="3 3 33 33" type="image" sprite="groupsIcon" ghost="true"/>
2075
2076+ <object name="unitGroupLabel[n]" type="text" style="largeCenteredOutlinedText" ghost="true"/>
2077
2078+ </object>
2079
2080+ </repeat>
2081
2082+ </object>-->
2083
2084+
2085
2086+ <!-- ================================ ================================ -->
2087
2088+ <!-- Information tooltip
2089
2090+ Follows the mouse around if 'independent' is set to 'true'. -->
2091
2092+ <!-- ================================ ================================ -->
2093
2094+ <object name="informationTooltip" type="tooltip" independent="true" style="informationTooltip"/>
2095
2096+
2097
2098+ <!-- ================================ ================================ -->
2099
2100+ <!-- Wall-dragging tooltip. Shows the total cost of building a wall while the player is dragging it. -->
2101
2102+ <!-- ================================ ================================ -->
2103
2104+ <object name="wallDragTooltip" type="tooltip" independent="true" style="informationTooltip"/>
2105
2106+
2107
2108+ <!-- ================================ ================================ -->
2109
2110+ <!-- START of BOTTOM PANEL -->
2111
2112+ <!-- ================================ ================================ -->
2113
2114+
2115
2116+ <object size="50%-512 100%-180 50%+512 100%">
2117
2118+
2119
2120+ <!-- ================================ ================================ -->
2121
2122+ <!-- Minimap -->
2123
2124+ <!-- ================================ ================================ -->
2125
2126+ <object
2127
2128+ name="minimapPanel"
2129
2130+ size="0 100%-212 212 100%"
2131
2132+ type="image"
2133
2134+ sprite="mapPanel"
2135
2136+ z="20"
2137
2138+ >
2139
2140+ <object name="minimap"
2141
2142+ type="minimap"
2143
2144+ size="14 14 100%-14 100%-14"
2145
2146+ >
2147
2148+ <action on="WorldClick">handleMinimapEvent(arguments[0]);</action>
2149
2150+ </object>
2151
2152+
2153
2154+ <object name="minimapOverlay" size="10 10 100%-10 100%-10" type="image" sprite="stretched:session/minimap_circle.png" ghost="true"/>
2155
2156+
2157
2158+ <!-- Idle Worker Button -->
2159
2160+ <object type="image"
2161
2162+ size="100%-36 4 100%-4 36"
2163
2164+ >
2165
2166+ <object type="button"
2167
2168+ style="iconButton"
2169
2170+ tooltip_style="sessionToolTip"
2171
2172+ tooltip="Find idle worker"
2173
2174+ hotkey="selection.idleworker"
2175
2176+ >
2177
2178+ <!-- TODO: should highlight the button if there's non-zero idle workers -->
2179
2180+ <object size="0 0 100% 100%" type="image" sprite="idleWorker" ghost="true" />
2181
2182+ <action on="Press">findIdleUnit(["Female", "Trade", "FishingBoat", "CitizenSoldier", "Healer"]);</action>
2183
2184+ </object>
2185
2186+ </object>
2187
2188+ </object>
2189
2190+
2191
2192+ <!-- ================================ ================================ -->
2193
2194+ <!-- Supplemental Details Panel (Left of Selection Details) -->
2195
2196+ <!-- ================================ ================================ -->
2197
2198+ <object size="50%-304 100%-170 50%-110 100%" name="supplementalSelectionDetails" type="image" sprite="supplementalDetailsPanel" z="20">
2199
2200+
2201
2202+ <object name="unitFormationPanel"
2203
2204+ size="24 12 100% 100%"
2205
2206+ >
2207
2208+ <object size="0 0 100% 100%">
2209
2210+ <repeat count="16">
2211
2212+ <object name="unitFormationButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
2213
2214+ <object name="unitFormationIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
2215
2216+ <object name="unitFormationSelection[n]" hidden="true" type="image" ghost="true" size="3 3 33 33" sprite="stretched:session/icons/corners.png"/>
2217
2218+ </object>
2219
2220+ </repeat>
2221
2222+ </object>
2223
2224+ </object>
2225
2226+
2227
2228+ <object name="unitGarrisonPanel"
2229
2230+ size="24 12 100% 100%"
2231
2232+ >
2233
2234+ <object size="0 0 100% 100%">
2235
2236+ <repeat count="12">
2237
2238+ <object name="unitGarrisonButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
2239
2240+ <object name="unitGarrisonIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
2241
2242+ <object name="unitGarrisonCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
2243
2244+ </object>
2245
2246+ </repeat>
2247
2248+ </object>
2249
2250+ </object>
2251
2252+
2253
2254+ <object name="unitBarterPanel"
2255
2256+ size="6 36 100% 100%"
2257
2258+ hidden="true"
2259
2260+ >
2261
2262+ <object ghost="true" style="resourceText" type="text" size="0 0 100% 20">Exchange resources:</object>
2263
2264+ <object size="0 32 100% 78">
2265
2266+ <repeat count="4">
2267
2268+ <object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
2269
2270+ <object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2271
2272+ <object name="unitBarterSellUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
2273
2274+ <object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
2275
2276+ <object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
2277
2278+ </object>
2279
2280+ </repeat>
2281
2282+ </object>
2283
2284+ <object size="0 78 100% 124">
2285
2286+ <repeat count="4">
2287
2288+ <object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
2289
2290+ <object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2291
2292+ <object name="unitBarterBuyUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
2293
2294+ <object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
2295
2296+ </object>
2297
2298+ </repeat>
2299
2300+ </object>
2301
2302+ </object>
2303
2304+
2305
2306+ <!-- Stance Selection -->
2307
2308+ <object name="unitStancePanel"
2309
2310+ style="TranslucentPanel"
2311
2312+ size="4 100%-43 100%-4 100%-4"
2313
2314+ type="text"
2315
2316+ >
2317
2318+ <object size="1 2 100% 100%">
2319
2320+ <repeat count="5">
2321
2322+ <object name="unitStanceButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
2323
2324+ <object name="unitStanceIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
2325
2326+ <object name="unitStanceSelection[n]" hidden="true" type="image" ghost="true" size="3 3 33 33" sprite="stretched:session/icons/corners.png"/>
2327
2328+ </object>
2329
2330+ </repeat>
2331
2332+ </object>
2333
2334+ </object>
2335
2336+ </object>
2337
2338+
2339
2340+ <!-- ================================ ================================ -->
2341
2342+ <!-- Selection Details Panel (Middle) -->
2343
2344+ <!-- ================================ ================================ -->
2345
2346+ <object name="selectionDetails"
2347
2348+ type="image"
2349
2350+ sprite="selectionDetailsPanel"
2351
2352+ size="50%-114 100%-205 50%+114 100%"
2353
2354+ hidden="false"
2355
2356+ >
2357
2358+ <!-- Unit details for Single Unit -->
2359
2360+ <object size="50%-112 0 50%+112 100%" name="detailsAreaSingle">
2361
2362+
2363
2364+ <!-- Stats Bars -->
2365
2366+ <object size= "2 0 100%-2 98" type="image" tooltip_style="sessionToolTip">
2367
2368+
2369
2370+ <object size="0 8 100% 60" type="image" sprite="edgedPanelShader">
2371
2372+ <!-- Health bar -->
2373
2374+ <object size="88 0 100% 24" name="healthSection">
2375
2376+ <object size="0 0 100% 16" name="healthLabel" type="text" style="StatsTextLeft" ghost="true">Health:</object>
2377
2378+ <object size="0 0 100% 16" name="healthStats" type="text" style="StatsTextRight" ghost="true"/>
2379
2380+ <object size="1 16 100% 23" name="health" type="image">
2381
2382+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2383
2384+ <object type="image" sprite="healthBackground" ghost="true"/>
2385
2386+ <object type="image" sprite="healthForeground" ghost="true" name="healthBar"/>
2387
2388+ <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
2389
2390+ </object>
2391
2392+ </object>
2393
2394+
2395
2396+ <!-- Stamina bar -->
2397
2398+ <object size="88 28 100% 52" name="staminaSection">
2399
2400+ <object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">Stamina:</object>
2401
2402+ <object size="0 0 100% 16" name="staminaStats" type="text" style="StatsTextRight" ghost="true"/>
2403
2404+ <object size="1 16 100% 23" name="stamina" type="image">
2405
2406+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2407
2408+ <object type="image" sprite="staminaBackground" ghost="true"/>
2409
2410+ <object type="image" sprite="staminaForeground" ghost="true" name="staminaBar"/>
2411
2412+ <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
2413
2414+ </object>
2415
2416+ </object>
2417
2418+
2419
2420+ <!-- Resource bar -->
2421
2422+ <object size="88 28 100% 52" name="resourceSection">
2423
2424+ <object size="0 0 100% 16" name="resourceLabel" type="text" style="StatsTextLeft" ghost="true"/>
2425
2426+ <object size="0 0 100% 16" name="resourceStats" type="text" style="StatsTextRight" ghost="true"/>
2427
2428+ <object size="1 16 100% 23" name="resources" type="image">
2429
2430+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2431
2432+ <object type="image" sprite="resourceBackground" ghost="true"/>
2433
2434+ <object type="image" sprite="resourceForeground" ghost="true" name="resourceBar"/>
2435
2436+ <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
2437
2438+ </object>
2439
2440+ </object>
2441
2442+ </object>
2443
2444+
2445
2446+ <object size="0 60 100% 96" type="image" sprite="edgedPanelShader">
2447
2448+ <!-- Attack and Armor -->
2449
2450+ <object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip="Attack and Armor" tooltip_style="sessionToolTip"/>
2451
2452+
2453
2454+ <!-- Resource carrying icon/counter -->
2455
2456+ <object size="100%-78 -2 100%-28 34" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
2457
2458+ <object size="100%-36 -2 100% 34" type="image" name="resourceCarryingIcon"/>
2459
2460+ </object>
2461
2462+
2463
2464+ <!-- Big unit icon -->
2465
2466+ <object size="-8 -8 88 88" type="image" name="iconBorder" sprite="iconBorder" tooltip_style="sessionToolTip">
2467
2468+ <object size="1 1 100%-1 100%-1" type="image" name="icon" ghost="true"/>
2469
2470+
2471
2472+ <!-- Experience bar -->
2473
2474+ <object size="2 2 6 100%-2" type="image" name="experience" tooltip="Experience" tooltip_style="sessionToolTip">
2475
2476+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2477
2478+ <object type="image" sprite="experienceBackground" ghost="true"/>
2479
2480+ <object type="image" sprite="experienceForeground" ghost="true" name="experienceBar"/>
2481
2482+ <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
2483
2484+ </object>
2485
2486+
2487
2488+ <object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip="Rank" tooltip_style="sessionToolTip"/>
2489
2490+ </object>
2491
2492+ </object>
2493
2494+
2495
2496+ <!-- Names (this must come before the attack and armor icon to avoid clipping issues) -->
2497
2498+ <object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader">
2499
2500+
2501
2502+ <!-- These images are used to clip off the top and bottom of the civ icon -->
2503
2504+ <object z="30" size="0 -5 100% 40" ghost="true" type="image" sprite="remove"/>
2505
2506+ <object z="30" size="0 100%-5 100% 100%+40" ghost="true" type="image" sprite="remove"/>
2507
2508+
2509
2510+ <object z="30" size="0 2 100% 45" ghost="true">
2511
2512+ <!-- Specific Name -->
2513
2514+ <object size="0 0 100% 20" name="specific" ghost="true" type="text" style="SpecificNameCentered"/>
2515
2516+
2517
2518+ <!-- Generic Name -->
2519
2520+ <object size="0 15 100% 36" name="generic" ghost="true" type="text" style="GenericNameCentered"/>
2521
2522+ </object>
2523
2524+
2525
2526+ <!-- Player Name and Civ -->
2527
2528+ <object size="0 40 100% 100%">
2529
2530+ <object size="50%-64 50%-64 50%+64 50%+64" name="playerCivIcon" type="image" ghost="true"/>
2531
2532+ <object size="0 0 100% 100%" name="playerColorBackground" type="image" sprite="playerColorBackground" ghost="true"/>
2533
2534+ <object size="0 0 100% 100%" type="image" sprite="bottomEdgedPanelShader" ghost="true"/>
2535
2536+
2537
2538+ <!-- Why is this being automatically ghosted? In the mean time, set ghost to false -->
2539
2540+ <object ghost="false" size="0 0 100% 100%-5" name="player" type="text" style="largeCenteredOutlinedText" tooltip_style="sessionToolTip"/>
2541
2542+ </object>
2543
2544+ </object>
2545
2546+
2547
2548+ </object>
2549
2550+
2551
2552+ <!-- Unit details for Multiple Units -->
2553
2554+ <object size="50%-112 0 50%+112 100%" name="detailsAreaMultiple">
2555
2556+
2557
2558+ <object name="unitSelectionPanel"
2559
2560+ size="20 12 100%-20 100%"
2561
2562+ >
2563
2564+ <object size="0 0 100% 100%">
2565
2566+ <repeat count="16">
2567
2568+ <object name="unitSelectionButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBold" z="100">
2569
2570+ <object name="unitSelectionIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
2571
2572+ <object name="unitSelectionCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
2573
2574+ <object size="0 100%-3 100% 100%" name="unitSelectionHealth[n]" ghost="true">
2575
2576+ </object>
2577
2578+ </object>
2579
2580+ </repeat>
2581
2582+ </object>
2583
2584+ </object>
2585
2586+
2587
2588+ <!-- Total -->
2589
2590+ <object size="100%-42 12 100%-8 46" type="image" sprite="groupsIcon">
2591
2592+ <object size="0 0 100% 100%" type="text" style="largeCenteredOutlinedText" name="numberOfUnits"/>
2593
2594+ </object>
2595
2596+
2597
2598+ <!-- Stats Bars -->
2599
2600+ <object size= "100%-38 50 100%-18 100%-44" type="image" tooltip_style="sessionToolTip">
2601
2602+ <!-- Health bar -->
2603
2604+ <object size="4 0 11 100%" type="image" name="healthMultiple" tooltip="Hitpoints" tooltip_style="sessionToolTip">
2605
2606+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2607
2608+ <object type="image" sprite="healthBackground" ghost="true"/>
2609
2610+ <object type="image" sprite="healthForeground" ghost="true" name="healthBarMultiple"/>
2611
2612+ <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
2613
2614+ </object>
2615
2616+
2617
2618+ <!-- Stamina bar -->
2619
2620+ <object size="15 0 22 100%" type="image" name="staminaMultiple" tooltip="Stamina" tooltip_style="sessionToolTipBold">
2621
2622+ <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
2623
2624+ <object type="image" sprite="staminaBackground" ghost="true"/>
2625
2626+ <object type="image" sprite="staminaForeground" ghost="true" name="staminaBarMultiple"/>
2627
2628+ <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
2629
2630+ </object>
2631
2632+ </object>
2633
2634+ </object>
2635
2636+
2637
2638+ <!-- Unit Commands -->
2639
2640+ <object name="unitCommandPanel"
2641
2642+ size="0 100%-36 100% 100%-4"
2643
2644+ type="image"
2645
2646+ z="30"
2647
2648+ >
2649
2650+ <object size="0 1 100% 100%">
2651
2652+ <repeat count="6">
2653
2654+ <object name="unitCommandButton[n]" hidden="true" style="iconButton" type="button" size="0 0 32 32" tooltip_style="sessionToolTipBottomBold">
2655
2656+ <object name="unitCommandIcon[n]" ghost="true" type="image" size="0 0 100% 100%" style="commandIcon"/>
2657
2658+ <object name="unitCommandCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
2659
2660+ </object>
2661
2662+ </repeat>
2663
2664+ </object>
2665
2666+ </object>
2667
2668+
2669
2670+ <!-- shading for unit commands area -->
2671
2672+ <object z="50" size="4 100%-36 100%-4 100%-4" ghost="true" type="image" sprite="bottomEdgedPanelShader"/>
2673
2674+
2675
2676+ </object> <!-- END OF SELECTION DETAILS -->
2677
2678+
2679
2680+ <!-- ================================ ================================ -->
2681
2682+ <!-- Commands Panel (Right of Selection Details) -->
2683
2684+ <!-- ================================ ================================ -->
2685
2686+ <object name="unitCommands"
2687
2688+ type="image"
2689
2690+ sprite="unitCommandsPanel"
2691
2692+ size="50%+110 100%-170 50%+512 100%"
2693
2694+ hidden="false"
2695
2696+ z="20"
2697
2698+ >
2699
2700+ <object name="unitConstructionPanel"
2701
2702+ size="10 12 100% 100%"
2703
2704+ >
2705
2706+ <object size="0 0 100% 100%">
2707
2708+ <repeat count="24">
2709
2710+ <object name="unitConstructionButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
2711
2712+ <object name="unitConstructionIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2713
2714+ <object name="unitConstructionUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
2715
2716+ </object>
2717
2718+ </repeat>
2719
2720+ </object>
2721
2722+ </object>
2723
2724+
2725
2726+ <object name="unitResearchPanel"
2727
2728+ size="10 100%-102 100% 100%"
2729
2730+ >
2731
2732+ <object size="0 0 100% 100%">
2733
2734+ <repeat count="16">
2735
2736+ <object name="unitResearchButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
2737
2738+ <object name="unitResearchIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2739
2740+ <object name="unitResearchSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
2741
2742+ <object name="unitResearchUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
2743
2744+ </object>
2745
2746+ </repeat>
2747
2748+ <repeat count="8">
2749
2750+ <object name="unitResearchPair[n]" hidden="true" size="0 0 46 92">
2751
2752+ <object name="unitResearchPairIcon[n]" type="image" ghost="true" size="8 38 38 54" sprite="stretched:session/icons/vertical_pair.png"/>
2753
2754+ </object>
2755
2756+ </repeat>
2757
2758+ </object>
2759
2760+ </object>
2761
2762+
2763
2764+ <object name="unitTrainingPanel"
2765
2766+ size="10 12 100% 100%"
2767
2768+ >
2769
2770+ <object size="0 0 100% 100%">
2771
2772+ <repeat count="24">
2773
2774+ <object name="unitTrainingButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
2775
2776+ <object name="unitTrainingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2777
2778+ <object name="unitTrainingUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
2779
2780+ </object>
2781
2782+ </repeat>
2783
2784+ </object>
2785
2786+ </object>
2787
2788+
2789
2790+ <object name="unitTradingPanel"
2791
2792+ size="10 12 100% 100%"
2793
2794+ >
2795
2796+ <object size="0 0 100% 100%">
2797
2798+ <repeat count="4">
2799
2800+ <object name="unitTradingButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
2801
2802+ <object name="unitTradingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2803
2804+ <object name="unitTradingSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
2805
2806+ </object>
2807
2808+ </repeat>
2809
2810+ </object>
2811
2812+ </object>
2813
2814+
2815
2816+ <object name="unitQueuePanel"
2817
2818+ size="4 -56 100% 0"
2819
2820+ type="image"
2821
2822+ sprite="queuePanelShader"
2823
2824+ >
2825
2826+ <object size="-4 -2 52 54" type="image" sprite="stretched:session/icons/production.png" tooltip_style="sessionToolTipBottom" tooltip="Production queue">
2827
2828+ <object name="queueProgress" ghost="true" style="iconButtonProgress" type="text"/>
2829
2830+ </object>
2831
2832+
2833
2834+ <object size="48 6 100% 100%">
2835
2836+ <repeat count="16">
2837
2838+ <object name="unitQueueButton[n]" hidden="true" style="iconButton" type="button" size="0 0 40 40" tooltip_style="sessionToolTipBottom">
2839
2840+ <object name="unitQueueIcon[n]" ghost="true" type="image" size="3 3 37 37"/>
2841
2842+ <object name="unitQueueProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 37 37" z="20"/>
2843
2844+ <object name="unitQueueCount[n]" ghost="true" style="groupIconsText" type="text" z="20"/>
2845
2846+ </object>
2847
2848+ </repeat>
2849
2850+ </object>
2851
2852+ </object>
2853
2854+
2855
2856+ <object name="unitGatePanel"
2857
2858+ size="10 12 100% 100%"
2859
2860+ >
2861
2862+ <object size="0 0 100% 100%">
2863
2864+ <repeat count="8">
2865
2866+ <object name="unitGateButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
2867
2868+ <object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
2869
2870+ <object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
2871
2872+ <object name="unitGateUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
2873
2874+ </object>
2875
2876+ </repeat>
2877
2878+ </object>
2879
2880+ </object>
2881
2882+
2883
2884+ </object> <!-- END OF UNIT COMMANDS -->
2885
2886+
2887
2888+ </object><!-- END OF BOTTOM PANEL -->
2889
2890+
2891
2892+ </object> <!-- END OF SN OBJECT -->
2893
2894+
2895
2896+ <!-- ================================ ================================ -->
2897
2898+ <!-- Selection bandbox -->
2899
2900+ <!-- ================================ ================================ -->
2901
2902+ <object name="bandbox" type="image" sprite="bandbox" ghost="true" hidden="true" z="200"/>
2903
2904+
2905
2906+ <!-- ================================ ================================ -->
2907
2908+ <!-- Network status -->
2909
2910+ <!-- ================================ ================================ -->
2911
2912+ <object name="netStatus" type="text" style="netStatus" z="100" hidden="true">
2913
2914+ <object type="button"
2915
2916+ name="disconnectedExitButton"
2917
2918+ style="StoneButton"
2919
2920+ size="50%-84 50%+128 50%+84 50%+160"
2921
2922+ tooltip_style="sessionToolTip"
2923
2924+ hidden="true"
2925
2926+ >
2927
2928+ <object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">Return to Main Menu</object>
2929
2930+ <action on="Press">leaveGame()</action>
2931
2932+ </object>
2933
2934+
2935
2936+ </object>
2937
2938+
2939
2940+</objects>
2941
2942Index: binaries/data/mods/public/gui/session/selection_details.js
2943
2944===================================================================
2945
2946--- binaries/data/mods/public/gui/session/selection_details.js (revision 12723)
2947
2948+++ binaries/data/mods/public/gui/session/selection_details.js (working copy)
2949
2950@@ -254,7 +254,7 @@
2951
2952 }
2953
2954
2955
2956 // Updates middle entity Selection Details Panel
2957
2958-function updateSelectionDetails()
2959
2960+function updateSelectionDetails(replay)
2961
2962 {
2963
2964 var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
2965
2966 var detailsPanel = getGUIObjectByName("selectionDetails");
2967
2968@@ -296,5 +296,5 @@
2969
2970 commandsPanel.hidden = false;
2971
2972
2973
2974 // Fill out commands panel for specific unit selected (or first unit of primary group)
2975
2976- updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
2977
2978+ updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection, replay);
2979
2980 }
2981
2982Index: binaries/data/mods/public/gui/summary/summary.js
2983
2984===================================================================
2985
2986--- binaries/data/mods/public/gui/summary/summary.js (revision 12723)
2987
2988+++ binaries/data/mods/public/gui/summary/summary.js (working copy)
2989
2990@@ -276,5 +276,6 @@
2991
2992 function onTick()
2993 {
2994 // Update music state
2995- global.music.updateTimer();
2996+ if (global.music)
2997+ global.music.updateTimer();
2998 }
2999Index: source/gui/scripting/ScriptFunctions.cpp
3000
3001===================================================================
3002
3003--- source/gui/scripting/ScriptFunctions.cpp (revision 12723)
3004
3005+++ source/gui/scripting/ScriptFunctions.cpp (working copy)
3006
3007@@ -167,6 +167,12 @@
3008
3009 return -1;
3010 }
3011
3012+void SetPlayerID(void* UNUSED(cbdata), int id)
3013+{
3014+ if (g_Game)
3015+ g_Game->SetPlayerID(id);
3016+}
3017+
3018 std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
3019 {
3020 std::wstring name = g_PlayerName.FromUTF8();
3021@@ -617,6 +623,7 @@
3022
3023 // Misc functions
3024 scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
3025 scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
3026+ scriptInterface.RegisterFunction<void, int, &SetPlayerID>("SetPlayerID");
3027 scriptInterface.RegisterFunction<std::wstring, &GetDefaultPlayerName>("GetDefaultPlayerName");
3028 scriptInterface.RegisterFunction<void, std::string, &OpenURL>("OpenURL");
3029 scriptInterface.RegisterFunction<void, &RestartInAtlas>("RestartInAtlas");
3030Index: source/network/NetTurnManager.cpp
3031
3032===================================================================
3033
3034--- source/network/NetTurnManager.cpp (revision 12723)
3035
3036+++ source/network/NetTurnManager.cpp (working copy)
3037
3038@@ -465,6 +465,39 @@
3039
3040
3041
3042
3043+CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
3044+ CNetLocalTurnManager(simulation, replay)
3045+{
3046+}
3047+
3048+void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
3049+{
3050+ m_ReplayCommands[turn][player].push_back(command);
3051+}
3052+
3053+void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
3054+{
3055+ DoTurn(turn);
3056+}
3057+
3058+void CNetReplayTurnManager::DoTurn(u32 turn)
3059+{
3060+ std::map<int, std::vector<std::string> > playerCommands = m_ReplayCommands[turn];
3061+ std::map<int, std::vector<std::string> >::iterator it;
3062+ for (it = playerCommands.begin(); it != playerCommands.end(); ++it)
3063+ {
3064+ int player = it->first;
3065+ for (size_t i = 0; i < it->second.size(); ++i)
3066+ {
3067+ CScriptValRooted data = m_Simulation2.GetScriptInterface().ParseJSON(it->second[i]);
3068+ AddCommand(m_ClientId, player, data, m_CurrentTurn + 1);
3069+ }
3070+ }
3071+}
3072+
3073+
3074+
3075+
3076 CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
3077 m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
3078 {
3079Index: source/network/NetTurnManager.h
3080
3081===================================================================
3082
3083--- source/network/NetTurnManager.h (revision 12723)
3084
3085+++ source/network/NetTurnManager.h (working copy)
3086
3087@@ -22,6 +22,7 @@
3088
3089
3090 #include <list>
3091 #include <map>
3092+#include <vector>
3093
3094 class CNetServerWorker;
3095 class CNetClient;
3096@@ -227,7 +228,25 @@
3097
3098 virtual void NotifyFinishedUpdate(u32 turn);
3099 };
3100
3101+/**
3102+ * Implementation of CNetTurnManager for replay games.
3103+ */
3104+class CNetReplayTurnManager : public CNetLocalTurnManager
3105+{
3106+public:
3107+ CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
3108
3109+ void StoreReplayCommand(u32 turn, int player, const std::string& command);
3110+
3111+protected:
3112+ virtual void NotifyFinishedUpdate(u32 turn);
3113+
3114+ void DoTurn(u32 turn);
3115+
3116+ std::map<u32, std::map<int, std::vector<std::string> > > m_ReplayCommands;
3117+};
3118+
3119+
3120 /**
3121 * The server-side counterpart to CNetClientTurnManager.
3122 * Records the turn state of each client, and sends turn advancement messages
3123Index: source/ps/Game.cpp
3124
3125===================================================================
3126
3127--- source/ps/Game.cpp (revision 12723)
3128
3129+++ source/ps/Game.cpp (working copy)
3130
3131@@ -59,7 +59,7 @@
3132
3133 * Constructor
3134 *
3135 **/
3136-CGame::CGame(bool disableGraphics):
3137+CGame::CGame(bool disableGraphics, bool replayLog):
3138 m_World(new CWorld(this)),
3139 m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), m_World->GetTerrain())),
3140 m_GameView(disableGraphics ? NULL : new CGameView(this)),
3141@@ -67,10 +67,15 @@
3142
3143 m_Paused(false),
3144 m_SimRate(1.0f),
3145 m_PlayerID(-1),
3146- m_IsSavedGame(false)
3147+ m_IsSavedGame(false),
3148+ m_IsReplay(false),
3149+ m_ReplayStream(NULL)
3150 {
3151- m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
3152 // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
3153+ if (replayLog)
3154+ m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
3155+ else
3156+ m_ReplayLogger = new CDummyReplayLogger();
3157
3158 // Need to set the CObjectManager references after various objects have
3159 // been initialised, so do it here rather than via the initialisers above.
3160@@ -97,6 +102,7 @@
3161
3162 delete m_Simulation2;
3163 delete m_World;
3164 delete m_ReplayLogger;
3165+ delete m_ReplayStream;
3166 }
3167
3168 void CGame::SetTurnManager(CNetTurnManager* turnManager)
3169@@ -161,6 +167,9 @@
3170
3171 if (m_IsSavedGame)
3172 RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
3173
3174+ if (m_IsReplay)
3175+ RegMemFun(this, &CGame::LoadReplayData, L"Loading replay data", 1000);
3176+
3177 LDR_EndRegistering();
3178 }
3179
3180@@ -184,6 +193,72 @@
3181
3182 return 0;
3183 }
3184
3185+int CGame::LoadReplayData()
3186+{
3187+ ENSURE(m_IsReplay);
3188+ ENSURE(!m_ReplayPath.empty());
3189+
3190+ CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
3191+
3192+ u32 currentTurn = 0;
3193+ std::string type;
3194+ while ((*m_ReplayStream >> type).good())
3195+ {
3196+ if (type == "turn")
3197+ {
3198+ u32 turn = 0;
3199+ u32 turnLength = 0;
3200+ *m_ReplayStream >> turn >> turnLength;
3201+ ENSURE(turn == currentTurn);
3202+ }
3203+ else if (type == "cmd")
3204+ {
3205+ int player;
3206+ *m_ReplayStream >> player;
3207+
3208+ std::string line;
3209+ std::getline(*m_ReplayStream, line);
3210+ replayTurnMgr->StoreReplayCommand(currentTurn, player, line);
3211+ }
3212+ else if (type == "hash" || type == "hash-quick")
3213+ {
3214+ // Ignored for now
3215+ std::string replayHash;
3216+ *m_ReplayStream >> replayHash;
3217+ }
3218+ else if (type == "end")
3219+ {
3220+ currentTurn++;
3221+ }
3222+ else
3223+ {
3224+ CancelLoad(L"Failed to load replay data (unrecognized content)");
3225+ }
3226+ }
3227+ m_FinalReplayTurn = currentTurn + 1;
3228+
3229+ return 0;
3230+}
3231+
3232+void CGame::StartReplay(const std::string& replayPath)
3233+{
3234+ m_IsReplay = true;
3235+
3236+ SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
3237+
3238+ m_ReplayPath = replayPath;
3239+ m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
3240+ ENSURE(m_ReplayStream->good());
3241+
3242+ std::string type;
3243+ ENSURE((*m_ReplayStream >> type).good() && type == "start");
3244+
3245+ std::string line;
3246+ std::getline(*m_ReplayStream, line);
3247+ CScriptValRooted attribs = m_Simulation2->GetScriptInterface().ParseJSON(line);
3248+ StartGame(attribs, "");
3249+}
3250+
3251 /**
3252 * Game initialization has been completed. Set game started flag and start the session.
3253 *
3254@@ -285,6 +360,9 @@
3255
3256 g_GUI->SendEventToAll("SimulationUpdate");
3257 }
3258
3259+ if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn)
3260+ g_GUI->SendEventToAll("ReplayFinished");
3261+
3262 GetView()->GetLOSTexture().MakeDirty();
3263 }
3264
3265Index: source/ps/Game.h
3266
3267===================================================================
3268
3269--- source/ps/Game.h (revision 12723)
3270
3271+++ source/ps/Game.h (working copy)
3272
3273@@ -19,6 +19,7 @@
3274
3275 #define INCLUDED_GAME
3276
3277 #include "ps/Errors.h"
3278+#include <map>
3279 #include <vector>
3280
3281 #include "scriptinterface/ScriptVal.h"
3282@@ -65,7 +66,7 @@
3283
3284 CNetTurnManager* m_TurnManager;
3285
3286 public:
3287- CGame(bool disableGraphics = false);
3288+ CGame(bool disableGraphics = false, bool replayLog = true);
3289 ~CGame();
3290
3291 /**
3292@@ -76,6 +77,8 @@
3293
3294 void StartGame(const CScriptValRooted& attribs, const std::string& savedState);
3295 PSRETURN ReallyStartGame();
3296
3297+ void StartReplay(const std::string& replayPath);
3298+
3299 /**
3300 * Periodic heartbeat that controls the process. performs all per-frame updates.
3301 * Simulation update is called and game status update is called.
3302@@ -168,6 +171,12 @@
3303
3304 int LoadInitialState();
3305 std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
3306 bool m_IsSavedGame; // true if loading a saved game; false for a new game
3307+
3308+ int LoadReplayData();
3309+ std::string m_ReplayPath;
3310+ bool m_IsReplay;
3311+ std::istream* m_ReplayStream;
3312+ u32 m_FinalReplayTurn;
3313 };
3314
3315 extern CGame *g_Game;
3316Index: source/ps/GameSetup/GameSetup.cpp
3317
3318===================================================================
3319
3320--- source/ps/GameSetup/GameSetup.cpp (revision 12723)
3321
3322+++ source/ps/GameSetup/GameSetup.cpp (working copy)
3323
3324@@ -825,6 +825,7 @@
3325
3326 }
3327
3328 bool Autostart(const CmdLineArgs& args);
3329+bool VisualReplay(const CmdLineArgs& args);
3330
3331 void Init(const CmdLineArgs& args, int UNUSED(flags))
3332 {
3333@@ -983,7 +984,7 @@
3334
3335
3336 try
3337 {
3338- if (!Autostart(args))
3339+ if (!VisualReplay(args) && !Autostart(args))
3340 {
3341 const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
3342 InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
3343@@ -1017,6 +1018,29 @@
3344
3345 g_DoRenderCursor = RenderingState;
3346 }
3347
3348+bool VisualReplay(const CmdLineArgs& args)
3349+{
3350+ CStr replayPath = args.Get("replay-visual");
3351+ if (!replayPath.empty())
3352+ {
3353+ g_Game = new CGame(false, false);
3354+
3355+ g_Game->SetPlayerID(1);
3356+ g_Game->StartReplay(replayPath);
3357+
3358+ // TODO: Non progressive load can fail - need a decent way to handle this
3359+ LDR_NonprogressiveLoad();
3360+
3361+ PSRETURN ret = g_Game->ReallyStartGame();
3362+ ENSURE(ret == PSRETURN_OK);
3363+
3364+ InitPs(true, L"page_replay.xml", JSVAL_VOID);
3365+ return true;
3366+ }
3367+
3368+ return false;
3369+}
3370+
3371 bool Autostart(const CmdLineArgs& args)
3372 {
3373 /*
3374Index: source/ps/Replay.cpp
3375
3376===================================================================
3377
3378--- source/ps/Replay.cpp (revision 12723)
3379
3380+++ source/ps/Replay.cpp (working copy)
3381
3382@@ -128,7 +128,7 @@
3383
3384 g_ScriptStatsTable = new CScriptStatsTable;
3385 g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
3386
3387- CGame game(true);
3388+ CGame game(true, false);
3389 g_Game = &game;
3390
3391 // Need some stuff for terrain movement costs:
3392Index: binaries/data/mods/public/gui/session/unit_commands.js
3393
3394===================================================================
3395
3396--- binaries/data/mods/public/gui/session/unit_commands.js (revision 13278)
3397
3398+++ binaries/data/mods/public/gui/session/unit_commands.js (working copy)
3399
3400@@ -996,14 +996,14 @@
3401
3402 * @param commandsPanel Reference to the "commandsPanel" GUI Object
3403
3404 * @param selection Array of currently selected entity IDs.
3405
3406 */
3407
3408-function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
3409
3410+function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection, replay)
3411
3412 {
3413
3414 // Panels that are active
3415
3416 var usedPanels = {};
3417
3418
3419
3420 // If the selection is friendly units, add the command panels
3421
3422 var player = Engine.GetPlayerID();
3423
3424- if (entState.player == player || g_DevSettings.controlAll)
3425
3426+ if (replay || entState.player == player || g_DevSettings.controlAll)
3427
3428 {
3429
3430 // Get player state to check some constraints
3431
3432 // e.g. presence of a hero or build limits
3433
3434@@ -1014,10 +1014,13 @@
3435
3436 setupUnitPanel(SELECTION, usedPanels, entState, playerState, g_Selection.groups.getTemplateNames(),
3437
3438 function (entType, rightPressed) { changePrimarySelectionGroup(entType, rightPressed); } );
3439
3440
3441
3442- var commands = getEntityCommandsList(entState);
3443
3444- if (commands.length)
3445
3446- setupUnitPanel(COMMAND, usedPanels, entState, playerState, commands,
3447
3448- function (item) { performCommand(entState.id, item.name); } );
3449
3450+ if (!replay)
3451
3452+ {
3453
3454+ var commands = getEntityCommandsList(entState);
3455
3456+ if (commands.length)
3457
3458+ setupUnitPanel(COMMAND, usedPanels, entState, playerState, commands,
3459
3460+ function (item) { performCommand(entState.id, item.name); } );
3461
3462+ }
3463
3464
3465
3466 if (entState.garrisonHolder)
3467
3468 {
3469
3470@@ -1033,157 +1036,160 @@
3471
3472 function (item) { unloadTemplate(item); } );
3473
3474 }
3475
3476
3477
3478- var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
3479
3480- if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
3481
3482- {
3483
3484- setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
3485
3486- function (item) { performFormation(entState.id, item); } );
3487
3488- }
3489
3490+ if (!replay)
3491
3492+ {
3493
3494+ var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
3495
3496+ if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
3497
3498+ {
3499
3500+ setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
3501
3502+ function (item) { performFormation(entState.id, item); } );
3503
3504+ }
3505
3506+
3507
3508+ // TODO: probably should load the stance list from a data file,
3509
3510+ // and/or vary depending on what units are selected
3511
3512+ var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
3513
3514+ if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
3515
3516+ {
3517
3518+ setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
3519
3520+ function (item) { performStance(entState.id, item); } );
3521
3522+ }
3523
3524+
3525
3526+ getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
3527
3528+ if (entState.barterMarket)
3529
3530+ {
3531
3532+ usedPanels["Barter"] = 1;
3533
3534+ setupUnitBarterPanel(entState, playerState);
3535
3536+ }
3537
3538+
3539
3540+ var buildableEnts = getAllBuildableEntities(selection);
3541
3542+ var trainableEnts = getAllTrainableEntities(selection);
3543
3544+
3545
3546+ // Whether the GUI's right panel has been filled.
3547
3548+ var rightUsed = true;
3549
3550+
3551
3552+ // The first selected entity's type has priority.
3553
3554+ if (entState.buildEntities)
3555
3556+ setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
3557
3558+ else if (entState.production && entState.production.entities)
3559
3560+ setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
3561
3562+ function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
3563
3564+ else if (entState.trader)
3565
3566+ setupUnitTradingPanel(usedPanels, entState, selection);
3567
3568+ else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
3569
3570+ {
3571
3572+ // Allow long wall pieces to be converted to gates
3573
3574+ var longWallTypes = {};
3575
3576+ var walls = [];
3577
3578+ var gates = [];
3579
3580+ for (var i in selection)
3581
3582+ {
3583
3584+ state = GetEntityState(selection[i]);
3585
3586+ if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
3587
3588+ {
3589
3590+ var gateTemplate = getWallGateTemplate(state.id);
3591
3592+ if (gateTemplate)
3593
3594+ {
3595
3596+ var wallName = GetTemplateData(state.template).name.generic;
3597
3598+ var gateName = GetTemplateData(gateTemplate).name.generic;
3599
3600+
3601
3602+ walls.push({
3603
3604+ "tooltip": "Convert " + wallName + " to " + gateName,
3605
3606+ "template": gateTemplate,
3607
3608+ "callback": function (item) { transformWallToGate(item.template); }
3609
3610+ });
3611
3612+ }
3613
3614+
3615
3616+ // We only need one entity per type.
3617
3618+ longWallTypes[state.template] = true;
3619
3620+ }
3621
3622+ else if (state.gate && !gates.length)
3623
3624+ for (var j = 0; j < GATE_ACTIONS.length; ++j)
3625
3626+ gates.push({
3627
3628+ "gate": state.gate,
3629
3630+ "tooltip": GATE_ACTIONS[j] + " gate",
3631
3632+ "locked": j == 0,
3633
3634+ "callback": function (item) { lockGate(item.locked); }
3635
3636+ });
3637
3638+ // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
3639
3640+ else if (state.gate && state.gate.locked != gates[0].gate.locked)
3641
3642+ for (var j = 0; j < gates.length; ++j)
3643
3644+ delete gates[j].gate.locked;
3645
3646+ }
3647
3648+
3649
3650+ // Place wall conversion options after gate lock/unlock icons.
3651
3652+ var items = gates.concat(walls);
3653
3654+ if (items.length)
3655
3656+ setupUnitPanel(GATE, usedPanels, entState, playerState, items);
3657
3658+ else
3659
3660+ rightUsed = false;
3661
3662+ }
3663
3664+ else if (entState.pack)
3665
3666+ {
3667
3668+ var items = [];
3669
3670+ var packButton = false;
3671
3672+ var unpackButton = false;
3673
3674+ var packCancelButton = false;
3675
3676+ var unpackCancelButton = false;
3677
3678+ for (var i in selection)
3679
3680+ {
3681
3682+ // Find un/packable entities
3683
3684+ var state = GetEntityState(selection[i]);
3685
3686+ if (state.pack)
3687
3688+ {
3689
3690+ if (state.pack.progress == 0)
3691
3692+ {
3693
3694+ if (!state.pack.packed)
3695
3696+ packButton = true;
3697
3698+ else if (state.pack.packed)
3699
3700+ unpackButton = true;
3701
3702+ }
3703
3704+ else
3705
3706+ {
3707
3708+ // Already un/packing - show cancel button
3709
3710+ if (!state.pack.packed)
3711
3712+ packCancelButton = true;
3713
3714+ else if (state.pack.packed)
3715
3716+ unpackCancelButton = true;
3717
3718+ }
3719
3720+ }
3721
3722+ }
3723
3724+ if (packButton)
3725
3726+ items.push({ "packing": false, "packed": false, "tooltip": "Pack", "callback": function() { packUnit(true); } });
3727
3728+ if (unpackButton)
3729
3730+ items.push({ "packing": false, "packed": true, "tooltip": "Unpack", "callback": function() { packUnit(false); } });
3731
3732+ if (packCancelButton)
3733
3734+ items.push({ "packing": true, "packed": false, "tooltip": "Cancel packing", "callback": function() { cancelPackUnit(true); } });
3735
3736+ if (unpackCancelButton)
3737
3738+ items.push({ "packing": true, "packed": true, "tooltip": "Cancel unpacking", "callback": function() { cancelPackUnit(false); } });
3739
3740+
3741
3742+ if (items.length)
3743
3744+ setupUnitPanel(PACK, usedPanels, entState, playerState, items);
3745
3746+ else
3747
3748+ rightUsed = false;
3749
3750+ }
3751
3752+ else
3753
3754+ rightUsed = false;
3755
3756+
3757
3758+ if (!rightUsed)
3759
3760+ {
3761
3762+ // The right pane is empty. Fill the pane with a sane type.
3763
3764+ // Prefer buildables for units and trainables for structures.
3765
3766+ if (buildableEnts.length && (hasClass(entState, "Unit") || !trainableEnts.length))
3767
3768+ setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
3769
3770+ else if (trainableEnts.length)
3771
3772+ setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
3773
3774+ function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
3775
3776+ }
3777
3778+ // Show technologies if the active panel has at most one row of icons.
3779
3780+ if (entState.production && entState.production.technologies.length)
3781
3782+ {
3783
3784+ var activepane = usedPanels[CONSTRUCTION] ? buildableEnts.length : trainableEnts.length;
3785
3786+ if (selection.length == 1 || activepane <= 8)
3787
3788+ setupUnitPanel(RESEARCH, usedPanels, entState, playerState, entState.production.technologies,
3789
3790+ function (researchType) { addResearchToQueue(entState.id, researchType); } );
3791
3792+ }
3793
3794+ }
3795
3796
3797
3798- // TODO: probably should load the stance list from a data file,
3799
3800- // and/or vary depending on what units are selected
3801
3802- var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
3803
3804- if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
3805
3806- {
3807
3808- setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
3809
3810- function (item) { performStance(entState.id, item); } );
3811
3812- }
3813
3814-
3815
3816- getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
3817
3818- if (entState.barterMarket)
3819
3820- {
3821
3822- usedPanels["Barter"] = 1;
3823
3824- setupUnitBarterPanel(entState, playerState);
3825
3826- }
3827
3828-
3829
3830- var buildableEnts = getAllBuildableEntities(selection);
3831
3832- var trainableEnts = getAllTrainableEntities(selection);
3833
3834-
3835
3836- // Whether the GUI's right panel has been filled.
3837
3838- var rightUsed = true;
3839
3840-
3841
3842- // The first selected entity's type has priority.
3843
3844- if (entState.buildEntities)
3845
3846- setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
3847
3848- else if (entState.production && entState.production.entities)
3849
3850- setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
3851
3852- function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
3853
3854- else if (entState.trader)
3855
3856- setupUnitTradingPanel(usedPanels, entState, selection);
3857
3858- else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
3859
3860- {
3861
3862- // Allow long wall pieces to be converted to gates
3863
3864- var longWallTypes = {};
3865
3866- var walls = [];
3867
3868- var gates = [];
3869
3870- for (var i in selection)
3871
3872- {
3873
3874- state = GetEntityState(selection[i]);
3875
3876- if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
3877
3878- {
3879
3880- var gateTemplate = getWallGateTemplate(state.id);
3881
3882- if (gateTemplate)
3883
3884- {
3885
3886- var wallName = GetTemplateData(state.template).name.generic;
3887
3888- var gateName = GetTemplateData(gateTemplate).name.generic;
3889
3890-
3891
3892- walls.push({
3893
3894- "tooltip": "Convert " + wallName + " to " + gateName,
3895
3896- "template": gateTemplate,
3897
3898- "callback": function (item) { transformWallToGate(item.template); }
3899
3900- });
3901
3902- }
3903
3904-
3905
3906- // We only need one entity per type.
3907
3908- longWallTypes[state.template] = true;
3909
3910- }
3911
3912- else if (state.gate && !gates.length)
3913
3914- for (var j = 0; j < GATE_ACTIONS.length; ++j)
3915
3916- gates.push({
3917
3918- "gate": state.gate,
3919
3920- "tooltip": GATE_ACTIONS[j] + " gate",
3921
3922- "locked": j == 0,
3923
3924- "callback": function (item) { lockGate(item.locked); }
3925
3926- });
3927
3928- // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
3929
3930- else if (state.gate && state.gate.locked != gates[0].gate.locked)
3931
3932- for (var j = 0; j < gates.length; ++j)
3933
3934- delete gates[j].gate.locked;
3935
3936- }
3937
3938-
3939
3940- // Place wall conversion options after gate lock/unlock icons.
3941
3942- var items = gates.concat(walls);
3943
3944- if (items.length)
3945
3946- setupUnitPanel(GATE, usedPanels, entState, playerState, items);
3947
3948- else
3949
3950- rightUsed = false;
3951
3952- }
3953
3954- else if (entState.pack)
3955
3956- {
3957
3958- var items = [];
3959
3960- var packButton = false;
3961
3962- var unpackButton = false;
3963
3964- var packCancelButton = false;
3965
3966- var unpackCancelButton = false;
3967
3968- for (var i in selection)
3969
3970- {
3971
3972- // Find un/packable entities
3973
3974- var state = GetEntityState(selection[i]);
3975
3976- if (state.pack)
3977
3978- {
3979
3980- if (state.pack.progress == 0)
3981
3982- {
3983
3984- if (!state.pack.packed)
3985
3986- packButton = true;
3987
3988- else if (state.pack.packed)
3989
3990- unpackButton = true;
3991
3992- }
3993
3994- else
3995
3996- {
3997
3998- // Already un/packing - show cancel button
3999
4000- if (!state.pack.packed)
4001
4002- packCancelButton = true;
4003
4004- else if (state.pack.packed)
4005
4006- unpackCancelButton = true;
4007
4008- }
4009
4010- }
4011
4012- }
4013
4014- if (packButton)
4015
4016- items.push({ "packing": false, "packed": false, "tooltip": "Pack", "callback": function() { packUnit(true); } });
4017
4018- if (unpackButton)
4019
4020- items.push({ "packing": false, "packed": true, "tooltip": "Unpack", "callback": function() { packUnit(false); } });
4021
4022- if (packCancelButton)
4023
4024- items.push({ "packing": true, "packed": false, "tooltip": "Cancel packing", "callback": function() { cancelPackUnit(true); } });
4025
4026- if (unpackCancelButton)
4027
4028- items.push({ "packing": true, "packed": true, "tooltip": "Cancel unpacking", "callback": function() { cancelPackUnit(false); } });
4029
4030-
4031
4032- if (items.length)
4033
4034- setupUnitPanel(PACK, usedPanels, entState, playerState, items);
4035
4036- else
4037
4038- rightUsed = false;
4039
4040- }
4041
4042- else
4043
4044- rightUsed = false;
4045
4046-
4047
4048- if (!rightUsed)
4049
4050- {
4051
4052- // The right pane is empty. Fill the pane with a sane type.
4053
4054- // Prefer buildables for units and trainables for structures.
4055
4056- if (buildableEnts.length && (hasClass(entState, "Unit") || !trainableEnts.length))
4057
4058- setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts, startBuildingPlacement);
4059
4060- else if (trainableEnts.length)
4061
4062- setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
4063
4064- function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
4065
4066- }
4067
4068- // Show technologies if the active panel has at most one row of icons.
4069
4070- if (entState.production && entState.production.technologies.length)
4071
4072- {
4073
4074- var activepane = usedPanels[CONSTRUCTION] ? buildableEnts.length : trainableEnts.length;
4075
4076- if (selection.length == 1 || activepane <= 8)
4077
4078- setupUnitPanel(RESEARCH, usedPanels, entState, playerState, entState.production.technologies,
4079
4080- function (researchType) { addResearchToQueue(entState.id, researchType); } );
4081
4082- }
4083
4084-
4085
4086 if (entState.production && entState.production.queue.length)
4087
4088 setupUnitPanel(QUEUE, usedPanels, entState, playerState, entState.production.queue,
4089
4090 function (item) { removeFromProductionQueue(entState.id, item.id); } );
4091
4092@@ -1204,7 +1210,7 @@
4093
4094 var panel = getGUIObjectByName("unit" + panelName + "Panel");
4095
4096 if (usedPanels[panelName])
4097
4098 panel.hidden = false;
4099
4100- else
4101
4102+ else if (panel)
4103
4104 panel.hidden = true;
4105
4106 }
4107
4108 }
4109
4110@@ -1212,8 +1218,11 @@
4111
4112 // Force hide commands panels
4113
4114 function hideUnitCommands()
4115
4116 {
4117
4118- for each (var panelName in g_unitPanels)
4119
4120- getGUIObjectByName("unit" + panelName + "Panel").hidden = true;
4121
4122+ for each (var panelName in g_unitPanels) {
4123
4124+ var panel = getGUIObjectByName("unit" + panelName + "Panel");
4125
4126+ if (panel)
4127
4128+ panel.hidden = true;
4129
4130+ }
4131
4132 }
4133
4134
4135
4136 // Get all of the available entities which can be trained by the selected entities
4137