Index: binaries/data/mods/public/gui/page_replay.xml
===================================================================
--- binaries/data/mods/public/gui/page_replay.xml (revision 0)
+++ binaries/data/mods/public/gui/page_replay.xml (working copy)
@@ -0,0 +1,15 @@
+
+
+ common/setup.xml
+ common/styles.xml
+ common/sprite1.xml
+ common/icon_sprites.xml
+
+ common/common_sprites.xml
+ common/common_styles.xml
+
+ session/sprites.xml
+ session/styles.xml
+ replay/replay.xml
+ common/global.xml
+
Index: binaries/data/mods/public/gui/replay/input.js
===================================================================
--- binaries/data/mods/public/gui/replay/input.js (revision 0)
+++ binaries/data/mods/public/gui/replay/input.js (working copy)
@@ -0,0 +1,457 @@
+const SDL_BUTTON_LEFT = 1;
+const SDL_BUTTON_MIDDLE = 2;
+const SDL_BUTTON_RIGHT = 3;
+const SDLK_LEFTBRACKET = 91;
+const SDLK_RIGHTBRACKET = 93;
+const SDLK_RSHIFT = 303;
+const SDLK_LSHIFT = 304;
+const SDLK_RCTRL = 305;
+const SDLK_LCTRL = 306;
+const SDLK_RALT = 307;
+const SDLK_LALT = 308;
+// TODO: these constants should be defined somewhere else instead, in
+// case any other code wants to use them too
+
+const ACTION_NONE = 0;
+const ACTION_GARRISON = 1;
+const ACTION_REPAIR = 2;
+var preSelectedAction = ACTION_NONE;
+
+const INPUT_NORMAL = 0;
+const INPUT_SELECTING = 1;
+const INPUT_BANDBOXING = 2;
+
+var inputState = INPUT_NORMAL;
+
+var mouseX = 0;
+var mouseY = 0;
+var mouseIsOverObject = false;
+
+// Number of pixels the mouse can move before the action is considered a drag
+var maxDragDelta = 4;
+
+// Time in milliseconds in which a double click is recognized
+const doubleClickTime = 500;
+var doubleClickTimer = 0;
+var doubleClicked = false;
+// Store the previously clicked entity - ensure a double/triple click happens on the same entity
+var prevClickedEntity = 0;
+
+// Same double-click behaviour for hotkey presses
+const doublePressTime = 500;
+var doublePressTimer = 0;
+var prevHotkey = 0;
+
+
+var dragStart; // used for remembering mouse coordinates at start of drag operations
+
+// Limits bandboxed selections to certain types of entities based on priority
+function getPreferredEntities(ents)
+{
+ var entStateList = [];
+ var preferredEnts = [];
+
+ // Check if there are units in the selection and get a list of entity states
+ for each (var ent in ents)
+ {
+ var entState = GetEntityState(ent);
+ if (!entState)
+ continue;
+ if (hasClass(entState, "Unit"))
+ preferredEnts.push(ent);
+
+ entStateList.push(entState);
+ }
+
+ // If there are no units, check if there are defensive entities in the selection
+ if (!preferredEnts.length)
+ for (var i = 0; i < ents.length; i++)
+ if (hasClass(entStateList[i], "Defensive"))
+ preferredEnts.push(ents[i]);
+
+ return preferredEnts;
+}
+
+// Removes any support units from the passed list of entities
+function getMilitaryEntities(ents)
+{
+ var militaryEnts = [];
+ for each (var ent in ents)
+ {
+ var entState = GetEntityState(ent);
+ if (!hasClass(entState, "Support"))
+ militaryEnts.push(ent);
+ }
+ return militaryEnts;
+}
+
+function handleInputBeforeGui(ev, hoveredObject)
+{
+ // Capture mouse position so we can use it for displaying cursors,
+ // and key states
+ switch (ev.type)
+ {
+ case "mousebuttonup":
+ case "mousebuttondown":
+ case "mousemotion":
+ mouseX = ev.x;
+ mouseY = ev.y;
+ break;
+ }
+
+ // Remember whether the mouse is over a GUI object or not
+ mouseIsOverObject = (hoveredObject != null);
+
+ // Close the menu when interacting with the game world
+ if (!mouseIsOverObject && (ev.type =="mousebuttonup" || ev.type == "mousebuttondown")
+ && (ev.button == SDL_BUTTON_LEFT || ev.button == SDL_BUTTON_RIGHT))
+ closeMenu();
+
+ // State-machine processing:
+ //
+ // (This is for states which should override the normal GUI processing - events will
+ // be processed here before being passed on, and propagation will stop if this function
+ // returns true)
+ //
+ // TODO: it'd probably be nice to have a better state-machine system, with guaranteed
+ // entry/exit functions, since this is a bit broken now
+
+ switch (inputState)
+ {
+ case INPUT_BANDBOXING:
+ switch (ev.type)
+ {
+ case "mousemotion":
+ var x0 = dragStart[0];
+ var y0 = dragStart[1];
+ var x1 = ev.x;
+ var y1 = ev.y;
+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
+
+ var bandbox = getGUIObjectByName("bandbox");
+ bandbox.size = [x0, y0, x1, y1].join(" ");
+ bandbox.hidden = false;
+
+ // TODO: Should we handle "control all units" here as well?
+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
+ g_Selection.setHighlightList(ents);
+
+ return false;
+
+ case "mousebuttonup":
+ if (ev.button == SDL_BUTTON_LEFT)
+ {
+ var x0 = dragStart[0];
+ var y0 = dragStart[1];
+ var x1 = ev.x;
+ var y1 = ev.y;
+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
+
+ var bandbox = getGUIObjectByName("bandbox");
+ bandbox.hidden = true;
+
+ // Get list of entities limited to preferred entities
+ // TODO: Should we handle "control all units" here as well?
+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
+ var preferredEntities = getPreferredEntities(ents)
+
+ if (preferredEntities.length)
+ {
+ ents = preferredEntities;
+
+ if (Engine.HotkeyIsPressed("selection.milonly"))
+ {
+ var militaryEntities = getMilitaryEntities(ents);
+ if (militaryEntities.length)
+ ents = militaryEntities;
+ }
+ }
+
+ // Remove the bandbox hover highlighting
+ g_Selection.setHighlightList([]);
+
+ // Update the list of selected units
+ if (Engine.HotkeyIsPressed("selection.add"))
+ {
+ g_Selection.addList(ents);
+ }
+ else if (Engine.HotkeyIsPressed("selection.remove"))
+ {
+ g_Selection.removeList(ents);
+ }
+ else
+ {
+ g_Selection.reset();
+ g_Selection.addList(ents);
+ }
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+ else if (ev.button == SDL_BUTTON_RIGHT)
+ {
+ // Cancel selection
+ var bandbox = getGUIObjectByName("bandbox");
+ bandbox.hidden = true;
+
+ g_Selection.setHighlightList([]);
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+ break;
+ }
+ break;
+ }
+
+ return false;
+}
+
+function handleInputAfterGui(ev)
+{
+ if (ev.hotkey == "session.showstatusbars")
+ {
+ g_ShowAllStatusBars = (ev.type == "hotkeydown");
+ recalculateStatusBarDisplay();
+ }
+
+ // State-machine processing:
+
+ switch (inputState)
+ {
+ case INPUT_NORMAL:
+ switch (ev.type)
+ {
+ case "mousemotion":
+ // Highlight the first hovered entity (if any)
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ if (ents.length)
+ g_Selection.setHighlightList([ents[0]]);
+ else
+ g_Selection.setHighlightList([]);
+
+ return false;
+
+ case "mousebuttondown":
+ if (ev.button == SDL_BUTTON_LEFT)
+ {
+ dragStart = [ ev.x, ev.y ];
+ inputState = INPUT_SELECTING;
+ return true;
+ }
+ break;
+ }
+ break;
+
+ case INPUT_SELECTING:
+ switch (ev.type)
+ {
+ case "mousemotion":
+ // If the mouse moved further than a limit, switch to bandbox mode
+ var dragDeltaX = ev.x - dragStart[0];
+ var dragDeltaY = ev.y - dragStart[1];
+
+ if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
+ {
+ inputState = INPUT_BANDBOXING;
+ return false;
+ }
+
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ g_Selection.setHighlightList(ents);
+ return false;
+
+ case "mousebuttonup":
+ if (ev.button == SDL_BUTTON_LEFT)
+ {
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ if (!ents.length)
+ {
+ if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove"))
+ {
+ g_Selection.reset();
+ resetIdleUnit();
+ }
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+
+ var selectedEntity = ents[0];
+ var now = new Date();
+
+ // If camera following and we select different unit, stop
+ if (Engine.GetFollowedEntity() != selectedEntity)
+ {
+ Engine.CameraFollow(0);
+ }
+
+ if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity))
+ {
+ // Double click or triple click has occurred
+ var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen");
+ var matchRank = true;
+ var templateToMatch;
+
+ // Check for double click or triple click
+ if (!doubleClicked)
+ {
+ // If double click hasn't already occurred, this is a double click.
+ // Select similar units regardless of rank
+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).identity.selectionGroupName;
+ if (templateToMatch)
+ {
+ matchRank = false;
+ }
+ else
+ { // No selection group name defined, so fall back to exact match
+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
+ }
+
+ doubleClicked = true;
+ // Reset the timer so the user has an extra period 'doubleClickTimer' to do a triple-click
+ doubleClickTimer = now.getTime();
+ }
+ else
+ {
+ // Double click has already occurred, so this is a triple click.
+ // Select units matching exact template name (same rank)
+ templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
+ }
+
+ // TODO: Should we handle "control all units" here as well?
+ ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
+ }
+ else
+ {
+ // It's single click right now but it may become double or triple click
+ doubleClicked = false;
+ doubleClickTimer = now.getTime();
+ prevClickedEntity = selectedEntity;
+
+ // We only want to include the first picked unit in the selection
+ ents = [ents[0]];
+ }
+
+ // Update the list of selected units
+ if (Engine.HotkeyIsPressed("selection.add"))
+ {
+ g_Selection.addList(ents);
+ }
+ else if (Engine.HotkeyIsPressed("selection.remove"))
+ {
+ g_Selection.removeList(ents);
+ }
+ else
+ {
+ g_Selection.reset();
+ g_Selection.addList(ents);
+ }
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+ break;
+ }
+ break;
+ }
+ return false;
+}
+
+// Called by unit selection buttons
+function changePrimarySelectionGroup(templateName)
+{
+ if (Engine.HotkeyIsPressed("session.deselectgroup"))
+ g_Selection.makePrimarySelection(templateName, true);
+ else
+ g_Selection.makePrimarySelection(templateName, false);
+}
+
+
+// Set the camera to follow the given unit
+function setCameraFollow(entity)
+{
+ // Follow the given entity if it's a unit
+ if (entity)
+ {
+ var entState = GetEntityState(entity);
+ if (entState && hasClass(entState, "Unit"))
+ {
+ Engine.CameraFollow(entity);
+ return;
+ }
+ }
+
+ // Otherwise stop following
+ Engine.CameraFollow(0);
+}
+
+var lastIdleUnit = 0;
+var currIdleClass = 0;
+var lastIdleType = undefined;
+
+function resetIdleUnit()
+{
+ lastIdleUnit = 0;
+ currIdleClass = 0;
+ lastIdleType = undefined;
+}
+
+function findIdleUnit(classes)
+{
+ var append = Engine.HotkeyIsPressed("selection.add");
+ var selectall = Engine.HotkeyIsPressed("selection.offscreen");
+
+ // Reset the last idle unit, etc., if the selection type has changed.
+ var type = classes.join();
+ if (selectall || type != lastIdleType)
+ resetIdleUnit();
+ lastIdleType = type;
+
+ // If selectall is true, there is no limit and it's necessary to iterate
+ // over all of the classes, resetting only when the first match is found.
+ var matched = false;
+
+ for (var i = 0; i < classes.length; ++i)
+ {
+ var data = { idleClass: classes[currIdleClass], prevUnit: lastIdleUnit, limit: 1 };
+ if (append)
+ data.excludeUnits = g_Selection.toList();
+
+ if (selectall)
+ data = { idleClass: classes[currIdleClass] };
+
+ // Check if we have new valid entity
+ var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
+ if (idleUnits.length && idleUnits[0] != lastIdleUnit)
+ {
+ lastIdleUnit = idleUnits[0];
+ if (!append && (!selectall || selectall && !matched))
+ g_Selection.reset()
+
+ if (selectall)
+ g_Selection.addList(idleUnits);
+ else
+ {
+ g_Selection.addList([lastIdleUnit]);
+ Engine.CameraFollow(lastIdleUnit);
+ return;
+ }
+
+ matched = true;
+ }
+
+ lastIdleUnit = 0;
+ currIdleClass = (currIdleClass + 1) % classes.length;
+ }
+
+ // TODO: display a message or play a sound to indicate no more idle units, or something
+ // Reset for next cycle
+ resetIdleUnit();
+}
+
+// Ignored: we ignore these callbacks in replay mode
+function removeFromProductionQueue() {};
+function unloadTemplate() {};
+function unloadAll() {};
Index: binaries/data/mods/public/gui/replay/input.js
===================================================================
--- binaries/data/mods/public/gui/replay/input.js (revision 0)
+++ binaries/data/mods/public/gui/replay/input.js (working copy)
Property changes on: binaries/data/mods/public/gui/replay/input.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/gui/replay/menu.js
===================================================================
--- binaries/data/mods/public/gui/replay/menu.js (revision 0)
+++ binaries/data/mods/public/gui/replay/menu.js (working copy)
@@ -0,0 +1,211 @@
+const PAUSE = "Pause";
+const RESUME = "Resume";
+
+/*
+ * MENU POSITION CONSTANTS
+*/
+
+// Menu / panel border size
+const MARGIN = 4;
+
+// Includes the main menu button
+const NUM_BUTTONS = 4;
+
+// Regular menu buttons
+const BUTTON_HEIGHT = 32;
+
+// The position where the bottom of the menu will end up (currently 228)
+const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN;
+
+// Menu starting position: bottom
+const MENU_BOTTOM = 0;
+
+// Menu starting position: top
+const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
+
+// Menu starting position: overall
+const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
+
+// Number of pixels per millisecond to move
+const MENU_SPEED = 1.2;
+
+var isMenuOpen = false;
+var menu;
+
+// Ignore size defined in XML and set the actual menu size here
+function initMenuPosition()
+{
+ menu = getGUIObjectByName("menu");
+ menu.size = INITIAL_MENU_POSITION;
+}
+
+
+// =============================================================================
+// Overall Menu
+// =============================================================================
+//
+// Slide menu
+function updateMenuPosition(dt)
+{
+ if (isMenuOpen)
+ {
+ var maxOffset = END_MENU_POSITION - menu.size.bottom;
+ if (maxOffset > 0)
+ {
+ var offset = Math.min(MENU_SPEED * dt, maxOffset);
+ var size = menu.size;
+ size.top += offset;
+ size.bottom += offset;
+ menu.size = size;
+ }
+ }
+ else
+ {
+ var maxOffset = menu.size.top - MENU_TOP;
+ if (maxOffset > 0)
+ {
+ var offset = Math.min(MENU_SPEED * dt, maxOffset);
+ var size = menu.size;
+ size.top -= offset;
+ size.bottom -= offset;
+ menu.size = size;
+ }
+ }
+}
+
+// Opens the menu by revealing the screen which contains the menu
+function openMenu()
+{
+// playButtonSound();
+ isMenuOpen = true;
+}
+
+// Closes the menu and resets position
+function closeMenu()
+{
+// playButtonSound();
+ isMenuOpen = false;
+}
+
+function toggleMenu()
+{
+ if (isMenuOpen == true)
+ closeMenu();
+ else
+ openMenu();
+}
+
+// Menu buttons
+// =============================================================================
+function settingsMenuButton()
+{
+ closeMenu();
+ closeOpenDialogs();
+ openSettings(true);
+}
+
+function pauseMenuButton()
+{
+ togglePause();
+}
+
+function resignMenuButton()
+{
+ closeMenu();
+ closeOpenDialogs();
+ pauseGame();
+ var btCaptions = ["Yes", "No"];
+ var btCode = [resignGame, resumeGame];
+ messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode);
+}
+
+function exitMenuButton()
+{
+ closeMenu();
+ closeOpenDialogs();
+ pauseGame();
+ var btCaptions = ["Yes", "No"];
+ var btCode = [leaveGame, resumeGame];
+ messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode);
+}
+
+function openDeleteDialog(selection)
+{
+ closeMenu();
+ closeOpenDialogs();
+
+ var deleteSelectedEntities = function ()
+ {
+ Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection});
+ };
+
+ var btCaptions = ["Yes", "No"];
+ var btCode = [deleteSelectedEntities, resumeGame];
+
+ messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode);
+}
+
+// Menu functions
+// =============================================================================
+
+function openSettings(pause)
+{
+ getGUIObjectByName("settingsDialogPanel").hidden = false;
+ if (pause)
+ pauseGame();
+}
+
+function closeSettings(resume)
+{
+ getGUIObjectByName("settingsDialogPanel").hidden = true;
+ if (resume)
+ resumeGame();
+}
+
+function pauseGame()
+{
+ getGUIObjectByName("pauseButtonText").caption = RESUME;
+ getGUIObjectByName("pauseOverlay").hidden = false;
+ setPaused(true);
+}
+
+function resumeGame()
+{
+ getGUIObjectByName("pauseButtonText").caption = PAUSE;
+ getGUIObjectByName("pauseOverlay").hidden = true;
+ setPaused(false);
+}
+
+function togglePause()
+{
+ closeMenu();
+ closeOpenDialogs();
+
+ var pauseOverlay = getGUIObjectByName("pauseOverlay");
+
+ if (pauseOverlay.hidden)
+ {
+ getGUIObjectByName("pauseButtonText").caption = RESUME;
+ setPaused(true);
+
+ }
+ else
+ {
+ setPaused(false);
+ getGUIObjectByName("pauseButtonText").caption = PAUSE;
+ }
+
+ pauseOverlay.hidden = !pauseOverlay.hidden;
+}
+
+function toggleDeveloperOverlay()
+{
+ var devCommands = getGUIObjectByName("devCommands");
+ devCommands.hidden = !devCommands.hidden;
+}
+
+function closeOpenDialogs()
+{
+ closeMenu();
+ closeSettings(false);
+}
Index: binaries/data/mods/public/gui/replay/menu.js
===================================================================
--- binaries/data/mods/public/gui/replay/menu.js (revision 0)
+++ binaries/data/mods/public/gui/replay/menu.js (working copy)
Property changes on: binaries/data/mods/public/gui/replay/menu.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/gui/replay/replay.js
===================================================================
--- binaries/data/mods/public/gui/replay/replay.js (revision 0)
+++ binaries/data/mods/public/gui/replay/replay.js (working copy)
@@ -0,0 +1,429 @@
+// Network Mode
+var g_IsNetworked = false;
+
+// Cache the basic player data (name, civ, color)
+var g_Players = [];
+// Cache the useful civ data
+var g_CivData = {};
+
+var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
+
+// Cache dev-mode settings that are frequently or widely used
+var g_DevSettings = {
+ controlAll: false
+};
+
+// Whether status bars should be shown for all of the player's units.
+var g_ShowAllStatusBars = false;
+
+// Indicate when one of the current player's training queues is blocked
+// (this is used to support population counter blinking)
+var g_IsTrainingBlocked = false;
+
+// Cache EntityStates
+var g_EntityStates = {}; // {id:entState}
+
+// Whether the player has lost/won and reached the end of their game
+var g_GameEnded = false;
+
+// Colors to flash when pop limit reached
+const DEFAULT_POPULATION_COLOR = "white";
+const POPULATION_ALERT_COLOR = "orange";
+
+function GetEntityState(entId)
+{
+ if (!(entId in g_EntityStates))
+ {
+ var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
+ g_EntityStates[entId] = entState;
+ }
+
+ return g_EntityStates[entId];
+}
+
+// Cache TemplateData
+var g_TemplateData = {}; // {id:template}
+
+
+function GetTemplateData(templateName)
+{
+ if (!(templateName in g_TemplateData))
+ {
+ var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
+ g_TemplateData[templateName] = template;
+ }
+
+ return g_TemplateData[templateName];
+}
+
+// Cache TechnologyData
+var g_TechnologyData = {}; // {id:template}
+
+function GetTechnologyData(technologyName)
+{
+ if (!(technologyName in g_TechnologyData))
+ {
+ var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
+ g_TechnologyData[technologyName] = template;
+ }
+
+ return g_TechnologyData[technologyName];
+}
+
+// Init
+function init(initData, hotloadData)
+{
+ if (initData)
+ {
+ g_IsNetworked = initData.isNetworked; // Set network mode
+ g_PlayerAssignments = initData.playerAssignments;
+
+ // Cache the player data
+ // (This may be updated at runtime by handleNetMessage)
+ g_Players = getPlayerData(g_PlayerAssignments);
+ }
+ else // Needed for autostart loading option
+ {
+ g_Players = getPlayerData(null);
+ }
+
+ // Cache civ data
+ g_CivData = loadCivData();
+ g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia", "Emblem": "session/icons/groups.png" };
+
+ initMenuPosition(); // set initial position
+
+ // Populate player selection dropdown
+ var playerNames = [];
+ var playerIDs = [];
+ for (var player in g_Players)
+ {
+ playerNames.push(g_Players[player].name);
+ playerIDs.push(player);
+ }
+
+ var viewPlayerDropdown = getGUIObjectByName("viewPlayer");
+ viewPlayerDropdown.list = playerNames;
+ viewPlayerDropdown.list_data = playerIDs;
+ viewPlayerDropdown.selected = 1;
+
+ // If in Atlas editor, disable the exit button
+ if (Engine.IsAtlasRunning())
+ getGUIObjectByName("menuExitButton").enabled = false;
+
+ if (hotloadData)
+ {
+ g_Selection.selected = hotloadData.selection;
+ }
+
+ onSimulationUpdate();
+
+ // Report the performance after 5 seconds (when we're still near
+ // the initial camera view) and a minute (when the profiler will
+ // have settled down if framerates as very low), to give some
+ // extremely rough indications of performance
+ setTimeout(function() { reportPerformance(5); }, 5000);
+ setTimeout(function() { reportPerformance(60); }, 60000);
+}
+
+function selectViewPlayer(playerID)
+{
+ Engine.SetPlayerID(playerID);
+ getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
+ getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name;
+}
+
+function reportPerformance(time)
+{
+ var settings = Engine.GetMapSettings();
+ var data = {
+ time: time,
+ map: settings.Name,
+ seed: settings.Seed, // only defined for random maps
+ size: settings.Size, // only defined for random maps
+ profiler: Engine.GetProfilerState()
+ };
+
+ Engine.SubmitUserReport("profile", 3, JSON.stringify(data));
+}
+
+function leaveGame()
+{
+ var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
+
+ var mapSettings = Engine.GetMapSettings();
+
+ endGame();
+
+ Engine.SwitchGuiPage("page_summary.xml", {
+ "gameResult" : "Replay mode ended",
+ "timeElapsed" : extendedSimState.timeElapsed,
+ "playerStates": extendedSimState.players,
+ "players": g_Players,
+ "mapSettings": mapSettings
+ });
+}
+
+// Return some data that we'll use when hotloading this file after changes
+function getHotloadData()
+{
+ return { selection: g_Selection.selected };
+}
+
+var lastTickTime = new Date;
+
+/**
+ * Called every frame.
+ */
+function onTick()
+{
+ var now = new Date;
+ var tickLength = new Date - lastTickTime;
+ lastTickTime = now;
+
+ while (true)
+ {
+ var message = Engine.PollNetworkClient();
+ if (!message)
+ break;
+ handleNetMessage(message);
+ }
+
+ // If the selection changed, we need to regenerate the sim display (the display depends on both the
+ // simulation state and the current selection).
+ if (g_Selection.dirty)
+ {
+ onSimulationUpdate();
+
+ // Display rally points for selected buildings
+ Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
+ }
+
+ // Run timers
+ updateTimers();
+
+ // Animate menu
+ updateMenuPosition(tickLength);
+
+ // When training is blocked, flash population (alternates colour every 500msec)
+ if (g_IsTrainingBlocked && (Date.now() % 1000) < 500)
+ getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR;
+ else
+ getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR;
+
+ // Clear renamed entities list
+ Engine.GuiInterfaceCall("ClearRenamedEntities");
+}
+
+function onReplayFinished()
+{
+ closeMenu();
+ closeOpenDialogs();
+ pauseGame();
+ var btCaptions = ["Yes", "No"];
+ var btCode = [leaveGame, resumeGame];
+ messageBox(400, 200, "The replay has finished. Do you want to quit?", "Confirmation", 0, btCaptions, btCode);
+}
+
+/**
+ * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
+ * update (see session.xml), or from onTick when the selection has changed.
+ */
+function onSimulationUpdate()
+{
+ g_Selection.dirty = false;
+ g_EntityStates = {};
+ g_TemplateData = {};
+ g_TechnologyData = {};
+
+ var simState = Engine.GuiInterfaceCall("GetSimulationState");
+
+ // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing
+ if (!simState)
+ return;
+
+ handleNotifications();
+
+ if (g_ShowAllStatusBars)
+ recalculateStatusBarDisplay();
+
+ updateDebug(simState);
+ updatePlayerDisplay(simState);
+ updateSelectionDetails(true);
+ updateResearchDisplay();
+ updateTimeElapsedCounter(simState);
+}
+
+function updateDebug(simState)
+{
+ var debug = getGUIObjectByName("debug");
+
+ if (getGUIObjectByName("devDisplayState").checked)
+ {
+ debug.hidden = false;
+ }
+ else
+ {
+ debug.hidden = true;
+ return;
+ }
+
+ var conciseSimState = deepcopy(simState);
+ conciseSimState.players = "<<>>";
+ var text = "simulation: " + uneval(conciseSimState);
+
+ var selection = g_Selection.toList();
+ if (selection.length)
+ {
+ var entState = GetEntityState(selection[0]);
+ if (entState)
+ {
+ var template = GetTemplateData(entState.template);
+ text += "\n\nentity: {\n";
+ for (var k in entState)
+ text += " "+k+":"+uneval(entState[k])+"\n";
+ text += "}\n\ntemplate: " + uneval(template);
+ }
+ }
+
+ debug.caption = text;
+}
+
+function updatePlayerDisplay(simState)
+{
+ var playerState = simState.players[Engine.GetPlayerID()];
+ if (!playerState)
+ return;
+
+ getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food;
+ getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood;
+ getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone;
+ getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal;
+ getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
+
+ g_IsTrainingBlocked = playerState.trainingBlocked;
+}
+
+function selectAndMoveTo(ent)
+{
+ var entState = GetEntityState(ent);
+ if (!entState)
+ return;
+
+ g_Selection.reset();
+ g_Selection.addList([ent]);
+
+ var position = entState.position;
+ Engine.CameraMoveTo(position.x, position.z);
+}
+
+function updateResearchDisplay()
+{
+ var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID());
+ if (!researchStarted)
+ return;
+
+ // Set up initial positioning.
+ var buttonSideLength = getGUIObjectByName("researchStartedButton[0]").size.right;
+ for (var i = 0; i < 10; ++i)
+ {
+ var button = getGUIObjectByName("researchStartedButton[" + i + "]");
+ var size = button.size;
+ size.top = (4 + buttonSideLength) * i;
+ size.bottom = size.top + buttonSideLength;
+ button.size = size;
+ }
+
+ var numButtons = 0;
+ for (var tech in researchStarted)
+ {
+ // Show at most 10 in-progress techs.
+ if (numButtons >= 10)
+ break;
+
+ var template = GetTechnologyData(tech);
+ var button = getGUIObjectByName("researchStartedButton[" + numButtons + "]");
+ button.hidden = false;
+ button.tooltip = getEntityNames(template);
+ button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher);
+
+ var icon = "stretched:session/portraits/" + template.icon;
+ getGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon;
+
+ // Scale the progress indicator.
+ var size = getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size;
+
+ // Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
+ size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left));
+ getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size;
+
+ ++numButtons;
+ }
+
+ // Hide unused buttons.
+ for (var i = numButtons; i < 10; ++i)
+ getGUIObjectByName("researchStartedButton[" + i + "]").hidden = true;
+}
+
+function updateTimeElapsedCounter(simState)
+{
+ var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
+ timeElapsedCounter.caption = timeToString(simState.timeElapsed);
+}
+
+// Toggles the display of status bars for all of the player's entities.
+function recalculateStatusBarDisplay()
+{
+ if (g_ShowAllStatusBars)
+ var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID());
+ else
+ {
+ var selected = g_Selection.toList();
+ for each (var ent in g_Selection.highlighted)
+ selected.push(ent);
+
+ // Remove selected entities from the 'all entities' array, to avoid disabling their status bars.
+ var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter(
+ function(idx) { return (selected.indexOf(idx) == -1); }
+ );
+ }
+
+ Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars });
+}
+
+// Temporarily adding this here
+const AMBIENT_TEMPERATE = "temperate";
+var currentAmbient;
+function playRandomAmbient(type)
+{
+ switch (type)
+ {
+ case AMBIENT_TEMPERATE:
+ // Seem to need the underscore at the end of "temperate" to avoid crash
+ // (Might be caused by trying to randomly load day_temperate.xml)
+// currentAmbient = newRandomSound("ambient", "temperate_", "dayscape");
+
+ const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
+ currentAmbient = new AmbientSound(AMBIENT);
+
+ if (currentAmbient)
+ {
+ currentAmbient.loop();
+ }
+ break;
+
+ default:
+ console.write("Unrecognized ambient type: " + type);
+ break;
+ }
+}
+
+// Temporarily adding this here
+function stopAmbient()
+{
+ if (currentAmbient)
+ {
+ currentAmbient.free();
+ currentAmbient = null;
+ }
+}
Index: binaries/data/mods/public/gui/replay/replay.js
===================================================================
--- binaries/data/mods/public/gui/replay/replay.js (revision 0)
+++ binaries/data/mods/public/gui/replay/replay.js (working copy)
Property changes on: binaries/data/mods/public/gui/replay/replay.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/gui/replay/replay.xml
===================================================================
--- binaries/data/mods/public/gui/replay/replay.xml (revision 0)
+++ binaries/data/mods/public/gui/replay/replay.xml (working copy)
@@ -0,0 +1,849 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Return to Main Menu
+ leaveGame()
+
+
+
+
+
Index: binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- binaries/data/mods/public/gui/session/selection_details.js (revision 12723)
+++ binaries/data/mods/public/gui/session/selection_details.js (working copy)
@@ -254,7 +254,7 @@
}
// Updates middle entity Selection Details Panel
-function updateSelectionDetails()
+function updateSelectionDetails(replay)
{
var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
var detailsPanel = getGUIObjectByName("selectionDetails");
@@ -296,5 +296,5 @@
commandsPanel.hidden = false;
// Fill out commands panel for specific unit selected (or first unit of primary group)
- updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
+ updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection, replay);
}
Index: binaries/data/mods/public/gui/summary/summary.js
===================================================================
--- binaries/data/mods/public/gui/summary/summary.js (revision 12723)
+++ binaries/data/mods/public/gui/summary/summary.js (working copy)
@@ -276,5 +276,6 @@
function onTick()
{
// Update music state
- global.music.updateTimer();
+ if (global.music)
+ global.music.updateTimer();
}
Index: source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- source/gui/scripting/ScriptFunctions.cpp (revision 12723)
+++ source/gui/scripting/ScriptFunctions.cpp (working copy)
@@ -167,6 +167,12 @@
return -1;
}
+void SetPlayerID(void* UNUSED(cbdata), int id)
+{
+ if (g_Game)
+ g_Game->SetPlayerID(id);
+}
+
std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
{
std::wstring name = g_PlayerName.FromUTF8();
@@ -617,6 +623,7 @@
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
+ scriptInterface.RegisterFunction("SetPlayerID");
scriptInterface.RegisterFunction("GetDefaultPlayerName");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("RestartInAtlas");
Index: source/network/NetTurnManager.cpp
===================================================================
--- source/network/NetTurnManager.cpp (revision 12723)
+++ source/network/NetTurnManager.cpp (working copy)
@@ -465,6 +465,39 @@
+CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
+ CNetLocalTurnManager(simulation, replay)
+{
+}
+
+void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
+{
+ m_ReplayCommands[turn][player].push_back(command);
+}
+
+void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
+{
+ DoTurn(turn);
+}
+
+void CNetReplayTurnManager::DoTurn(u32 turn)
+{
+ std::map > playerCommands = m_ReplayCommands[turn];
+ std::map >::iterator it;
+ for (it = playerCommands.begin(); it != playerCommands.end(); ++it)
+ {
+ int player = it->first;
+ for (size_t i = 0; i < it->second.size(); ++i)
+ {
+ CScriptValRooted data = m_Simulation2.GetScriptInterface().ParseJSON(it->second[i]);
+ AddCommand(m_ClientId, player, data, m_CurrentTurn + 1);
+ }
+ }
+}
+
+
+
+
CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
{
Index: source/network/NetTurnManager.h
===================================================================
--- source/network/NetTurnManager.h (revision 12723)
+++ source/network/NetTurnManager.h (working copy)
@@ -22,6 +22,7 @@
#include
#include