Index: binaries/data/mods/public/gui/aiconfig/aiconfig.js =================================================================== --- binaries/data/mods/public/gui/aiconfig/aiconfig.js +++ binaries/data/mods/public/gui/aiconfig/aiconfig.js @@ -16,20 +16,20 @@ let aiSelection = Engine.GetGUIObjectByName("aiSelection"); aiSelection.list = g_AIDescriptions.map(ai => ai.data.name); aiSelection.selected = g_AIDescriptions.findIndex(ai => ai.id == settings.id); - aiSelection.hidden = !settings.isController; + aiSelection.hidden = !settings.canPlayerChange; let aiSelectionText = Engine.GetGUIObjectByName("aiSelectionText"); aiSelectionText.caption = aiSelection.list[aiSelection.selected]; - aiSelectionText.hidden = settings.isController; + aiSelectionText.hidden = settings.canPlayerChange; let aiDiff = Engine.GetGUIObjectByName("aiDifficulty"); aiDiff.list = prepareForDropdown(g_Settings.AIDifficulties).Title; aiDiff.selected = settings.difficulty; - aiDiff.hidden = !settings.isController; + aiDiff.hidden = !settings.canPlayerChange; let aiDiffText = Engine.GetGUIObjectByName("aiDifficultyText"); aiDiffText.caption = aiDiff.list[aiDiff.selected]; - aiDiffText.hidden = settings.isController; + aiDiffText.hidden = settings.canPlayerChange; } function selectAI(idx) Index: binaries/data/mods/public/gui/common/settings.js =================================================================== --- binaries/data/mods/public/gui/common/settings.js +++ binaries/data/mods/public/gui/common/settings.js @@ -32,14 +32,15 @@ "AIDescriptions": loadAIDescriptions(), "AIDifficulties": loadAIDifficulties(), "Ceasefire": loadCeasefire(), - "VictoryDurations": loadVictoryDuration(), "GameSpeeds": loadSettingValuesFile("game_speeds.json"), + "GuestSettings": loadSettingValuesFile("guest_settings.json"), "MapTypes": loadMapTypes(), "MapSizes": loadSettingValuesFile("map_sizes.json"), "PlayerDefaults": loadPlayerDefaults(), "PopulationCapacities": loadPopulationCapacities(), "StartingResources": loadSettingValuesFile("starting_resources.json"), - "VictoryConditions": loadVictoryConditions() + "VictoryConditions": loadVictoryConditions(), + "VictoryDurations": loadVictoryDuration() }; if (Object.keys(settings).some(key => settings[key] === undefined)) Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.js +++ binaries/data/mods/public/gui/gamesetup/gamesetup.js @@ -3,6 +3,7 @@ const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire); const g_GameSpeeds = prepareForDropdown(g_Settings && g_Settings.GameSpeeds.filter(speed => !speed.ReplayOnly)); +const g_GuestSettings = prepareForDropdown(g_Settings && g_Settings.GuestSettings); const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes); const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes); const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities); @@ -123,6 +124,7 @@ "netstatus": msg => handleNetStatusMessage(msg), "netwarn": msg => addNetworkWarning(msg), "gamesetup": msg => handleGamesetupMessage(msg), + "changesetting": msg => handleChangeSettingMessage(msg), "players": msg => handlePlayerAssignmentMessage(msg), "ready": msg => handleReadyMessage(msg), "start": msg => handleGamestartMessage(msg), @@ -311,6 +313,7 @@ "populationCap", "startingResources", "ceasefire", + "guestSettings", ], "Checkbox": [ "exploreMap", @@ -526,6 +529,18 @@ g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[idx]; }, }, + "guestSettings": { + "title": () => translate("Guest Settings"), + "tooltip": () => translate("Set which settings can be changed by every player."), + "labels": () => g_GuestSettings.Title, + "ids": () => g_GuestSettings.Data, + "default": () => g_GuestSettings.Default, + "defined": () => g_GameAttributes.guestSettings !== undefined, + "get": () => g_GameAttributes.guestSettings, + "select": (idx) => { + g_GameAttributes.guestSettings = g_GuestSettings.Data[idx]; + }, + }, }; /** @@ -764,10 +779,10 @@ g_PlayerAssignments[Engine.GetPlayerGUID()].player == -1 && !g_IsController, }, "civResetButton": { - "hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController, + "hidden": () => g_GameAttributes.mapType == "scenario" || !canPlayerChange("civResetButton"), }, "teamResetButton": { - "hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController, + "hidden": () => g_GameAttributes.mapType == "scenario" || !canPlayerChange("teamResetButton"), }, // Display these after having hidden every GUI object in the "More Options" dialog "moreOptionsLabel": { @@ -954,18 +969,8 @@ dropdown.list_data = data.ids(idx); dropdown.onSelectionChange = function() { - - if (!g_IsController || - g_IsInGuiUpdate || - !this.list_data[this.selected] || - data.hidden && data.hidden(idx) || - data.enabled && !data.enabled(idx)) - return; - - data.select(this.selected, idx); - - supplementDefaults(); - updateGameAttributes(); + changeSetting(this,{ "setting": name, "index": idx, "type": "dropdown", + "value": this.list_data[this.selected] }); }; } @@ -979,19 +984,7 @@ { let [guiName, guiIdx] = getGUIObjectNameFromSetting(name); Engine.GetGUIObjectByName(guiName + guiIdx).onPress = function() { - - let obj = g_Checkboxes[name]; - - if (!g_IsController || - g_IsInGuiUpdate || - obj.enabled && !obj.enabled() || - obj.hidden && obj.hidden()) - return; - - obj.set(this.checked); - - supplementDefaults(); - updateGameAttributes(); + changeSetting(this, { "setting": name, "type": "checkbox", "value": this.checked }); }; } @@ -1054,6 +1047,76 @@ } /** + * Returns if the player is allowed to change this setting + */ +function canPlayerChange(setting, index = -1, guid = Engine.GetPlayerGUID()) +{ + if (g_IsController) + return true; + + if (g_PlayerAssignments[guid] === undefined || g_PlayerAssignments[guid].player <= 0 || setting == "guestSettings") + return false; + + if (g_GuestSettings.Data.indexOf(setting) == -1) + return g_GameAttributes.guestSettings == "all"; + + if (index >= 0 && index != g_PlayerAssignments[guid].player-1) + return g_GameAttributes.guestSettings == "all"; + + return g_GuestSettings.Data.indexOf(g_GameAttributes.guestSettings) >= g_GuestSettings.Data.indexOf(setting); +} + +/** + * Changes a setting. + */ +function changeSetting(element, data, guid, force = false) +{ + if (!data || !data.setting || !data.type) + return; + + if (!canPlayerChange(data.setting, data.index || -1, guid) || g_IsInGuiUpdate && !force) + return; + + if (data.type == "misc") + { + switch (data.setting) + { + case "resetCivilizations": + resetCivilizations(); + break; + case "resetTeams": + resetTeams(); + break; + case "aiSettings": + AIConfigCallback(data.value, false); + break; + } + } + else if (data.type == "checkbox" || data.type == "dropdown") + { + let obj = (data.type == "checkbox" ? g_Checkboxes : + (data.index === undefined ? g_Dropdowns : g_PlayerDropdowns))[data.setting]; + if (obj.enabled && !obj.enabled() || obj.hidden && obj.hidden()) + return; + if (data.type == "checkbox") + obj.set(data.value); + else + { + if (!data.value) + return; + let selected = element.list_data.indexOf(data.value); + if (selected != -1) + obj.select(selected, data.index); + } + if (!g_IsController) + Engine.NetworkChangeSetting(data); + + supplementDefaults(); + updateGameAttributes(); + } +} + +/** * Called when the client disconnects. * The other cases from NetClient should never occur in the gamesetup. */ @@ -1108,7 +1171,7 @@ } /** - * Called whenever the host changed any setting. + * Called whenever the host sent new game attributes. */ function handleGamesetupMessage(message) { @@ -1130,6 +1193,28 @@ } /** + * Called whenever a client changes a setting. + * @param {Object} message + */ +function handleChangeSettingMessage(message) +{ + if (!message.guid || !message.data /*|| !message.data.setting || !message.data.type || + !canPlayerChange(message.data.setting, message.data.index || -1, message.guid)*/) + return; + + + let [guiName, guiIdx] = getGUIObjectNameFromSetting(message.data.setting); + let idxName = message.data.index === undefined ? "": "[" + message.data.index + "]"; + + let element = Engine.GetGUIObjectByName(guiName + guiIdx + idxName); + changeSetting(element, message.data, message.guid, true); + /*warn("The player with guid " + message.guid + " has changed the setting " + + message.data.setting + " with type " + message.data.type + " and index " + + message.data.index + " to the value " + message.data.value + ". Type is " + typeof message.data.value);*/ + +} + +/** * Called whenever a client joins/leaves or any gamesetting is changed. */ function handlePlayerAssignmentMessage(message) @@ -1516,7 +1601,7 @@ let enabled = !indexHidden && (!obj.enabled || obj.enabled(idx)); let hidden = indexHidden || obj.hidden && obj.hidden(idx); - dropdown.hidden = !g_IsController || !enabled || hidden; + dropdown.hidden = !canPlayerChange(name, idx) || !enabled || hidden; dropdown.selected = indexHidden ? -1 : selected; dropdown.tooltip = !indexHidden && obj.tooltip ? obj.tooltip(idx) : ""; @@ -1528,7 +1613,7 @@ if (label && !indexHidden) { - label.hidden = g_IsController && enabled || hidden; + label.hidden = canPlayerChange(name, idx) && enabled || hidden; label.caption = selected == -1 ? translateWithContext("option value", "Unknown") : dropdown.list[selected]; } } @@ -1553,11 +1638,11 @@ checkbox.checked = checked; checkbox.enabled = enabled; - checkbox.hidden = hidden || !g_IsController; + checkbox.hidden = hidden || !canPlayerChange(name); checkbox.tooltip = obj.tooltip ? obj.tooltip() : ""; label.caption = checked ? translate("Yes") : translate("No"); - label.hidden = hidden || g_IsController; + label.hidden = hidden || canPlayerChange(name); if (frame) frame.hidden = hidden; @@ -1795,7 +1880,7 @@ Engine.PushGuiPage("page_aiconfig.xml", { "callback": "AIConfigCallback", - "isController": g_IsController, + "canPlayerChange": canPlayerChange("aiSettings"), "playerSlot": playerSlot, "id": g_GameAttributes.settings.PlayerData[playerSlot].AI, "difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff @@ -1805,12 +1890,18 @@ /** * Called after closing the dialog. */ -function AIConfigCallback(ai) +function AIConfigCallback(ai, resetViewedAI = true) { - g_LastViewedAIPlayer = -1; + if (resetViewedAI) + g_LastViewedAIPlayer = -1; - if (!ai.save || !g_IsController) + if (!ai.save) return; + if (!g_IsController) + { + Engine.NetworkChangeSetting({ "setting": "aiSettings", "type": "misc", "value": ai}); + return; + } g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id; g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty; @@ -1979,6 +2070,12 @@ function resetCivilizations() { + if (!g_IsController) + { + Engine.NetworkChangeSetting({ "setting": "resetCivilizations", "type": "misc" }); + return; + } + for (let i in g_GameAttributes.settings.PlayerData) g_GameAttributes.settings.PlayerData[i].Civ = "random"; @@ -1987,6 +2084,12 @@ function resetTeams() { + if (!g_IsController) + { + Engine.NetworkChangeSetting({ "setting": "resetTeams", "type": "misc" }); + return; + } + for (let i in g_GameAttributes.settings.PlayerData) g_GameAttributes.settings.PlayerData[i].Team = -1; Index: binaries/data/mods/public/gui/gamesetup/gamesetup.xml =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.xml +++ binaries/data/mods/public/gui/gamesetup/gamesetup.xml @@ -110,7 +110,7 @@ Select player. - + pScriptInterface->GetContext(); + JSAutoRequest rq(cx); + JS::RootedValue keyValuePair(cx, keyValuePair1); + + g_NetClient->SendChangeSettingMessage(&keyValuePair, *(pCxPrivate->pScriptInterface)); +} + void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue attribs1) { ENSURE(g_NetClient); @@ -1033,6 +1045,7 @@ scriptInterface.RegisterFunction("GetPlayerGUID"); scriptInterface.RegisterFunction("KickPlayer"); scriptInterface.RegisterFunction("PollNetworkClient"); + scriptInterface.RegisterFunction("NetworkChangeSetting"); scriptInterface.RegisterFunction("SetNetworkGameAttributes"); scriptInterface.RegisterFunction("AssignNetworkPlayer"); scriptInterface.RegisterFunction("ClearAllPlayerReady"); Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -191,6 +191,8 @@ void SendAssignPlayerMessage(const int playerID, const CStr& guid); + void SendChangeSettingMessage(JS::MutableHandleValue keyValuePair, ScriptInterface& scriptInterface); + void SendChatMessage(const std::wstring& text); void SendReadyMessage(const int status); @@ -225,6 +227,7 @@ static bool OnReady(void* context, CFsmEvent* event); static bool OnGameSetup(void* context, CFsmEvent* event); static bool OnPlayerAssignment(void* context, CFsmEvent* event); + static bool OnChangeSetting(void* context, CFsmEvent* event); static bool OnInGame(void* context, CFsmEvent* event); static bool OnGameStart(void* context, CFsmEvent* event); static bool OnJoinSyncStart(void* context, CFsmEvent* event); Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -96,6 +96,7 @@ AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context); AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context); AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context); + AddTransition(NCS_PREGAME, (uint)NMT_CHANGE_SETTING, NCS_PREGAME, (void*)&OnChangeSetting, context); AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context); AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context); AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context); @@ -343,6 +344,17 @@ SendMessage(&assignPlayer); } +void CNetClient::SendChangeSettingMessage(JS::MutableHandleValue keyValuePair, ScriptInterface& scriptInterface) +{ + JSContext* cx = scriptInterface.GetContext(); + JS::RootedValue keyValuePairRooted(cx); + keyValuePairRooted = keyValuePair; + + CChangeSettingMessage changeSetting(GetScriptInterface()); + changeSetting.m_Data = keyValuePairRooted; + SendMessage(&changeSetting); +} + void CNetClient::SendChatMessage(const std::wstring& text) { CChatMessage chat; @@ -632,6 +644,25 @@ return true; } +bool CNetClient::OnChangeSetting(void* context, CFsmEvent* event) +{ + ENSURE(event->GetType() == (uint)NMT_CHANGE_SETTING); + + CNetClient* client = (CNetClient*)context; + JSContext* cx = client->GetScriptInterface().GetContext(); + JSAutoRequest rq(cx); + + CChangeSettingMessage* message = (CChangeSettingMessage*)event->GetParamRef(); + + JS::RootedValue msg(cx); + client->GetScriptInterface().Eval("({'type':'changesetting'})", &msg); + client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false); + client->GetScriptInterface().SetProperty(msg, "data", message->m_Data, false); + client->PushGuiMessage(msg); + + return true; +} + bool CNetClient::OnGameStart(void* context, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_GAME_START); Index: source/network/NetMessage.h =================================================================== --- source/network/NetMessage.h +++ source/network/NetMessage.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -154,6 +154,26 @@ ScriptInterface& m_ScriptInterface; }; +/** + * Special message type to send single setting changes. + */ +class CChangeSettingMessage : public CNetMessage +{ + NONCOPYABLE(CChangeSettingMessage); +public: + CChangeSettingMessage(ScriptInterface& scriptInterface); + CChangeSettingMessage(ScriptInterface& scriptInterface, CStr& guid, JS::HandleValue data); + virtual u8* Serialize(u8* pBuffer) const; + virtual const u8* Deserialize(const u8* pStart, const u8* pEnd); + virtual size_t GetSerializedLength() const; + virtual CStr ToString() const; + + CStr m_GUID; + JS::PersistentRootedValue m_Data; +private: + ScriptInterface& m_ScriptInterface; +}; + // This time, the classes are created #include "NetMessages.h" Index: source/network/NetMessage.cpp =================================================================== --- source/network/NetMessage.cpp +++ source/network/NetMessage.cpp @@ -103,6 +103,10 @@ switch (header.GetType()) { + case NMT_CHANGE_SETTING: + pNewMessage = new CChangeSettingMessage(scriptInterface); + break; + case NMT_GAME_SETUP: pNewMessage = new CGameSetupMessage(scriptInterface); break; Index: source/network/NetMessageSim.cpp =================================================================== --- source/network/NetMessageSim.cpp +++ source/network/NetMessageSim.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -229,3 +229,53 @@ stream << "CGameSetupMessage { m_Data: " << source << " }"; return CStr(stream.str()); } + + +CChangeSettingMessage::CChangeSettingMessage(ScriptInterface& scriptInterface) : +CNetMessage(NMT_CHANGE_SETTING), m_ScriptInterface(scriptInterface), m_Data(scriptInterface.GetJSRuntime()) +{ +} + +CChangeSettingMessage::CChangeSettingMessage(ScriptInterface& scriptInterface, CStr& guid, JS::HandleValue data) : +CNetMessage(NMT_CHANGE_SETTING), m_ScriptInterface(scriptInterface), +m_GUID(guid), m_Data(scriptInterface.GetJSRuntime(), data) +{ +} + +u8* CChangeSettingMessage::Serialize(u8* pBuffer) const +{ + // TODO: ought to handle serialization exceptions + u8* pos = CNetMessage::Serialize(pBuffer); + CBufferBinarySerializer serializer(m_ScriptInterface, pos); + serializer.StringASCII("guid", m_GUID, 0, UINT32_MAX); + serializer.ScriptVal("command", const_cast(&m_Data)); + return serializer.GetBuffer(); +} + +const u8* CChangeSettingMessage::Deserialize(const u8* pStart, const u8* pEnd) +{ + // TODO: ought to handle serialization exceptions + const u8* pos = CNetMessage::Deserialize(pStart, pEnd); + std::istringstream stream(std::string(pos, pEnd)); + CStdDeserializer deserializer(m_ScriptInterface, stream); + deserializer.StringASCII("guid", m_GUID, 0, UINT32_MAX); + deserializer.ScriptVal("command", const_cast(&m_Data)); + return pEnd; +} + +size_t CChangeSettingMessage::GetSerializedLength() const +{ + CLengthBinarySerializer serializer(m_ScriptInterface); + serializer.StringASCII("guid", m_GUID, 0, UINT32_MAX); + serializer.ScriptVal("command", const_cast(&m_Data)); + return CNetMessage::GetSerializedLength() + serializer.GetLength(); +} + +CStr CChangeSettingMessage::ToString() const +{ + std::string source = m_ScriptInterface.ToString(const_cast(&m_Data)); + + std::stringstream stream; + stream << "CChangeSettingMessage { m_GUID: " << m_GUID << ", m_Data: " << source << " }"; + return CStr(stream.str()); +} Index: source/network/NetMessages.h =================================================================== --- source/network/NetMessages.h +++ source/network/NetMessages.h @@ -28,7 +28,7 @@ #define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' #define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' -#define PS_PROTOCOL_VERSION 0x01010015 // Arbitrary protocol +#define PS_PROTOCOL_VERSION 0x01010016 // Arbitrary protocol #define PS_DEFAULT_PORT 0x5073 // 'P', 's' // Defines the list of message types. The order of the list must not change. @@ -53,6 +53,7 @@ NMT_CLEAR_ALL_READY, NMT_GAME_SETUP, NMT_ASSIGN_PLAYER, + NMT_CHANGE_SETTING, NMT_PLAYER_ASSIGNMENT, NMT_FILE_TRANSFER_REQUEST, Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -244,6 +244,7 @@ static bool OnChat(void* context, CFsmEvent* event); static bool OnReady(void* context, CFsmEvent* event); static bool OnClearAllReady(void* context, CFsmEvent* event); + static bool OnChangeSetting(void* context, CFsmEvent* event); static bool OnGameSetup(void* context, CFsmEvent* event); static bool OnAssignPlayer(void* context, CFsmEvent* event); static bool OnStartGame(void* context, CFsmEvent* event); Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -638,6 +638,7 @@ session->AddTransition(NSS_PREGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, (void*)&OnDisconnect, context); session->AddTransition(NSS_PREGAME, (uint)NMT_CHAT, NSS_PREGAME, (void*)&OnChat, context); session->AddTransition(NSS_PREGAME, (uint)NMT_READY, NSS_PREGAME, (void*)&OnReady, context); + session->AddTransition(NSS_PREGAME, (uint)NMT_CHANGE_SETTING, NSS_PREGAME, (void*)&OnChangeSetting, context); session->AddTransition(NSS_PREGAME, (uint)NMT_CLEAR_ALL_READY, NSS_PREGAME, (void*)&OnClearAllReady, context); session->AddTransition(NSS_PREGAME, (uint)NMT_GAME_SETUP, NSS_PREGAME, (void*)&OnGameSetup, context); session->AddTransition(NSS_PREGAME, (uint)NMT_ASSIGN_PLAYER, NSS_PREGAME, (void*)&OnAssignPlayer, context); @@ -1171,6 +1172,24 @@ return true; } +bool CNetServerWorker::OnChangeSetting(void* context, CFsmEvent* event) +{ + ENSURE(event->GetType() == (uint)NMT_CHANGE_SETTING); + + CNetServerSession* session = (CNetServerSession*)context; + CNetServerWorker& server = session->GetServer(); + + CChangeSettingMessage* message = (CChangeSettingMessage*)event->GetParamRef(); + message->m_GUID = session->GetGUID(); + // Forward message to host + for (CNetServerSession* session : server.m_Sessions) + if (session->GetGUID() == server.m_HostGUID) + return session->SendMessage(message); + + LOGERROR("No host found to send the ChangeSettingMessage to."); + return false; +} + bool CNetServerWorker::OnLoadedGame(void* context, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);