Ticket #9: visualreplay-WIP-r12723.patch

File visualreplay-WIP-r12723.patch, 95.1 KB (added by historic_bruno, 12 years ago)
  • binaries/data/mods/public/gui/page_replay.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<page>
     3    <include>common/setup.xml</include>
     4    <include>common/styles.xml</include>
     5    <include>common/sprite1.xml</include>
     6    <include>common/icon_sprites.xml</include>
     7
     8    <include>common/common_sprites.xml</include>
     9    <include>common/common_styles.xml</include>
     10
     11    <include>session/sprites.xml</include>
     12    <include>session/styles.xml</include>
     13    <include>replay/replay.xml</include>
     14    <include>common/global.xml</include>
     15</page>
  • binaries/data/mods/public/gui/replay/input.js

     
     1const SDL_BUTTON_LEFT = 1;
     2const SDL_BUTTON_MIDDLE = 2;
     3const SDL_BUTTON_RIGHT = 3;
     4const SDLK_LEFTBRACKET = 91;
     5const SDLK_RIGHTBRACKET = 93;
     6const SDLK_RSHIFT = 303;
     7const SDLK_LSHIFT = 304;
     8const SDLK_RCTRL = 305;
     9const SDLK_LCTRL = 306;
     10const SDLK_RALT = 307;
     11const SDLK_LALT = 308;
     12// TODO: these constants should be defined somewhere else instead, in
     13// case any other code wants to use them too
     14
     15const ACTION_NONE = 0;
     16const ACTION_GARRISON = 1;
     17const ACTION_REPAIR = 2;
     18var preSelectedAction = ACTION_NONE;
     19
     20const INPUT_NORMAL = 0;
     21const INPUT_SELECTING = 1;
     22const INPUT_BANDBOXING = 2;
     23
     24var inputState = INPUT_NORMAL;
     25
     26var mouseX = 0;
     27var mouseY = 0;
     28var mouseIsOverObject = false;
     29
     30// Number of pixels the mouse can move before the action is considered a drag
     31var maxDragDelta = 4;
     32
     33// Time in milliseconds in which a double click is recognized
     34const doubleClickTime = 500;
     35var doubleClickTimer = 0;
     36var doubleClicked = false;
     37// Store the previously clicked entity - ensure a double/triple click happens on the same entity
     38var prevClickedEntity = 0;
     39
     40// Same double-click behaviour for hotkey presses
     41const doublePressTime = 500;
     42var doublePressTimer = 0;
     43var prevHotkey = 0;
     44
     45
     46var dragStart; // used for remembering mouse coordinates at start of drag operations
     47
     48// Limits bandboxed selections to certain types of entities based on priority
     49function getPreferredEntities(ents)
     50{
     51    var entStateList = [];
     52    var preferredEnts = [];
     53
     54    // Check if there are units in the selection and get a list of entity states
     55    for each (var ent in ents)
     56    {
     57        var entState = GetEntityState(ent);
     58        if (!entState)
     59            continue;
     60        if (hasClass(entState, "Unit"))
     61            preferredEnts.push(ent);
     62
     63        entStateList.push(entState);
     64    }
     65
     66    // If there are no units, check if there are defensive entities in the selection
     67    if (!preferredEnts.length)
     68        for (var i = 0; i < ents.length; i++)
     69            if (hasClass(entStateList[i], "Defensive"))
     70                preferredEnts.push(ents[i]);
     71
     72    return preferredEnts;
     73}
     74
     75// Removes any support units from the passed list of entities
     76function getMilitaryEntities(ents)
     77{
     78    var militaryEnts = [];
     79    for each (var ent in ents)
     80    {
     81        var entState = GetEntityState(ent);
     82        if (!hasClass(entState, "Support"))
     83            militaryEnts.push(ent);
     84    }
     85    return militaryEnts;
     86}
     87
     88function handleInputBeforeGui(ev, hoveredObject)
     89{
     90    // Capture mouse position so we can use it for displaying cursors,
     91    // and key states
     92    switch (ev.type)
     93    {
     94    case "mousebuttonup":
     95    case "mousebuttondown":
     96    case "mousemotion":
     97        mouseX = ev.x;
     98        mouseY = ev.y;
     99        break;
     100    }
     101
     102    // Remember whether the mouse is over a GUI object or not
     103    mouseIsOverObject = (hoveredObject != null);
     104
     105    // Close the menu when interacting with the game world
     106    if (!mouseIsOverObject && (ev.type =="mousebuttonup" || ev.type == "mousebuttondown")
     107        && (ev.button == SDL_BUTTON_LEFT || ev.button == SDL_BUTTON_RIGHT))
     108        closeMenu();
     109
     110    // State-machine processing:
     111    //
     112    // (This is for states which should override the normal GUI processing - events will
     113    // be processed here before being passed on, and propagation will stop if this function
     114    // returns true)
     115    //
     116    // TODO: it'd probably be nice to have a better state-machine system, with guaranteed
     117    // entry/exit functions, since this is a bit broken now
     118
     119    switch (inputState)
     120    {
     121    case INPUT_BANDBOXING:
     122        switch (ev.type)
     123        {
     124        case "mousemotion":
     125            var x0 = dragStart[0];
     126            var y0 = dragStart[1];
     127            var x1 = ev.x;
     128            var y1 = ev.y;
     129            if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
     130            if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
     131
     132            var bandbox = getGUIObjectByName("bandbox");
     133            bandbox.size = [x0, y0, x1, y1].join(" ");
     134            bandbox.hidden = false;
     135
     136            // TODO: Should we handle "control all units" here as well?
     137            var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
     138            g_Selection.setHighlightList(ents);
     139
     140            return false;
     141
     142        case "mousebuttonup":
     143            if (ev.button == SDL_BUTTON_LEFT)
     144            {
     145                var x0 = dragStart[0];
     146                var y0 = dragStart[1];
     147                var x1 = ev.x;
     148                var y1 = ev.y;
     149                if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
     150                if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
     151
     152                var bandbox = getGUIObjectByName("bandbox");
     153                bandbox.hidden = true;
     154
     155                // Get list of entities limited to preferred entities
     156                // TODO: Should we handle "control all units" here as well?
     157                var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
     158                var preferredEntities = getPreferredEntities(ents)
     159
     160                if (preferredEntities.length)
     161                {
     162                    ents = preferredEntities;
     163
     164                    if (Engine.HotkeyIsPressed("selection.milonly"))
     165                    {
     166                        var militaryEntities = getMilitaryEntities(ents);
     167                        if (militaryEntities.length)
     168                            ents = militaryEntities;
     169                    }
     170                }
     171
     172                // Remove the bandbox hover highlighting
     173                g_Selection.setHighlightList([]);
     174
     175                // Update the list of selected units
     176                if (Engine.HotkeyIsPressed("selection.add"))
     177                {
     178                    g_Selection.addList(ents);
     179                }
     180                else if (Engine.HotkeyIsPressed("selection.remove"))
     181                {
     182                    g_Selection.removeList(ents);
     183                }
     184                else
     185                {
     186                    g_Selection.reset();
     187                    g_Selection.addList(ents);
     188                }
     189
     190                inputState = INPUT_NORMAL;
     191                return true;
     192            }
     193            else if (ev.button == SDL_BUTTON_RIGHT)
     194            {
     195                // Cancel selection
     196                var bandbox = getGUIObjectByName("bandbox");
     197                bandbox.hidden = true;
     198
     199                g_Selection.setHighlightList([]);
     200
     201                inputState = INPUT_NORMAL;
     202                return true;
     203            }
     204            break;
     205        }
     206        break;
     207    }
     208
     209    return false;
     210}
     211
     212function handleInputAfterGui(ev)
     213{
     214    if (ev.hotkey == "session.showstatusbars")
     215    {
     216        g_ShowAllStatusBars = (ev.type == "hotkeydown");
     217        recalculateStatusBarDisplay();
     218    }
     219
     220    // State-machine processing:
     221
     222    switch (inputState)
     223    {
     224    case INPUT_NORMAL:
     225        switch (ev.type)
     226        {
     227        case "mousemotion":
     228            // Highlight the first hovered entity (if any)
     229            var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
     230            if (ents.length)
     231                g_Selection.setHighlightList([ents[0]]);
     232            else
     233                g_Selection.setHighlightList([]);
     234
     235            return false;
     236
     237        case "mousebuttondown":
     238            if (ev.button == SDL_BUTTON_LEFT)
     239            {
     240                dragStart = [ ev.x, ev.y ];
     241                inputState = INPUT_SELECTING;
     242                return true;
     243            }
     244            break;
     245        }
     246        break;
     247       
     248    case INPUT_SELECTING:
     249        switch (ev.type)
     250        {
     251        case "mousemotion":
     252            // If the mouse moved further than a limit, switch to bandbox mode
     253            var dragDeltaX = ev.x - dragStart[0];
     254            var dragDeltaY = ev.y - dragStart[1];
     255
     256            if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
     257            {
     258                inputState = INPUT_BANDBOXING;
     259                return false;
     260            }
     261
     262            var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
     263            g_Selection.setHighlightList(ents);
     264            return false;
     265
     266        case "mousebuttonup":
     267            if (ev.button == SDL_BUTTON_LEFT)
     268            {
     269                var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
     270                if (!ents.length)
     271                {
     272                    if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove"))
     273                    {
     274                        g_Selection.reset();
     275                        resetIdleUnit();
     276                    }
     277                    inputState = INPUT_NORMAL;
     278                    return true;
     279                }
     280
     281                var selectedEntity = ents[0];
     282                var now = new Date();
     283
     284                // If camera following and we select different unit, stop
     285                if (Engine.GetFollowedEntity() != selectedEntity)
     286                {
     287                    Engine.CameraFollow(0);
     288                }
     289
     290                if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity))
     291                {
     292                    // Double click or triple click has occurred
     293                    var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen");
     294                    var matchRank = true;
     295                    var templateToMatch;
     296
     297                    // Check for double click or triple click
     298                    if (!doubleClicked)
     299                    {
     300                        // If double click hasn't already occurred, this is a double click.
     301                        // Select similar units regardless of rank
     302                        templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).identity.selectionGroupName;
     303                        if (templateToMatch)
     304                        {
     305                            matchRank = false;
     306                        }
     307                        else
     308                        {   // No selection group name defined, so fall back to exact match
     309                            templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
     310                        }
     311
     312                        doubleClicked = true;
     313                        // Reset the timer so the user has an extra period 'doubleClickTimer' to do a triple-click
     314                        doubleClickTimer = now.getTime();
     315                    }
     316                    else
     317                    {
     318                        // Double click has already occurred, so this is a triple click.
     319                        // Select units matching exact template name (same rank)
     320                        templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template;
     321                    }
     322
     323                    // TODO: Should we handle "control all units" here as well?
     324                    ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
     325                }
     326                else
     327                {
     328                    // It's single click right now but it may become double or triple click
     329                    doubleClicked = false;
     330                    doubleClickTimer = now.getTime();
     331                    prevClickedEntity = selectedEntity;
     332
     333                    // We only want to include the first picked unit in the selection
     334                    ents = [ents[0]];
     335                }
     336
     337                // Update the list of selected units
     338                if (Engine.HotkeyIsPressed("selection.add"))
     339                {
     340                    g_Selection.addList(ents);
     341                }
     342                else if (Engine.HotkeyIsPressed("selection.remove"))
     343                {
     344                    g_Selection.removeList(ents);
     345                }
     346                else
     347                {
     348                    g_Selection.reset();
     349                    g_Selection.addList(ents);
     350                }
     351
     352                inputState = INPUT_NORMAL;
     353                return true;
     354            }
     355            break;
     356        }
     357        break;
     358    }
     359    return false;
     360}
     361
     362// Called by unit selection buttons
     363function changePrimarySelectionGroup(templateName)
     364{
     365    if (Engine.HotkeyIsPressed("session.deselectgroup"))
     366        g_Selection.makePrimarySelection(templateName, true);
     367    else
     368        g_Selection.makePrimarySelection(templateName, false);
     369}
     370
     371
     372// Set the camera to follow the given unit
     373function setCameraFollow(entity)
     374{
     375    // Follow the given entity if it's a unit
     376    if (entity)
     377    {
     378        var entState = GetEntityState(entity);
     379        if (entState && hasClass(entState, "Unit"))
     380        {
     381            Engine.CameraFollow(entity);
     382            return;
     383        }
     384    }
     385
     386    // Otherwise stop following
     387    Engine.CameraFollow(0);
     388}
     389
     390var lastIdleUnit = 0;
     391var currIdleClass = 0;
     392var lastIdleType = undefined;
     393
     394function resetIdleUnit()
     395{
     396    lastIdleUnit = 0;
     397    currIdleClass = 0;
     398    lastIdleType = undefined;
     399}
     400
     401function findIdleUnit(classes)
     402{
     403    var append = Engine.HotkeyIsPressed("selection.add");
     404    var selectall = Engine.HotkeyIsPressed("selection.offscreen");
     405
     406    // Reset the last idle unit, etc., if the selection type has changed.
     407    var type = classes.join();
     408    if (selectall || type != lastIdleType)
     409        resetIdleUnit();
     410    lastIdleType = type;
     411
     412    // If selectall is true, there is no limit and it's necessary to iterate
     413    // over all of the classes, resetting only when the first match is found.
     414    var matched = false;
     415
     416    for (var i = 0; i < classes.length; ++i)
     417    {
     418        var data = { idleClass: classes[currIdleClass], prevUnit: lastIdleUnit, limit: 1 };
     419        if (append)
     420            data.excludeUnits = g_Selection.toList();
     421
     422        if (selectall)
     423            data = { idleClass: classes[currIdleClass] };
     424
     425        // Check if we have new valid entity
     426        var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
     427        if (idleUnits.length && idleUnits[0] != lastIdleUnit)
     428        {
     429            lastIdleUnit = idleUnits[0];
     430            if (!append && (!selectall || selectall && !matched))
     431                g_Selection.reset()
     432
     433            if (selectall)
     434                g_Selection.addList(idleUnits);
     435            else
     436            {
     437                g_Selection.addList([lastIdleUnit]);
     438                Engine.CameraFollow(lastIdleUnit);
     439                return;
     440            }
     441
     442            matched = true;
     443        }
     444
     445        lastIdleUnit = 0;
     446        currIdleClass = (currIdleClass + 1) % classes.length;
     447    }
     448
     449    // TODO: display a message or play a sound to indicate no more idle units, or something
     450    // Reset for next cycle
     451    resetIdleUnit();
     452}
     453
     454// Ignored: we ignore these callbacks in replay mode
     455function removeFromProductionQueue() {};
     456function unloadTemplate() {};
     457function unloadAll() {};
  • binaries/data/mods/public/gui/replay/input.js

  • binaries/data/mods/public/gui/replay/menu.js

    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
     
     1const PAUSE = "Pause";
     2const RESUME = "Resume";
     3
     4/*
     5 * MENU POSITION CONSTANTS
     6*/
     7
     8// Menu / panel border size
     9const MARGIN = 4;
     10
     11// Includes the main menu button
     12const NUM_BUTTONS = 4;
     13
     14// Regular menu buttons
     15const BUTTON_HEIGHT = 32;
     16
     17// The position where the bottom of the menu will end up (currently 228)
     18const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN;
     19
     20// Menu starting position: bottom
     21const MENU_BOTTOM = 0;
     22
     23// Menu starting position: top
     24const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
     25
     26// Menu starting position: overall
     27const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
     28
     29// Number of pixels per millisecond to move
     30const MENU_SPEED = 1.2;
     31
     32var isMenuOpen = false;
     33var menu;
     34
     35// Ignore size defined in XML and set the actual menu size here
     36function initMenuPosition()
     37{
     38    menu = getGUIObjectByName("menu");
     39    menu.size = INITIAL_MENU_POSITION;
     40}
     41
     42
     43// =============================================================================
     44// Overall Menu
     45// =============================================================================
     46//
     47// Slide menu
     48function updateMenuPosition(dt)
     49{
     50    if (isMenuOpen)
     51    {
     52        var maxOffset = END_MENU_POSITION - menu.size.bottom;
     53        if (maxOffset > 0)
     54        {
     55            var offset = Math.min(MENU_SPEED * dt, maxOffset);
     56            var size = menu.size;
     57            size.top += offset;
     58            size.bottom += offset;
     59            menu.size = size;
     60        }
     61    }
     62    else
     63    {
     64        var maxOffset = menu.size.top - MENU_TOP;
     65        if (maxOffset > 0)
     66        {
     67            var offset = Math.min(MENU_SPEED * dt, maxOffset);
     68            var size = menu.size;
     69            size.top -= offset;
     70            size.bottom -= offset;
     71            menu.size = size;
     72        }
     73    }
     74}
     75
     76// Opens the menu by revealing the screen which contains the menu
     77function openMenu()
     78{
     79//  playButtonSound();
     80    isMenuOpen = true;
     81}
     82
     83// Closes the menu and resets position
     84function closeMenu()
     85{
     86//  playButtonSound();
     87    isMenuOpen = false;
     88}
     89
     90function toggleMenu()
     91{
     92    if (isMenuOpen == true)
     93        closeMenu();
     94    else
     95        openMenu();
     96}
     97
     98// Menu buttons
     99// =============================================================================
     100function settingsMenuButton()
     101{
     102    closeMenu();
     103    closeOpenDialogs();
     104    openSettings(true);
     105}
     106
     107function pauseMenuButton()
     108{
     109    togglePause();
     110}
     111
     112function resignMenuButton()
     113{
     114    closeMenu();
     115    closeOpenDialogs();
     116    pauseGame();
     117    var btCaptions = ["Yes", "No"];
     118    var btCode = [resignGame, resumeGame];
     119    messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode);
     120}
     121
     122function exitMenuButton()
     123{
     124    closeMenu();
     125    closeOpenDialogs();
     126    pauseGame();
     127    var btCaptions = ["Yes", "No"];
     128    var btCode = [leaveGame, resumeGame];
     129    messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode);
     130}
     131
     132function openDeleteDialog(selection)
     133{
     134    closeMenu();
     135    closeOpenDialogs();
     136
     137    var deleteSelectedEntities = function ()
     138    {
     139        Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection});
     140    };
     141
     142    var btCaptions = ["Yes", "No"];
     143    var btCode = [deleteSelectedEntities, resumeGame];
     144
     145    messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode);
     146}
     147
     148// Menu functions
     149// =============================================================================
     150
     151function openSettings(pause)
     152{
     153    getGUIObjectByName("settingsDialogPanel").hidden = false;
     154    if (pause)
     155        pauseGame();
     156}
     157
     158function closeSettings(resume)
     159{
     160        getGUIObjectByName("settingsDialogPanel").hidden = true;
     161    if (resume)
     162        resumeGame();
     163}
     164
     165function pauseGame()
     166{
     167    getGUIObjectByName("pauseButtonText").caption = RESUME;
     168    getGUIObjectByName("pauseOverlay").hidden = false;
     169    setPaused(true);
     170}
     171
     172function resumeGame()
     173{
     174    getGUIObjectByName("pauseButtonText").caption = PAUSE;
     175    getGUIObjectByName("pauseOverlay").hidden = true;
     176    setPaused(false);
     177}
     178
     179function togglePause()
     180{
     181    closeMenu();
     182    closeOpenDialogs();
     183
     184    var pauseOverlay = getGUIObjectByName("pauseOverlay");
     185
     186    if (pauseOverlay.hidden)
     187    {
     188        getGUIObjectByName("pauseButtonText").caption = RESUME;
     189        setPaused(true);
     190
     191    }
     192    else
     193    {
     194        setPaused(false);
     195        getGUIObjectByName("pauseButtonText").caption = PAUSE;
     196    }
     197
     198    pauseOverlay.hidden = !pauseOverlay.hidden;
     199}
     200
     201function toggleDeveloperOverlay()
     202{
     203    var devCommands = getGUIObjectByName("devCommands");
     204    devCommands.hidden = !devCommands.hidden;
     205}
     206
     207function closeOpenDialogs()
     208{
     209    closeMenu();
     210    closeSettings(false);
     211}
  • binaries/data/mods/public/gui/replay/menu.js

  • binaries/data/mods/public/gui/replay/replay.js

    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
     
     1// Network Mode
     2var g_IsNetworked = false;
     3
     4// Cache the basic player data (name, civ, color)
     5var g_Players = [];
     6// Cache the useful civ data
     7var g_CivData = {};
     8
     9var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
     10
     11// Cache dev-mode settings that are frequently or widely used
     12var g_DevSettings = {
     13    controlAll: false
     14};
     15
     16// Whether status bars should be shown for all of the player's units.
     17var g_ShowAllStatusBars = false;
     18
     19// Indicate when one of the current player's training queues is blocked
     20// (this is used to support population counter blinking)
     21var g_IsTrainingBlocked = false;
     22
     23// Cache EntityStates
     24var g_EntityStates = {}; // {id:entState}
     25
     26// Whether the player has lost/won and reached the end of their game
     27var g_GameEnded = false;
     28
     29// Colors to flash when pop limit reached
     30const DEFAULT_POPULATION_COLOR = "white";
     31const POPULATION_ALERT_COLOR = "orange";
     32
     33function GetEntityState(entId)
     34{
     35    if (!(entId in g_EntityStates))
     36    {
     37        var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
     38        g_EntityStates[entId] = entState;
     39    }
     40
     41    return g_EntityStates[entId];
     42}
     43
     44// Cache TemplateData
     45var g_TemplateData = {}; // {id:template}
     46
     47
     48function GetTemplateData(templateName)
     49{
     50    if (!(templateName in g_TemplateData))
     51    {
     52        var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
     53        g_TemplateData[templateName] = template;
     54    }
     55
     56    return g_TemplateData[templateName];
     57}
     58
     59// Cache TechnologyData
     60var g_TechnologyData = {}; // {id:template}
     61
     62function GetTechnologyData(technologyName)
     63{
     64    if (!(technologyName in g_TechnologyData))
     65    {
     66        var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
     67        g_TechnologyData[technologyName] = template;
     68    }
     69
     70    return g_TechnologyData[technologyName];
     71}
     72
     73// Init
     74function init(initData, hotloadData)
     75{
     76    if (initData)
     77    {
     78        g_IsNetworked = initData.isNetworked; // Set network mode
     79        g_PlayerAssignments = initData.playerAssignments;
     80
     81        // Cache the player data
     82        // (This may be updated at runtime by handleNetMessage)
     83        g_Players = getPlayerData(g_PlayerAssignments);
     84    }
     85    else // Needed for autostart loading option
     86    {
     87        g_Players = getPlayerData(null);
     88    }
     89
     90    // Cache civ data
     91    g_CivData = loadCivData();
     92    g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia", "Emblem": "session/icons/groups.png" };
     93
     94    initMenuPosition(); // set initial position
     95
     96    // Populate player selection dropdown
     97    var playerNames = [];
     98    var playerIDs = [];
     99    for (var player in g_Players)
     100    {
     101        playerNames.push(g_Players[player].name);
     102        playerIDs.push(player);
     103    }
     104
     105    var viewPlayerDropdown = getGUIObjectByName("viewPlayer");
     106    viewPlayerDropdown.list = playerNames;
     107    viewPlayerDropdown.list_data = playerIDs;
     108    viewPlayerDropdown.selected = 1;
     109   
     110    // If in Atlas editor, disable the exit button
     111    if (Engine.IsAtlasRunning())
     112        getGUIObjectByName("menuExitButton").enabled = false;
     113
     114    if (hotloadData)
     115    {
     116        g_Selection.selected = hotloadData.selection;
     117    }
     118
     119    onSimulationUpdate();
     120
     121    // Report the performance after 5 seconds (when we're still near
     122    // the initial camera view) and a minute (when the profiler will
     123    // have settled down if framerates as very low), to give some
     124    // extremely rough indications of performance
     125    setTimeout(function() { reportPerformance(5); }, 5000);
     126    setTimeout(function() { reportPerformance(60); }, 60000);
     127}
     128
     129function selectViewPlayer(playerID)
     130{
     131    Engine.SetPlayerID(playerID);
     132    getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
     133    getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name;
     134}
     135
     136function reportPerformance(time)
     137{
     138    var settings = Engine.GetMapSettings();
     139    var data = {
     140        time: time,
     141        map: settings.Name,
     142        seed: settings.Seed, // only defined for random maps
     143        size: settings.Size, // only defined for random maps
     144        profiler: Engine.GetProfilerState()
     145    };
     146
     147    Engine.SubmitUserReport("profile", 3, JSON.stringify(data));
     148}
     149
     150function leaveGame()
     151{
     152    var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
     153
     154    var mapSettings = Engine.GetMapSettings();
     155
     156    endGame();
     157
     158    Engine.SwitchGuiPage("page_summary.xml", {
     159                            "gameResult"  : "Replay mode ended",
     160                            "timeElapsed" : extendedSimState.timeElapsed,
     161                            "playerStates": extendedSimState.players,
     162                            "players": g_Players,
     163                            "mapSettings": mapSettings
     164                         });
     165}
     166
     167// Return some data that we'll use when hotloading this file after changes
     168function getHotloadData()
     169{
     170    return { selection: g_Selection.selected };
     171}
     172
     173var lastTickTime = new Date;
     174
     175/**
     176 * Called every frame.
     177 */
     178function onTick()
     179{
     180    var now = new Date;
     181    var tickLength = new Date - lastTickTime;
     182    lastTickTime = now;
     183
     184    while (true)
     185    {
     186        var message = Engine.PollNetworkClient();
     187        if (!message)
     188            break;
     189        handleNetMessage(message);
     190    }
     191
     192    // If the selection changed, we need to regenerate the sim display (the display depends on both the
     193    // simulation state and the current selection).
     194    if (g_Selection.dirty)
     195    {
     196        onSimulationUpdate();
     197
     198        // Display rally points for selected buildings
     199        Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
     200    }
     201
     202    // Run timers
     203    updateTimers();
     204
     205    // Animate menu
     206    updateMenuPosition(tickLength);
     207
     208    // When training is blocked, flash population (alternates colour every 500msec)
     209    if (g_IsTrainingBlocked && (Date.now() % 1000) < 500)
     210        getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR;
     211    else
     212        getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR;
     213
     214    // Clear renamed entities list
     215    Engine.GuiInterfaceCall("ClearRenamedEntities");
     216}
     217
     218function onReplayFinished()
     219{
     220    closeMenu();
     221    closeOpenDialogs();
     222    pauseGame();
     223    var btCaptions = ["Yes", "No"];
     224    var btCode = [leaveGame, resumeGame];
     225    messageBox(400, 200, "The replay has finished. Do you want to quit?", "Confirmation", 0, btCaptions, btCode);
     226}
     227
     228/**
     229 * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
     230 * update (see session.xml), or from onTick when the selection has changed.
     231 */
     232function onSimulationUpdate()
     233{
     234    g_Selection.dirty = false;
     235    g_EntityStates = {};
     236    g_TemplateData = {};
     237    g_TechnologyData = {};
     238
     239    var simState = Engine.GuiInterfaceCall("GetSimulationState");
     240
     241    // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing
     242    if (!simState)
     243        return;
     244
     245    handleNotifications();
     246
     247    if (g_ShowAllStatusBars)
     248        recalculateStatusBarDisplay();
     249
     250    updateDebug(simState);
     251    updatePlayerDisplay(simState);
     252    updateSelectionDetails(true);
     253    updateResearchDisplay();
     254    updateTimeElapsedCounter(simState);
     255}
     256
     257function updateDebug(simState)
     258{
     259    var debug = getGUIObjectByName("debug");
     260
     261    if (getGUIObjectByName("devDisplayState").checked)
     262    {
     263        debug.hidden = false;
     264    }
     265    else
     266    {
     267        debug.hidden = true;
     268        return;
     269    }
     270
     271    var conciseSimState = deepcopy(simState);
     272    conciseSimState.players = "<<<omitted>>>";
     273    var text = "simulation: " + uneval(conciseSimState);
     274
     275    var selection = g_Selection.toList();
     276    if (selection.length)
     277    {
     278        var entState = GetEntityState(selection[0]);
     279        if (entState)
     280        {
     281            var template = GetTemplateData(entState.template);
     282            text += "\n\nentity: {\n";
     283            for (var k in entState)
     284                text += "  "+k+":"+uneval(entState[k])+"\n";
     285            text += "}\n\ntemplate: " + uneval(template);
     286        }
     287    }
     288
     289    debug.caption = text;
     290}
     291
     292function updatePlayerDisplay(simState)
     293{
     294    var playerState = simState.players[Engine.GetPlayerID()];
     295    if (!playerState)
     296        return;
     297
     298    getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food;
     299    getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood;
     300    getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone;
     301    getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal;
     302    getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
     303
     304    g_IsTrainingBlocked = playerState.trainingBlocked;
     305}
     306
     307function selectAndMoveTo(ent)
     308{
     309    var entState = GetEntityState(ent);
     310    if (!entState)
     311        return;
     312
     313    g_Selection.reset();
     314    g_Selection.addList([ent]);
     315
     316    var position = entState.position;
     317    Engine.CameraMoveTo(position.x, position.z);
     318}
     319
     320function updateResearchDisplay()
     321{
     322    var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID());
     323    if (!researchStarted)
     324        return;
     325
     326    // Set up initial positioning.
     327    var buttonSideLength = getGUIObjectByName("researchStartedButton[0]").size.right;
     328    for (var i = 0; i < 10; ++i)
     329    {
     330        var button = getGUIObjectByName("researchStartedButton[" + i + "]");
     331        var size = button.size;
     332        size.top = (4 + buttonSideLength) * i;
     333        size.bottom = size.top + buttonSideLength;
     334        button.size = size;
     335    }
     336
     337    var numButtons = 0;
     338    for (var tech in researchStarted)
     339    {
     340        // Show at most 10 in-progress techs.
     341        if (numButtons >= 10)
     342            break;
     343
     344        var template = GetTechnologyData(tech);
     345        var button = getGUIObjectByName("researchStartedButton[" + numButtons + "]");
     346        button.hidden = false;
     347        button.tooltip = getEntityNames(template);
     348        button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher);
     349
     350        var icon = "stretched:session/portraits/" + template.icon;
     351        getGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon;
     352
     353        // Scale the progress indicator.
     354        var size = getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size;
     355
     356        // Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
     357        size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left));
     358        getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size;
     359
     360        ++numButtons;
     361    }
     362
     363    // Hide unused buttons.
     364    for (var i = numButtons; i < 10; ++i)
     365        getGUIObjectByName("researchStartedButton[" + i + "]").hidden = true;
     366}
     367
     368function updateTimeElapsedCounter(simState)
     369{
     370    var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
     371    timeElapsedCounter.caption = timeToString(simState.timeElapsed);
     372}
     373
     374// Toggles the display of status bars for all of the player's entities.
     375function recalculateStatusBarDisplay()
     376{
     377    if (g_ShowAllStatusBars)
     378        var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID());
     379    else
     380    {
     381        var selected = g_Selection.toList();
     382        for each (var ent in g_Selection.highlighted)
     383            selected.push(ent);
     384
     385        // Remove selected entities from the 'all entities' array, to avoid disabling their status bars.
     386        var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter(
     387                function(idx) { return (selected.indexOf(idx) == -1); }
     388        );
     389    }
     390
     391    Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars });
     392}
     393
     394// Temporarily adding this here
     395const AMBIENT_TEMPERATE = "temperate";
     396var currentAmbient;
     397function playRandomAmbient(type)
     398{
     399    switch (type)
     400    {
     401        case AMBIENT_TEMPERATE:
     402            // Seem to need the underscore at the end of "temperate" to avoid crash
     403            // (Might be caused by trying to randomly load day_temperate.xml)
     404//          currentAmbient = newRandomSound("ambient", "temperate_", "dayscape");
     405
     406            const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
     407            currentAmbient = new AmbientSound(AMBIENT);
     408
     409            if (currentAmbient)
     410            {
     411                currentAmbient.loop();
     412            }
     413            break;
     414
     415        default:
     416            console.write("Unrecognized ambient type: " + type);
     417            break;
     418    }
     419}
     420
     421// Temporarily adding this here
     422function stopAmbient()
     423{
     424    if (currentAmbient)
     425    {
     426        currentAmbient.free();
     427        currentAmbient = null;
     428    }
     429}
  • binaries/data/mods/public/gui/replay/replay.js

  • binaries/data/mods/public/gui/replay/replay.xml

    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
     
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<objects>
     4
     5  <script file="gui/common/functions_civinfo.js"/>
     6  <script file="gui/common/functions_utility.js" />
     7  <script file="gui/common/functions_global_object.js" />
     8  <script file="gui/common/timer.js"/>
     9  <script file="gui/replay/input.js"/>
     10  <script file="gui/replay/menu.js"/>
     11  <script file="gui/replay/replay.js"/>
     12  <script file="gui/session/messages.js"/>
     13  <script file="gui/session/selection.js"/>
     14  <script file="gui/session/selection_details.js"/>
     15  <script file="gui/session/unit_commands.js"/>
     16  <script file="gui/session/utility_functions.js"/>
     17
     18  <object name="sn" hotkey="session.gui.toggle">
     19    <action on="Tick">
     20        onTick();
     21    </action>
     22
     23    <action on="SimulationUpdate">
     24        onSimulationUpdate();
     25    </action>
     26   
     27    <action on="ReplayFinished">
     28        onReplayFinished();
     29    </action>
     30
     31    <action on="Press">
     32        this.hidden = !this.hidden;
     33    </action>
     34
     35    <!-- ================================  ================================ -->
     36    <!-- HOTKEYS (For some reason, they won't work properly unless outside menu) -->
     37    <!-- ================================  ================================ -->
     38
     39    <!-- Exit button Hotkey  -->
     40    <!--
     41      <action on="Press"><![CDATA[
     42        messageBox(400, 200, "Do you really want to quit?", "Confirmation", 0,
     43          ["Yes", "No!"], [leaveGame, null]);
     44      ]]></action>
     45    -->
     46
     47    <object hotkey="leave">
     48        <action on="Press">closeOpenDialogs();</action>
     49    </object>
     50
     51    <!-- Menu -->
     52    <object hotkey="menu.toggle">
     53        <action on="Press">openMenu();</action>
     54    </object>
     55     
     56    <!-- Unit silhouettes -->
     57    <object hotkey="silhouettes">
     58        <action on="Press">renderer.silhouettes = !renderer.silhouettes;</action>
     59    </object>
     60
     61    <!-- Sky -->
     62    <object hotkey="showsky">
     63        <action on="Press">renderer.showsky = !renderer.showsky;</action>
     64    </object>
     65
     66    <!-- Pause -->
     67    <object hotkey="pause">
     68        <action on="Press">togglePause();</action>
     69    </object>
     70
     71    <!-- camera.follow mode - follow the first unit in the selection -->
     72    <object hotkey="camera.follow">
     73        <action on="Press">setCameraFollow(g_Selection.toList()[0]);</action>
     74    </object>
     75
     76      <!-- Find idle warrior - TODO: Potentially move this to own UI button? -->
     77    <object hotkey="selection.idlewarrior">
     78        <action on="Press">findIdleUnit(["Hero", "Champion", "CitizenSoldier", "Siege", "Warship"]);</action>
     79    </object>
     80
     81    <!-- ================================  ================================ -->
     82    <!-- Developer / Debug items -->
     83    <!-- ================================  ================================ -->
     84
     85    <!-- Debug text -->
     86    <object name="debug"
     87      type="text"
     88      size="0 70 80% 100%"
     89      ghost="true"
     90      textcolor="yellow"
     91      font="mono-stroke-10"
     92      />
     93
     94    <!-- Dev/cheat commands -->
     95    <object name="devCommands" size="100%-156 50%-88 100%-8 50%+88" type="image" sprite="devCommandsBackground" z="40"
     96      hidden="true" hotkey="session.devcommands.toggle">
     97        <action on="Press">
     98      toggleDeveloperOverlay();
     99        </action>
     100
     101        <object size="0 16 100%-18 32" type="text" style="devCommandsText">Display selection state</object>
     102        <object size="100%-16 16 100% 32" type="checkbox" name="devDisplayState" style="StoneCrossBox"/>
     103
     104        <object size="0 32 100%-18 48" type="text" style="devCommandsText">Pathfinder overlay</object>
     105        <object size="100%-16 32 100% 48" type="checkbox" style="StoneCrossBox">
     106      <action on="Press">Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", this.checked);</action>
     107        </object>
     108
     109        <object size="0 48 100%-18 64" type="text" style="devCommandsText">Obstruction overlay</object>
     110        <object size="100%-16 48 100% 64" type="checkbox" style="StoneCrossBox">
     111      <action on="Press">Engine.GuiInterfaceCall("SetObstructionDebugOverlay", this.checked);</action>
     112        </object>
     113
     114        <object size="0 64 100%-18 80" type="text" style="devCommandsText">Unit motion overlay</object>
     115        <object size="100%-16 64 100% 80" type="checkbox" style="StoneCrossBox">
     116      <action on="Press">g_Selection.SetMotionDebugOverlay(this.checked);</action>
     117        </object>
     118
     119        <object size="0 80 100%-18 96" type="text" style="devCommandsText">Range overlay</object>
     120        <object size="100%-16 80 100% 96" type="checkbox" style="StoneCrossBox">
     121      <action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
     122        </object>
     123
     124        <object size="0 96 100%-18 112" type="text" style="devCommandsText">Bounding box overlay</object>
     125        <object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
     126      <action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
     127        </object>
     128
     129        <object size="0 112 100%-18 128" type="text" style="devCommandsText">Restrict camera</object>
     130        <object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox" checked="true">
     131      <action on="Press">gameView.constrainCamera = this.checked;</action>
     132        </object>
     133
     134        <object size="0 128 100%-18 144" type="text" style="devCommandsText">Reveal map</object>
     135        <object size="100%-16 128 100% 144" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
     136      <action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
     137        </object>
     138
     139    </object>
     140
     141    <!-- ================================  ================================ -->
     142    <!-- Time elapsed counter -->
     143    <!-- ================================  ================================ -->
     144    <object size="100%-100 50 100%-10 70" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" ghost="true">
     145        <action on="Press"><![CDATA[
     146          this.hidden = !this.hidden;
     147        ]]>
     148        </action>
     149    </object>
     150
     151    <object size="50%-400 50 50%-50 100" type="text" font="serif-bold-24" textcolor="white" ghost="true">Non-interactive Replay Mode</object>
     152   
     153    <!-- ================================  ================================ -->
     154    <!-- Pause Overlay -->
     155    <!-- ================================  ================================ -->
     156    <object type="button"
     157      name="pauseOverlay"
     158      size="0 0 100% 100%"
     159      tooltip_style="sessionToolTip"
     160      hidden="true"
     161      z="0"
     162    >
     163        <object size="0 0 100% 100%" type="image" sprite="devCommandsBackground" ghost="true" z="0"/>
     164        <object size="50%-128 50%-20 50%+128 50%+20" type="text" style="PauseText" ghost="true" z="0">Replay Paused</object>
     165        <object size="50%-128 50%+20 50%+128 50%+30" type="text" style="PauseMessageText" ghost="true" z="0">Click to Resume Replay</object>
     166        <action on="Press">togglePause();</action>
     167    </object>
     168
     169    <!-- ================================  ================================ -->
     170    <!-- Notification Area -->
     171    <!-- ================================  ================================ -->
     172    <object name="notificationPanel" type="image" size="50%-300 60 50%+300 120" ghost="true">
     173        <object name="notificationText" size="0 0 100% 100%" type="text" style="notificationPanel" ghost="true"/>
     174    </object>
     175   
     176    <!-- ================================  ================================ -->
     177    <!-- Chat -->
     178    <!-- ================================  ================================ -->
     179
     180    <!-- Chat panel -->
     181    <object name="chatPanel" size="0 130 100% 100%-240" type="image" ghost="true">
     182        <object name="chatText" size="3 1 100%-1 100%-1" type="text" style="chatPanel" ghost="true"/>
     183    </object>
     184
     185    <!-- ================================  ================================ -->
     186    <!-- Settings Window -->
     187    <!-- ================================  ================================ -->
     188    <object name="settingsDialogPanel"
     189      style="StoneDialog"
     190      type="image"
     191      size="50%-180 50%-200 50%+180 50%+100"
     192      hidden="true"
     193    >
     194        <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">Settings</object>
     195
     196        <object style="TranslucentPanelThinBorder"
     197          type="image"
     198          size="32 32 100%-32 100%-70"
     199        >
     200      <!-- Settings / shadows -->
     201      <object size="0 10 100%-80 35" type="text" style="RightLabelText" ghost="true">Enable Shadows</object>
     202      <object name="shadowsCheckbox" size="100%-56 15 100%-30 40" type="checkbox" style="StoneCrossBox" checked="true">
     203          <action on="Load">if (renderer.shadows) this.checked = true; else this.checked = false;</action>
     204          <action on="Press">renderer.shadows = this.checked;</action>
     205      </object>
     206
     207      <!-- Settings / Shadow PCF -->
     208      <object size="0 35 100%-80 60" type="text" style="RightLabelText" ghost="true">Enable Shadow Filtering</object>
     209      <object name="shadowPCFCheckbox" size="100%-56 40 100%-30 65" type="checkbox" style="StoneCrossBox" checked="true">
     210          <action on="Load">if (renderer.shadowPCF) this.checked = true; else this.checked = false;</action>
     211          <action on="Press">renderer.shadowPCF = this.checked;</action>
     212      </object>
     213
     214      <!-- Settings / Water -->
     215      <object size="0 60 100%-80 85" type="text" style="RightLabelText" ghost="true">Enable Water Reflections</object>
     216      <object name="fancyWaterCheckbox" size="100%-56 65 100%-30 90"  type="checkbox" style="StoneCrossBox" checked="true">
     217          <action on="Load">if (renderer.fancyWater) this.checked = true; else this.checked = false;</action>
     218          <action on="Press">renderer.fancyWater = this.checked;</action>
     219      </object>
     220
     221      <!-- Settings / Particles -->
     222      <object size="0 85 100%-80 110" type="text" style="RightLabelText" ghost="true">Enable Particles</object>
     223      <object name="particlesCheckbox" size="100%-56 90 100%-30 115" type="checkbox" style="StoneCrossBox" checked="true">
     224          <action on="Load">if (renderer.particles) this.checked = true; else this.checked = false;</action>
     225          <action on="Press">renderer.particles = this.checked;</action>
     226      </object>
     227
     228      <!-- Settings / Unit Silhouettes -->
     229      <object size="0 110 100%-80 135" type="text" style="RightLabelText" ghost="true">Enable Unit Silhouettes</object>
     230      <object name="silhouettesCheckbox" size="100%-56 115 100%-30 140" type="checkbox" style="StoneCrossBox" checked="true">
     231          <action on="Load">if (renderer.silhouettes) this.checked = true; else this.checked = false;</action>
     232          <action on="Press">renderer.silhouettes = this.checked;</action>
     233      </object>
     234
     235      <!-- Settings / Dev Overlay -->
     236      <object size="0 160 100%-80 185" type="text" style="RightLabelText" ghost="true">Developer Overlay</object>
     237      <object size="100%-56 165 100%-30 190" type="checkbox" style="StoneCrossBox" checked="false">
     238          <action on="Press">toggleDeveloperOverlay();</action>
     239      </object>
     240        </object>
     241
     242        <!-- Close button -->
     243        <object type="button"
     244          style="StoneButton"
     245          size="50%-64 100%-52 50%+64 100%-24"
     246          tooltip_style="sessionToolTip"
     247        >
     248      Close
     249      <action on="Press">closeSettings(true);</action>
     250        </object>
     251    </object>
     252
     253    <!-- ================================  ================================ -->
     254    <!-- Top Panel  -->
     255    <!-- ================================  ================================ -->
     256    <object name="topPanel"
     257      type="image"
     258      sprite="topPanel"
     259      size="-3 0 100%+3 36"
     260    >
     261        <!-- ================================  ================================ -->
     262        <!-- Player resource bar -->
     263        <!-- ================================  ================================ -->
     264        <object
     265      size="10 0 45% 100%"
     266        >
     267      <!-- Food -->
     268      <object size="0 0 90 100%" type="image" style="resourceCounter" tooltip="Food" tooltip_style="sessionToolTipBold">
     269          <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
     270          <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/>
     271      </object>
     272
     273      <!-- Wood -->
     274      <object size="90 0 180 100%" type="image" style="resourceCounter" tooltip="Wood" tooltip_style="sessionToolTipBold">
     275          <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
     276          <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/>
     277      </object>
     278
     279      <!-- Stone -->
     280      <object size="180 0 270 100%" type="image" style="resourceCounter" tooltip="Stone" tooltip_style="sessionToolTipBold">
     281          <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
     282          <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/>
     283      </object>
     284
     285      <!-- Metal -->
     286      <object size="270 0 360 100%"  type="image" style="resourceCounter" tooltip="Metal" tooltip_style="sessionToolTipBold">
     287          <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
     288          <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/>
     289      </object>
     290
     291      <!-- Population -->
     292      <object size="360 0 450 100%" type="image" style="resourceCounter" tooltip="Population (current / limit)" tooltip_style="sessionToolTipBold">
     293          <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
     294          <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
     295      </object>
     296        </object>
     297
     298        <!-- ================================  ================================ -->
     299        <!-- Civ Icon -->
     300        <!-- ================================  ================================ -->
     301        <object size="50%-48 -26 50%+48 70" name="civIcon" type="image" tooltip_style="sessionToolTipBold"/>
     302
     303        <object size="50%+50 5 50%+150 100%-5" name="viewPlayer" type="dropdown" style="StoneDropDown" tooltip_style="sessionToolTipBold" tooltip="Choose player to view">
     304          <action on="SelectionChange">selectViewPlayer(this.selected);</action>
     305        </object>
     306       
     307        <!-- ================================  ================================ -->
     308        <!-- Phase -->
     309        <!-- ================================  ================================ -->
     310        <!--<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>-->
     311
     312
     313        <!-- ================================  ================================ -->
     314        <!-- ALPHA LABELS (alpha, build time, revision) -->
     315        <!-- ================================  ================================ -->
     316
     317        <!-- Displays Alpha name and number -->
     318        <object size="70%-50 0 70%+128 100%" name="alphaLabel" type="text" style="CenteredLabelText" text_valign="top" ghost="true">
     319      ALPHA XI : Kronos<!-- IMPORTANT: remember to update pregame/mainmenu.xml in sync with this -->
     320
     321      <!-- Displays build date and revision number-->
     322      <object size="50%-128 0 50%+128 100%-2" name="buildTimeLabel" type="text" style="BuildNameText" ghost="true">
     323          <action on="Load"><![CDATA[this.caption = buildTime(0) + " (" + buildTime(2) + ")";]]>
     324          </action>
     325      </object>
     326        </object>
     327
     328        <!-- ================================  ================================ -->
     329        <!-- Menu Button -->
     330        <!-- ================================  ================================ -->
     331        <object type="button"
     332          name="menuButton"
     333          size="100%-164 4 100%-8 32"
     334          style="StoneButtonFancy"
     335          tooltip_style="sessionToolTip"
     336          z="70"
     337        >
     338      <!-- This object covers up the text on the menu
     339      buttons as they slide by so that you don't see
     340      them on top of the main menu button -->
     341      <object size="0 -4 100% 0" type="image" sprite="horizontalThinBorder" ghost="true"/>
     342
     343      <object size="50%-32 50%-16 50%+32 50%+16" type="image" sprite="menuButton" ghost="true">MENU</object>
     344      <action on="Press">
     345          toggleMenu();
     346      </action>
     347        </object> <!-- END OF MENU BUTTON -->
     348    </object> <!-- END OF TOP PANEL -->
     349
     350    <!-- ================================  ================================ -->
     351    <!-- Menu -->
     352    <!-- ================================  ================================ -->
     353    <object name="menu"
     354      style="StonePanelThinBorder"
     355      type="image"
     356      hidden="false"
     357      z="40"
     358    >
     359        <object size="4 36 100%-4 50%+20">
     360
     361      <!-- Settings button -->
     362      <object type="button"
     363        name="settingsButton"
     364        style="StoneButtonFancy"
     365        size="0 0 100% 28"
     366        tooltip_style="sessionToolTip"
     367      >
     368          Settings
     369          <action on="Press">settingsMenuButton();</action>
     370      </object>
     371
     372      <!-- Exit button -->
     373      <object type="button"
     374        name="menuExitButton"
     375        style="StoneButtonFancy"
     376        size="0 32 100% 60"
     377        tooltip_style="sessionToolTip"
     378      >
     379          Exit
     380          <action on="Press">exitMenuButton();</action>
     381      </object>
     382
     383      <!-- Pause / Resume Button -->
     384      <object type="button"
     385        name="pauseButton"
     386        style="StoneButtonFancy"
     387        size="0 64 100% 92"
     388        tooltip_style="sessionToolTip"
     389      >
     390          <object name="pauseButtonText" type="text" style="CenteredButtonText" ghost="true">Pause</object>
     391          <action on="Press">togglePause();</action>
     392      </object>
     393        </object>
     394    </object>
     395
     396    <!-- In-progress research -->
     397    <object size="100%-50 85 100%-10 100%-200">
     398      <repeat count="10">
     399        <object name="researchStartedButton[n]" hidden="true" style="iconButton" type="button" size="0 0 40 40" tooltip_style="sessionToolTipBottom">
     400          <object name="researchStartedIcon[n]" ghost="true" type="image" size="3 3 37 37"/>
     401          <object name="researchStartedProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 37 37"/>
     402        </object>
     403      </repeat>
     404    </object>
     405
     406    <!-- ================================  ================================ -->
     407    <!-- Unit Selection Groups -->
     408    <!-- ================================  ================================ -->
     409    <!--<object
     410        name="unitGroupPanel"
     411        size="0% 50%-216 0%+36 50%+144"
     412    >
     413        <repeat count="10">
     414      <object name="unitGroupButton[n]" size="0 0 36 36" type="button" hidden="false" style="iconButton" tooltip_style="sessionToolTipBottomBold"
     415        tooltip="Click to select grouped units.">
     416          <object name="unitGroupIcon[n]" size="3 3 33 33" type="image" sprite="groupsIcon" ghost="true"/>
     417          <object name="unitGroupLabel[n]" type="text" style="largeCenteredOutlinedText" ghost="true"/>
     418      </object>
     419        </repeat>
     420    </object>-->
     421
     422    <!-- ================================  ================================ -->
     423    <!-- Information tooltip
     424      Follows the mouse around if 'independent' is set to 'true'. -->
     425    <!-- ================================  ================================ -->
     426    <object name="informationTooltip" type="tooltip" independent="true" style="informationTooltip"/>
     427
     428    <!-- ================================  ================================ -->
     429    <!-- Wall-dragging tooltip. Shows the total cost of building a wall while the player is dragging it. -->
     430    <!-- ================================  ================================ -->
     431    <object name="wallDragTooltip" type="tooltip" independent="true" style="informationTooltip"/>
     432
     433    <!-- ================================  ================================ -->
     434    <!-- START of BOTTOM PANEL -->
     435    <!-- ================================  ================================ -->
     436
     437    <object size="50%-512 100%-180 50%+512 100%">
     438
     439        <!-- ================================  ================================ -->
     440        <!-- Minimap -->
     441        <!-- ================================  ================================ -->
     442        <object
     443      name="minimapPanel"
     444      size="0 100%-212 212 100%"
     445      type="image"
     446      sprite="mapPanel"
     447      z="20"
     448        >
     449      <object name="minimap"
     450        type="minimap"
     451        size="14 14 100%-14 100%-14"
     452      >
     453          <action on="WorldClick">handleMinimapEvent(arguments[0]);</action>
     454      </object>
     455
     456      <object name="minimapOverlay" size="10 10 100%-10 100%-10" type="image" sprite="stretched:session/minimap_circle.png" ghost="true"/>
     457
     458      <!-- Idle Worker Button -->
     459      <object type="image"
     460        size="100%-36 4 100%-4 36"
     461      >
     462          <object type="button"
     463            style="iconButton"
     464            tooltip_style="sessionToolTip"
     465            tooltip="Find idle worker"
     466            hotkey="selection.idleworker"
     467          >
     468        <!-- TODO: should highlight the button if there's non-zero idle workers -->
     469        <object size="0 0 100% 100%" type="image" sprite="idleWorker" ghost="true" />
     470        <action on="Press">findIdleUnit(["Female", "Trade", "FishingBoat", "CitizenSoldier", "Healer"]);</action>
     471          </object>
     472      </object>
     473        </object>
     474
     475        <!-- ================================  ================================ -->
     476        <!-- Supplemental Details Panel (Left of Selection Details) -->
     477        <!-- ================================  ================================ -->
     478        <object size="50%-304 100%-170 50%-110 100%" name="supplementalSelectionDetails" type="image" sprite="supplementalDetailsPanel" z="20">
     479
     480      <object name="unitFormationPanel"
     481        size="24 12 100% 100%"
     482      >
     483          <object size="0 0 100% 100%">
     484        <repeat count="16">
     485            <object name="unitFormationButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
     486          <object name="unitFormationIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     487          <object name="unitFormationSelection[n]" hidden="true" type="image" ghost="true" size="3 3 33 33" sprite="stretched:session/icons/corners.png"/>
     488            </object>
     489        </repeat>
     490          </object>
     491      </object>
     492
     493      <object name="unitGarrisonPanel"
     494        size="24 12 100% 100%"
     495      >
     496          <object size="0 0 100% 100%">
     497        <repeat count="12">
     498            <object name="unitGarrisonButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
     499          <object name="unitGarrisonIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     500          <object name="unitGarrisonCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
     501            </object>
     502        </repeat>
     503          </object>
     504      </object>
     505
     506      <object name="unitBarterPanel"
     507        size="6 36 100% 100%"
     508        hidden="true"
     509      >
     510          <object ghost="true" style="resourceText" type="text" size="0 0 100% 20">Exchange resources:</object>
     511          <object size="0 32 100% 78">
     512        <repeat count="4">
     513            <object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
     514          <object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     515          <object name="unitBarterSellUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
     516          <object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     517          <object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
     518            </object>
     519        </repeat>
     520          </object>
     521          <object size="0 78 100% 124">
     522        <repeat count="4">
     523            <object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
     524          <object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     525          <object name="unitBarterBuyUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
     526          <object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     527            </object>
     528        </repeat>
     529          </object>
     530      </object>
     531
     532      <!-- Stance Selection -->
     533      <object name="unitStancePanel"
     534        style="TranslucentPanel"
     535        size="4 100%-43 100%-4 100%-4"
     536        type="text"
     537      >
     538          <object size="1 2 100% 100%">
     539        <repeat count="5">
     540            <object name="unitStanceButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" z="100">
     541          <object name="unitStanceIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     542          <object name="unitStanceSelection[n]" hidden="true" type="image" ghost="true" size="3 3 33 33" sprite="stretched:session/icons/corners.png"/>
     543            </object>
     544        </repeat>
     545          </object>
     546      </object>
     547        </object>
     548
     549        <!-- ================================  ================================ -->
     550        <!-- Selection Details Panel (Middle) -->
     551        <!-- ================================  ================================ -->
     552        <object name="selectionDetails"
     553          type="image"
     554          sprite="selectionDetailsPanel"
     555          size="50%-114 100%-205 50%+114 100%"
     556          hidden="false"
     557        >
     558      <!-- Unit details for Single Unit -->
     559      <object size="50%-112 0 50%+112 100%" name="detailsAreaSingle">
     560         
     561          <!-- Stats Bars -->
     562          <object size= "2 0 100%-2 98" type="image" tooltip_style="sessionToolTip">
     563       
     564        <object size="0 8 100% 60" type="image" sprite="edgedPanelShader">
     565            <!-- Health bar -->
     566            <object size="88 0 100% 24" name="healthSection">
     567          <object size="0 0 100% 16" name="healthLabel" type="text" style="StatsTextLeft" ghost="true">Health:</object>
     568          <object size="0 0 100% 16" name="healthStats" type="text" style="StatsTextRight" ghost="true"/>
     569          <object size="1 16 100% 23" name="health" type="image">
     570              <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     571              <object type="image" sprite="healthBackground" ghost="true"/>
     572              <object type="image" sprite="healthForeground" ghost="true" name="healthBar"/>
     573              <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
     574          </object>
     575            </object>
     576
     577            <!-- Stamina bar -->
     578            <object size="88 28 100% 52" name="staminaSection">
     579          <object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">Stamina:</object>
     580          <object size="0 0 100% 16" name="staminaStats" type="text" style="StatsTextRight" ghost="true"/>
     581          <object size="1 16 100% 23" name="stamina" type="image">
     582              <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     583              <object type="image" sprite="staminaBackground" ghost="true"/>
     584              <object type="image" sprite="staminaForeground" ghost="true" name="staminaBar"/>
     585              <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
     586          </object>
     587            </object>
     588
     589            <!-- Resource bar -->
     590            <object size="88 28 100% 52" name="resourceSection">
     591          <object size="0 0 100% 16" name="resourceLabel" type="text" style="StatsTextLeft" ghost="true"/>
     592          <object size="0 0 100% 16" name="resourceStats" type="text" style="StatsTextRight" ghost="true"/>
     593          <object size="1 16 100% 23" name="resources" type="image">
     594              <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     595              <object type="image" sprite="resourceBackground" ghost="true"/>
     596              <object type="image" sprite="resourceForeground" ghost="true" name="resourceBar"/>
     597              <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
     598          </object>
     599            </object>
     600        </object>
     601       
     602        <object size="0 60 100% 96" type="image" sprite="edgedPanelShader">
     603            <!-- Attack and Armor -->
     604            <object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip="Attack and Armor" tooltip_style="sessionToolTip"/>
     605
     606            <!-- Resource carrying icon/counter -->
     607            <object size="100%-78 -2 100%-28 34" type="text" name="resourceCarryingText" style="CarryingTextRight"/>
     608            <object size="100%-36 -2 100% 34" type="image" name="resourceCarryingIcon"/>
     609        </object>
     610       
     611        <!-- Big unit icon -->
     612        <object size="-8 -8 88 88" type="image" name="iconBorder" sprite="iconBorder" tooltip_style="sessionToolTip">
     613            <object size="1 1 100%-1 100%-1" type="image" name="icon" ghost="true"/>
     614       
     615            <!-- Experience bar -->
     616            <object size="2 2 6 100%-2" type="image" name="experience" tooltip="Experience" tooltip_style="sessionToolTip">
     617          <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     618          <object type="image" sprite="experienceBackground" ghost="true"/>
     619          <object type="image" sprite="experienceForeground" ghost="true" name="experienceBar"/>
     620          <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
     621            </object>
     622       
     623            <object z="20" size="4 4 20 20" name="rankIcon" type="image" tooltip="Rank" tooltip_style="sessionToolTip"/>
     624        </object>
     625          </object>
     626         
     627          <!-- Names (this must come before the attack and armor icon to avoid clipping issues) -->
     628          <object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader">
     629       
     630        <!-- These images are used to clip off the top and bottom of the civ icon -->
     631        <object z="30" size="0 -5 100% 40" ghost="true" type="image" sprite="remove"/>
     632        <object z="30" size="0 100%-5 100% 100%+40" ghost="true" type="image" sprite="remove"/>
     633       
     634        <object z="30" size="0 2 100% 45" ghost="true">
     635            <!-- Specific Name -->
     636            <object size="0 0 100% 20" name="specific" ghost="true" type="text" style="SpecificNameCentered"/>
     637
     638            <!-- Generic Name -->
     639            <object size="0 15 100% 36" name="generic" ghost="true" type="text" style="GenericNameCentered"/>
     640        </object>
     641       
     642        <!-- Player Name and Civ -->
     643        <object size="0 40 100% 100%">
     644            <object size="50%-64 50%-64 50%+64 50%+64" name="playerCivIcon" type="image" ghost="true"/>
     645            <object size="0 0 100% 100%" name="playerColorBackground" type="image" sprite="playerColorBackground" ghost="true"/>
     646            <object size="0 0 100% 100%" type="image" sprite="bottomEdgedPanelShader" ghost="true"/>
     647           
     648            <!-- Why is this being automatically ghosted? In the mean time, set ghost to false -->
     649            <object ghost="false" size="0 0 100% 100%-5" name="player" type="text" style="largeCenteredOutlinedText" tooltip_style="sessionToolTip"/>
     650        </object>
     651          </object>
     652
     653      </object>
     654
     655      <!-- Unit details for Multiple Units -->
     656      <object size="50%-112 0 50%+112 100%" name="detailsAreaMultiple">
     657
     658          <object name="unitSelectionPanel"
     659            size="20 12 100%-20 100%"
     660          >
     661        <object size="0 0 100% 100%">
     662            <repeat count="16">
     663          <object name="unitSelectionButton[n]" hidden="true" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBold" z="100">
     664              <object name="unitSelectionIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     665              <object name="unitSelectionCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
     666              <object size="0 100%-3 100% 100%" name="unitSelectionHealth[n]" ghost="true">
     667              </object>
     668          </object>
     669            </repeat>
     670        </object>
     671          </object>
     672
     673          <!-- Total -->
     674          <object size="100%-42 12 100%-8 46" type="image" sprite="groupsIcon">
     675        <object size="0 0 100% 100%" type="text" style="largeCenteredOutlinedText" name="numberOfUnits"/>
     676          </object>
     677
     678          <!-- Stats Bars -->
     679          <object size= "100%-38 50 100%-18 100%-44" type="image" tooltip_style="sessionToolTip">
     680        <!-- Health bar -->
     681        <object size="4 0 11 100%" type="image" name="healthMultiple" tooltip="Hitpoints" tooltip_style="sessionToolTip">
     682            <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     683            <object type="image" sprite="healthBackground" ghost="true"/>
     684            <object type="image" sprite="healthForeground" ghost="true" name="healthBarMultiple"/>
     685            <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
     686        </object>
     687
     688        <!-- Stamina bar -->
     689        <object size="15 0 22 100%" type="image" name="staminaMultiple" tooltip="Stamina" tooltip_style="sessionToolTipBold">
     690            <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
     691            <object type="image" sprite="staminaBackground" ghost="true"/>
     692            <object type="image" sprite="staminaForeground" ghost="true" name="staminaBarMultiple"/>
     693            <object type="image" sprite="statsBarShaderVertical" ghost="true"/>
     694        </object>
     695          </object>
     696      </object>
     697
     698      <!-- Unit Commands -->
     699      <object name="unitCommandPanel"
     700        size="0 100%-36 100% 100%-4"
     701        type="image"
     702        z="30"
     703      >
     704          <object size="0 1 100% 100%">
     705        <repeat count="6">
     706            <object name="unitCommandButton[n]" hidden="true" style="iconButton" type="button" size="0 0 32 32" tooltip_style="sessionToolTipBottomBold">
     707          <object name="unitCommandIcon[n]" ghost="true" type="image" size="0 0 100% 100%" style="commandIcon"/>
     708          <object name="unitCommandCount[n]" ghost="true" style="groupIconsText" type="text" size="0 0 100% 100%"/>
     709            </object>
     710        </repeat>
     711          </object>
     712      </object>
     713
     714      <!-- shading for unit commands area -->
     715      <object z="50" size="4 100%-36 100%-4 100%-4" ghost="true" type="image" sprite="bottomEdgedPanelShader"/>
     716
     717        </object> <!-- END OF SELECTION DETAILS -->
     718
     719        <!-- ================================  ================================ -->
     720        <!-- Commands Panel (Right of Selection Details) -->
     721        <!-- ================================  ================================ -->
     722       <object name="unitCommands"
     723          type="image"
     724          sprite="unitCommandsPanel"
     725          size="50%+110 100%-170 50%+512 100%"
     726          hidden="false"
     727          z="20"
     728        >
     729      <object name="unitConstructionPanel"
     730        size="10 12 100% 100%"
     731      >
     732          <object size="0 0 100% 100%">
     733        <repeat count="24">
     734            <object name="unitConstructionButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     735          <object name="unitConstructionIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     736          <object name="unitConstructionUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     737            </object>
     738        </repeat>
     739          </object>
     740      </object>
     741
     742      <object name="unitResearchPanel"
     743        size="10 100%-102 100% 100%"
     744      >
     745          <object size="0 0 100% 100%">
     746        <repeat count="16">
     747            <object name="unitResearchButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     748          <object name="unitResearchIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     749          <object name="unitResearchSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     750          <object name="unitResearchUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 60"/>
     751            </object>
     752        </repeat>
     753        <repeat count="8">
     754            <object name="unitResearchPair[n]" hidden="true" size="0 0 46 92">
     755          <object name="unitResearchPairIcon[n]" type="image" ghost="true" size="8 38 38 54" sprite="stretched:session/icons/vertical_pair.png"/>
     756            </object>
     757        </repeat>
     758          </object>
     759      </object>
     760
     761      <object name="unitTrainingPanel"
     762        size="10 12 100% 100%"
     763      >
     764          <object size="0 0 100% 100%">
     765        <repeat count="24">
     766            <object name="unitTrainingButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     767          <object name="unitTrainingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     768          <object name="unitTrainingUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     769            </object>
     770        </repeat>
     771          </object>
     772      </object>
     773
     774      <object name="unitTradingPanel"
     775        size="10 12 100% 100%"
     776      >
     777          <object size="0 0 100% 100%">
     778        <repeat count="4">
     779            <object name="unitTradingButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     780          <object name="unitTradingIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     781          <object name="unitTradingSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
     782            </object>
     783        </repeat>
     784          </object>
     785      </object>
     786
     787      <object name="unitQueuePanel"
     788        size="4 -56 100% 0"
     789        type="image"
     790        sprite="queuePanelShader"
     791      >
     792          <object size="-4 -2 52 54" type="image" sprite="stretched:session/icons/production.png" tooltip_style="sessionToolTipBottom" tooltip="Production queue">
     793        <object name="queueProgress" ghost="true" style="iconButtonProgress" type="text"/>
     794          </object>
     795
     796          <object size="48 6 100% 100%">
     797        <repeat count="16">
     798            <object name="unitQueueButton[n]" hidden="true" style="iconButton" type="button" size="0 0 40 40" tooltip_style="sessionToolTipBottom">
     799          <object name="unitQueueIcon[n]" ghost="true" type="image" size="3 3 37 37"/>
     800          <object name="unitQueueProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 37 37" z="20"/>
     801          <object name="unitQueueCount[n]" ghost="true" style="groupIconsText" type="text" z="20"/>
     802            </object>
     803        </repeat>
     804          </object>
     805      </object>
     806
     807      <object name="unitGatePanel"
     808        size="10 12 100% 100%"
     809      >
     810          <object size="0 0 100% 100%">
     811        <repeat count="8">
     812            <object name="unitGateButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
     813          <object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
     814          <object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
     815          <object name="unitGateUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>
     816            </object>
     817        </repeat>
     818          </object>
     819      </object>
     820
     821        </object> <!-- END OF UNIT COMMANDS -->
     822
     823    </object><!-- END OF BOTTOM PANEL -->
     824
     825  </object> <!-- END OF SN OBJECT -->
     826
     827  <!-- ================================  ================================ -->
     828  <!-- Selection bandbox -->
     829  <!-- ================================  ================================ -->
     830  <object name="bandbox" type="image" sprite="bandbox" ghost="true" hidden="true" z="200"/>
     831
     832  <!-- ================================  ================================ -->
     833  <!-- Network status -->
     834  <!-- ================================  ================================ -->
     835  <object name="netStatus" type="text" style="netStatus" z="100" hidden="true">
     836    <object type="button"
     837      name="disconnectedExitButton"
     838      style="StoneButton"
     839      size="50%-84 50%+128 50%+84 50%+160"
     840      tooltip_style="sessionToolTip"
     841      hidden="true"
     842    >
     843        <object size="0 0 100% 100%" type="text" style="CenteredButtonText" name="disconnectedExitButtonText" ghost="true">Return to Main Menu</object>
     844        <action on="Press">leaveGame()</action>
     845    </object>
     846
     847  </object>
     848
     849</objects>
  • binaries/data/mods/public/gui/session/selection_details.js

     
    254254}
    255255
    256256// Updates middle entity Selection Details Panel
    257 function updateSelectionDetails()
     257function updateSelectionDetails(replay)
    258258{
    259259    var supplementalDetailsPanel = getGUIObjectByName("supplementalSelectionDetails");
    260260    var detailsPanel = getGUIObjectByName("selectionDetails");
     
    296296    commandsPanel.hidden = false;
    297297
    298298    // Fill out commands panel for specific unit selected (or first unit of primary group)
    299     updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
     299    updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection, replay);
    300300}
  • binaries/data/mods/public/gui/session/unit_commands.js

     
    852852 * @param supplementalDetailsPanel Reference to the "supplementalSelectionDetails" GUI Object
    853853 * @param commandsPanel Reference to the "commandsPanel" GUI Object
    854854 * @param selection Array of currently selected entity IDs.
     855 * @param replay True if we're in visual replay mode
    855856 */
    856 function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
     857function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection, replay)
    857858{
    858859    // Panels that are active
    859860    var usedPanels = {};
    860861
    861862    // If the selection is friendly units, add the command panels
    862863    var player = Engine.GetPlayerID();
    863     if (entState.player == player || g_DevSettings.controlAll)
     864    if (replay || entState.player == player || g_DevSettings.controlAll)
    864865    {
    865866        if (selection.length > 1)
    866867            setupUnitPanel(SELECTION, usedPanels, entState, g_Selection.groups.getTemplateNames(),
    867868                function (entType) { changePrimarySelectionGroup(entType); } );
    868869
    869         var commands = getEntityCommandsList(entState);
    870         if (commands.length)
    871             setupUnitPanel(COMMAND, usedPanels, entState, commands,
    872                 function (item) { performCommand(entState.id, item.name); } );
     870        if (!replay)
     871        {
     872            var commands = getEntityCommandsList(entState);
     873            if (commands.length)
     874                setupUnitPanel(COMMAND, usedPanels, entState, commands,
     875                    function (item) { performCommand(entState.id, item.name); } );
     876        }
    873877
    874878        if (entState.garrisonHolder)
    875879        {
     
    885889                function (item) { unloadTemplate(item); } );
    886890        }
    887891
    888         var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
    889         if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
     892        if (!replay)
    890893        {
    891             setupUnitPanel(FORMATION, usedPanels, entState, formations,
    892                 function (item) { performFormation(entState.id, item); } );
    893         }
     894            var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
     895            if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
     896            {
     897                setupUnitPanel(FORMATION, usedPanels, entState, formations,
     898                    function (item) { performFormation(entState.id, item); } );
     899            }
    894900
    895         // TODO: probably should load the stance list from a data file,
    896         // and/or vary depending on what units are selected
    897         var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
    898         if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
    899         {
    900             setupUnitPanel(STANCE, usedPanels, entState, stances,
    901                 function (item) { performStance(entState.id, item); } );
    902         }
     901            // TODO: probably should load the stance list from a data file,
     902            // and/or vary depending on what units are selected
     903            var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
     904            if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
     905            {
     906                setupUnitPanel(STANCE, usedPanels, entState, stances,
     907                    function (item) { performStance(entState.id, item); } );
     908            }
    903909
    904         getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
    905         if (entState.barterMarket)
    906         {
    907             usedPanels["Barter"] = 1;
    908             setupUnitBarterPanel(entState);
    909         }
     910            getGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
     911            if (entState.barterMarket)
     912            {
     913                usedPanels["Barter"] = 1;
     914                setupUnitBarterPanel(entState);
     915            }
    910916
    911         var buildableEnts = [];
    912         var trainableEnts = [];
    913         var state;
    914         // Get all buildable and trainable entities
    915         for (var i in selection)
    916         {
    917             if ((state = GetEntityState(selection[i])) && state.buildEntities && state.buildEntities.length)
    918                 buildableEnts = buildableEnts.concat(state.buildEntities);
    919             if ((state = GetEntityState(selection[i])) && state.production && state.production.entities.length)
    920                 trainableEnts = trainableEnts.concat(state.production.entities);
    921         }
    922        
    923         // Remove duplicates
    924         removeDupes(buildableEnts);
    925         removeDupes(trainableEnts);
     917            var buildableEnts = [];
     918            var trainableEnts = [];
     919            var state;
     920            // Get all buildable and trainable entities
     921            for (var i in selection)
     922            {
     923                if ((state = GetEntityState(selection[i])) && state.buildEntities && state.buildEntities.length)
     924                    buildableEnts = buildableEnts.concat(state.buildEntities);
     925                if ((state = GetEntityState(selection[i])) && state.production && state.production.entities.length)
     926                    trainableEnts = trainableEnts.concat(state.production.entities);
     927            }
     928           
     929            // Remove duplicates
     930            removeDupes(buildableEnts);
     931            removeDupes(trainableEnts);
    926932
    927         // Whether the GUI's right panel has been filled.
    928         var rightUsed = true;
     933            // Whether the GUI's right panel has been filled.
     934            var rightUsed = true;
    929935
    930         // The first selected entity's type has priority.
    931         if (entState.buildEntities)
    932             setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
    933         else if (entState.production && entState.production.entities)
    934             setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
    935                 function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
    936         else if (entState.trader)
    937             setupUnitTradingPanel(usedPanels, entState, selection);
    938         else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
    939         {
    940             // Allow long wall pieces to be converted to gates
    941             var longWallTypes = {};
    942             var walls = [];
    943             var gates = [];
    944             for (var i in selection)
     936            // The first selected entity's type has priority.
     937            if (entState.buildEntities)
     938                setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
     939            else if (entState.production && entState.production.entities)
     940                setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
     941                    function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
     942            else if (entState.trader)
     943                setupUnitTradingPanel(usedPanels, entState, selection);
     944            else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
    945945            {
    946                 state = GetEntityState(selection[i]);
    947                 if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
     946                // Allow long wall pieces to be converted to gates
     947                var longWallTypes = {};
     948                var walls = [];
     949                var gates = [];
     950                for (var i in selection)
    948951                {
    949                     var gateTemplate = getWallGateTemplate(state.id);
    950                     if (gateTemplate)
     952                    state = GetEntityState(selection[i]);
     953                    if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
    951954                    {
    952                         var wallName = GetTemplateData(state.template).name.generic;
    953                         var gateName = GetTemplateData(gateTemplate).name.generic;
     955                        var gateTemplate = getWallGateTemplate(state.id);
     956                        if (gateTemplate)
     957                        {
     958                            var wallName = GetTemplateData(state.template).name.generic;
     959                            var gateName = GetTemplateData(gateTemplate).name.generic;
    954960
    955                         walls.push({
    956                             "tooltip": "Convert " + wallName + " to " + gateName,
    957                             "template": gateTemplate,
    958                             "callback": function (item) { transformWallToGate(item.template); }
    959                         });
     961                            walls.push({
     962                                "tooltip": "Convert " + wallName + " to " + gateName,
     963                                "template": gateTemplate,
     964                                "callback": function (item) { transformWallToGate(item.template); }
     965                            });
     966                        }
     967
     968                        // We only need one entity per type.
     969                        longWallTypes[state.template] = true;
    960970                    }
     971                    else if (state.gate && !gates.length)
     972                        for (var j = 0; j < GATE_ACTIONS.length; ++j)
     973                            gates.push({
     974                                "gate": state.gate,
     975                                "tooltip": GATE_ACTIONS[j] + " gate",
     976                                "locked": j == 0,
     977                                "callback": function (item) { lockGate(item.locked); }
     978                            });
     979                    // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
     980                    else if (state.gate && state.gate.locked != gates[0].gate.locked)
     981                        for (var j = 0; j < gates.length; ++j)
     982                            delete gates[j].gate.locked;
     983                }
    961984
    962                     // We only need one entity per type.
    963                     longWallTypes[state.template] = true;
    964                 }
    965                 else if (state.gate && !gates.length)
    966                     for (var j = 0; j < GATE_ACTIONS.length; ++j)
    967                         gates.push({
    968                             "gate": state.gate,
    969                             "tooltip": GATE_ACTIONS[j] + " gate",
    970                             "locked": j == 0,
    971                             "callback": function (item) { lockGate(item.locked); }
    972                         });
    973                 // Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
    974                 else if (state.gate && state.gate.locked != gates[0].gate.locked)
    975                     for (var j = 0; j < gates.length; ++j)
    976                         delete gates[j].gate.locked;
     985                // Place wall conversion options after gate lock/unlock icons.
     986                var items = gates.concat(walls);
     987                if (items.length)
     988                    setupUnitPanel(GATE, usedPanels, entState, items);
     989                else
     990                    rightUsed = false;
    977991            }
    978 
    979             // Place wall conversion options after gate lock/unlock icons.
    980             var items = gates.concat(walls);
    981             if (items.length)
    982                 setupUnitPanel(GATE, usedPanels, entState, items);
    983992            else
    984993                rightUsed = false;
    985         }
    986         else
    987             rightUsed = false;
    988994
    989         if (!rightUsed)
    990         {
    991             // The right pane is empty. Fill the pane with a sane type.
    992             // Prefer buildables for units and trainables for structures.
    993             if (buildableEnts.length && (hasClass(entState, "Unit") || !trainableEnts.length))
    994                 setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
    995             else if (trainableEnts.length)
    996                 setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
    997                     function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
    998         }
     995            if (!rightUsed)
     996            {
     997                // The right pane is empty. Fill the pane with a sane type.
     998                // Prefer buildables for units and trainables for structures.
     999                if (buildableEnts.length && (hasClass(entState, "Unit") || !trainableEnts.length))
     1000                    setupUnitPanel(CONSTRUCTION, usedPanels, entState, buildableEnts, startBuildingPlacement);
     1001                else if (trainableEnts.length)
     1002                    setupUnitPanel(TRAINING, usedPanels, entState, trainableEnts,
     1003                        function (trainEntType) { addTrainingToQueue(selection, trainEntType); } );
     1004            }
    9991005
    1000         // Show technologies if the active panel has at most one row of icons.
    1001         if (entState.production && entState.production.technologies.length)
    1002         {
    1003             var activepane = usedPanels[CONSTRUCTION] ? buildableEnts.length : trainableEnts.length;
    1004             if (selection.length == 1 || activepane <= 8)
    1005                 setupUnitPanel(RESEARCH, usedPanels, entState, entState.production.technologies,
    1006                     function (researchType) { addResearchToQueue(entState.id, researchType); } );
     1006            // Show technologies if the active panel has at most one row of icons.
     1007            if (entState.production && entState.production.technologies.length)
     1008            {
     1009                var activepane = usedPanels[CONSTRUCTION] ? buildableEnts.length : trainableEnts.length;
     1010                if (selection.length == 1 || activepane <= 8)
     1011                    setupUnitPanel(RESEARCH, usedPanels, entState, entState.production.technologies,
     1012                        function (researchType) { addResearchToQueue(entState.id, researchType); } );
     1013            }
    10071014        }
    10081015
    10091016        if (entState.production && entState.production.queue.length)
  • binaries/data/mods/public/gui/summary/summary.js

     
    276276function onTick()
    277277{
    278278    // Update music state
    279     global.music.updateTimer();
     279    if (global.music)
     280        global.music.updateTimer();
    280281}
  • source/gui/scripting/ScriptFunctions.cpp

     
    167167    return -1;
    168168}
    169169
     170void SetPlayerID(void* UNUSED(cbdata), int id)
     171{
     172    if (g_Game)
     173        g_Game->SetPlayerID(id);
     174}
     175
    170176std::wstring GetDefaultPlayerName(void* UNUSED(cbdata))
    171177{
    172178    std::wstring name = g_PlayerName.FromUTF8();
     
    617623    // Misc functions
    618624    scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
    619625    scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
     626    scriptInterface.RegisterFunction<void, int, &SetPlayerID>("SetPlayerID");
    620627    scriptInterface.RegisterFunction<std::wstring, &GetDefaultPlayerName>("GetDefaultPlayerName");
    621628    scriptInterface.RegisterFunction<void, std::string, &OpenURL>("OpenURL");
    622629    scriptInterface.RegisterFunction<void, &RestartInAtlas>("RestartInAtlas");
  • source/network/NetTurnManager.cpp

     
    465465
    466466
    467467
     468CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     469    CNetLocalTurnManager(simulation, replay)
     470{
     471}
     472
     473void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
     474{
     475    m_ReplayCommands[turn][player].push_back(command);
     476}
     477
     478void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     479{
     480    DoTurn(turn);
     481}
     482
     483void CNetReplayTurnManager::DoTurn(u32 turn)
     484{
     485    std::map<int, std::vector<std::string> > playerCommands = m_ReplayCommands[turn];
     486    std::map<int, std::vector<std::string> >::iterator it;
     487    for (it = playerCommands.begin(); it != playerCommands.end(); ++it)
     488    {
     489        int player = it->first;
     490        for (size_t i = 0; i < it->second.size(); ++i)
     491        {
     492            CScriptValRooted data = m_Simulation2.GetScriptInterface().ParseJSON(it->second[i]);
     493            AddCommand(m_ClientId, player, data, m_CurrentTurn + 1);
     494        }
     495    }
     496}
     497
     498
     499
     500
    468501CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    469502    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
    470503{
  • source/network/NetTurnManager.h

     
    2222
    2323#include <list>
    2424#include <map>
     25#include <vector>
    2526
    2627class CNetServerWorker;
    2728class CNetClient;
     
    227228    virtual void NotifyFinishedUpdate(u32 turn);
    228229};
    229230
     231/**
     232 * Implementation of CNetTurnManager for replay games.
     233 */
     234class CNetReplayTurnManager : public CNetLocalTurnManager
     235{
     236public:
     237    CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
    230238
     239    void StoreReplayCommand(u32 turn, int player, const std::string& command);
     240
     241protected:
     242    virtual void NotifyFinishedUpdate(u32 turn);
     243
     244    void DoTurn(u32 turn);
     245
     246    std::map<u32, std::map<int, std::vector<std::string> > > m_ReplayCommands;
     247};
     248
     249
    231250/**
    232251 * The server-side counterpart to CNetClientTurnManager.
    233252 * Records the turn state of each client, and sends turn advancement messages
  • source/ps/Game.cpp

     
    5959 * Constructor
    6060 *
    6161 **/
    62 CGame::CGame(bool disableGraphics):
     62CGame::CGame(bool disableGraphics, bool replayLog):
    6363    m_World(new CWorld(this)),
    6464    m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), m_World->GetTerrain())),
    6565    m_GameView(disableGraphics ? NULL : new CGameView(this)),
     
    6767    m_Paused(false),
    6868    m_SimRate(1.0f),
    6969    m_PlayerID(-1),
    70     m_IsSavedGame(false)
     70    m_IsSavedGame(false),
     71    m_IsReplay(false),
     72    m_ReplayStream(NULL)
    7173{
    72     m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
    7374    // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
     75    if (replayLog)
     76        m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
     77    else
     78        m_ReplayLogger = new CDummyReplayLogger();
    7479
    7580    // Need to set the CObjectManager references after various objects have
    7681    // been initialised, so do it here rather than via the initialisers above.
     
    97102    delete m_Simulation2;
    98103    delete m_World;
    99104    delete m_ReplayLogger;
     105    delete m_ReplayStream;
    100106}
    101107
    102108void CGame::SetTurnManager(CNetTurnManager* turnManager)
     
    161167    if (m_IsSavedGame)
    162168        RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
    163169
     170    if (m_IsReplay)
     171        RegMemFun(this, &CGame::LoadReplayData, L"Loading replay data", 1000);
     172
    164173    LDR_EndRegistering();
    165174}
    166175
     
    184193    return 0;
    185194}
    186195
     196int CGame::LoadReplayData()
     197{
     198    ENSURE(m_IsReplay);
     199    ENSURE(!m_ReplayPath.empty());
     200
     201    CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
     202
     203    u32 currentTurn = 0;
     204    std::string type;
     205    while ((*m_ReplayStream >> type).good())
     206    {
     207        if (type == "turn")
     208        {
     209            u32 turn = 0;
     210            u32 turnLength = 0;
     211            *m_ReplayStream >> turn >> turnLength;
     212            ENSURE(turn == currentTurn);
     213        }
     214        else if (type == "cmd")
     215        {
     216            int player;
     217            *m_ReplayStream >> player;
     218
     219            std::string line;
     220            std::getline(*m_ReplayStream, line);
     221            replayTurnMgr->StoreReplayCommand(currentTurn, player, line);
     222        }
     223        else if (type == "hash" || type == "hash-quick")
     224        {
     225            // Ignored for now
     226            std::string replayHash;
     227            *m_ReplayStream >> replayHash;
     228        }
     229        else if (type == "end")
     230        {
     231            currentTurn++;
     232        }
     233        else
     234        {
     235            CancelLoad(L"Failed to load replay data (unrecognized content)");
     236        }
     237    }
     238    m_FinalReplayTurn = currentTurn + 1;
     239
     240    return 0;
     241}
     242
     243void CGame::StartReplay(const std::string& replayPath)
     244{
     245    m_IsReplay = true;
     246
     247    SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
     248
     249    m_ReplayPath = replayPath;
     250    m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
     251    ENSURE(m_ReplayStream->good());
     252
     253    std::string type;
     254    ENSURE((*m_ReplayStream >> type).good() && type == "start");
     255
     256    std::string line;
     257    std::getline(*m_ReplayStream, line);
     258    CScriptValRooted attribs = m_Simulation2->GetScriptInterface().ParseJSON(line);
     259    StartGame(attribs, "");
     260}
     261
    187262/**
    188263 * Game initialization has been completed. Set game started flag and start the session.
    189264 *
     
    285360                g_GUI->SendEventToAll("SimulationUpdate");
    286361            }
    287362
     363            if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn)
     364                g_GUI->SendEventToAll("ReplayFinished");
     365
    288366            GetView()->GetLOSTexture().MakeDirty();
    289367        }
    290368       
  • source/ps/Game.h

     
    1919#define INCLUDED_GAME
    2020
    2121#include "ps/Errors.h"
     22#include <map>
    2223#include <vector>
    2324
    2425#include "scriptinterface/ScriptVal.h"
     
    6566    CNetTurnManager* m_TurnManager;
    6667
    6768public:
    68     CGame(bool disableGraphics = false);
     69    CGame(bool disableGraphics = false, bool replayLog = true);
    6970    ~CGame();
    7071
    7172    /**
     
    7677    void StartGame(const CScriptValRooted& attribs, const std::string& savedState);
    7778    PSRETURN ReallyStartGame();
    7879
     80    void StartReplay(const std::string& replayPath);
     81
    7982    /**
    8083     * Periodic heartbeat that controls the process. performs all per-frame updates.
    8184     * Simulation update is called and game status update is called.
     
    168171    int LoadInitialState();
    169172    std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
    170173    bool m_IsSavedGame; // true if loading a saved game; false for a new game
     174
     175    int LoadReplayData();
     176    std::string m_ReplayPath;
     177    bool m_IsReplay;
     178    std::istream* m_ReplayStream;
     179    u32 m_FinalReplayTurn;
    171180};
    172181
    173182extern CGame *g_Game;
  • source/ps/GameSetup/GameSetup.cpp

     
    825825}
    826826
    827827bool Autostart(const CmdLineArgs& args);
     828bool VisualReplay(const CmdLineArgs& args);
    828829
    829830void Init(const CmdLineArgs& args, int UNUSED(flags))
    830831{
     
    983984
    984985    try
    985986    {
    986         if (!Autostart(args))
     987        if (!VisualReplay(args) && !Autostart(args))
    987988        {
    988989            const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
    989990            InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID);
     
    10171018    g_DoRenderCursor = RenderingState;
    10181019}
    10191020
     1021bool VisualReplay(const CmdLineArgs& args)
     1022{
     1023    CStr replayPath = args.Get("replay-visual");
     1024    if (!replayPath.empty())
     1025    {
     1026        g_Game = new CGame(false, false);
     1027       
     1028        g_Game->SetPlayerID(1);
     1029        g_Game->StartReplay(replayPath);
     1030
     1031        // TODO: Non progressive load can fail - need a decent way to handle this
     1032        LDR_NonprogressiveLoad();
     1033
     1034        PSRETURN ret = g_Game->ReallyStartGame();
     1035        ENSURE(ret == PSRETURN_OK);
     1036
     1037        InitPs(true, L"page_replay.xml", JSVAL_VOID);
     1038        return true;
     1039    }
     1040
     1041    return false;
     1042}
     1043
    10201044bool Autostart(const CmdLineArgs& args)
    10211045{
    10221046    /*
  • source/ps/Replay.cpp

     
    128128    g_ScriptStatsTable = new CScriptStatsTable;
    129129    g_ProfileViewer.AddRootTable(g_ScriptStatsTable);
    130130
    131     CGame game(true);
     131    CGame game(true, false);
    132132    g_Game = &game;
    133133
    134134    // Need some stuff for terrain movement costs: