Ticket #9: visual_replay_a18_megapatch.patch

File visual_replay_a18_megapatch.patch, 72.0 KB (added by elexis, 9 years ago)

Everybody who wants to replay games on alpha 18 can use this patch. It also contains other things like #3241. It doesn't change the simulation state of the game, so you can play in the a18 lobby as usual (tested many weeks).

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

     
    510510
    511511    case "chat":
    512512        addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
    513513        break;
    514514
     515    case "kicked":
     516        addChatMessage({ "type": message.ban ? "banned" : "kicked", "username": message.username });
     517        break;
     518
    515519    // Singular client to host message
    516520    case "ready":
    517521        g_ReadyChanged -= 1;
    518522        if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
    519523            addChatMessage({ "type": "ready", "guid": message.guid, "ready": +message.status == 1 });
     
    16841688}
    16851689
    16861690function submitChatInput()
    16871691{
    16881692    var input = Engine.GetGUIObjectByName("chatInput");
    1689     var text = input.caption;
    1690     if (text.length)
     1693    var text = input.caption.trim();
     1694    input.caption = "";
     1695
     1696    if (!text.length)
     1697        return;
     1698
     1699    if (text.indexOf("/") == 0)
    16911700    {
    1692         Engine.SendNetworkChat(text);
    1693         input.caption = "";
     1701        let kick = text.indexOf("/kick ") == 0;
     1702        let ban = text.indexOf("/ban ") == 0;
     1703        if (kick || ban)
     1704            Engine.KickPlayer(text.substr(text.indexOf(" ") + 1), ban);
     1705        return;
    16941706    }
     1707
     1708    Engine.SendNetworkChat(text);
    16951709}
    16961710
    16971711function addChatMessage(msg)
    16981712{
    16991713    var username = "";
     
    17331747    case "disconnect":
    17341748        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
    17351749        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has left"), { username: formattedUsername }) }) + '[/font]';
    17361750        break;
    17371751
     1752    case "kicked":
     1753        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has been kicked."), { username: username }) }) + '[/font]';
     1754        break;
     1755
     1756    case "banned":
     1757        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has been banned."), { username: username }) }) + '[/font]';
     1758        break;
     1759       
    17381760    case "message":
    17391761        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
    17401762        var formattedUsernamePrefix = '[font="sans-bold-13"]' + sprintf(translate("<%(username)s>"), { username: formattedUsername }) + '[/font]'
    17411763        formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
    17421764        break;
  • binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml

     
    22
    33<objects>
    44
    55    <script file="gui/common/network.js"/>
    66    <script file="gui/common/functions_global_object.js"/>
     7    <script file="gui/common/functions_utility.js"/>
    78    <script file="gui/gamesetup/gamesetup_mp.js"/>
    89
    910    <!-- Add a translucent black background to fade out the menu page -->
    1011    <object type="image" sprite="ModernFade"/>
    1112
     
    4647            </object>
    4748
    4849            <object hotkey="confirm" type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
    4950                <translatableAttribute id="caption">Continue</translatableAttribute>
    5051                <action on="Press">
    51                     var joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;
     52                    var joinPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("joinPlayerName").caption, true, true);
    5253                    var joinServer = Engine.GetGUIObjectByName("joinServer").caption;
    5354                    if (startJoin(joinPlayerName, joinServer))
    5455                        switchSetupPage("pageJoin", "pageConnecting");
    5556                </action>
    5657            </object>
     
    8889            </object>
    8990
    9091            <object type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
    9192                <translatableAttribute id="caption">Continue</translatableAttribute>
    9293                <action on="Press">
    93                     var hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
     94                    var hostPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("hostPlayerName").caption, true, true);
    9495                    var hostServerName = Engine.GetGUIObjectByName("hostServerName").caption;
    9596                    if (startHost(hostPlayerName, hostServerName))
    9697                        switchSetupPage("pageHost", "pageConnecting");
    9798                </action>
    9899            </object>
  • binaries/data/mods/public/gui/options/options.js

     
    3030        [translate("Real Water Depth"), translate("Use actual water depth in rendering calculations"), {"renderer":"WaterRealDepth", "config":"waterrealdepth"}, "boolean"],
    3131        [translate("Water Reflections"), translate("Allow water to reflect a mirror image"), {"renderer":"WaterReflection", "config":"waterreflection"}, "boolean"],
    3232        [translate("Water Refraction"), translate("Use a real water refraction map and not transparency"), {"renderer":"WaterRefraction", "config":"waterrefraction"}, "boolean"],
    3333        [translate("Shadows on Water"), translate("Cast shadows on water"), {"renderer":"WaterShadows", "config":"watershadows"}, "boolean"],
    3434        [translate("VSync"), translate("Run vertical sync to fix screen tearing. REQUIRES GAME RESTART"), {"config":"vsync"}, "boolean"],
     35        [translate("Limit FPS in Menus"), translate("Limit frames per second in all menus."), {"config":"gui.menu.limit_fps"}, "boolean"],
    3536    ],
    3637    "soundSetting":
    3738    [
    3839        [translate("Master Gain"), translate("Master audio gain"), {"config":"sound.mastergain", "function":"Engine.SetMasterGain(Number(this.caption));"}, "number"],
    3940        [translate("Music Gain"), translate("In game music gain"), {"config":"sound.musicgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
  • binaries/data/mods/public/gui/session/input.js

     
    157157    else if (placementSupport.mode === "wall")
    158158    {
    159159        if (placementSupport.wallSet && placementSupport.position)
    160160        {
    161161            // Fetch an updated list of snapping candidate entities
    162             placementSupport.wallSnapEntities = Engine.PickSimilarFriendlyEntities(
     162            placementSupport.wallSnapEntities = Engine.PickSimilarPlayerEntities(
    163163                placementSupport.wallSet.templates.tower,
    164164                placementSupport.wallSnapEntitiesIncludeOffscreen,
    165165                true, // require exact template match
    166166                true  // include foundations
    167167            );
     
    547547        switch (ev.type)
    548548        {
    549549        case "mousemotion":
    550550            var rect = updateBandbox(bandbox, ev, false);
    551551
    552             var ents = Engine.PickFriendlyEntitiesInRect(rect[0], rect[1], rect[2], rect[3], Engine.GetPlayerID());
     552            var ents = Engine.PickPlayerEntitiesInRect(rect[0], rect[1], rect[2], rect[3], Engine.GetPlayerID());
    553553            var preferredEntities = getPreferredEntities(ents);
    554554            g_Selection.setHighlightList(preferredEntities);
    555555
    556556            return false;
    557557
     
    559559            if (ev.button == SDL_BUTTON_LEFT)
    560560            {
    561561                var rect = updateBandbox(bandbox, ev, true);
    562562
    563563                // Get list of entities limited to preferred entities
    564                 var ents = getPreferredEntities(Engine.PickFriendlyEntitiesInRect(rect[0], rect[1], rect[2], rect[3], Engine.GetPlayerID()));
     564                var ents = getPreferredEntities(Engine.PickPlayerEntitiesInRect(rect[0], rect[1], rect[2], rect[3], Engine.GetPlayerID()));
    565565
    566566                // Remove the bandbox hover highlighting
    567567                g_Selection.setHighlightList([]);
    568568
    569569                // Update the list of selected units
     
    10411041                        // Select units matching exact template name (same rank)
    10421042                        templateToMatch = GetEntityState(selectedEntity).template;
    10431043                    }
    10441044
    10451045                    // TODO: Should we handle "control all units" here as well?
    1046                     ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
     1046                    ents = Engine.PickSimilarPlayerEntities(templateToMatch, showOffscreen, matchRank, false);
    10471047                }
    10481048                else
    10491049                {
    10501050                    // It's single click right now but it may become double or triple click
    10511051                    doubleClicked = false;
  • binaries/data/mods/public/gui/session/messages.js

     
    236236            obj.caption = translate("Connection to the server has been authenticated.");
    237237            obj.hidden = false;
    238238            break;
    239239        case "disconnected":
    240240            g_Disconnected = true;
    241             obj.caption = translate("Connection to the server has been lost.") + "\n\n" + translate("The game has ended.");
     241            obj.caption = translate("Connection to the server has been lost.") + "\n" +
     242                sprintf(translate("Reason: %(reason)s."), { reason: getDisconnectReason(message.reason) }) + "\n" +
     243                translate("The game has ended.");
    242244            obj.hidden = false;
    243245            break;
    244246        default:
    245247            error("Unrecognised netstatus type '" + message.status + "'");
    246248            break;
     
    314316}
    315317
    316318function submitChatInput()
    317319{
    318320    var input = Engine.GetGUIObjectByName("chatInput");
    319     var text = input.caption;
     321    var text = input.caption.trim();
    320322    var isCheat = false;
     323   
    321324    if (text.length)
    322325    {
     326        // Parse cheats
    323327        if (!g_IsObserver && g_Players[Engine.GetPlayerID()].cheatsEnabled)
    324328        {
    325329            for each (var cheat in Object.keys(cheats))
    326330            {
    327331                // Line must start with the cheat.
     
    365369                isCheat = true;
    366370                break;
    367371            }
    368372        }
    369373
     374        // Parse direct commands
     375        if (text.indexOf("/") == 0)
     376        {
     377            if (text == "/list")
     378                addChatMessage({ "type": "clientlist", "guid": "local"});
     379            else
     380            {
     381                let kick = text.indexOf("/kick ") == 0;
     382                let ban = text.indexOf("/ban ") == 0;
     383                if (kick || ban)
     384                    Engine.KickPlayer(text.substr(text.indexOf(" ") + 1), ban);
     385            }
     386        }
    370387        // Observers should only send messages to "/all"
    371         if (!isCheat && (!g_IsObserver || text.indexOf("/") == -1 || text.indexOf("/all ") == 0))
     388        else if (!isCheat && (!g_IsObserver || text.indexOf("/") == -1 || text.indexOf("/all ") == 0))
    372389        {
    373390            if (Engine.GetGUIObjectByName("toggleTeamChat").checked)
    374391                text = "/team " + text;
    375392
    376393            if (g_IsNetworked)
  • binaries/data/mods/public/gui/session/minimap_panel.xml

     
    1111        <object type="button"
    1212            tooltip_style="sessionToolTip"
    1313            hotkey="selection.idleworker"
    1414        >
    1515            <translatableAttribute id="tooltip">Find idle worker</translatableAttribute>
    16             <action on="Press">findIdleUnit(["Female", "Trade", "FishingBoat", "CitizenSoldier", "Healer"]);</action>
     16            <action on="Press">findIdleUnit(["Female", "Trader", "FishingBoat", "CitizenSoldier", "Healer"]);</action>
    1717            <action on="MouseEnter">Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle-highlight.png";</action>
    1818            <action on="MouseLeave">Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle.png";</action>
    1919            <action on="MouseLeftPress">Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle.png";</action>
    2020            <action on="MouseLeftRelease">Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle-highlight.png";</action>
    2121        </object>
  • binaries/data/mods/public/gui/session/session.js

     
    546546        if (battleState)
    547547            global.music.setState(global.music.states[battleState]);
    548548    }
    549549}
    550550
     551function onReplayFinished()
     552{
     553    closeMenu();
     554    closeOpenDialogs();
     555    pauseGame();
     556    var btCaptions = [translateWithContext("replayFinished", "Yes"), translateWithContext("replayFinished", "No")];
     557    var btCode = [leaveGame, resumeGame];
     558    messageBox(400, 200, translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"), translateWithContext("replayFinished","Confirmation"), 0, btCaptions, btCode);
     559}
     560
    551561/**
    552562* updates a status bar on the GUI
    553563* nameOfBar: name of the bar
    554564* points: points to show
    555565* maxPoints: max points
     
    706716    Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food);
    707717    Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood);
    708718    Engine.GetGUIObjectByName("resourceStone").caption = Math.floor(playerState.resourceCounts.stone);
    709719    Engine.GetGUIObjectByName("resourceMetal").caption = Math.floor(playerState.resourceCounts.metal);
    710720    Engine.GetGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
    711 
     721    Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n"
     722                    + sprintf(translate("Maximum population: %(popCap)s"), { "popCap": playerState.popMax });
     723     
    712724    g_IsTrainingBlocked = playerState.trainingBlocked;
    713725}
    714726
    715727function selectAndMoveTo(ent)
    716728{
     
    785797
    786798// Toggles the display of status bars for all of the player's entities.
    787799function recalculateStatusBarDisplay()
    788800{
    789801    if (g_ShowAllStatusBars)
    790         var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID());
     802        var entities = g_IsObserver ? Engine.PickAllPlayersEntitiesOnScreen() : Engine.PickPlayerEntitiesOnScreen(Engine.GetPlayerID());
    791803    else
    792804    {
    793805        var selected = g_Selection.toList();
    794806        for each (var ent in g_Selection.highlighted)
    795807            selected.push(ent);
    796 
     808       
    797809        // Remove selected entities from the 'all entities' array, to avoid disabling their status bars.
    798         var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter(
    799                 function(idx) { return (selected.indexOf(idx) == -1); }
    800         );
     810        var entities = Engine.GuiInterfaceCall(g_IsObserver ? "GetAllPlayerEntities" : "GetPlayerEntities").filter(idx => selected.indexOf(idx) == -1);
    801811    }
    802812
    803813    Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars });
    804814}
    805 
    806815// Update the additional list of entities to be highlighted.
    807816function updateAdditionalHighlight()
    808817{
    809818    var entsAdd = [];    // list of entities units to be highlighted
    810819    var entsRemove = [];
  • binaries/data/mods/public/gui/session/session.xml

     
    55<script file="gui/common/colorFades.js"/>
    66<script file="gui/common/functions_civinfo.js"/>
    77<script file="gui/common/functions_global_object.js"/>
    88<script file="gui/common/functions_utility.js"/>
    99<script file="gui/common/l10n.js"/>
     10<script file="gui/common/network.js"/>
    1011<script file="gui/common/music.js"/>
    1112<script file="gui/common/timer.js"/>
    1213<script file="gui/common/tooltips.js"/>
    1314<!-- load all scripts in this directory -->
    1415<script directory="gui/session/"/>
     
    2021
    2122    <action on="SimulationUpdate">
    2223        onSimulationUpdate();
    2324    </action>
    2425
     26    <action on="ReplayFinished">
     27        onReplayFinished();
     28    </action>
     29
    2530    <action on="Press">
    2631        this.hidden = !this.hidden;
    2732    </action>
    2833
    2934    <!-- ================================  ================================ -->
  • binaries/data/mods/public/gui/session/top_panel/resource_population.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22<object name="population" size="370 0 460 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    3     <translatableAttribute id="tooltip">Population (current / limit)</translatableAttribute>
    43    <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
    54    <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
    65</object>
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    857857{
    858858    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    859859    return cmpRangeManager.GetEntitiesByPlayer(player);
    860860};
    861861
     862GuiInterface.prototype.GetAllPlayerEntities = function(player)
     863{
     864    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     865    return cmpRangeManager.GetAllPlayerEntities(player);
     866};
     867
    862868/**
    863869 * Displays the rally points of a given list of entities (carried in cmd.entities).
    864870 *
    865871 * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
    866872 * be rendered, in order to support instantaneously rendering a rally point marker at a specified location
     
    18311837
    18321838    "SetSelectionHighlight": 1,
    18331839    "GetAllBuildableEntities": 1,
    18341840    "SetStatusBars": 1,
    18351841    "GetPlayerEntities": 1,
     1842    "GetAllPlayerEntities": 1,
    18361843    "DisplayRallyPoint": 1,
    18371844    "SetBuildingPlacementPreview": 1,
    18381845    "SetWallPlacementPreview": 1,
    18391846    "GetFoundationSnapData": 1,
    18401847    "PlaySound": 1,
  • source/gui/scripting/ScriptFunctions.cpp

     
    6060#include "renderer/scripting/JSInterface_Renderer.h"
    6161#include "simulation2/Simulation2.h"
    6262#include "simulation2/components/ICmpAIManager.h"
    6363#include "simulation2/components/ICmpCommandQueue.h"
    6464#include "simulation2/components/ICmpGuiInterface.h"
     65#include "simulation2/components/ICmpPlayerManager.h"
    6566#include "simulation2/components/ICmpRangeManager.h"
    6667#include "simulation2/components/ICmpSelectable.h"
    6768#include "simulation2/components/ICmpTemplateManager.h"
    6869#include "simulation2/helpers/Selection.h"
    6970#include "soundmanager/SoundManager.h"
     
    152153entity_id_t PickEntityAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
    153154{
    154155    return EntitySelection::PickEntityAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false);
    155156}
    156157
    157 std::vector<entity_id_t> PickFriendlyEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
     158std::vector<entity_id_t> PickPlayerEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
    158159{
    159160    return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
    160161}
    161162
    162 std::vector<entity_id_t> PickFriendlyEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
     163std::vector<entity_id_t> PickPlayerEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
    163164{
    164     return PickFriendlyEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
     165    return PickPlayerEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
    165166}
    166167
    167 std::vector<entity_id_t> PickSimilarFriendlyEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
     168std::vector<entity_id_t> PickAllPlayersEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate)
     169{
     170    std::vector<entity_id_t> entities;
     171
     172    CmpPtr<ICmpPlayerManager> cmpPlayerManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
     173
     174    if (!cmpPlayerManager)
     175        return entities;
     176
     177    i32 numPlayers = cmpPlayerManager->GetNumPlayers();
     178    for (i32 player = 1; player < numPlayers; ++player)
     179    {
     180        std::vector<entity_id_t> ents = PickPlayerEntitiesOnScreen(pCxPrivate, player);
     181        entities.insert(entities.end(), ents.begin(), ents.end());
     182    }
     183    return entities;
     184}
     185
     186std::vector<entity_id_t> PickSimilarPlayerEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
    168187{
    169188    return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
    170189}
    171190
    172191CFixedVector3D GetTerrainAtScreenPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
     
    346365    SAFE_DELETE(g_NetServer);
    347366    SAFE_DELETE(g_NetClient);
    348367    SAFE_DELETE(g_Game);
    349368}
    350369
     370void KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW playerName, bool ban)
     371{
     372    if (g_NetServer)
     373        g_NetServer->KickPlayer(playerName, ban);
     374}
     375
    351376JS::Value PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
    352377{
    353378    if (!g_NetClient)
    354379        return JS::UndefinedValue();
    355380
     
    931956    scriptInterface.RegisterFunction<JS::Value, std::wstring, JS::HandleValue, &GuiInterfaceCall>("GuiInterfaceCall");
    932957    scriptInterface.RegisterFunction<void, JS::HandleValue, &PostNetworkCommand>("PostNetworkCommand");
    933958
    934959    // Entity picking
    935960    scriptInterface.RegisterFunction<entity_id_t, int, int, &PickEntityAtPoint>("PickEntityAtPoint");
    936     scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
    937     scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
    938     scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
     961    scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, int, int, int, &PickPlayerEntitiesInRect>("PickPlayerEntitiesInRect");
     962    scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickPlayerEntitiesOnScreen>("PickPlayerEntitiesOnScreen");
     963    scriptInterface.RegisterFunction<std::vector<entity_id_t>, &PickAllPlayersEntitiesOnScreen>("PickAllPlayersEntitiesOnScreen");
     964    scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities");
    939965    scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtScreenPoint>("GetTerrainAtScreenPoint");
    940966
    941967    // Network / game setup functions
    942968    scriptInterface.RegisterFunction<void, &StartNetworkGame>("StartNetworkGame");
    943969    scriptInterface.RegisterFunction<void, JS::HandleValue, int, &StartGame>("StartGame");
    944970    scriptInterface.RegisterFunction<void, &Script_EndGame>("EndGame");
    945971    scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
    946972    scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
    947973    scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
     974    scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer");
    948975    scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient");
    949976    scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
    950977    scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
    951978    scriptInterface.RegisterFunction<void, std::string, int, &SetNetworkPlayerStatus>("SetNetworkPlayerStatus");
    952979    scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady");
  • source/main.cpp

     
    4141#include "lib/external_libraries/libsdl.h"
    4242
    4343#include "ps/ArchiveBuilder.h"
    4444#include "ps/CConsole.h"
    4545#include "ps/CLogger.h"
     46#include "ps/ConfigDB.h"
    4647#include "ps/Filesystem.h"
    4748#include "ps/Game.h"
    4849#include "ps/Globals.h"
    4950#include "ps/Hotkey.h"
    5051#include "ps/Loader.h"
     
    303304        need_update = false;
    304305        // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
    305306        SDL_Delay(10);
    306307    }
    307308
    308     // TODO: throttling: limit update and render frequency to the minimum.
     309    // Throttling: limit update and render frequency to the minimum.
    309310    // this is mostly relevant for "inactive" state, so that other windows
    310311    // get enough CPU time, but it's always nice for power+thermal management.
     312    const float maxFPSMenu = 50.0;
     313    bool limit_fps = false;
     314    CFG_GET_VAL("gui.menu.limit_fps", limit_fps);
     315    if (limit_fps && (!g_Game || !g_Game->IsGameStarted()))
     316        SDL_Delay((1000.0 / maxFPSMenu) - realTimeSinceLastFrame);
    311317
    312318
    313319    // this scans for changed files/directories and reloads them, thus
    314320    // allowing hotloading (changes are immediately assimilated in-game).
    315321    ReloadChangedFiles();
     
    445451        return;
    446452
    447453    // run non-visual simulation replay if requested
    448454    if (args.Has("replay"))
    449455    {
     456        std::string replayFile = args.Get("replay");
     457        if (!FileExists(OsPath(replayFile)))
     458        {
     459            debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
     460            return;
     461        }
     462
    450463        Paths paths(args);
    451464        g_VFS = CreateVfs(20 * MiB);
    452465        g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
    453466        MountMods(paths, GetMods(args, INIT_MODS));
    454467
    455468        {
    456469            CReplayPlayer replay;
    457             replay.Load(args.Get("replay"));
    458             replay.Replay(args.Has("serializationtest"));
     470            replay.Load(replayFile);
     471            replay.Replay(args.Has("serializationtest"), args.Has("ooslog"));
    459472        }
    460473
    461474        g_VFS.reset();
    462475
    463476        CXeromyces::Terminate();
    464477        return;
    465478    }
     479 
     480    // If visual replay file does not exist, quit before starting the renderer
     481    if (args.Has("replay-visual"))
     482    {
     483        std::string replayFile = args.Get("replay-visual");
     484        if (!FileExists(OsPath(replayFile)))
     485        {
     486            debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
     487            return;
     488        }
     489    }
     490
    466491
    467492    // run in archive-building mode if requested
    468493    if (args.Has("archivebuild"))
    469494    {
    470495        Paths paths(args);
  • source/network/NetClient.cpp

     
    9191
    9292    AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
    9393    AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
    9494    AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
    9595    AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
     96    AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
    9697    AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
    9798    AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
    9899
    99100    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
    100101    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
     
    107108    AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
    108109    AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
    109110    AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
    110111    AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
    111112
     113    AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
    112114    AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
    113115    AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
    114116    AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
    115117    AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
    116118    AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
     
    582584    client->m_ClientTurnManager->UpdateFastForward();
    583585
    584586    return true;
    585587}
    586588
     589bool CNetClient::OnKicked(void *context, CFsmEvent* event)
     590{
     591    ENSURE(event->GetType() == (uint)NMT_KICKED);
     592
     593    CNetClient* client = (CNetClient*)context;
     594    JSContext* cx = client->GetScriptInterface().GetContext();
     595
     596    CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
     597    JS::RootedValue msg(cx);
     598    client->GetScriptInterface().Eval("({'type':'kicked'})", &msg);
     599    client->GetScriptInterface().SetProperty(msg, "username", message->m_Name, false);
     600    client->GetScriptInterface().SetProperty(msg, "ban", message->m_Ban, false);
     601    client->PushGuiMessage(msg);
     602
     603    return true;
     604}
     605
    587606bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
    588607{
    589608    ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
    590609
    591610    CNetClient* client = (CNetClient*)context;
  • source/network/NetClient.h

     
    193193    static bool OnInGame(void* context, CFsmEvent* event);
    194194    static bool OnGameStart(void* context, CFsmEvent* event);
    195195    static bool OnJoinSyncStart(void* context, CFsmEvent* event);
    196196    static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
    197197    static bool OnLoadedGame(void* context, CFsmEvent* event);
    198 
     198    static bool OnKicked(void* context, CFsmEvent* event);
    199199    /**
    200200     * Take ownership of a session object, and use it for all network communication.
    201201     */
    202202    void SetAndOwnSession(CNetClientSession* session);
    203203
  • source/network/NetHost.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    6060enum NetDisconnectReason
    6161{
    6262    NDR_UNKNOWN = 0,
    6363    NDR_UNEXPECTED_SHUTDOWN,
    6464    NDR_INCORRECT_PROTOCOL_VERSION,
    65     NDR_SERVER_ALREADY_IN_GAME
     65    NDR_SERVER_ALREADY_IN_GAME,
     66    NDR_KICKED,
     67    NDR_BANNED
    6668};
    6769
    6870class CNetHost
    6971{
    7072public:
  • source/network/NetMessage.cpp

     
    133133
    134134    case NMT_LOADED_GAME:
    135135        pNewMessage = new CLoadedGameMessage;
    136136        break;
    137137
     138    case NMT_KICKED:
     139        pNewMessage = new CKickedMessage;
     140        break;
     141
    138142    case NMT_SERVER_HANDSHAKE:
    139143        pNewMessage = new CSrvHandshakeMessage;
    140144        break;
    141145
    142146    case NMT_SERVER_HANDSHAKE_RESPONSE:
  • source/network/NetMessages.h

     
    6161    NMT_GAME_START,
    6262    NMT_END_COMMAND_BATCH,
    6363    NMT_SYNC_CHECK, // OOS-detection hash checking
    6464    NMT_SYNC_ERROR, // OOS-detection error
    6565    NMT_SIMULATION_COMMAND,
    66     NMT_LAST                // Last message in the list
     66    NMT_LAST,               // Last message in the list
     67    NMT_KICKED
    6768};
    6869
    6970// Authentication result codes
    7071enum AuthenticateResultCode
    7172{
     
    135136
    136137START_NMT_CLASS_(FileTransferRequest, NMT_FILE_TRANSFER_REQUEST)
    137138    NMT_FIELD_INT(m_RequestID, u32, 4)
    138139END_NMT_CLASS()
    139140
     141START_NMT_CLASS_(Kicked, NMT_KICKED)
     142    NMT_FIELD(CStrW, m_Name)
     143    NMT_FIELD_INT(m_Ban, bool, 1)
     144END_NMT_CLASS()
     145
    140146START_NMT_CLASS_(FileTransferResponse, NMT_FILE_TRANSFER_RESPONSE)
    141147    NMT_FIELD_INT(m_RequestID, u32, 4)
    142148    NMT_FIELD_INT(m_Length, u32, 4)
    143149END_NMT_CLASS()
    144150
  • source/network/NetServer.cpp

     
    119119
    120120CNetServerWorker::CNetServerWorker(int autostartPlayers) :
    121121    m_AutostartPlayers(autostartPlayers),
    122122    m_Shutdown(false),
    123123    m_ScriptInterface(NULL),
    124     m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
     124    m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL)
    125125{
    126126    m_State = SERVER_STATE_UNCONNECTED;
    127127
    128128    m_ServerTurnManager = NULL;
    129129
     
    603603    session->SetFirstState(NSS_HANDSHAKE);
    604604}
    605605
    606606bool CNetServerWorker::HandleConnect(CNetServerSession* session)
    607607{
     608    CNetServerWorker& server = session->GetServer();
     609
     610    // Disconnect banned IPs
     611    if (std::find(server.m_BannedIPs.begin(), server.m_BannedIPs.end(), session->GetIPAddress()) != server.m_BannedIPs.end())
     612    {
     613        session->Disconnect(NDR_UNKNOWN);
     614        return false;
     615    }
     616
     617    // Send handshake challenge
    608618    CSrvHandshakeMessage handshake;
    609619    handshake.m_Magic = PS_PROTOCOL_MAGIC;
    610620    handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
    611621    handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
    612622    return session->SendMessage(&handshake);
     
    614624
    615625void CNetServerWorker::OnUserJoin(CNetServerSession* session)
    616626{
    617627    AddPlayer(session->GetGUID(), session->GetUserName());
    618628
     629    if (m_HostGUID.empty())
     630        m_HostGUID = session->GetGUID();
     631
    619632    CGameSetupMessage gameSetupMessage(GetScriptInterface());
    620633    gameSetupMessage.m_Data = m_GameAttributes.get();
    621634    session->SendMessage(&gameSetupMessage);
    622635
    623636    CPlayerAssignmentMessage assignMessage;
     
    629642{
    630643    RemovePlayer(session->GetGUID());
    631644
    632645    if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
    633646        m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
    634 
    635     // TODO: ought to switch the player controlled by that client
    636     // back to AI control, or something?
    637647}
    638648
    639649void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
    640650{
    641651    // Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
     
    706716        it->second.m_Status = 0;
    707717
    708718    SendPlayerAssignments();
    709719}
    710720
     721void CNetServerWorker::KickPlayer(const CStrW& playerName, const bool ban)
     722{
     723    // Find the user with that name
     724    std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
     725        [&](CNetServerSession* session) { return session->GetUserName() == playerName; });
     726
     727    // and return if no one or the host has that name
     728    if (it == m_Sessions.end() || (*it)->GetGUID() == m_HostGUID)
     729        return;
     730
     731    if (ban)
     732    {
     733        // Remember name
     734        if (std::find(m_BannedPlayers.begin(), m_BannedPlayers.end(), playerName) == m_BannedPlayers.end())
     735            m_BannedPlayers.push_back(playerName);
     736
     737        // Remember IP address
     738        CStr ipAddress = GetPlayerIPAddress(playerName);
     739        if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), ipAddress) == m_BannedIPs.end())
     740            m_BannedIPs.push_back(ipAddress);
     741    }
     742
     743    // Disconnect that user
     744    (*it)->Disconnect(NDR_UNKNOWN);
     745
     746    // Send message notifying other clients
     747    CKickedMessage kickedMessage;
     748    kickedMessage.m_Name = playerName;
     749    kickedMessage.m_Ban = ban;
     750    Broadcast(&kickedMessage);
     751}
     752
     753CStr CNetServerWorker::GetPlayerIPAddress(const CStrW& playerName)
     754{
     755    for (CNetServerSession* session : m_Sessions)
     756        if (session->GetUserName() == playerName)
     757            return session->GetIPAddress();
     758    return "(error)";
     759}
     760
    711761void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
    712762{
    713763    // Remove anyone who's already assigned to this player
    714764    for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
    715765    {
     
    763813    ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
    764814
    765815    CNetServerSession* session = (CNetServerSession*)context;
    766816    CNetServerWorker& server = session->GetServer();
    767817
     818    // Check protocol version
    768819    CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
    769820    if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
    770821    {
    771822        session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
    772823        return false;
    773824    }
    774825
     826    // Send handshake response
    775827    CSrvHandshakeResponseMessage handshakeResponse;
    776828    handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
    777829    handshakeResponse.m_Message = server.m_WelcomeMessage;
    778830    handshakeResponse.m_Flags = 0;
    779831    session->SendMessage(&handshakeResponse);
     
    790842
    791843    CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
    792844
    793845    CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));
    794846
     847    // Disconnect banned usernames
     848    if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), username) != server.m_BannedPlayers.end())
     849    {
     850        session->Disconnect(NDR_UNKNOWN);
     851        return true;
     852    }
     853
    795854    bool isRejoining = false;
    796855
    797856    if (server.m_State != SERVER_STATE_PREGAME)
    798857    {
    799858//      isRejoining = true; // uncomment this to test rejoining even if the player wasn't connected previously
     
    11071166bool CNetServer::SetupConnection()
    11081167{
    11091168    return m_Worker->SetupConnection();
    11101169}
    11111170
     1171void CNetServer::KickPlayer(const CStrW& playerName, const bool ban)
     1172{
     1173    CScopeLock lock(m_Worker->m_WorkerMutex);
     1174    m_Worker->KickPlayer(playerName, ban);
     1175}
     1176
    11121177void CNetServer::AssignPlayer(int playerID, const CStr& guid)
    11131178{
    11141179    CScopeLock lock(m_Worker->m_WorkerMutex);
    11151180    m_Worker->m_AssignPlayerQueue.push_back(std::make_pair(playerID, guid));
    11161181}
  • source/network/NetServer.h

     
    120120     * The given GUID will be (re)assigned to the given player ID.
    121121     * Any player currently using that ID will be unassigned.
    122122     * The changes will be asynchronously propagated to all clients.
    123123     */
    124124    void AssignPlayer(int playerID, const CStr& guid);
    125    
     125
    126126    /**
    127127     * Call from the GUI to update the player readiness.
    128128     * The changes will be asynchronously propagated to all clients.
    129129     */
    130130    void SetPlayerReady(const CStr& guid, int ready);
     
    132132    /**
    133133     * Call from the GUI to set the all player readiness to 0.
    134134     * The changes will be asynchronously propagated to all clients.
    135135     */
    136136    void ClearAllPlayerReady();
    137    
     137
     138    /**
     139     * Disconnects a player from the gamesetup / session.
     140     */
     141    void KickPlayer(const CStrW& playerName, const bool ban);
     142
    138143    /**
    139144     * Call from the GUI to asynchronously notify all clients that they should start loading the game.
    140145     */
    141146    void StartGame();
    142147
     
    181186     * Send a message to the given network peer.
    182187     */
    183188    bool SendMessage(ENetPeer* peer, const CNetMessage* message);
    184189
    185190    /**
     191     * Disconnected a player from the match / gamesetup and optionally prevents him/her from rejoining.
     192     */
     193    void KickPlayer(const CStrW& playerName, const bool ban);
     194
     195    /**
    186196     * Send a message to all clients who have completed the full connection process
    187197     * (i.e. are in the pre-game or in-game states).
    188198     */
    189199    bool Broadcast(const CNetMessage* message);
    190200
     201    /**
     202     * Returns the IP address of the given connected player.
     203     */
     204    CStr GetPlayerIPAddress(const CStrW& playerName);
     205
    191206private:
    192207    friend class CNetServer;
    193208    friend class CNetFileReceiveTask_ServerRejoin;
    194209
    195210    CNetServerWorker(int autostartPlayers);
     
    243258     */
    244259    void SetTurnLength(u32 msecs);
    245260
    246261    void AddPlayer(const CStr& guid, const CStrW& name);
    247262    void RemovePlayer(const CStr& guid);
    248     void SetPlayerReady(const CStr& guid, const int ready);
     263    void SetPlayerReady(const CStr& guid, const int ready);
     264    CStr GetHostGUID();
    249265    void SendPlayerAssignments();
    250266    void ClearAllPlayerReady();
    251267
    252268    void SetupSession(CNetServerSession* session);
    253269    bool HandleConnect(CNetServerSession* session);
     
    268284
    269285    void ConstructPlayerAssignmentMessage(CPlayerAssignmentMessage& message);
    270286
    271287    void HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
    272288
    273 
    274289    /**
    275290     * Internal script context for (de)serializing script messages,
    276291     * and for storing game attributes.
    277292     * (TODO: we shouldn't bother deserializing (except for debug printing of messages),
    278293     * we should just forward messages blindly and efficiently.)
     
    296311    NetServerState m_State;
    297312
    298313    CStrW m_ServerName;
    299314    CStrW m_WelcomeMessage;
    300315
     316    std::vector<CStr> m_BannedIPs;
     317    std::vector<CStrW> m_BannedPlayers;
     318
    301319    u32 m_NextHostID;
    302320
    303321    CNetServerTurnManager* m_ServerTurnManager;
    304322
     323    CStr m_HostGUID;
     324
    305325    /**
    306326     * A copy of all simulation commands received so far, indexed by
    307327     * turn number, to simplify support for rejoining etc.
    308328     * TODO: verify this doesn't use too much RAM.
    309329     */
  • source/network/NetSession.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    173173CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) :
    174174    m_Server(server), m_FileTransferer(this), m_Peer(peer)
    175175{
    176176}
    177177
     178CStr CNetServerSession::GetIPAddress()
     179{
     180    char ipAddress[256] = "(error)";
     181    enet_address_get_host_ip(&(m_Peer->address), ipAddress, ARRAY_SIZE(ipAddress));
     182    return CStr(ipAddress);
     183}
     184
    178185void CNetServerSession::Disconnect(u32 reason)
    179186{
    180187    Update((uint)NMT_CONNECTION_LOST, NULL);
    181188
    182189    enet_peer_disconnect(m_Peer, reason);
  • source/network/NetSession.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    121121
    122122    u32 GetHostID() const { return m_HostID; }
    123123    void SetHostID(u32 id) { m_HostID = id; }
    124124
    125125    /**
     126     * Returns the IP address of the client.
     127     */
     128    CStr GetIPAddress();
     129
     130    /**
    126131     * Sends a disconnection notification to the client,
    127132     * and sends a NMT_CONNECTION_LOST message to the session FSM.
    128133     * The server will receive a disconnection notification after a while.
    129134     * The server will not receive any further messages sent via this session.
    130135     */
  • source/network/NetTurnManager.cpp

     
    1 /* Copyright (C) 2012 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    223223    NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
    224224
    225225    // Only complain the first time
    226226    if (m_HasSyncError)
    227227        return;
    228     m_HasSyncError = true;
    229228
    230229    bool quick = !TurnNeedsFullHash(turn);
    231230    std::string hash;
    232     bool ok = m_Simulation2.ComputeStateHash(hash, quick);
    233     ENSURE(ok);
     231    ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    234232
    235233    OsPath path = psLogDir()/"oos_dump.txt";
    236234    std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
    237235    m_Simulation2.DumpDebugState(file);
    238236    file.close();
    239237
     238    hash = Hexify(hash);
     239    const std::string& expectedHashHex = Hexify(expectedHash);
     240
     241    DisplayOOSError(turn, hash, expectedHashHex, false, &path);
     242}
     243
     244void CNetTurnManager::DisplayOOSError(u32 turn, std::string& hash, const std::string& expectedHash, const bool isReplay, OsPath* path = NULL)
     245{
     246    m_HasSyncError = true;
     247
    240248    std::stringstream msg;
    241     msg << "Out of sync on turn " << turn << ": expected hash " << Hexify(expectedHash) << "\n\n";
    242     msg << "Current state: turn " << m_CurrentTurn << ", hash " << Hexify(hash) << "\n\n";
    243     msg << "Dumping current state to " << utf8_from_wstring(path.string());
     249    msg << "Out of sync on turn " << turn << ": expected hash " << expectedHash << "\n";
     250
     251    if (expectedHash != hash || m_CurrentTurn != turn)
     252        msg << "\nCurrent state: turn " << m_CurrentTurn << ", hash " << hash << "\n\n";
     253
     254    if (isReplay)
     255        msg << "\nThe current game state is different from the original game state.\n\n";
     256    else
     257    {
     258        if (expectedHash == hash)
     259            msg << "Your game state is identical to the hosts game state.\n\n";
     260        else
     261            msg << "Your game state is different from the hosts game state.\n\n";
     262    }
     263
     264    if (path)
     265        msg << "Dumping current state to " << utf8_from_wstring(OsPath(*path).string());
     266
     267    LOGERROR("%s", msg.str());
     268
    244269    if (g_GUI)
    245270        g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
    246     else
    247         LOGERROR("%s", msg.str());
    248271}
    249272
    250273void CNetTurnManager::Interpolate(float simFrameLength, float realFrameLength)
    251274{
    252275    // TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
     
    320343void CNetTurnManager::QuickSave()
    321344{
    322345    TIMER(L"QuickSave");
    323346   
    324347    std::stringstream stream;
    325     bool ok = m_Simulation2.SerializeState(stream);
    326     if (!ok)
     348    if (!m_Simulation2.SerializeState(stream))
    327349    {
    328350        LOGERROR("Failed to quicksave game");
    329351        return;
    330352    }
    331353
     
    348370        LOGERROR("Cannot quickload game - no game was quicksaved");
    349371        return;
    350372    }
    351373
    352374    std::stringstream stream(m_QuickSaveState);
    353     bool ok = m_Simulation2.DeserializeState(stream);
    354     if (!ok)
     375    if (!m_Simulation2.DeserializeState(stream))
    355376    {
    356377        LOGERROR("Failed to quickload game");
    357378        return;
    358379    }
    359380
     
    400421{
    401422    bool quick = !TurnNeedsFullHash(turn);
    402423    std::string hash;
    403424    {
    404425        PROFILE3("state hash check");
    405         bool ok = m_Simulation2.ComputeStateHash(hash, quick);
    406         ENSURE(ok);
     426        ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
    407427    }
    408428
    409429    NETTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));
    410430
    411431    m_Replay.Hash(hash, quick);
     
    450470{
    451471#if 0 // this hurts performance and is only useful for verifying log replays
    452472    std::string hash;
    453473    {
    454474        PROFILE3("state hash check");
    455         bool ok = m_Simulation2.ComputeStateHash(hash);
    456         ENSURE(ok);
     475        ENSURE(m_Simulation2.ComputeStateHash(hash));
    457476    }
    458477    m_Replay.Hash(hash);
    459478#endif
    460479}
    461480
    462481void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
    463482{
    464483    debug_warn(L"This should never be called");
    465484}
    466485
     486CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
     487    CNetLocalTurnManager(simulation, replay)
     488{
     489}
     490
     491void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
     492{
     493    // Using the pair we make sure that commands per turn will be processed in the correct order
     494    m_ReplayCommands[turn].push_back(std::make_pair(player, command));
     495}
     496
     497void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
     498{
     499    m_ReplayHash[turn] = std::make_pair(hash, quick);
     500}
     501
     502void CNetReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
     503{
     504    m_ReplayTurnLengths[turn] = turnLength;
     505
     506    // Initialize turn length
     507    if (turn == 0)
     508        m_TurnLength = m_ReplayTurnLengths[0];
     509}
     510
     511void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
     512{
     513    m_FinalReplayTurn = turn;
     514}
     515
     516void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
     517{
     518    if (turn > m_FinalReplayTurn)
     519        return;
    467520
     521    debug_printf("Executing turn %d of %d\n", turn, m_FinalReplayTurn);
     522    DoTurn(turn);
    468523
     524    // Compare hash if it exists in the replay and if we didn't have an oos already
     525    if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
     526        return;
     527
     528    std::string expectedHash = m_ReplayHash[turn].first;
     529    bool quickHash = m_ReplayHash[turn].second;
     530
     531    // Compute hash
     532    std::string hash;
     533    ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
     534    hash = Hexify(hash);
     535
     536    if (hash != expectedHash)
     537        DisplayOOSError(turn-1, hash, expectedHash, true);
     538}
     539
     540void CNetReplayTurnManager::DoTurn(u32 turn)
     541{
     542    // Save turn length
     543    m_TurnLength = m_ReplayTurnLengths[turn];
     544
     545    // Simulate commands for that turn
     546    for(std::vector<std::pair<player_id_t, std::string> >::iterator it = m_ReplayCommands[turn].begin(); it != m_ReplayCommands[turn].end(); ++it)
     547    {
     548        JS::RootedValue command(m_Simulation2.GetScriptInterface().GetContext());
     549        m_Simulation2.GetScriptInterface().ParseJSON(it->second, &command);
     550        AddCommand(m_ClientId, it->first, command, m_CurrentTurn + 1);
     551    }
     552}
    469553
    470554CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
    471555    m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
    472556{
    473557    // The first turn we will actually execute is number 2,
  • source/network/NetTurnManager.h

     
    1 /* Copyright (C) 2012 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    1717
    1818#ifndef INCLUDED_NETTURNMANAGER
    1919#define INCLUDED_NETTURNMANAGER
    2020
    2121#include "simulation2/helpers/SimulationCommand.h"
     22#include "lib/os_path.h"
    2223
    2324#include <list>
    2425#include <map>
     26#include <vector>
    2527
    2628class CNetServerWorker;
    2729class CNetClient;
    2830class CSimulationMessage;
    2931class CSimulation2;
     
    106108     * Called when there has been an out-of-sync error.
    107109     */
    108110    virtual void OnSyncError(u32 turn, const std::string& expectedHash);
    109111
    110112    /**
     113     * Shows a message box when an out of sync error has been detected in the session or visual replay.
     114     */
     115    virtual void DisplayOOSError(u32 turn, std::string& hash, const std::string& expectedHash, const bool isReplay, OsPath* path);
     116
     117    /**
    111118     * Called by simulation code, to add a new command to be distributed to all clients and executed soon.
    112119     */
    113120    virtual void PostCommand(JS::HandleValue data) = 0;
    114121
    115122    /**
     
    187194    std::list<std::string> m_TimeWarpStates;
    188195    std::string m_QuickSaveState; // TODO: should implement a proper disk-based quicksave system
    189196    std::string m_QuickSaveMetadata;
    190197};
    191198
     199
    192200/**
    193201 * Implementation of CNetTurnManager for network clients.
    194202 */
    195203class CNetClientTurnManager : public CNetTurnManager
    196204{
     
    231239
    232240    virtual void NotifyFinishedUpdate(u32 turn);
    233241};
    234242
    235243
     244
     245/**
     246 * Implementation of CNetTurnManager for replay games.
     247 */
     248class CNetReplayTurnManager : public CNetLocalTurnManager
     249{
     250public:
     251    CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay);
     252
     253    void StoreReplayCommand(u32 turn, int player, const std::string& command);
     254
     255    void StoreReplayTurnLength(u32 turn, u32 turnLength);
     256
     257    void StoreReplayHash(u32 turn, const std::string& hash, bool quick);
     258
     259    void StoreFinalReplayTurn(u32 turn);
     260
     261
     262protected:
     263    virtual void NotifyFinishedUpdate(u32 turn);
     264
     265    void DoTurn(u32 turn);
     266
     267    // Contains the commands of every player on each turn
     268    std::map<u32, std::vector<std::pair<player_id_t, std::string> > > m_ReplayCommands;
     269
     270    // Contains the length of every turn
     271    std::map<u32, u32> m_ReplayTurnLengths;
     272
     273    // Contains all replay hash values and weather or not the quick hash method was used
     274    std::map<u32, std::pair<std::string, bool> > m_ReplayHash;
     275
     276    // The number of the last turn in the replay
     277    u32 m_FinalReplayTurn;
     278};
    236279/**
    237280 * The server-side counterpart to CNetClientTurnManager.
    238281 * Records the turn state of each client, and sends turn advancement messages
    239282 * when all clients are ready.
    240283 *
  • source/ps/Game.cpp

     
    6161
    6262/**
    6363 * Constructor
    6464 *
    6565 **/
    66 CGame::CGame(bool disableGraphics):
     66CGame::CGame(bool disableGraphics, bool replayLog):
    6767    m_World(new CWorld(this)),
    6868    m_Simulation2(new CSimulation2(&m_World->GetUnitManager(), g_ScriptRuntime, m_World->GetTerrain())),
    6969    m_GameView(disableGraphics ? NULL : new CGameView(this)),
    7070    m_GameStarted(false),
    7171    m_Paused(false),
    7272    m_SimRate(1.0f),
    7373    m_PlayerID(-1),
    74     m_IsSavedGame(false)
     74    m_IsSavedGame(false),
     75    m_IsReplay(false),
     76    m_ReplayStream(NULL)
    7577{
    76     m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
    7778    // TODO: should use CDummyReplayLogger unless activated by cmd-line arg, perhaps?
     79    if (replayLog)
     80        m_ReplayLogger = new CReplayLogger(m_Simulation2->GetScriptInterface());
     81    else
     82        m_ReplayLogger = new CDummyReplayLogger();
    7883
    7984    // Need to set the CObjectManager references after various objects have
    8085    // been initialised, so do it here rather than via the initialisers above.
    8186    if (m_GameView)
    8287        m_World->GetUnitManager().SetObjectManager(m_GameView->GetObjectManager());
     
    99104    delete m_TurnManager;
    100105    delete m_GameView;
    101106    delete m_Simulation2;
    102107    delete m_World;
    103108    delete m_ReplayLogger;
     109    delete m_ReplayStream;
    104110}
    105111
    106112void CGame::SetTurnManager(CNetTurnManager* turnManager)
    107113{
    108114    if (m_TurnManager)
     
    112118
    113119    if (m_TurnManager)
    114120        m_TurnManager->SetPlayerID(m_PlayerID);
    115121}
    116122
     123int CGame::LoadReplayData()
     124{
     125    ENSURE(m_IsReplay);
     126    ENSURE(!m_ReplayPath.empty());
     127
     128    CNetReplayTurnManager* replayTurnMgr = static_cast<CNetReplayTurnManager*>(GetTurnManager());
     129
     130    u32 currentTurn = 0;
     131    std::string type;
     132    while ((*m_ReplayStream >> type).good())
     133    {
     134        if (type == "turn")
     135        {
     136            u32 turn = 0;
     137            u32 turnLength = 0;
     138            *m_ReplayStream >> turn >> turnLength;
     139            ENSURE(turn == currentTurn);
     140            replayTurnMgr->StoreReplayTurnLength(currentTurn, turnLength);
     141        }
     142        else if (type == "cmd")
     143        {
     144            player_id_t player;
     145            *m_ReplayStream >> player;
     146
     147            std::string line;
     148            std::getline(*m_ReplayStream, line);
     149            replayTurnMgr->StoreReplayCommand(currentTurn, player, line);
     150        }
     151        else if (type == "hash" || type == "hash-quick")
     152        {
     153            bool quick = (type == "hash-quick");
     154            std::string replayHash;
     155            *m_ReplayStream >> replayHash;
     156            replayTurnMgr->StoreReplayHash(currentTurn, replayHash, quick);
     157        }
     158        else if (type == "end")
     159        {
     160            currentTurn++;
     161        }
     162        else
     163        {
     164            CancelLoad(L"Failed to load replay data (unrecognized content)");
     165        }
     166    }
     167    m_FinalReplayTurn = currentTurn;
     168    replayTurnMgr->StoreFinalReplayTurn(currentTurn);
     169    return 0;
     170}
     171
     172bool CGame::StartReplay(const std::string& replayPath)
     173{
     174    m_IsReplay = true;
     175    ScriptInterface& scriptInterface = m_Simulation2->GetScriptInterface();
     176
     177    SetTurnManager(new CNetReplayTurnManager(*m_Simulation2, GetReplayLogger()));
     178
     179    m_ReplayPath = replayPath;
     180    m_ReplayStream = new std::ifstream(m_ReplayPath.c_str());
     181
     182    std::string type;
     183    ENSURE((*m_ReplayStream >> type).good() && type == "start");
     184
     185    std::string line;
     186    std::getline(*m_ReplayStream, line);
     187    JS::RootedValue attribs(scriptInterface.GetContext());
     188    scriptInterface.ParseJSON(line, &attribs);
     189    StartGame(&attribs, "");
     190
     191    return true;
     192}
    117193
    118194/**
    119195 * Initializes the game with the set of attributes provided.
    120196 * Makes calls to initialize the game view, world, and simulation objects.
    121197 * Calls are made to facilitate progress reporting of the initialization.
     
    174250        RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 80);
    175251
    176252    if (m_IsSavedGame)
    177253        RegMemFun(this, &CGame::LoadInitialState, L"Loading game", 1000);
    178254
     255    if (m_IsReplay)
     256        RegMemFun(this, &CGame::LoadReplayData, L"Loading replay data", 1000);
     257
    179258    LDR_EndRegistering();
    180259}
    181260
    182261int CGame::LoadInitialState()
    183262{
     
    261340int CGame::GetPlayerID()
    262341{
    263342    return m_PlayerID;
    264343}
    265344
    266 void CGame::SetPlayerID(int playerID)
     345void CGame::SetPlayerID(player_id_t playerID)
    267346{
    268347    m_PlayerID = playerID;
    269348    if (m_TurnManager)
    270349        m_TurnManager->SetPlayerID(m_PlayerID);
    271350}
    272351
    273352void CGame::StartGame(JS::MutableHandleValue attribs, const std::string& savedState)
    274353{
    275     m_ReplayLogger->StartGame(attribs);
     354    if (m_ReplayLogger != false)
     355        m_ReplayLogger->StartGame(attribs);
     356
    276357    RegisterInit(attribs, savedState);
    277358}
    278359
    279360// TODO: doInterpolate is optional because Atlas interpolates explicitly,
    280361// so that it has more control over the update rate. The game might want to
     
    308389        {
    309390            {
    310391                PROFILE3("gui sim update");
    311392                g_GUI->SendEventToAll("SimulationUpdate");
    312393            }
     394            if (m_IsReplay && m_TurnManager->GetCurrentTurn() == m_FinalReplayTurn - 1)
     395                g_GUI->SendEventToAll("ReplayFinished");
    313396
    314397            GetView()->GetLOSTexture().MakeDirty();
    315398        }
    316399       
    317400        if (CRenderer::IsInitialised())
     
    360443            m_PlayerColours[i] = cmpPlayer->GetColour();
    361444    }
    362445}
    363446
    364447
    365 CColor CGame::GetPlayerColour(int player) const
     448CColor CGame::GetPlayerColour(player_id_t player) const
    366449{
    367450    if (player < 0 || player >= (int)m_PlayerColours.size())
    368451        return BrokenColor;
    369452
    370453    return m_PlayerColours[player];
  • source/ps/Game.h

     
    1 /* Copyright (C) 2013 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    2020
    2121#include "ps/Errors.h"
    2222#include <vector>
    2323
    2424#include "scriptinterface/ScriptVal.h"
     25#include "simulation2/helpers/Player.h"
    2526
    2627class CWorld;
    2728class CSimulation2;
    2829class CGameView;
    2930class CNetTurnManager;
     
    5859    /**
    5960     * Timescale multiplier for simulation rate.
    6061     **/
    6162    float m_SimRate;
    6263
    63     int m_PlayerID;
     64    player_id_t m_PlayerID;
    6465
    6566    CNetTurnManager* m_TurnManager;
    6667
    6768public:
    68     CGame(bool disableGraphics = false);
     69    CGame(bool disableGraphics = false, bool replayLog = true);
    6970    ~CGame();
    7071
    7172    /**
    7273     * the game is paused and no updates will be performed if true.
    7374     **/
    7475    bool m_Paused;
    7576
    7677    void StartGame(JS::MutableHandleValue attribs, const std::string& savedState);
    7778    PSRETURN ReallyStartGame();
    7879
     80    bool 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.
    8285     *
    8386     * @param deltaRealTime Elapsed real time since last beat/frame, in seconds.
     
    8891    bool Update(const double deltaRealTime, bool doInterpolate = true);
    8992
    9093    void Interpolate(float simFrameLength, float realFrameLength);
    9194
    9295    int GetPlayerID();
    93     void SetPlayerID(int playerID);
     96    void SetPlayerID(player_id_t playerID);
    9497
    9598    /**
    9699     * Retrieving player colours from scripts is slow, so this updates an
    97100     * internal cache of all players' colours.
    98101     * Call this just before rendering, so it will always have the latest
    99102     * colours.
    100103     */
    101104    void CachePlayerColours();
    102105
    103     CColor GetPlayerColour(int player) const;
     106    CColor GetPlayerColour(player_id_t player) const;
    104107
    105108    /**
    106109     * Get m_GameStarted.
    107110     *
    108111     * @return bool the value of m_GameStarted.
     
    169172    std::vector<CColor> m_PlayerColours;
    170173
    171174    int LoadInitialState();
    172175    std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState
    173176    bool m_IsSavedGame; // true if loading a saved game; false for a new game
     177
     178    int LoadReplayData();
     179    std::string m_ReplayPath;
     180    bool m_IsReplay;
     181    std::istream* m_ReplayStream;
     182    u32 m_FinalReplayTurn;
    174183};
    175184
    176185extern CGame *g_Game;
    177186
    178187#endif
  • source/ps/GameSetup/GameSetup.cpp

     
    880880    srand(time(NULL));  // NOTE: this rand should *not* be used for simulation!
    881881}
    882882
    883883bool Autostart(const CmdLineArgs& args);
    884884
     885// Returns true if and only if the user has intended to replay a file
     886bool VisualReplay(const std::string replayFile);
     887
    885888bool Init(const CmdLineArgs& args, int flags)
    886889{
    887890    h_mgr_init();
    888891
    889892    // Do this as soon as possible, because it chdirs
     
    10721075
    10731076    ogl_WarnIfError();
    10741077
    10751078    try
    10761079    {
    1077         if (!Autostart(args))
     1080        if (!VisualReplay(args.Get("replay-visual")) && !Autostart(args))
    10781081        {
    10791082            const bool setup_gui = ((flags & INIT_NO_GUI) == 0);
    10801083            // We only want to display the splash screen at startup
    10811084            shared_ptr<ScriptInterface> scriptInterface = g_GUI->GetScriptInterface();
    10821085            JSContext* cx = scriptInterface->GetContext();
     
    14721475    }
    14731476
    14741477    return true;
    14751478}
    14761479
     1480bool VisualReplay(const std::string replayFile)
     1481{
     1482    if (!FileExists(OsPath(replayFile)))
     1483        return false;
     1484
     1485    g_Game = new CGame(false, false);
     1486    g_Game->SetPlayerID(-1);
     1487    g_Game->StartReplay(replayFile);
     1488
     1489    // TODO: Non progressive load can fail - need a decent way to handle this
     1490    LDR_NonprogressiveLoad();
     1491
     1492    PSRETURN ret = g_Game->ReallyStartGame();
     1493    ENSURE(ret == PSRETURN_OK);
     1494
     1495    ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
     1496
     1497    InitPs(true, L"page_session.xml", &scriptInterface, JS::UndefinedHandleValue);
     1498    return true;
     1499}
     1500
    14771501void CancelLoad(const CStrW& message)
    14781502{
    14791503    shared_ptr<ScriptInterface> pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
    14801504    JSContext* cx = pScriptInterface->GetContext();
    14811505    JSAutoRequest rq(cx);
  • source/ps/Replay.cpp

     
    1 /* Copyright (C) 2014 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    3131#include "scriptinterface/ScriptInterface.h"
    3232#include "scriptinterface/ScriptStats.h"
    3333#include "simulation2/Simulation2.h"
    3434#include "simulation2/helpers/SimulationCommand.h"
    3535
     36#include <ctime>
    3637#include <sstream>
    3738#include <fstream>
    3839#include <iomanip>
    3940
    40 #if MSC_VERSION
    41 #include <process.h>
    42 #define getpid _getpid // use the non-deprecated function name
    43 #endif
    44 
    4541static std::string Hexify(const std::string& s)
    4642{
    4743    std::stringstream str;
    4844    str << std::hex;
    4945    for (size_t i = 0; i < s.size(); ++i)
     
    5248}
    5349
    5450CReplayLogger::CReplayLogger(ScriptInterface& scriptInterface) :
    5551    m_ScriptInterface(scriptInterface)
    5652{
    57     // Construct the directory name based on the PID, to be relatively unique.
    58     // Append "-1", "-2" etc if we run multiple matches in a single session,
    59     // to avoid accidentally overwriting earlier logs.
    60 
    61     std::wstringstream name;
    62     name << getpid();
    63 
    64     static int run = -1;
    65     if (++run)
    66         name << "-" << run;
     53    // Get current date
     54    time_t t = time(NULL);
     55    struct tm* now = localtime(&t);
     56    char date[16];
     57    sprintf_s(date, ARRAY_SIZE(date), "%04d-%02d-%02d_a18_", 1900+now->tm_year, 1+now->tm_mon, now->tm_mday);
     58
     59    // Construct a unique directory name based on date and sequential number per date
     60    int i = 1;
     61    OsPath path;
     62    std::stringstream pathname;
     63    do
     64    {
     65        pathname.str("");
     66        pathname.clear();
     67        pathname << date << i++;
     68        path = psLogDir() / L"sim_log" / pathname.str() / L"commands.txt";
     69    } while(DirectoryExists(path.Parent()));
    6770
    68     OsPath path = psLogDir() / L"sim_log" / name.str() / L"commands.txt";
     71    // Create directory and commands.txt
    6972    CreateDirectories(path.Parent(), 0700);
    7073    m_Stream = new std::ofstream(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
    7174}
    7275
    7376CReplayLogger::~CReplayLogger()
     
    120123
    121124    m_Stream = new std::ifstream(path.c_str());
    122125    ENSURE(m_Stream->good());
    123126}
    124127
    125 void CReplayPlayer::Replay(bool serializationtest)
     128void CReplayPlayer::Replay(bool serializationtest, bool ooslog)
    126129{
    127130    ENSURE(m_Stream);
    128131
    129132    new CProfileViewer;
    130133    new CProfileManager;
     
    137140
    138141    CGame game(true);
    139142    g_Game = &game;
    140143    if (serializationtest)
    141144        game.GetSimulation2()->EnableSerializationTest();
     145    if (ooslog)
     146        game.GetSimulation2()->EnableOOSLog();
    142147       
    143148    JSContext* cx = game.GetSimulation2()->GetScriptInterface().GetContext();
    144149    JSAutoRequest rq(cx);
    145150
    146151    // Need some stuff for terrain movement costs:
  • source/ps/Replay.h

     
    1 /* Copyright (C) 2014 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    5454 */
    5555class CDummyReplayLogger : public IReplayLogger
    5656{
    5757public:
    5858    virtual void StartGame(JS::MutableHandleValue UNUSED(attribs)) { }
    59     virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), const std::vector<SimulationCommand>& UNUSED(commands)) { }
     59    virtual void Turn(u32 UNUSED(n), u32 UNUSED(turnLength), std::vector<SimulationCommand>& UNUSED(commands)) { }
    6060    virtual void Hash(const std::string& UNUSED(hash), bool UNUSED(quick)) { }
    6161};
    6262
    6363/**
    6464 * Implementation of IReplayLogger that saves data to a file in the logs directory.
     
    8787public:
    8888    CReplayPlayer();
    8989    ~CReplayPlayer();
    9090
    9191    void Load(const std::string& path);
    92     void Replay(bool serializationtest);
     92    void Replay(bool serializationtest, bool ooslog);
    9393
    9494private:
    9595    std::istream* m_Stream;
    9696};
    9797
  • source/simulation2/components/CCmpRangeManager.cpp

     
    903903        }
    904904
    905905        return entities;
    906906    }
    907907
     908    virtual std::vector<entity_id_t> GetAllPlayerEntities()
     909    {
     910        // Compute ownership mask for all players
     911        u32 ownerMask = 0;
     912        for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID + 1; ++player)
     913            ownerMask |= CalcOwnerMask(player);
     914
     915        // Get a list of all entities of all players
     916        std::vector<entity_id_t> entities;
     917        for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
     918        {
     919            // Check owner and add to list if it matches
     920            if (CalcOwnerMask(it->second.owner) & ownerMask)
     921                entities.push_back(it->first);
     922        }
     923
     924        return entities;
     925    }
     926
    908927    virtual void SetDebugOverlay(bool enabled)
    909928    {
    910929        m_DebugOverlayEnabled = enabled;
    911930        m_DebugOverlayDirty = true;
    912931        if (!enabled)
  • source/simulation2/components/ICmpRangeManager.cpp

     
    4343DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
    4444DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector<entity_id_t>, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
    4545DEFINE_INTERFACE_METHOD_3("SetEntityFlag", void, ICmpRangeManager, SetEntityFlag, entity_id_t, std::string, bool)
    4646DEFINE_INTERFACE_METHOD_1("GetEntityFlagMask", u8, ICmpRangeManager, GetEntityFlagMask, std::string)
    4747DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector<entity_id_t>, ICmpRangeManager, GetEntitiesByPlayer, player_id_t)
     48DEFINE_INTERFACE_METHOD_0("GetAllPlayerEntities", std::vector<entity_id_t>, ICmpRangeManager, GetAllPlayerEntities)
    4849DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
    4950DEFINE_INTERFACE_METHOD_1("ExploreAllTiles", void, ICmpRangeManager, ExploreAllTiles, player_id_t)
    5051DEFINE_INTERFACE_METHOD_0("ExploreTerritories", void, ICmpRangeManager, ExploreTerritories)
    5152DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)
    5253DEFINE_INTERFACE_METHOD_1("GetLosRevealAll", bool, ICmpRangeManager, GetLosRevealAll, player_id_t)
  • source/simulation2/components/ICmpRangeManager.h

     
    177177     * @return list of entities matching the query, ordered by increasing distance from the source entity.
    178178     */
    179179    virtual std::vector<entity_id_t> ResetActiveQuery(tag_t tag) = 0;
    180180
    181181    /**
    182      * Returns list of all entities for specific player.
     182     * Returns a list of all entities for specific player.
    183183     * (This is on this interface because it shares a lot of the implementation.
    184184     * Maybe it should be extended to be more like ExecuteQuery without
    185185     * the range parameter.)
    186186     */
    187187    virtual std::vector<entity_id_t> GetEntitiesByPlayer(player_id_t player) = 0;
    188188
    189189    /**
     190     * Returns a list of all entities for all players.
     191     */
     192    virtual std::vector<entity_id_t> GetAllPlayerEntities() = 0;
     193
     194    /**
    190195     * Toggle the rendering of debug info.
    191196     */
    192197    virtual void SetDebugOverlay(bool enabled) = 0;
    193198
    194199    /**
  • source/simulation2/helpers/Selection.cpp

     
    162162    }
    163163
    164164    return hitEnts;
    165165}
    166166
     167std::vector<entity_id_t> EntitySelection::PickAllPlayerEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables)
     168{
     169    PROFILE2("PickAllPlayerEntitiesInRect");
     170    // Make sure sx0 <= sx1, and sy0 <= sy1
     171    if (sx0 > sx1)
     172        std::swap(sx0, sx1);
     173    if (sy0 > sy1)
     174        std::swap(sy0, sy1);
     175
     176    CmpPtr<ICmpRangeManager> cmpRangeManager(simulation, SYSTEM_ENTITY);
     177    ENSURE(cmpRangeManager);
     178
     179    std::vector<entity_id_t> hitEnts;
     180
     181    CComponentManager& componentManager = simulation.GetSimContext().GetComponentManager();
     182
     183    std::vector<entity_id_t> ents = cmpRangeManager->GetAllPlayerEntities();
     184    for (std::vector<entity_id_t>::iterator it = ents.begin(); it != ents.end(); ++it)
     185    {
     186        CmpPtr<ICmpOwnership> cmpOwnership(simulation.GetSimContext(), *it);
     187        if (CheckEntityVisibleAndInRect(componentManager.LookupEntityHandle(*it), cmpRangeManager, camera, sx0, sy0, sx1, sy1, cmpOwnership->GetOwner(), allowEditorSelectables))
     188            hitEnts.push_back(*it);
     189    }
     190
     191    return hitEnts;
     192}
     193
    167194std::vector<entity_id_t> EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera,
    168195    const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank,
    169196    bool allowEditorSelectables, bool allowFoundations)
    170197{
    171198    PROFILE2("PickSimilarEntities");
  • source/simulation2/helpers/Selection.h

     
    6363 * @return unordered list of selected entities.
    6464 */
    6565std::vector<entity_id_t> PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t owner, bool allowEditorSelectables);
    6666
    6767/**
     68 * Finds all selectable entities within the given screen coordinate rectangle,
     69 * belonging to any player.
     70 *
     71 * @param camera use this view to convert screen to world coordinates.
     72 * @param sx0,sy0,sx1,sy1 diagonally opposite corners of the rectangle in 2D screen coordinates.
     73 * @param owner player whose entities we are selecting. Ownership is ignored if
     74 *  INVALID_PLAYER is used.
     75 * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface
     76 *  will be selected (including decorative actors), else only those selectable ingame.
     77 *
     78 * @return unordered list of selected entities.
     79 */
     80std::vector<entity_id_t> PickAllPlayerEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables);
     81
     82/**
    6883 * Finds all entities with the given entity template name, belonging to the given player.
    6984 *
    7085 * @param camera use this view to convert screen to world coordinates.
    7186 * @param templateName the name of the template to match, or the selection group name
    7287 *  for similar matching.