Ticket #785: gamespeed-03152013.patch

File gamespeed-03152013.patch, 38.4 KB (added by historic_bruno, 11 years ago)

WIP patch with multiplayer voting, etc.

  • binaries/data/mods/public/gui/common/functions_utility.js

     
    134134
    135135// ====================================================================
    136136
    137 // Load default player data, for when it's not otherwise specified
    138 function initPlayerDefaults()
     137// Parse and return JSON data from file in simulation/data/*
     138// returns valid object or undefined on error
     139function parseJSONFromDataFile(filename)
    139140{
    140     var filename = "simulation/data/player_defaults.json";
    141     var defaults = [];
    142     var rawData = readFile(filename);
     141    var path = "simulation/data/"+filename;
     142    var rawData = readFile(path);
    143143    if (!rawData)
    144         error("Failed to read player defaults file: "+filename);
    145    
     144        error("Failed to read file: "+path);
     145
    146146    try
    147     {   // Catch nasty errors from JSON parsing
     147    {
     148        // Catch nasty errors from JSON parsing
    148149        // TODO: Need more info from the parser on why it failed: line number, position, etc!
    149150        var data = JSON.parse(rawData);
    150         if (!data || !data.PlayerData)
    151             error("Failed to parse player defaults in: "+filename+" (check for valid JSON data)");
    152        
    153         defaults = data.PlayerData;
     151        return data;
    154152    }
    155153    catch(err)
    156154    {
    157         error(err.toString()+": parsing player defaults in "+filename);
     155        error(err.toString()+": parsing JSON data in "+path);
    158156    }
    159    
     157
     158    return undefined;
     159}
     160
     161// ====================================================================
     162
     163// Load default player data, for when it's not otherwise specified
     164function initPlayerDefaults()
     165{
     166    var defaults = [];
     167
     168    var data = parseJSONFromDataFile("player_defaults.json");
     169    if (!data || !data.PlayerData)
     170        error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)");
     171    else
     172        defaults = data.PlayerData;
     173
    160174    return defaults;
    161175}
    162176
     
    165179// Load map size data
    166180function initMapSizes()
    167181{
    168     var filename = "simulation/data/map_sizes.json";
    169182    var sizes = {
    170         names: [],
    171         tiles: [],
    172         default: 0
     183        "names":[],
     184        "tiles": [],
     185        "default": 0
    173186    };
    174     var rawData = readFile(filename);
    175     if (!rawData)
    176         error("Failed to read map sizes file: "+filename);
    177    
    178     try
    179     {   // Catch nasty errors from JSON parsing
    180         // TODO: Need more info from the parser on why it failed: line number, position, etc!
    181         var data = JSON.parse(rawData);
    182         if (!data || !data.Sizes)
    183             error("Failed to parse map sizes in: "+filename+" (check for valid JSON data)");
    184        
     187
     188    var data = parseJSONFromDataFile("map_sizes.json");
     189    if (!data || !data.Sizes)
     190        error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)");
     191    else
     192    {
    185193        for (var i = 0; i < data.Sizes.length; ++i)
    186194        {
    187195            sizes.names.push(data.Sizes[i].LongName);
    188196            sizes.tiles.push(data.Sizes[i].Tiles);
    189            
     197
    190198            if (data.Sizes[i].Default)
    191                 sizes.default = i;
     199                sizes["default"] = i;
    192200        }
    193201    }
    194     catch(err)
     202
     203    return sizes;
     204}
     205
     206// ====================================================================
     207
     208// Load game speed data
     209function initGameSpeeds()
     210{
     211    var gameSpeeds = {
     212        "names": [],
     213        "speeds": [],
     214        "default": 0
     215    };
     216
     217    var data = parseJSONFromDataFile("game_speeds.json");
     218    if (!data || !data.Speeds)
     219        error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)");
     220    else
    195221    {
    196         error(err.toString()+": parsing map sizes in "+filename);
     222        for (var i = 0; i < data.Speeds.length; ++i)
     223        {
     224            gameSpeeds.names.push(data.Speeds[i].Name);
     225            gameSpeeds.speeds.push(data.Speeds[i].Speed);
     226
     227            if (data.Speeds[i].Default)
     228                gameSpeeds["default"] = i;
     229        }
    197230    }
    198    
    199     return sizes;
     231
     232    return gameSpeeds;
    200233}
    201234
     235
    202236// ====================================================================
    203237
    204238// Convert integer color values to string (for use in GUI objects)
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

     
    3636    settings: {}
    3737};
    3838
     39var g_GameSpeeds = {};
    3940var g_MapSizes = {};
    4041
    4142var g_AIs = [];
     
    9798    g_DefaultPlayerData.shift();
    9899    for (var i = 0; i < g_DefaultPlayerData.length; i++)
    99100        g_DefaultPlayerData[i].Civ = "random";
    100    
     101
     102    g_GameSpeeds = initGameSpeeds();
    101103    g_MapSizes = initMapSizes();
    102104
    103105    // Init civs
     
    135137        numPlayersSelection.list = players;
    136138        numPlayersSelection.list_data = players;
    137139        numPlayersSelection.selected = MAX_PLAYERS - 1;
     140       
     141        var gameSpeed = getGUIObjectByName("gameSpeed");
     142        gameSpeed.hidden = false;
     143        getGUIObjectByName("gameSpeedText").hidden = true;
     144        gameSpeed.list = g_GameSpeeds.names;
     145        gameSpeed.list_data = g_GameSpeeds.speeds;
     146        gameSpeed.onSelectionChange = function()
     147        {
     148            // Update attributes so other players can see change
     149            if (this.selected != -1)
     150            {
     151                g_GameAttributes.gameSpeed = g_GameSpeeds.speeds[this.selected];
     152                g_GameAttributes.gameSpeedName = g_GameSpeeds.names[this.selected];
     153            }
    138154
     155            if (!g_IsInGuiUpdate)
     156                updateGameAttributes();
     157        }
     158        gameSpeed.selected = g_GameSpeeds["default"];
     159
     160        var lockGameSpeed = getGUIObjectByName("lockGameSpeed");
     161        lockGameSpeed.hidden = false;
     162        getGUIObjectByName("lockGameSpeedText").hidden = true;
     163        lockGameSpeed.onPress = function()
     164        {
     165            // Update attributes so other players can see change
     166            g_GameAttributes.lockGameSpeed = this.checked;
     167
     168            if (!g_IsInGuiUpdate)
     169                updateGameAttributes();
     170        }
     171
    139172        var populationCaps = getGUIObjectByName("populationCap");
    140173        populationCaps.list = POPULATION_CAP;
    141174        populationCaps.list_data = POPULATION_CAP_DATA;
     
    245278        getGUIObjectByName("mapSelection").hidden = true;
    246279        getGUIObjectByName("victoryConditionText").hidden = false;
    247280        getGUIObjectByName("victoryCondition").hidden = true;
    248        
     281        getGUIObjectByName("gameSpeedText").hidden = false;
     282        getGUIObjectByName("gameSpeed").hidden = true;
     283        getGUIObjectByName("lockGameSpeedText").hidden = false;
     284        getGUIObjectByName("lockGameSpeed").hidden = true;
     285
    249286        // Disable player and game options controls
    250287        // TODO: Shouldn't players be able to choose their own assignment?
    251288        for (var i = 0; i < MAX_PLAYERS; ++i)
     
    934971        populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
    935972        var startingResourcesBox = getGUIObjectByName("startingResources");
    936973        startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
     974        getGUIObjectByName("gameSpeedText").caption = g_GameAttributes.gameSpeedName;
     975        getGUIObjectByName("lockGameSpeedText").caption = g_GameAttributes.lockGameSpeed ? "Yes" : "No";
    937976        initMapNameList();
    938977    }
    939978
     
    956995    var populationCapText = getGUIObjectByName("populationCapText");
    957996    var startingResourcesText = getGUIObjectByName("startingResourcesText");
    958997   
    959     var sizeIdx = (g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes.default);
     998    var sizeIdx = (g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes["default"]);
    960999    var victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
    9611000    enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
    9621001    enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
  • binaries/data/mods/public/gui/gamesetup/gamesetup.xml

     
    178178        </object>
    179179
    180180        <!-- More Options -->
    181         <object name="moreOptions" type="image" sprite="StoneWindow" size="50%-200 50%-120 50%+200 50%+155" z="70" hidden="true">
     181        <object name="moreOptions" type="image" sprite="StoneWindow" size="50%-200 50%-164 50%+200 50%+169" z="70" hidden="true">
    182182            <object style="TitleText" type="text" size="50%-128 11 50%+128 27">
    183183                More Options
    184184            </object>
    185185           
    186186            <object size="14 38 94% 66">
    187187                <object size="0 0 40% 28">
    188                     <object size="0 0 100% 100%" type="text" style="RightLabelText">Victory condition:</object>
     188                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Game Speed:</object>
    189189                </object>
     190                <object name="gameSpeedText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
     191                <object name="gameSpeed" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select game speed."/>
     192            </object>
     193           
     194            <object size="14 68 94% 96">
     195                <object size="0 0 40% 28">
     196                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Lock Game Speed:</object>
     197                </object>
     198                <object name="lockGameSpeedText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
     199                <object name="lockGameSpeed" size="40%+4 50%-8 40%+20 50%+8" type="checkbox" style="StoneCrossBox" hidden="true" tooltip_style="onscreenToolTip" tooltip="Toggle locked game speed."/>
     200            </object>
     201           
     202            <object size="14 98 94% 126">
     203                <object size="0 0 40% 28">
     204                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Victory Condition:</object>
     205                </object>
    190206                <object name="victoryConditionText" size="40% 0 100% 100%" type="text" style="LeftLabelText"/>
    191207                <object name="victoryCondition" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select victory condition."/>
    192208            </object>
    193209           
    194             <object size="14 68 94% 96">
     210            <object size="14 128 94% 156">
    195211                <object size="0 0 40% 28">
    196212                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Population Cap:</object>
    197213                </object>
     
    199215                <object name="populationCap" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select population cap."/>
    200216            </object>
    201217           
    202             <object size="14 98 94% 126">
     218            <object size="14 158 94% 186">
    203219                <object size="0 0 40% 28">
    204220                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Starting Resources:</object>
    205221                </object>
     
    207223                <object name="startingResources" size="40% 0 100% 28" type="dropdown" style="StoneDropDown" hidden="true" tooltip_style="onscreenToolTip" tooltip="Select the game's starting resources."/>
    208224            </object>
    209225           
    210             <object size="14 128 94% 216">
     226            <object size="14 188 94% 276">
    211227                <object size="0 0 40% 28">
    212                     <object size="0 0 100% 100%" type="text" style="RightLabelText">Reveal map:</object>
     228                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Reveal Map:</object>
    213229                </object>
    214230                <object size="0 30 40% 58">
    215                     <object size="0 0 100% 100%" type="text" style="RightLabelText">Teams locked:</object>
     231                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Teams Locked:</object>
    216232                </object>
    217233                <object size="0 60 40% 88" name="enableCheatsDesc" hidden="true">
    218234                    <object size="0 0 100% 100%" type="text" style="RightLabelText">Cheats:</object>
     
    237253                name="hideMoreOptions"
    238254                type="button"
    239255                style="StoneButton"
    240                 size="50%-70 218 50%+70 246"
     256                size="50%-70 278 50%+70 304"
    241257                tooltip_style="onscreenToolTip"
    242258                tooltip="Close more game options window"
    243259            >
  • binaries/data/mods/public/gui/session/menu.js

     
    361361        openDiplomacy();
    362362};
    363363
     364function toggleGameSpeed()
     365{
     366    var gameSpeed = getGUIObjectByName("gameSpeed");
     367    gameSpeed.hidden = !gameSpeed.hidden;
     368}
     369
    364370function pauseGame()
    365371{
    366372    getGUIObjectByName("pauseButtonText").caption = RESUME;
  • binaries/data/mods/public/gui/session/messages.js

     
    186186    case "chat":
    187187        addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
    188188        break;
     189   
     190    case "requestspeed":
     191        addChatMessage({ "type": "requestspeed", "guid": message.guid, "speed": message.speed });
     192        break;
    189193
     194    case "speedchanged":
     195        g_CurrentSpeed = message.speed;
     196        addChatMessage({ "type": "speedchanged", "speed": message.speed });
     197        break;
     198
    190199    // To prevent errors, ignore these message types that occur during autostart
    191200    case "gamesetup":
    192201    case "start":
     
    352361
    353362        formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has sent you " + amounts + ".";
    354363        break;
     364    case "requestspeed":
     365        formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] voted to change game speed to " + msg.speed/100.0 + "x.";
     366        break;
     367    case "speedchanged":
     368        formatted = "Game speed has been changed to " + msg.speed/100.0 + "x.";
     369        break;
    355370    case "message":
    356371        // May have been hidden by the 'team' command.
    357372        if (msg.hide)
  • binaries/data/mods/public/gui/session/session.js

     
    66// Cache the useful civ data
    77var g_CivData = {};
    88
     9var g_GameSpeeds = {};
     10var g_CurrentSpeed;
     11
    912var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
    1013
    1114// Cache dev-mode settings that are frequently or widely used
     
    8386        g_Players = getPlayerData(g_PlayerAssignments);
    8487
    8588        if (initData.savedGUIData)
    86         {
    8789            restoreSavedGameData(initData.savedGUIData);
    88         }
     90
     91        getGUIObjectByName("gameSpeedButton").hidden = initData.attribs && initData.attribs.lockGameSpeed;
    8992    }
    9093    else // Needed for autostart loading option
    9194    {
     
    9699    g_CivData = loadCivData();
    97100    g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" };
    98101
     102    g_GameSpeeds = initGameSpeeds();
     103    g_CurrentSpeed = Engine.GetGameSpeed();
     104    warn("current speed: "+g_CurrentSpeed);
     105    var gameSpeed = getGUIObjectByName("gameSpeed");
     106    gameSpeed.list = g_GameSpeeds.names;
     107    gameSpeed.list_data = g_GameSpeeds.speeds;
     108    var idx = -1;
     109    for (var s in g_GameSpeeds.speeds)
     110    {
     111        if (g_GameSpeeds.speeds[s] == g_CurrentSpeed)
     112        {
     113            idx = s;
     114            break;
     115        }
     116    }
     117    gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"];
     118    gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); }
     119
    99120    getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem;
    100121    getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name;
    101122    initMenuPosition(); // set initial position
     
    347368    }
    348369}
    349370
     371function changeGameSpeed(speed)
     372{
     373    // For networked games, we must request a speed change in a sync'd way
     374    if (g_IsNetworked)
     375        Engine.RequestNetworkGameSpeed(speed);
     376    else
     377    {
     378        Engine.SetSimRate(speed/100.0);
     379        g_CurrentSpeed = Engine.GetGameSpeed();
     380    }
     381}
     382
    350383/**
    351384 * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
    352385 * update (see session.xml), or from onTick when the selection has changed.
     
    521554function updateTimeElapsedCounter(simState)
    522555{
    523556    var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter");
    524     timeElapsedCounter.caption = timeToString(simState.timeElapsed);
     557    timeElapsedCounter.caption = timeToString(simState.timeElapsed) + " (" + g_CurrentSpeed/100.0 + "x)";
    525558}
    526559
    527560// Toggles the display of status bars for all of the player's entities.
  • binaries/data/mods/public/gui/session/session.xml

     
    220220    <!-- ================================  ================================ -->
    221221    <!-- Time elapsed counter -->
    222222    <!-- ================================  ================================ -->
    223     <object size="100%-100 50 100%-10 70" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" hidden="true">
     223   
     224    <object size="100%-120 45 100%-10 65" type="text" name="timeElapsedCounter" style="SettingsText" hotkey="timeelapsedcounter.toggle" hidden="true">
    224225        <action on="Press">this.hidden = !this.hidden;</action>
    225226    </object>
    226227
     
    513514        <!-- ================================  ================================ -->
    514515
    515516        <!-- Displays Alpha name and number -->
    516         <object size="70%-128 0 70%+128 100%" name="alphaLabel" type="text" style="CenteredLabelText" text_valign="top" ghost="true">
     517        <object size="50%+48 0 100%-226 100%" name="alphaLabel" type="text" style="CenteredLabelText" text_valign="top" ghost="true">
    517518        ALPHA XIII : Magadha<!-- IMPORTANT: remember to update pregame/mainmenu.xml in sync with this -->
    518519
    519520        <!-- Displays build date and revision number-->
     
    522523        </object>
    523524        </object>
    524525
     526        <!-- ================================  ================================ -->
     527        <!-- Game Speed Button -->
    525528        <!-- ================================  ================================ -->
     529        <object type="button"
     530        name="gameSpeedButton"
     531        size="100%-226 4 100%-198 32"
     532        style="iconButton"
     533        tooltip_style="sessionToolTip"
     534        tooltip="Game speed"
     535        >
     536            <object size="5 5 100%-5 100%-5" type="image" sprite="stretched:session/icons/resources/time_small.png" ghost="true"/>
     537            <action on="Press">
     538                toggleGameSpeed();
     539            </action>
     540        </object>
     541
     542        <object size="100%-230 45 100%-140 70" name="gameSpeed" type="dropdown" buffer_zone="5" style="StoneDropDown" hidden="true" tooltip="Choose game speed" tooltip_style="sessionToolTip"/>
     543       
     544        <!-- ================================  ================================ -->
    526545        <!-- Diplomacy Button -->
    527546        <!-- ================================  ================================ -->
    528547        <object type="button"
    529548        name="diplomacyButton1"
    530         size="100%-196 0 100%-164 32"
     549        size="100%-194 4 100%-166 32"
    531550        style="iconButton"
    532551        tooltip_style="sessionToolTip"
    533552        tooltip="Diplomacy"
  • source/gui/scripting/ScriptFunctions.cpp

     
    1 /* Copyright (C) 2012 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    358358    g_NetClient->SendChatMessage(message);
    359359}
    360360
     361/**
     362 * Send a request for changing game speed in a networked game.
     363 * Requires the network client to have been initialized.
     364 * @param speed Requested game speed in percent, 100=normal, 50=half speed, etc.
     365 */
     366void RequestNetworkGameSpeed(void* UNUSED(cbdata), u32 speed)
     367{
     368    ENSURE(g_NetClient);
     369
     370    g_NetClient->RequestGameSpeed(speed);
     371}
     372
    361373std::vector<CScriptValRooted> GetAIs(void* cbdata)
    362374{
    363375    CGUIManager* guiManager = static_cast<CGUIManager*> (cbdata);
     
    520532}
    521533
    522534
    523 
     535/**
     536 * Set the simulation rate (game speed) directly.
     537 * Only reliably useful in non-networked games.
     538 *
     539 * @param rate Desired simulation rate, 1.0 is normal, 0.0 is paused
     540 */
    524541void SetSimRate(void* UNUSED(cbdata), float rate)
    525542{
    526     g_Game->SetSimRate(rate);
     543    if (g_Game)
     544        g_Game->SetSimRate(rate);
    527545}
    528546
     547/**
     548 * Get the current game speed
     549 * @return positive integer game speed if game started, else -1
     550 */
     551int GetGameSpeed(void* UNUSED(cbdata))
     552{
     553    int speed = -1;
     554    if (g_Game && g_Game->IsGameStarted())
     555        speed = g_Game->GetGameSpeed();
     556    return speed;
     557}
     558
    529559void SetTurnLength(void* UNUSED(cbdata), int length)
    530560{
    531561    if (g_NetServer)
     
    638668    scriptInterface.RegisterFunction<void, CScriptVal, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
    639669    scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
    640670    scriptInterface.RegisterFunction<void, std::wstring, &SendNetworkChat>("SendNetworkChat");
     671    scriptInterface.RegisterFunction<void, u32, &RequestNetworkGameSpeed>("RequestNetworkGameSpeed");
     672    scriptInterface.RegisterFunction<int, &GetGameSpeed>("GetGameSpeed");
    641673    scriptInterface.RegisterFunction<std::vector<CScriptValRooted>, &GetAIs>("GetAIs");
    642674
    643675    // Saved games
  • source/network/NetClient.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    9898    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
    9999    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
    100100    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
     101    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_REQUEST_SPEED, NCS_JOIN_SYNCING, (void*)&OnRequestSpeed, context);
     102    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SPEED_CHANGED, NCS_JOIN_SYNCING, (void*)&OnSpeedChanged, context);
    101103
    102104    AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
    103105    AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
     
    110112    AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
    111113    AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
    112114    AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
     115    AddTransition(NCS_INGAME, (uint)NMT_REQUEST_SPEED, NCS_INGAME, (void*)&OnRequestSpeed, context);
     116    AddTransition(NCS_INGAME, (uint)NMT_SPEED_CHANGED, NCS_INGAME, (void*)&OnSpeedChanged, context);
    113117
    114118    // Set first state
    115119    SetFirstState(NCS_UNCONNECTED);
     
    247251    SendMessage(&chat);
    248252}
    249253
     254void CNetClient::RequestGameSpeed(u32 speed)
     255{
     256    CRequestSpeedMessage msg;
     257    msg.m_Speed = speed;
     258    SendMessage(&msg);
     259}
     260
    250261bool CNetClient::HandleMessage(CNetMessage* message)
    251262{
    252263    // Handle non-FSM messages first
     
    503514    CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
    504515
    505516    client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
     517    client->m_Game->SetGameSpeed(endMessage->m_GameSpeed);
    506518   
    507519    // Execute all the received commands for the latest turn
    508520    client->m_ClientTurnManager->UpdateFastForward();
     
    550562        {
    551563            CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
    552564            client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
     565            client->m_Game->SetGameSpeed(endMessage->m_GameSpeed);
    553566        }
    554567    }
    555568
     
    577590
    578591    return guid;
    579592}
     593
     594bool CNetClient::OnRequestSpeed(void* context, CFsmEvent* event)
     595{
     596    ENSURE(event->GetType() == (uint)NMT_REQUEST_SPEED);
     597
     598    CNetClient* client = (CNetClient*)context;
     599
     600    CRequestSpeedMessage* message = (CRequestSpeedMessage*)event->GetParamRef();
     601
     602    CScriptValRooted msg;
     603    client->GetScriptInterface().Eval("({'type':'requestspeed'})", msg);
     604    client->GetScriptInterface().SetProperty(msg.get(), "guid", std::string(message->m_GUID));
     605    client->GetScriptInterface().SetProperty(msg.get(), "speed", message->m_Speed);
     606    client->PushGuiMessage(msg);
     607
     608    return true;
     609}
     610
     611bool CNetClient::OnSpeedChanged(void* context, CFsmEvent* event)
     612{
     613    ENSURE(event->GetType() == (uint)NMT_SPEED_CHANGED);
     614
     615    CNetClient* client = (CNetClient*)context;
     616
     617    CSpeedChangedMessage* message = (CSpeedChangedMessage*)event->GetParamRef();
     618
     619    CScriptValRooted msg;
     620    client->GetScriptInterface().Eval("({'type':'speedchanged'})", msg);
     621    client->GetScriptInterface().SetProperty(msg.get(), "speed", message->m_Speed);
     622    client->PushGuiMessage(msg);
     623
     624    return true;
     625}
  • source/network/NetClient.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    165165
    166166    void SendChatMessage(const std::wstring& text);
    167167
     168    void RequestGameSpeed(u32 speed);
     169
    168170private:
    169171    // Net message / FSM transition handlers
    170172    static bool OnConnect(void* context, CFsmEvent* event);
     
    179181    static bool OnJoinSyncStart(void* context, CFsmEvent* event);
    180182    static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
    181183    static bool OnLoadedGame(void* context, CFsmEvent* event);
     184    static bool OnRequestSpeed(void* context, CFsmEvent* event);
     185    static bool OnSpeedChanged(void* context, CFsmEvent* event);
    182186
    183187    /**
    184188     * Take ownership of a session object, and use it for all network communication.
  • source/network/NetMessage.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    179179        pNewMessage = new CSimulationMessage(scriptInterface);
    180180        break;
    181181
     182    case NMT_REQUEST_SPEED:
     183        pNewMessage = new CRequestSpeedMessage;
     184        break;
     185
     186    case NMT_SPEED_CHANGED:
     187        pNewMessage = new CSpeedChangedMessage;
     188        break;
     189
    182190    default:
    183191        LOGERROR(L"CNetMessageFactory::CreateMessage(): Unknown message type '%d' received", header.GetType());
    184192        break;
  • source/network/NetMessages.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    6262    NMT_SYNC_CHECK, // OOS-detection hash checking
    6363    NMT_SYNC_ERROR, // OOS-detection error
    6464    NMT_SIMULATION_COMMAND,
     65    NMT_REQUEST_SPEED,
     66    NMT_SPEED_CHANGED,
    6567    NMT_LAST                // Last message in the list
    6668};
    6769
     
    158160START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH)
    159161    NMT_FIELD_INT(m_Turn, u32, 4)
    160162    NMT_FIELD_INT(m_TurnLength, u32, 2)
     163    NMT_FIELD_INT(m_GameSpeed, u32, 2) // ignored when client->server, valid when server->client
    161164END_NMT_CLASS()
    162165
    163166START_NMT_CLASS_(SyncCheck, NMT_SYNC_CHECK)
     
    170173    NMT_FIELD(CStr, m_HashExpected)
    171174END_NMT_CLASS()
    172175
     176START_NMT_CLASS_(RequestSpeed, NMT_REQUEST_SPEED)
     177    NMT_FIELD(CStr8, m_GUID) // ignored when client->server, valid when server->client
     178    NMT_FIELD_INT(m_Speed, u32, 2)
     179END_NMT_CLASS()
     180
     181START_NMT_CLASS_(SpeedChanged, NMT_SPEED_CHANGED)
     182    NMT_FIELD_INT(m_Speed, u32, 2)
     183END_NMT_CLASS()
     184
    173185END_NMTS()
    174186
    175187#else
  • source/network/NetServer.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    120120
    121121    m_ServerName = DEFAULT_SERVER_NAME;
    122122    m_WelcomeMessage = DEFAULT_WELCOME_MESSAGE;
     123
     124    m_InitialGameSpeed = m_GameSpeed = 100;
     125    m_AllowGameSpeedRequests = true;
    123126}
    124127
    125128CNetServerWorker::~CNetServerWorker()
     
    216219    return ok;
    217220}
    218221
     222u32 CNetServerWorker::GetGameSpeed()
     223{
     224    return m_GameSpeed;
     225}
     226
    219227void* CNetServerWorker::RunThread(void* data)
    220228{
    221229    debug_SetThreadName("NetServer");
     
    440448    session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, (void*)&OnInGame, context);
    441449    session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, (void*)&OnInGame, context);
    442450    session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, (void*)&OnInGame, context);
     451    session->AddTransition(NSS_INGAME, (uint)NMT_REQUEST_SPEED, NSS_INGAME, (void*)&OnRequestSpeed, context);
    443452
    444453    // Set first state
    445454    session->SetFirstState(NSS_HANDSHAKE);
     
    743752    return true;
    744753}
    745754
     755bool CNetServerWorker::OnRequestSpeed(void* context, CFsmEvent* event)
     756{
     757    ENSURE(event->GetType() == (uint)NMT_REQUEST_SPEED);
     758
     759    CNetServerSession* session = (CNetServerSession*)context;
     760    CNetServerWorker& server = session->GetServer();
     761
     762    CRequestSpeedMessage* requestMessage = (CRequestSpeedMessage*)event->GetParamRef();
     763
     764    // TODO: this shouldn't happen but maybe we need a better error?
     765    if (!server.m_AllowGameSpeedRequests)
     766        return false;
     767
     768    // Clamp speed to minimum of 25, no upper limit is necessary
     769    // (slower speeds mean future messages will be very slowly processed by clients,
     770    // so we can't use this to implement pause either)
     771    // TODO: restriction can be removed if we sync time between clients
     772    u32 desiredSpeed = std::max(requestMessage->m_Speed, (u32)25);
     773    session->SetGameSpeed(desiredSpeed);
     774
     775    // Set GUID so players know who requested this
     776    requestMessage->m_GUID = session->GetGUID();
     777    requestMessage->m_Speed = desiredSpeed;
     778    server.Broadcast(requestMessage);
     779
     780    server.UpdateGameSpeed();
     781
     782    return true;
     783}
     784
     785void CNetServerWorker::UpdateGameSpeed()
     786{
     787    // Current "voting" approach:
     788    // choose least speed that's closest to initial game speed
     789    // this effectively requires all players' consent to change speed
     790    u32 minDiff = UINT32_MAX;
     791    u32 closest = m_InitialGameSpeed;
     792    for (size_t i = 0; i < m_Sessions.size(); ++i)
     793    {
     794        // Only count enabled players
     795        if (m_PlayerAssignments[m_Sessions[i]->GetGUID()].m_Enabled)
     796        {
     797            u32 desired = m_Sessions[i]->GetGameSpeed();
     798            u32 diff = 0;
     799            if (desired > m_InitialGameSpeed)
     800                diff = desired - m_InitialGameSpeed;
     801            else if (desired < m_InitialGameSpeed)
     802                diff = m_InitialGameSpeed - desired;
     803
     804            if (diff < minDiff)
     805            {
     806                minDiff = diff;
     807                closest = desired;
     808            }
     809            else if (diff == minDiff && desired < closest)
     810            {
     811                // Choose lowest in case of tie
     812                closest = desired;
     813            }
     814        }
     815    }
     816
     817    if (m_GameSpeed != closest)
     818    {
     819        m_GameSpeed = closest;
     820
     821        CSpeedChangedMessage msg;
     822        msg.m_Speed = m_GameSpeed;
     823        Broadcast(&msg);
     824    }
     825}
     826
    746827bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event)
    747828{
    748829    ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
     
    750831    CNetServerSession* session = (CNetServerSession*)context;
    751832    CNetServerWorker& server = session->GetServer();
    752833
     834    session->SetGameSpeed(server.m_GameSpeed);
     835
    753836    // We're in the loading state, so wait until every player has loaded before
    754837    // starting the game
    755838    ENSURE(server.m_State == SERVER_STATE_LOADING);
     
    780863
    781864    u32 turn = message->m_CurrentTurn;
    782865    u32 readyTurn = server.m_ServerTurnManager->GetReadyTurn();
     866    u32 gameSpeed = server.m_GameSpeed;
     867    session->SetGameSpeed(gameSpeed);
    783868
    784869    // Send them all commands received since their saved state,
    785870    // and turn-ended messages for any turns that have already been processed
     
    794879            CEndCommandBatchMessage endMessage;
    795880            endMessage.m_Turn = i;
    796881            endMessage.m_TurnLength = server.m_ServerTurnManager->GetSavedTurnLength(i);
     882            endMessage.m_GameSpeed = gameSpeed;
    797883            session->SendMessage(&endMessage);
    798884        }
    799885    }
     
    849935    UpdateGameAttributes(m_GameAttributes);
    850936    SendPlayerAssignments();
    851937
     938    // TODO: this is yucky, but what's the alternative?
     939    bool lockGameSpeed;
     940    if (GetScriptInterface().HasProperty(m_GameAttributes.get(), "lockGameSpeed") && GetScriptInterface().GetProperty(m_GameAttributes.get(), "lockGameSpeed", lockGameSpeed))
     941        m_AllowGameSpeedRequests = !lockGameSpeed;
     942
     943    u32 gameSpeed;
     944    if (GetScriptInterface().HasProperty(m_GameAttributes.get(), "gameSpeed") && GetScriptInterface().GetProperty(m_GameAttributes.get(), "gameSpeed", gameSpeed))
     945        m_InitialGameSpeed = m_GameSpeed = gameSpeed;
     946
    852947    CGameStartMessage gameStart;
    853948    Broadcast(&gameStart);
    854949}
  • source/network/NetServer.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    2424#include "ps/ThreadUtil.h"
    2525#include "scriptinterface/ScriptVal.h"
    2626
     27#include <map>
    2728#include <vector>
    2829
    2930class CNetServerSession;
     
    175176     */
    176177    bool Broadcast(const CNetMessage* message);
    177178
     179    u32 GetGameSpeed();
     180
    178181private:
    179182    friend class CNetServer;
    180183    friend class CNetFileReceiveTask_ServerRejoin;
     
    244247    static bool OnAuthenticate(void* context, CFsmEvent* event);
    245248    static bool OnInGame(void* context, CFsmEvent* event);
    246249    static bool OnChat(void* context, CFsmEvent* event);
     250    static bool OnRequestSpeed(void* context, CFsmEvent* event);
    247251    static bool OnLoadedGame(void* context, CFsmEvent* event);
    248252    static bool OnJoinSyncingLoadedGame(void* context, CFsmEvent* event);
    249253    static bool OnDisconnect(void* context, CFsmEvent* event);
     
    254258
    255259    void HandleMessageReceive(const CNetMessage* message, CNetServerSession* session);
    256260
     261    void UpdateGameSpeed();
    257262
     263
    258264    /**
    259265     * Internal script context for (de)serializing script messages,
    260266     * and for storing game attributes.
     
    296302     */
    297303    std::string m_JoinSyncFile;
    298304
     305    // Current game speed
     306    u32 m_GameSpeed;
     307    u32 m_InitialGameSpeed;
     308
     309    // If false, we reject RequestGameSpeed messages
     310    bool m_AllowGameSpeedRequests;
     311
    299312private:
    300313    // Thread-related stuff:
    301314
  • source/network/NetSession.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    122122    u32 GetHostID() const { return m_HostID; }
    123123    void SetHostID(u32 id) { m_HostID = id; }
    124124
     125    u32 GetGameSpeed() const { return m_GameSpeed; }
     126    void SetGameSpeed(u32 speed) { m_GameSpeed = speed; }
     127
    125128    /**
    126129     * Sends a disconnection notification to the client,
    127130     * and sends a NMT_CONNECTION_LOST message to the session FSM.
     
    154157    CStr m_GUID;
    155158    CStrW m_UserName;
    156159    u32 m_HostID;
     160    u32 m_GameSpeed;
    157161};
    158162
    159163#endif  // NETSESSION_H
  • source/network/NetTurnManager.cpp

     
    1 /* Copyright (C) 2012 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    274274
    275275void CNetTurnManager::FinishedAllCommands(u32 turn, u32 turnLength)
    276276{
    277     NETTURN_LOG((L"FinishedAllCommands(%d, %d)\n", turn, turnLength));
     277    NETTURN_LOG((L"FinishedAllCommands(%d, %d, %hs)\n", turn, turnLength, gameSpeed.ToString().c_str()));
    278278
    279279    ENSURE(turn == m_ReadyTurn + 1);
    280280    m_ReadyTurn = turn;
     
    396396    CEndCommandBatchMessage msg;
    397397    msg.m_TurnLength = DEFAULT_TURN_LENGTH_MP; // TODO: why do we send this?
    398398    msg.m_Turn = turn;
     399    msg.m_GameSpeed = UINT32_MAX;
    399400    m_NetClient.SendMessage(&msg);
    400401}
    401402
     
    508509    CEndCommandBatchMessage msg;
    509510    msg.m_TurnLength = m_TurnLength;
    510511    msg.m_Turn = m_ReadyTurn;
     512    msg.m_GameSpeed = m_NetServer.GetGameSpeed();
    511513    m_NetServer.Broadcast(&msg);
    512514
    513515    // Save the turn length in case it's needed later
  • source/ps/Game.cpp

     
    126126    std::string mapType;
    127127    m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "mapType", mapType);
    128128
     129    // Setting game speed here only affects non-networked games
     130    // (NetClient will do this later for networked games)
     131    u32 gameSpeed;
     132    if (m_Simulation2->GetScriptInterface().HasProperty(attribs.get(), "gameSpeed") && m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "gameSpeed", gameSpeed))
     133        SetGameSpeed(gameSpeed);
     134
    129135    LDR_BeginRegistering();
    130136
    131137    RegMemFun(m_Simulation2, &CSimulation2::ProgressiveLoad, L"Simulation init", 1000);
     
    268274        return true;
    269275
    270276    const double deltaSimTime = deltaRealTime * m_SimRate;
    271    
     277
    272278    bool ok = true;
    273279    if (deltaSimTime)
    274280    {
  • source/ps/Game.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2013 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
     
    138138
    139139    /**
    140140     * Set the simulation scale multiplier.
     141     * In a networked game, this must be synced.
    141142     *
    142143     * @param simRate Float value to set m_SimRate to.
    143144     *                      Because m_SimRate is also used to
    144145     *                      scale TimeSinceLastFrame it must be
    145146     *                      clamped to 0.0f.
    146      **/
     147     */
    147148    inline void SetSimRate(float simRate)
    148     {    m_SimRate = std::max(simRate, 0.0f); }
     149    {   if (isfinite(simRate)) m_SimRate = std::max(simRate, 0.0f); }
    149150
    150151    /**
     152     * Set the simulation scale multiplier using integer game speed.
     153     * In a networked game, this must be synced.
     154     *
     155     * @param speed Integer speed as percentage of normal (100=normal, 50=half speed, etc.)
     156     */
     157    inline void SetGameSpeed(u32 speed)
     158    {   m_SimRate = (float)speed/100.0f; }
     159
     160    /**
     161     * Get the current simulation scale multiplier in integer game speed.
     162     */
     163    inline int GetGameSpeed()
     164    {   return m_SimRate*100; }
     165
     166    /**
    151167     * Replace the current turn manager.
    152168     * This class will take ownership of the pointer.
    153169     */