Ticket #3168: t3168_let_observer_change_perspective_v1.patch

File t3168_let_observer_change_perspective_v1.patch, 28.0 KB (added by elexis, 8 years ago)
  • binaries/data/mods/public/gui/session/menu.js

    function exitMenuButton()  
    140140            "buttons": [resumeGame, leaveGame]
    141141        }
    142142    };
    143143
    144144    let messageType = g_IsNetworked && g_IsController ? "host" :
    145         (g_IsNetworked && !g_GameEnded && !g_IsObserver ? "client" : "singleplayer");
     145        (g_IsNetworked && !g_GameEnded && Engine.GetPlayerID() != -1 ? "client" : "singleplayer");
    146146
    147147    messageBox(
    148148        400, 200,
    149149        messageTypes[messageType].caption,
    150150        translate("Confirmation"),
    function closeChat()  
    218218    Engine.GetGUIObjectByName("chatInput").caption = ""; // Clear chat input
    219219    Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus
    220220    Engine.GetGUIObjectByName("chatDialogPanel").hidden = true;
    221221}
    222222
     223/**
     224 * Chat is sent via GUID, not playerID.
     225 */
    223226function updateTeamCheckbox(check)
    224227{
    225228    Engine.GetGUIObjectByName("toggleTeamChatLabel").hidden = g_IsObserver;
    226229    let toggleTeamChat = Engine.GetGUIObjectByName("toggleTeamChat");
    227230    toggleTeamChat.hidden = g_IsObserver;
    function tributeResource(data)  
    264267
    265268function openDiplomacy()
    266269{
    267270    if (g_IsTradeOpen)
    268271        closeTrade();
    269     g_IsDiplomacyOpen = true;
    270272
    271     let we = Engine.GetPlayerID();
     273    if (g_ViewedPlayer < 1)
     274    {
     275        closeDiplomacy();
     276        return;
     277    }
     278
     279    g_IsDiplomacyOpen = true;
    272280
    273281    // Get offset for one line
    274282    let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
    275283    let rowsize = onesize.bottom - onesize.top;
    276284
    function openDiplomacy()  
    290298
    291299        Engine.GetGUIObjectByName("diplomacyPlayerName["+(i-1)+"]").caption = "[color=\"" + playerColor + "\"]" + g_Players[i].name + "[/color]";
    292300        Engine.GetGUIObjectByName("diplomacyPlayerCiv["+(i-1)+"]").caption = g_CivData[g_Players[i].civ].Name;
    293301
    294302        Engine.GetGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (g_Players[i].team < 0) ? translateWithContext("team", "None") : g_Players[i].team+1;
    295 
    296         if (i != we)
    297             Engine.GetGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (g_Players[i].isAlly[we] ? translate("Ally") : (g_Players[i].isNeutral[we] ? translate("Neutral") : translate("Enemy")));
     303        Engine.GetGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = g_Players[i].isAlly[g_ViewedPlayer] ? translate("Ally") : (g_Players[i].isNeutral[g_ViewedPlayer] ? translate("Neutral") : translate("Enemy"));
    298304
    299305        // Don't display the options for ourself, or if we or the other player aren't active anymore
    300         if (i == we || g_Players[we].state != "active" || g_Players[i].state != "active")
     306        if (i == g_ViewedPlayer || g_Players[g_ViewedPlayer].state != "active" || g_Players[i].state != "active")
    301307        {
    302308            // Hide the unused/unselectable options
    303309            for (let a of ["TributeFood", "TributeWood", "TributeStone", "TributeMetal", "Ally", "Neutral", "Enemy"])
    304310                Engine.GetGUIObjectByName("diplomacyPlayer"+a+"["+(i-1)+"]").hidden = true;
    305311            Engine.GetGUIObjectByName("diplomacyAttackRequest["+(i-1)+"]").hidden = true;
    function openDiplomacy()  
    337343                    };
    338344                    if (!isBatchTrainPressed)
    339345                        g_FlushTributing();
    340346                };
    341347            })(i, resource, button);
     348            button.enabled = controlsPlayer(g_ViewedPlayer);
    342349            button.hidden = false;
    343350            button.tooltip = formatTributeTooltip(g_Players[i], resource, 100);
    344351        }
    345352
    346353        // Attack Request
    347354        let simState = GetSimState();
    348355        let button = Engine.GetGUIObjectByName("diplomacyAttackRequest["+(i-1)+"]");
    349         button.hidden = simState.ceasefireActive || !(g_Players[i].isEnemy[we]);
     356        button.hidden = simState.ceasefireActive || !(g_Players[i].isEnemy[g_ViewedPlayer]);
     357        button.enabled = controlsPlayer(g_ViewedPlayer);
    350358        button.tooltip = translate("Request your allies to attack this enemy");
    351         button.onpress = (function(i, we){ return function() {
    352             Engine.PostNetworkCommand({ "type": "attack-request", "source": we, "target": i });
    353         }; })(i, we);
     359        button.onpress = (function(i) { return function() {
     360            Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "target": i });
     361        }; })(i);
    354362
    355363        // Skip our own teams on teams locked
    356         if (g_Players[we].teamsLocked && g_Players[we].team != -1 && g_Players[we].team == g_Players[i].team)
     364        if (g_Players[g_ViewedPlayer].teamsLocked && g_Players[g_ViewedPlayer].team != -1 && g_Players[g_ViewedPlayer].team == g_Players[i].team)
    357365            continue;
    358366
    359367        // Diplomacy settings
    360368        // Set up the buttons
    361369        for (let setting of ["Ally", "Neutral", "Enemy"])
    362370        {
    363371            let button = Engine.GetGUIObjectByName("diplomacyPlayer"+setting+"["+(i-1)+"]");
    364372
    365             button.caption = g_Players[we]["is"+setting][i] ? translate("x") : "";
     373            button.caption = g_Players[g_ViewedPlayer]["is" + setting][i] ? translate("x") : "";
    366374            button.onpress = (function(e){ return function() { setDiplomacy(e); }; })({ "player": i, "to": setting.toLowerCase() });
     375            button.enabled = controlsPlayer(g_ViewedPlayer);
    367376            button.hidden = simState.ceasefireActive;
    368377        }
    369378    }
    370379
    371380    Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
    function toggleDiplomacy()  
    387396
    388397function openTrade()
    389398{
    390399    if (g_IsDiplomacyOpen)
    391400        closeDiplomacy();
     401
     402    if (g_ViewedPlayer < 1)
     403    {
     404        closeTrade();
     405        return;
     406    }
     407
    392408    g_IsTradeOpen = true;
    393409
    394410    var updateButtons = function()
    395411    {
    396412        for (var res in button)
    397413        {
    398414            button[res].label.caption = proba[res] + "%";
    399             if (res == selec)
    400             {
    401                 button[res].sel.hidden = false;
    402                 button[res].up.hidden = true;
    403                 button[res].dn.hidden = true;
    404             }
    405             else
    406             {
    407                 button[res].sel.hidden = true;
    408                 button[res].up.hidden = (proba[res] == 100 || proba[selec] == 0);
    409                 button[res].dn.hidden = (proba[res] == 0 || proba[selec] == 100);
    410             }
     415
     416            button[res].sel.hidden = !controlsPlayer(g_ViewedPlayer) || res != selec;
     417            button[res].up.hidden = !controlsPlayer(g_ViewedPlayer) || res == selec || proba[res] == 100 || proba[selec] == 0;
     418            button[res].dn.hidden = !controlsPlayer(g_ViewedPlayer) || res == selec || proba[res] == 0 || proba[selec] == 100;
    411419        }
    412420    };
    413421
    414     var proba = Engine.GuiInterfaceCall("GetTradingGoods");
     422    var proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
    415423    var button = {};
    416424    var selec = RESOURCES[0];
    417425    for (var i = 0; i < RESOURCES.length; ++i)
    418426    {
    419427        var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
    function openTrade()  
    434442        var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
    435443        var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
    436444        var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
    437445        button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
    438446
     447        buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
    439448        buttonResource.onpress = (function(resource){
    440449            return function() {
    441450                if (Engine.HotkeyIsPressed("session.fulltradeswap"))
    442451                {
    443452                    for (var ress of RESOURCES)
    function openTrade()  
    448457                selec = resource;
    449458                updateButtons();
    450459            };
    451460        })(resource);
    452461
     462        buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
    453463        buttonUp.onpress = (function(resource){
    454464            return function() {
    455465                proba[resource] += Math.min(STEP, proba[selec]);
    456466                proba[selec]    -= Math.min(STEP, proba[selec]);
    457467                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    458468                updateButtons();
    459469            };
    460470        })(resource);
    461471
     472        buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
    462473        buttonDn.onpress = (function(resource){
    463474            return function() {
    464475                proba[selec]    += Math.min(STEP, proba[resource]);
    465476                proba[resource] -= Math.min(STEP, proba[resource]);
    466477                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    function openTrade()  
    468479            };
    469480        })(resource);
    470481    }
    471482    updateButtons();
    472483
    473     let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber");
     484    let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer);
    474485    Engine.GetGUIObjectByName("landTraders").caption = getIdleLandTradersText(traderNumber);
    475486    Engine.GetGUIObjectByName("shipTraders").caption = getIdleShipTradersText(traderNumber);
    476487
    477488    Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
    478489}
    function openStrucTree()  
    645656    closeOpenDialogs();
    646657    pauseGame();
    647658
    648659    // TODO add info about researched techs and unlocked entities
    649660    Engine.PushGuiPage("page_structree.xml", {
    650         "civ" : g_Players[Engine.GetPlayerID()].civ,
     661        "civ" : g_Players[g_ViewedPlayer].civ,
    651662        "callback": "resumeGame",
    652663    });
    653664}
    654665
    655666/**
  • binaries/data/mods/public/gui/session/messages.js

    function getCheatsData()  
    267267 * @param {string} text
    268268 * @returns {boolean} - True if a cheat was executed.
    269269 */
    270270function executeCheat(text)
    271271{
    272     if (g_IsObserver || !g_Players[Engine.GetPlayerID()].cheatsEnabled)
     272    if (Engine.GetPlayerID() == -1 || !g_Players[Engine.GetPlayerID()].cheatsEnabled)
    273273        return false;
    274274
    275275    // Find the cheat code that is a prefix of the user input
    276276    let cheatCode = Object.keys(g_Cheats).find(cheatCode => text.indexOf(cheatCode) == 0);
    277277    if (!cheatCode)
    function formatDefeatMessage(msg)  
    566566    });
    567567}
    568568
    569569function formatDiplomacyMessage(msg)
    570570{
    571     // Check observer first
    572     let use = {
    573         "observer": g_IsObserver,
    574         "active": Engine.GetPlayerID() == msg.sourcePlayer,
    575         "passive": Engine.GetPlayerID() == msg.targetPlayer
    576     };
    577 
    578     let messageType = Object.keys(use).find(v => use[v]);
    579     if (!messageType)
     571    let messageType;
     572    switch (Engine.GetPlayerID())
     573    {
     574    // Check observer first, since we also want to see if the selected player in the developer-overlay has changed the diplomacy
     575    case -1:
     576        messageType = "observer";
     577        break;
     578    case msg.sourcePlayer:
     579        messageType = "active";
     580        break;
     581    case msg.targetPlayer:
     582        messageType = "passive";
     583        break;
     584    default:
    580585        return "";
     586    }
    581587
    582588    return sprintf(g_DiplomacyMessages[messageType][msg.status], {
    583589        "player": colorizePlayernameByID(messageType == "active" ? msg.targetPlayer : msg.sourcePlayer),
    584590        "player2": colorizePlayernameByID(messageType == "active" ? msg.sourcePlayer : msg.targetPlayer)
    585591    });
    function formatDiplomacyMessage(msg)  
    587593
    588594function formatTributeMessage(msg)
    589595{
    590596    // Check observer first, since we also want to see if the selected player in the developer-overlay has sent tributes
    591597    let message = "";
    592     if (g_IsObserver)
     598    if (Engine.GetPlayerID() == -1)
    593599        message = translate("%(player)s has sent %(player2)s %(amounts)s.");
    594600    else if (msg.targetPlayer == Engine.GetPlayerID())
    595601        message = translate("%(player)s has sent you %(amounts)s.");
    596602
    597603    return sprintf(message, {
    function formatChatCommand(msg)  
    664670function checkChatAddressee(msg)
    665671{
    666672    if (msg.text[0] != '/')
    667673        return true;
    668674
    669     if (g_IsObserver)
     675    if (Engine.GetPlayerID() == -1)
    670676        return false;
    671677
    672678    let cmd = msg.text.split(/\s/)[0];
    673679    msg.text = msg.text.substr(cmd.length + 1);
    674680
  • binaries/data/mods/public/gui/session/session.js

    var g_IsController;  
    2020/**
    2121 * Unique ID for lobby reports
    2222 */
    2323var g_MatchID;
    2424
     25/**
     26 * True if the current user has observer capabilities.
     27 */
    2528var g_IsObserver = false;
    2629
     30/**
     31 * The playerID selected in the change perspective tool.
     32 */
     33var g_ViewedPlayer = Engine.GetPlayerID();
     34
    2735// Cache the basic player data (name, civ, color)
    2836var g_Players = [];
    2937
    3038var lastTickTime = new Date();
    3139
    var g_CivData = {};  
    3644
    3745var g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1 } };
    3846
    3947// Cache dev-mode settings that are frequently or widely used
    4048var g_DevSettings = {
     49    "changePerspective": false,
    4150    "controlAll": false
    4251};
    4352
    4453// Whether status bars should be shown for all of the player's units.
    4554var g_ShowAllStatusBars = false;
    function init(initData, hotloadData)  
    180189    }
    181190
    182191    g_CivData = loadCivData();
    183192    g_CivData.gaia = { "Code": "gaia", "Name": translate("Gaia") };
    184193
    185     if (Engine.GetPlayerID() <= 0)
    186         g_IsObserver = true;
     194    setObserverMode(Engine.GetPlayerID() == -1);
    187195
    188196    updateTopPanel();
    189197
    190198    let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
    191199    gameSpeed.list = g_GameSpeeds.Title;
    function init(initData, hotloadData)  
    194202    gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default;
    195203    gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); };
    196204    initMenuPosition();
    197205
    198206    // Populate player selection dropdown
    199     let playerNames = [];
    200     let playerIDs = [];
     207    let playerNames = ["Observer"];
     208    let playerIDs = [-1];
    201209    for (let player in g_Players)
    202210    {
    203211        playerNames.push(g_Players[player].name);
    204212        playerIDs.push(player);
    205213    }
    206214
    207215    let viewPlayerDropdown = Engine.GetGUIObjectByName("viewPlayer");
    208216    viewPlayerDropdown.list = playerNames;
    209217    viewPlayerDropdown.list_data = playerIDs;
    210     viewPlayerDropdown.selected = Engine.GetPlayerID();
     218    viewPlayerDropdown.selected = Engine.GetPlayerID() + 1;
    211219
    212220    // If in Atlas editor, disable the exit button
    213221    if (Engine.IsAtlasRunning())
    214222        Engine.GetGUIObjectByName("menuExitButton").enabled = false;
    215223
    216224    if (hotloadData)
    217225        g_Selection.selected = hotloadData.selection;
    218226
    219227    // Starting for the first time:
    220228    initMusic();
    221     if (!g_IsObserver)
     229    if (Engine.GetPlayerID() != -1)
    222230        global.music.storeTracks(g_CivData[g_Players[Engine.GetPlayerID()].civ].Music);
    223231    global.music.setState(global.music.states.PEACE);
    224232    playAmbient();
    225233
    226234    onSimulationUpdate();
    function init(initData, hotloadData)  
    234242    // and it generates a massive amount of data to transmit and store
    235243    //setTimeout(function() { reportPerformance(5); }, 5000);
    236244    //setTimeout(function() { reportPerformance(60); }, 60000);
    237245}
    238246
     247function toggleChangePerspective(enabled)
     248{
     249    g_DevSettings.changePerspective = enabled;
     250    Engine.GetGUIObjectByName("viewPlayer").hidden = !enabled && !g_IsObserver;
     251    selectViewPlayer(g_ViewedPlayer);
     252}
     253
     254/**
     255 * Change perspective tool.
     256 * Shown to observers or when enabling the developers option.
     257 */
    239258function selectViewPlayer(playerID)
    240259{
    241     Engine.SetPlayerID(playerID);
     260    if (playerID < -1 || playerID > g_Players.length - 1 ||
     261            !g_DevSettings.changePerspective && !g_IsObserver)
     262        return;
     263
     264    g_ViewedPlayer = playerID;
     265    Engine.SetPlayerID(g_DevSettings.changePerspective ? playerID : -1);
     266
    242267    updateTopPanel();
     268    onSimulationUpdate();
     269
     270    let viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
     271    let alphaLabel = Engine.GetGUIObjectByName("alphaLabel");
     272    alphaLabel.hidden = g_ViewedPlayer > 0 && !viewPlayer.hidden;
     273    if (g_ViewedPlayer < 1)
     274        alphaLabel.size = "200 0 100%-475 100%";
     275    else
     276        alphaLabel.size = "50%+20 0 100%-226 100%";
    243277
    244278    if (g_IsDiplomacyOpen)
    245279        openDiplomacy();
    246280
    247281    if (g_IsTradeOpen)
    248282        openTrade();
     283}
     284
     285function setObserverMode(enabled)
     286{
     287    g_IsObserver = enabled;
    249288
    250     let playerState = GetSimState().players[playerID];
    251     g_DevSettings.controlAll = playerState && playerState.controlsAll;
     289    let viewPlayerDropdown = Engine.GetGUIObjectByName("viewPlayer");
     290
     291    viewPlayerDropdown.hidden = !enabled;
     292    if (enabled)
     293        viewPlayerDropdown.selected = 0;
    252294
    253     let control = Engine.GetGUIObjectByName("devControlAll");
    254     control.checked = g_DevSettings.controlAll;
    255     control.enabled = playerID > 0;
     295    Engine.GetGUIObjectByName("alphaLabel").hidden = enabled;
    256296}
    257297
    258298/**
    259299 * Returns true if the current user can issue commands for that player.
    260300 */
    function controlsPlayer(playerID)  
    263303    return Engine.GetPlayerID() == playerID || g_DevSettings.controlAll;
    264304}
    265305
    266306function updateTopPanel()
    267307{
    268     let playerID = Engine.GetPlayerID();
    269     let isPlayer =  playerID > 0;
     308    let isPlayer = g_ViewedPlayer > 0;
    270309    if (isPlayer)
    271310    {
    272         let civName = g_CivData[g_Players[playerID].civ].Name;
    273         Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
     311        let civName = g_CivData[g_Players[g_ViewedPlayer].civ].Name;
     312        Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[g_ViewedPlayer].civ].Emblem;
    274313        Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf(translate("%(civ)s - Structure Tree"), { "civ": civName });
    275314    }
    276    
     315
    277316    // Hide stuff gaia/observers don't use.
    278317    Engine.GetGUIObjectByName("food").hidden = !isPlayer;
    279318    Engine.GetGUIObjectByName("wood").hidden = !isPlayer;
    280319    Engine.GetGUIObjectByName("stone").hidden = !isPlayer;
    281320    Engine.GetGUIObjectByName("metal").hidden = !isPlayer;
    282321    Engine.GetGUIObjectByName("population").hidden = !isPlayer;
    283322    Engine.GetGUIObjectByName("civIcon").hidden = !isPlayer;
    284323    Engine.GetGUIObjectByName("diplomacyButton1").hidden = !isPlayer;
    285324    Engine.GetGUIObjectByName("tradeButton1").hidden = !isPlayer;
    286     Engine.GetGUIObjectByName("observerText").hidden = playerID >= 0;
     325    Engine.GetGUIObjectByName("observerText").hidden = isPlayer;
    287326
    288327    // Disable stuff observers shouldn't use
    289     let isActive = isPlayer && GetSimState().players[playerID].state == "active";
     328    let isActive = isPlayer && GetSimState().players[g_ViewedPlayer].state == "active" && controlsPlayer(g_ViewedPlayer);
    290329    Engine.GetGUIObjectByName("pauseButton").enabled = isActive || !g_IsNetworked;
    291330    Engine.GetGUIObjectByName("menuResignButton").enabled = isActive;
    292331
    293332    // Enable observer-only "summary" button.
    294     Engine.GetGUIObjectByName("summaryButton").enabled = !isActive;
     333    Engine.GetGUIObjectByName("summaryButton").enabled = Engine.GetPlayerID() == -1;
    295334}
    296335
    297336function reportPerformance(time)
    298337{
    299338    let settings = Engine.GetMapSettings();
    function leaveGame(willRejoin)  
    341380{
    342381    let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
    343382    let mapSettings = Engine.GetMapSettings();
    344383    let gameResult;
    345384
    346     if (g_IsObserver)
     385    if (Engine.GetPlayerID() == -1)
    347386    {
    348387        // Observers don't win/lose.
    349388        gameResult = translate("You have left the game.");
    350389        global.music.setState(global.music.states.VICTORY);
    351390    }
    function onTick()  
    456495        g_Selection.dirty = false;
    457496
    458497        onSimulationUpdate();
    459498
    460499        // Display rally points for selected buildings
    461         if (!g_IsObserver)
     500        if (Engine.GetPlayerID() != -1)
    462501            Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
    463502    }
    464503
    465504    updateTimers();
    466505
    function onTick()  
    472511    Engine.GuiInterfaceCall("ClearRenamedEntities");
    473512}
    474513
    475514function checkPlayerState()
    476515{
    477     if (g_GameEnded || g_IsObserver)
     516    if (g_GameEnded || Engine.GetPlayerID() < 1)
    478517        return;
    479518
    480519    // Send a game report for each player in this game.
    481520    let m_simState = GetSimState();
    482521    let playerState = m_simState.players[Engine.GetPlayerID()];
    function checkPlayerState()  
    523562
    524563    if (playerState.state == "defeated")
    525564    {
    526565        title = translate("DEFEATED!");
    527566        global.music.setState(global.music.states.DEFEAT);
     567        setObserverMode(true);
    528568    }
    529569    else if (playerState.state == "won")
    530570    {
    531571        title = translate("VICTORIOUS!");
    532572        global.music.setState(global.music.states.VICTORY);
    function onSimulationUpdate()  
    572612
    573613    updateHero();
    574614    updateGroups();
    575615    updateDebug();
    576616    updatePlayerDisplay();
     617    updateResearchDisplay();
    577618    updateSelectionDetails();
    578619    updateBuildingPlacementPreview();
    579620    updateTimeNotifications();
    580621
    581     if (Engine.GetPlayerID() > 0)
     622    if (g_ViewedPlayer > 0)
    582623    {
    583         let playerState = GetSimState().players[Engine.GetPlayerID()];
     624        let playerState = GetSimState().players[g_ViewedPlayer];
    584625        g_DevSettings.controlAll = playerState && playerState.controlsAll;
    585626        Engine.GetGUIObjectByName("devControlAll").checked = g_DevSettings.controlAll;
    586627    }
    587628
    588     if (!g_IsObserver)
    589         updateResearchDisplay();
    590 
    591     if (!g_IsObserver && !g_GameEnded)
     629    if (g_ViewedPlayer != -1 && !g_GameEnded)
    592630    {
    593631        // Update music state on basis of battle state.
    594         let battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID());
    595         if (battleState)
     632        let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer);
     633        if (battleState && global.music)
    596634            global.music.setState(global.music.states[battleState]);
    597635    }
    598636}
    599637
    600638function onReplayFinished()
    function updateGUIStatusBar(nameOfBar, p  
    649687}
    650688
    651689
    652690function updateHero()
    653691{
    654     let playerState = GetSimState().players[Engine.GetPlayerID()];
    655692    let unitHeroPanel = Engine.GetGUIObjectByName("unitHeroPanel");
    656693    let heroButton = Engine.GetGUIObjectByName("unitHeroButton");
    657694
     695    let playerState = GetSimState().players[g_ViewedPlayer];
    658696    if (!playerState || playerState.heroes.length <= 0)
    659697    {
    660698        g_PreviousHeroHitPoints = undefined;
    661699        unitHeroPanel.hidden = true;
    662700        return;
    function updateHero()  
    691729    heroButton.tooltip = tooltip;
    692730   
    693731    // update heros health bar
    694732    updateGUIStatusBar("heroHealthBar", heroState.hitpoints, heroState.maxHitpoints);
    695733   
    696     // define the hit points if not defined
     734    let heroHP = {
     735        "hitpoints": heroState.hitpoints,
     736        "player": g_ViewedPlayer
     737    };
     738
    697739    if (!g_PreviousHeroHitPoints)
    698         g_PreviousHeroHitPoints = heroState.hitpoints;
    699    
     740        g_PreviousHeroHitPoints = heroHP;
     741
    700742    // if the health of the hero changed since the last update, trigger the animation
    701     if (heroState.hitpoints < g_PreviousHeroHitPoints)
     743    if (g_PreviousHeroHitPoints.player == heroHP.player && g_PreviousHeroHitPoints.hitpoints > heroHP.hitpoints)
    702744        startColorFade("heroHitOverlay", 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
    703745
    704     g_PreviousHeroHitPoints = heroState.hitpoints;
     746    g_PreviousHeroHitPoints = heroHP;
    705747}
    706748
    707 
    708749function updateGroups()
    709750{
    710751    let guiName = "Group";
    711752    g_Groups.update();
    712753    for (let i = 0; i < 10; ++i)
    function updateDebug()  
    754795    debug.caption = text.replace(/\[/g, "\\[");
    755796}
    756797
    757798function updatePlayerDisplay()
    758799{
    759     let playerState = GetSimState().players[Engine.GetPlayerID()];
     800    let playerState = GetSimState().players[g_ViewedPlayer];
    760801    if (!playerState)
    761802        return;
    762803
    763804    Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food);
    764805    Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood);
    function selectAndMoveTo(ent)  
    784825    Engine.CameraMoveTo(position.x, position.z);
    785826}
    786827
    787828function updateResearchDisplay()
    788829{
    789     let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID());
    790     if (!researchStarted)
    791         return;
     830    let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", g_ViewedPlayer);
    792831
    793832    // Set up initial positioning.
    794833    let buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right;
    795834    for (let i = 0; i < 10; ++i)
    796835    {
  • binaries/data/mods/public/gui/session/session.xml

     
    6969
    7070        <object size="0 16 100%-18 32" type="text" style="devCommandsText">
    7171            <translatableAttribute id="caption">Change perspective</translatableAttribute>
    7272        </object>
    7373        <object size="100%-16 16 100% 32" type="checkbox" style="ModernTickBox">
    74             <action on="Press">Engine.GetGUIObjectByName("viewPlayer").hidden = !this.checked;</action>
     74            <action on="Press">toggleChangePerspective(this.checked);</action>
    7575        </object>
    7676
    7777        <object size="0 32 100%-18 48" type="text" style="devCommandsText">
    7878            <translatableAttribute id="caption">Display selection state</translatableAttribute>
    7979        </object>
  • binaries/data/mods/public/gui/session/top_panel.xml

     
    1212    <!-- ================================  ================================ -->
    1313
    1414    <!-- ================================  ================================ -->
    1515    <!-- Switch the view perspective to another player's (largely for AI development) -->
    1616    <!-- ================================  ================================ -->
    17     <object size="50%+50 5 50%+150 100%-5" name="viewPlayer" type="dropdown" hidden="true" style="ModernDropDown" tooltip_style="sessionToolTipBold">
     17    <object size="100%-465 5 100%-265 100%-5" name="viewPlayer" type="dropdown" hidden="true" style="ModernDropDown" tooltip_style="sessionToolTipBold">
    1818        <translatableAttribute id="tooltip">Choose player to view</translatableAttribute>
    19         <action on="SelectionChange">selectViewPlayer(this.selected);</action>
     19        <action on="SelectionChange">selectViewPlayer(this.selected - 1);</action>
    2020    </object>
    2121
    2222    <!-- ================================  ================================ -->
    23     <!-- Observer Mode Warning -->
     23    <!-- Observer Mode Label -->
    2424    <!-- ================================  ================================ -->
    2525    <object size="50 4 50% 100%-2" name="observerText" type="text" style="ModernLabelText" text_align="left" hidden="true">
    26         <translatableAttribute id="caption">Observer Mode (experimental)</translatableAttribute>
     26        <translatableAttribute id="caption">Observer Mode</translatableAttribute>
    2727    </object>
    2828
    2929</object>
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    GuiInterface.prototype.CheckTechnologyRe  
    613613    return cmpTechnologyManager.CanResearch(data.tech);
    614614};
    615615
    616616// Returns technologies that are being actively researched, along with
    617617// which entity is researching them and how far along the research is.
    618 GuiInterface.prototype.GetStartedResearch = function(player)
     618GuiInterface.prototype.GetStartedResearch = function(player, viewedPlayer)
    619619{
    620     let cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
     620    let cmpTechnologyManager = QueryPlayerIDInterface(viewedPlayer, IID_TechnologyManager);
    621621    if (!cmpTechnologyManager)
    622         return false;
     622        return {};
    623623
    624624    let ret = {};
    625625    for (let tech in cmpTechnologyManager.GetTechsStarted())
    626626    {
    627627        ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
    GuiInterface.prototype.GetStartedResearc  
    633633    }
    634634    return ret;
    635635};
    636636
    637637// Returns the battle state of the player.
    638 GuiInterface.prototype.GetBattleState = function(player)
     638GuiInterface.prototype.GetBattleState = function(player, viewedPlayer)
    639639{
    640     let cmpBattleDetection = QueryPlayerIDInterface(player, IID_BattleDetection);
     640    let cmpBattleDetection = QueryPlayerIDInterface(viewedPlayer, IID_BattleDetection);
    641641
    642642    if (!cmpBattleDetection)
    643643        return false;
    644644
    645645    return cmpBattleDetection.GetState();
    GuiInterface.prototype.SetMotionDebugOve  
    17711771GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
    17721772{
    17731773    Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).SetDebugOverlay(enabled);
    17741774};
    17751775
    1776 GuiInterface.prototype.GetTraderNumber = function(player)
     1776GuiInterface.prototype.GetTraderNumber = function(player, viewedPlayer)
    17771777{
    17781778    let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    1779     let traders = cmpRangeManager.GetEntitiesByPlayer(player).filter(e => Engine.QueryInterface(e, IID_Trader));
     1779    let traders = cmpRangeManager.GetEntitiesByPlayer(viewedPlayer).filter(e => Engine.QueryInterface(e, IID_Trader));
    17801780
    17811781    let landTrader = { "total": 0, "trading": 0, "garrisoned": 0 };
    17821782    let shipTrader = { "total": 0, "trading": 0 };
    17831783
    17841784    for each (let ent in traders)
    GuiInterface.prototype.GetTraderNumber =  
    18101810    }
    18111811
    18121812    return { "landTrader": landTrader, "shipTrader": shipTrader };
    18131813};
    18141814
    1815 GuiInterface.prototype.GetTradingGoods = function(player)
     1815GuiInterface.prototype.GetTradingGoods = function(player, viewedPlayer)
    18161816{
    1817     return QueryPlayerIDInterface(player).GetTradingGoods();
     1817    return QueryPlayerIDInterface(viewedPlayer).GetTradingGoods();
    18181818};
    18191819
    18201820GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
    18211821{
    18221822    this.renamedEntities.push(msg);