diff --git a/Dockerfile b/Dockerfile index ecb5cbb..3998599 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,17 +11,20 @@ apt-get install -y nodejs && \ apt-get install -y wget && \ apt-get install -y vim && \ apt-get install -y unzip && \ +apt-get install -y patch && \ apt-get install -y libgl1-mesa-glx -RUN useradd -k /etc/skel -m pa +RUN useradd -k /etc/skel -m pa COPY auth_papatcher.sh /home/pa/auth_papatcher.sh COPY NodePAMaster_conf.json /home/pa/NodePAMaster_conf.json COPY update_conf_file.sh /home/pa/update_conf_file.sh COPY runServer.sh /home/pa/runServer.sh +COPY mikeyh_lobby.patch /home/pa/mikeyh_lobby.patch COPY *.sed /home/pa/ RUN chown pa:pa /home/pa/auth_papatcher.sh && \ chown pa:pa /home/pa/update_conf_file.sh && \ chown pa:pa /home/pa/runServer.sh && \ chown pa:pa /home/pa/NodePAMaster_conf.json && \ +chown pa:pa /home/pa/mikeyh_lobby.patch && \ chown pa:pa /home/pa/*.sed USER pa WORKDIR /home/pa diff --git a/buildImage.sh b/buildImage.sh index fe0407e..e0f19c9 100755 --- a/buildImage.sh +++ b/buildImage.sh @@ -56,7 +56,8 @@ sed -i "/listonpastats/ s/true/$serverlistonpastats/" NodePAMaster_conf.json if [ "$1" == "nobuild" ]; then exit 0;fi -sudo docker build --rm=true -t "uggla/paserver" . +if [ "$1" == "debug" ]; then REMOVE_INTERMEDIATE_IMG=false;else REMOVE_INTERMEDIATE_IMG=true;fi +sudo docker build --rm=$REMOVE_INTERMEDIATE_IMG -t "uggla/paserver" . paversion=$(sudo docker run -ti --rm --entrypoint="/bin/cat" uggla/paserver:latest .local/Uber\ Entertainment/Planetary\ Annihilation/stable/version.txt | sed 's/\s//g') echo "Tag image with version : $paversion" sudo docker tag uggla/paserver:latest uggla/paserver:$paversion diff --git a/mikeyh_lobby.patch b/mikeyh_lobby.patch new file mode 100644 index 0000000..80c26a3 --- /dev/null +++ b/mikeyh_lobby.patch @@ -0,0 +1,6686 @@ +diff -Naur server-script.ori/main.js server-script/main.js +--- server-script.ori/main.js 2016-02-07 14:27:52.000000000 +0100 ++++ server-script/main.js 2016-02-06 20:36:55.331908378 +0100 +@@ -1,259 +1,262 @@ +-var env = require('env'); +-var server_utils = require('server_utils'); +-var sim_utils = require('sim_utils'); +-var utils = require('utils'); +-var content_manager = require('content_manager'); +-var _ = require('thirdparty/lodash'); +- +-// Timeout values, in seconds. +-var EMPTY_TIMEOUT = 120; +- +-function shutdownWhenEmpty() { +- var emptyTimeout; +- utils.pushCallback(server, 'onConnect', function(onConnect, client, reconnect) { +- if (!client.rejected && emptyTimeout) { +- clearTimeout(emptyTimeout); +- emptyTimeout = undefined; +- } +- return onConnect; +- }); +- setInterval(function() { +- var before = server.connected; +- server_utils.refreshConnectionCount(); +- if (server.connected != before) { +- console.error('Whoa! Connection count got out of sync! Something went wrong somewhere. (before=', before, ', after=', server.connected, ')'); +- } +- if (!emptyTimeout && server.connected === 0) { +- emptyTimeout = setTimeout(function() { +- sim.shutdown(true); +- server.exit(); +- }, EMPTY_TIMEOUT * 1000); +- } +- }, 1000); +-} +- +-function setState(newState) { +- console.log("Changing state from", curState.name, "to", newState.name); +- +- if (newState === curState) +- return; +- +- if (curState.exit && !curState.exit(newState)) +- { +- console.log("Cancelling state change"); +- +- return; +- } +- +- curState = newState; +- server.setGameState(curState.name); +- +- // Calling both of these APIs is primarily a matter of getting test coverage +- // One or the other can be removed as deemed necessary +- server.incrementTitleStatistic("State_" + curState.name, 1); +- server.recordGameEvent("State_" + curState.name); +- +- var result; +- if (newState.enter) +- result = newState.enter.apply(this, Array.prototype.slice.call(arguments).slice(1)); +- if (curState !== newState) +- return; // This means entering the state caused another state change. +- if (result !== undefined) +- curState.hello_response.data = result; +- else +- delete curState.hello_response.data; +- var stateMessage = { +- message_type: 'server_state', +- payload: curState.hello_response +- }; +- +- if (!curState.getClientState) { +- server.broadcast(stateMessage); +- return; +- } +- +- if (!stateMessage.payload.data) +- stateMessage.payload.data = {}; +- var numClients = server.clients.length; +- for (var c = 0; c < numClients; ++c) { +- var client = server.clients[c]; +- if (client.connected) { +- stateMessage.payload.data.client = curState.getClientState(client); +- client.message(stateMessage); +- } +- } +- delete stateMessage.payload.data.client; +-} +- +-function updateStateData(data) +-{ +- curState.hello_response.data = data; +-} +- +-var states = {}; +-var gameModes = {}; +- +-// Note: Game Modes must be exported before loading the states, since states +-// register their game mode as part of loading. +-exports.gameModes = gameModes; +-exports.gameMode = ''; +- +-function addState(state, name) { +- if (states.hasOwnProperty(name)) +- return state[name]; +- state.name = name; +- state.hello_response = { +- state: name, +- url: state.url +- }; +- states[name] = state; +- return state; +-} +- +-var cmdlineAllowCheats = (env.indexOf('--allow-cheats') >= 0); +-exports.cheats = { +- cheat_flags: { +- allow_change_vision: cmdlineAllowCheats, +- allow_change_control: cmdlineAllowCheats, +- allow_create_unit: cmdlineAllowCheats, +- allow_mod_data_updates: cmdlineAllowCheats, +- +- any_enabled: cmdlineAllowCheats +- } +-} +- +-function loadState(name) { +- if (states.hasOwnProperty(name)) +- return states[name]; +- var state = require('states/' + name); +- if (state) +- addState(state, name); +- return state; +-} +- +-_.forEach([ +- 'empty', +- 'config', +- 'lobby', +- 'landing', +- 'playing', +- 'game_over', +- 'gw_lobby', +- 'load_replay', +- 'load_save', +- 'replay', +- 'ladder_lobby' +- ], +- loadState +-); +- +-var curState = states.empty; +- +-server.handlers.hello = function(msg) { +- var helloMessage = curState.hello_response; +- var perClient = !!curState.getClientState; +- if (perClient) { +- helloMessage.data.client = curState.getClientState(msg.client); +- } +- server.respond(msg).succeed(helloMessage); +- if (perClient) +- delete helloMessage.data.client; +-}; +- +-server_utils.debug_messages = env.indexOf('--squelch-messages') < 0; +-server_utils.log_lobby_description = env.indexOf('--squelch-messages') < 0; +-exports.keep_alive = env.indexOf('--keep-alive') >= 0; +-var timeLimit = env.indexOf('--time-limit'); +-if (timeLimit >= 0) { +- exports.time_limit = Number(env[timeLimit + 1]); +- if (isNaN(exports.time_limit) || (exports.time_limit <= 0)) +- delete exports.time_limit; +- else +- console.log("Time limit set to", exports.time_limit, "seconds."); +-} +-exports.no_players = env.indexOf('--no-players') >= 0; +-var spectators = env.indexOf('--spectators'); +-if (spectators >= 0) +- exports.spectators = Number(env[spectators + 1]); +-else +- exports.spectators = 0; +- +-var gameModeIndex = env.indexOf('--game-mode'); +-if (gameModeIndex >= 0) +-{ +- var gameMode = env[gameModeIndex + 1]; +- +- // Game mode can contain a content specification, in the form of +- // "content1,content2:gamemode". This is so that we can pass in +- // content requirements through UberNet. +- var contentSeparator = gameMode.indexOf(':'); +- if (contentSeparator >= 0) +- { +- var content = gameMode.substr(0, contentSeparator).split(","); +- if (!_.isEmpty(content)) +- content_manager.setRequiredContent(content); +- gameMode = gameMode.substr(contentSeparator + 1); +- } +- exports.gameMode = gameMode; +-} +- +-exports.setState = setState; +-exports.updateStateData = updateStateData; +-exports.states = states; +-exports.loadState = loadState; +-exports.shutdownWhenEmpty = shutdownWhenEmpty; +-exports.setStateUrl = function(state, url) { +- state.hello_response.url = url; +-}; +- +-var serverNameIndex = env.indexOf('--server-name'); +-if (serverNameIndex >= 0) +- exports.serverName = env[serverNameIndex + 1]; +- +-var stateIndex = env.indexOf('--state'); +-if (stateIndex >= 0) { +- var initialStateName = env[stateIndex + 1]; +- var initialState = loadState(initialStateName); +- if (initialState) +- curState = initialState; +- else +- console.error("Unable to load state", initialStateName); +-} +- +-var initialStateEnterData = undefined; +-var loadReplayIndex = env.indexOf('--load-replay'); +-var loadTimeIndex = env.indexOf('--load-time'); /* todo: specify load time with message instead of parameter */ +-if (loadReplayIndex >= 0) { +- +- var path = env[loadReplayIndex + 1]; +- var split_index = path.lastIndexOf('#t='); +- var replayFileName = (split_index === -1) ? path : path.substr(0, split_index); +- var loadTime = loadTimeIndex >= 0 ? env[loadTimeIndex + 1] : -1; +- if (split_index !== -1) +- loadTime = Number(path.substr(split_index+3)); +- if (_.isNaN(loadTime)) +- loadTime = -1; +- +- loadTime = -1; /* server crashes if the load time is not -1. */ +- +- var replaySentinelFileName = ''; +- var replaySentinelFileNameIndex = env.indexOf('--load-replay-sentinel'); +- if (replaySentinelFileNameIndex >= 0) +- replaySentinelFileName = env[replaySentinelFileNameIndex + 1]; +- +- initialStateEnterData = { +- 'name': replayFileName, +- 'sentinel_name': replaySentinelFileName, +- 'time': loadTime, +- 'view_replay': exports.gameMode !== 'loadsave' +- }; +- +- /* this is a little hacky */ +- curState = states.load_save; +-} +- +-// Make sure our current state gets initialized. +-var enter_result = curState.enter(initialStateEnterData); +-if (enter_result) +- updateStateData(enter_result); +- ++var env = require('env'); ++var server_utils = require('server_utils'); ++var sim_utils = require('sim_utils'); ++var utils = require('utils'); ++var content_manager = require('content_manager'); ++var _ = require('thirdparty/lodash'); ++ ++// Timeout values, in seconds. ++var EMPTY_TIMEOUT = 120; ++ ++exports.MAX_PLAYERS = 10; ++exports.MAX_SPECTATORS = 3; ++ ++function shutdownWhenEmpty() { ++ var emptyTimeout; ++ utils.pushCallback(server, 'onConnect', function(onConnect, client, reconnect) { ++ if (!client.rejected && emptyTimeout) { ++ clearTimeout(emptyTimeout); ++ emptyTimeout = undefined; ++ } ++ return onConnect; ++ }); ++ setInterval(function() { ++ var before = server.connected; ++ server_utils.refreshConnectionCount(); ++ if (server.connected != before) { ++ console.error('Whoa! Connection count got out of sync! Something went wrong somewhere. (before=', before, ', after=', server.connected, ')'); ++ } ++ if (!emptyTimeout && server.connected === 0) { ++ emptyTimeout = setTimeout(function() { ++ sim.shutdown(true); ++ server.exit(); ++ }, EMPTY_TIMEOUT * 1000); ++ } ++ }, 1000); ++} ++ ++function setState(newState) { ++ console.log("Changing state from", curState.name, "to", newState.name); ++ ++ if (newState === curState) ++ return; ++ ++ if (curState.exit && !curState.exit(newState)) ++ { ++ console.log("Cancelling state change"); ++ ++ return; ++ } ++ ++ curState = newState; ++ server.setGameState(curState.name); ++ ++ // Calling both of these APIs is primarily a matter of getting test coverage ++ // One or the other can be removed as deemed necessary ++ server.incrementTitleStatistic("State_" + curState.name, 1); ++ server.recordGameEvent("State_" + curState.name); ++ ++ var result; ++ if (newState.enter) ++ result = newState.enter.apply(this, Array.prototype.slice.call(arguments).slice(1)); ++ if (curState !== newState) ++ return; // This means entering the state caused another state change. ++ if (result !== undefined) ++ curState.hello_response.data = result; ++ else ++ delete curState.hello_response.data; ++ var stateMessage = { ++ message_type: 'server_state', ++ payload: curState.hello_response ++ }; ++ ++ if (!curState.getClientState) { ++ server.broadcast(stateMessage); ++ return; ++ } ++ ++ if (!stateMessage.payload.data) ++ stateMessage.payload.data = {}; ++ var numClients = server.clients.length; ++ for (var c = 0; c < numClients; ++c) { ++ var client = server.clients[c]; ++ if (client.connected) { ++ stateMessage.payload.data.client = curState.getClientState(client); ++ client.message(stateMessage); ++ } ++ } ++ delete stateMessage.payload.data.client; ++} ++ ++function updateStateData(data) ++{ ++ curState.hello_response.data = data; ++} ++ ++var states = {}; ++var gameModes = {}; ++ ++// Note: Game Modes must be exported before loading the states, since states ++// register their game mode as part of loading. ++exports.gameModes = gameModes; ++exports.gameMode = ''; ++ ++function addState(state, name) { ++ if (states.hasOwnProperty(name)) ++ return state[name]; ++ state.name = name; ++ state.hello_response = { ++ state: name, ++ url: state.url ++ }; ++ states[name] = state; ++ return state; ++} ++ ++var cmdlineAllowCheats = (env.indexOf('--allow-cheats') >= 0); ++exports.cheats = { ++ cheat_flags: { ++ allow_change_vision: cmdlineAllowCheats, ++ allow_change_control: cmdlineAllowCheats, ++ allow_create_unit: cmdlineAllowCheats, ++ allow_mod_data_updates: cmdlineAllowCheats, ++ ++ any_enabled: cmdlineAllowCheats ++ } ++} ++ ++function loadState(name) { ++ if (states.hasOwnProperty(name)) ++ return states[name]; ++ var state = require('states/' + name); ++ if (state) ++ addState(state, name); ++ return state; ++} ++ ++_.forEach([ ++ 'empty', ++ 'config', ++ 'lobby', ++ 'landing', ++ 'playing', ++ 'game_over', ++ 'gw_lobby', ++ 'load_replay', ++ 'load_save', ++ 'replay', ++ 'ladder_lobby' ++ ], ++ loadState ++); ++ ++var curState = states.empty; ++ ++server.handlers.hello = function(msg) { ++ var helloMessage = curState.hello_response; ++ var perClient = !!curState.getClientState; ++ if (perClient) { ++ helloMessage.data.client = curState.getClientState(msg.client); ++ } ++ server.respond(msg).succeed(helloMessage); ++ if (perClient) ++ delete helloMessage.data.client; ++}; ++ ++server_utils.debug_messages = env.indexOf('--squelch-messages') < 0; ++server_utils.log_lobby_description = env.indexOf('--squelch-messages') < 0; ++exports.keep_alive = env.indexOf('--keep-alive') >= 0; ++var timeLimit = env.indexOf('--time-limit'); ++if (timeLimit >= 0) { ++ exports.time_limit = Number(env[timeLimit + 1]); ++ if (isNaN(exports.time_limit) || (exports.time_limit <= 0)) ++ delete exports.time_limit; ++ else ++ console.log("Time limit set to", exports.time_limit, "seconds."); ++} ++exports.no_players = env.indexOf('--no-players') >= 0; ++var spectators = env.indexOf('--spectators'); ++if (spectators >= 0) ++ exports.spectators = Number(env[spectators + 1]); ++else ++ exports.spectators = 0; ++ ++var gameModeIndex = env.indexOf('--game-mode'); ++if (gameModeIndex >= 0) ++{ ++ var gameMode = env[gameModeIndex + 1]; ++ ++ // Game mode can contain a content specification, in the form of ++ // "content1,content2:gamemode". This is so that we can pass in ++ // content requirements through UberNet. ++ var contentSeparator = gameMode.indexOf(':'); ++ if (contentSeparator >= 0) ++ { ++ var content = gameMode.substr(0, contentSeparator).split(","); ++ if (!_.isEmpty(content)) ++ content_manager.setRequiredContent(content); ++ gameMode = gameMode.substr(contentSeparator + 1); ++ } ++ exports.gameMode = gameMode; ++} ++ ++exports.setState = setState; ++exports.updateStateData = updateStateData; ++exports.states = states; ++exports.loadState = loadState; ++exports.shutdownWhenEmpty = shutdownWhenEmpty; ++exports.setStateUrl = function(state, url) { ++ state.hello_response.url = url; ++}; ++ ++var serverNameIndex = env.indexOf('--server-name'); ++if (serverNameIndex >= 0) ++ exports.serverName = env[serverNameIndex + 1]; ++ ++var stateIndex = env.indexOf('--state'); ++if (stateIndex >= 0) { ++ var initialStateName = env[stateIndex + 1]; ++ var initialState = loadState(initialStateName); ++ if (initialState) ++ curState = initialState; ++ else ++ console.error("Unable to load state", initialStateName); ++} ++ ++var initialStateEnterData = undefined; ++var loadReplayIndex = env.indexOf('--load-replay'); ++var loadTimeIndex = env.indexOf('--load-time'); /* todo: specify load time with message instead of parameter */ ++if (loadReplayIndex >= 0) { ++ ++ var path = env[loadReplayIndex + 1]; ++ var split_index = path.lastIndexOf('#t='); ++ var replayFileName = (split_index === -1) ? path : path.substr(0, split_index); ++ var loadTime = loadTimeIndex >= 0 ? env[loadTimeIndex + 1] : -1; ++ if (split_index !== -1) ++ loadTime = Number(path.substr(split_index+3)); ++ if (_.isNaN(loadTime)) ++ loadTime = -1; ++ ++ loadTime = -1; /* server crashes if the load time is not -1. */ ++ ++ var replaySentinelFileName = ''; ++ var replaySentinelFileNameIndex = env.indexOf('--load-replay-sentinel'); ++ if (replaySentinelFileNameIndex >= 0) ++ replaySentinelFileName = env[replaySentinelFileNameIndex + 1]; ++ ++ initialStateEnterData = { ++ 'name': replayFileName, ++ 'sentinel_name': replaySentinelFileName, ++ 'time': loadTime, ++ 'view_replay': exports.gameMode !== 'loadsave' ++ }; ++ ++ /* this is a little hacky */ ++ curState = states.load_save; ++} ++ ++// Make sure our current state gets initialized. ++var enter_result = curState.enter(initialStateEnterData); ++if (enter_result) ++ updateStateData(enter_result); ++ +diff -Naur server-script.ori/sim_utils.js.bkp server-script/sim_utils.js.bkp +--- server-script.ori/sim_utils.js.bkp 1970-01-01 01:00:00.000000000 +0100 ++++ server-script/sim_utils.js.bkp 2016-02-06 19:44:44.000000000 +0100 +@@ -0,0 +1,105 @@ ++var Q = require('thirdparty/q'); ++var _ = require('thirdparty/lodash'); ++ ++exports.MAX_PLANETS = 16; ++ ++function validatePlanet(planet) ++{ ++ if (!planet.generator) ++ return "No generator"; ++ ++ //normalize generator values ++ function normalizeNumber(val, min, max, dflt) { ++ if (typeof val == 'number') ++ return Math.min(Math.max(val, min), max); ++ return dflt; ++ } ++ ++ planet.generator.seed = normalizeNumber(planet.generator.seed, 0, Number.MAX_VALUE, 12345); ++ planet.generator.heightRange = normalizeNumber(planet.generator.heightRange, 0, 100, 50); ++ planet.generator.biomeScale = normalizeNumber(planet.generator.biomeScale, 0, 100, 50); ++ planet.generator.waterHeight = normalizeNumber(planet.generator.waterHeight, 0, 100, 50); ++ planet.generator.waterDepth = normalizeNumber(planet.generator.waterDepth, 0, 100, 0); ++ planet.generator.temperature = normalizeNumber(planet.generator.temperature, 0, 100, 50); ++ planet.generator.metalDensity = normalizeNumber(planet.generator.metalDensity, 0, 100, 50); ++ planet.generator.metalClusters = normalizeNumber(planet.generator.metalClusters, 0, 100, 50); ++ if (planet.generator.biome === "moon") ++ planet.generator.waterHeight = -1; ++ ++ var d = Q.defer(); ++ ++ file.load('/pa/terrain/' + planet.generator.biome + '.json', function(data) { ++ var data = JSON.parse(data); ++ if (typeof data.radius_range === "undefined") ++ data.radius_range = [100, 1300]; // default value ++ ++ planet.generator.radius = normalizeNumber(planet.generator.radius, data.radius_range[0], data.radius_range[1], ++ (data.radius_range[0] + data.radius_range[1]) / 2.0); ++ ++ d.resolve(true); ++ }); ++ ++ return d; ++} ++ ++exports.validateSystemConfig = function(systemConfig) { ++ var planets = systemConfig.planets; ++ if (!planets || !planets.length) ++ return "No planets."; ++ ++ if (planets.length > 16) ++ return "Too many planets. (Current limit = " + exports.MAX_PLANETS + ")"; ++ ++ var hasStartingPlanet = false; ++ for (var p = 0; p < planets.length; ++p) ++ { ++ if (planets[p].starting_planet) ++ { ++ hasStartingPlanet = true; ++ break; ++ } ++ } ++ if (!hasStartingPlanet) ++ return "No starting planets."; ++ ++ var validationPromises = []; ++ ++ for (var p = 0; p < planets.length; ++p) ++ { ++ var retVal = validatePlanet(planets[p]); ++ if (_.isString(retVal)) ++ return "Planet " + p + " invalid: " + retVal; ++ else ++ validationPromises.push(retVal); ++ } ++ ++ return Q.all(validationPromises).then( function() { ++ var starting_planets = 0; ++ for (var p = 0; p < planets.length; ++p) { ++ if (!!planets[p].starting_planet) ++ starting_planets++; ++ } ++ ++ if (starting_planets < 1) ++ planets[0].starting_planet = true; ++ ++ systemConfig.planets = planets; ++ }); ++}; ++ ++exports.waitUntil = function(time) { ++ var result = Q.defer(); ++ var maybeResolve = function() { ++ var now = sim.time; ++ if (now >= time) ++ result.resolve(); ++ else ++ setTimeout(maybeResolve, (time - now) * 1000); ++ }; ++ maybeResolve(); ++ return result.promise; ++}; ++ ++exports.waitForSeconds = function(duration) { ++ return exports.waitUntil(sim.time + duration); ++}; +diff -Naur server-script.ori/states/droplet_test.js server-script/states/droplet_test.js +--- server-script.ori/states/droplet_test.js 2016-02-07 14:27:03.000000000 +0100 ++++ server-script/states/droplet_test.js 1970-01-01 01:00:00.000000000 +0100 +@@ -1,37 +0,0 @@ +-var main = require('main'); +-var utils = require('utils'); +-var _ = require('thirdparty/lodash'); +- +-function fixupConfig(gameConfig) { +- gameConfig.armies = gameConfig.armies || []; +-} +- +-function loadConfig(gameConfig) { +- fixupConfig(gameConfig); +- +- var systemConfig = gameConfig.system; +- sim.systemName = systemConfig.name; +- sim.planets = systemConfig.planets; +- sim.paused = true; +- sim.create(); +-} +- +-exports.url = 'coui://ui/main/game/live_game/live_game.html'; +-exports.enter = function() { +- server.maxClients = 0; +- server.beacon = undefined; +- +- var droptestConfig = require('droplet_test_config').config; +- loadConfig(droptestConfig); +- +- var playingState = main.loadState('droplet_test_playing'); +- if (sim.ready) +- main.setState(playingState, droptestConfig); +- else { +- utils.pushCallback(sim, 'onReady', function (onReady) { +- sim.onReady.pop(); +- main.setState(playingState, droptestConfig); +- return onReady; +- }); +- } +-}; +diff -Naur server-script.ori/states/empty.js server-script/states/empty.js +--- server-script.ori/states/empty.js 2016-02-07 14:34:26.000000000 +0100 ++++ server-script/states/empty.js 2016-02-07 12:45:49.641872614 +0100 +@@ -1,84 +1,87 @@ +-var main = require('main'); +-var utils = require('utils'); +-var content_manager = require('content_manager'); +-var _ = require('thirdparty/lodash'); +- +-var cleanup = []; +- +-var EMPTY_TIMEOUT = 5 * 60; +- +-exports.url = ''; +-exports.enter = function() { +- +- if (main.no_players) { +- main.setState(main.states.config); +- return; +- } +- +- var modNames = []; +- var mods = server.getMods(); +- if (mods !== undefined && mods.mounted_mods !== undefined) { +- _.forEach(mods.mounted_mods, function (element) { +- modNames.push(element.display_name); +- }); +- } +- +- server.maxClients = 1; +- if (main.serverName) +- server.beacon = { +- uuid: server.uuid(), +- full: false, +- started: false, +- players: 0, +- creator: null, +- max_players: 1, +- spectators: 0, +- max_spectators: 0, +- mode: 'Waiting', +- mod_names: modNames, +- cheat_config: main.cheats, +- player_names: [], +- spectator_names: [], +- require_password: false, +- whitelist: [], +- blacklist: [], +- tag: '', +- game_name: main.serverName, +- game: { +- name: main.serverName +- }, +- required_content: content_manager.getRequiredContent(), +- bounty_mode: false, +- bounty_value: 0.5, +- sandbox: false +- }; +- else +- server.beacon = undefined; +- +- utils.pushCallback(server, 'onConnect', function(onConnect, client, reconnect) { +- if (client.rejected) +- { +- console.log("Rejected connection from misconfigured client, and shutting down."); +- server.exit(); +- } +- else +- main.setState(main.gameModes[main.gameMode] || main.states.lobby, client); +- return onConnect; +- }); +- cleanup.push(function() { server.onConnect.pop(); }); +- +- if (!main.keep_alive) { +- var timeout = setTimeout(function() { server.exit(); }, EMPTY_TIMEOUT * 1000); +- cleanup.push(function() { clearTimeout(timeout); }); +- } +-}; +- +-exports.exit = function(newState) { +- _.forEachRight(cleanup, function(c) { c(); }); +- cleanup = []; +- +- if (server.clients.length && !main.keep_alive) +- main.shutdownWhenEmpty(); +- +- return true; +-}; ++var main = require('main'); ++var utils = require('utils'); ++var content_manager = require('content_manager'); ++var _ = require('thirdparty/lodash'); ++ ++var cleanup = []; ++ ++var MAX_PLAYERS = main.MAX_PLAYERS; ++var MAX_SPECTATORS = main.MAX_SPECTATORS; ++ ++var EMPTY_TIMEOUT = 5 * 60; ++ ++exports.url = ''; ++exports.enter = function() { ++ ++ if (main.no_players) { ++ main.setState(main.states.config); ++ return; ++ } ++ ++ var modNames = []; ++ var mods = server.getMods(); ++ if (mods !== undefined && mods.mounted_mods !== undefined) { ++ _.forEach(mods.mounted_mods, function (element) { ++ modNames.push(element.display_name); ++ }); ++ } ++ ++ server.maxClients = 1; ++ if (main.serverName) ++ server.beacon = { ++ uuid: server.uuid(), ++ full: false, ++ started: false, ++ players: 0, ++ creator: null, ++ max_players: MAX_PLAYERS, ++ spectators: 0, ++ max_spectators: MAX_SPECTATORS, ++ mode: 'Waiting', ++ mod_names: modNames, ++ cheat_config: main.cheats, ++ player_names: [], ++ spectator_names: [], ++ require_password: false, ++ whitelist: [], ++ blacklist: [], ++ tag: '', ++ game_name: main.serverName, ++ game: { ++ name: main.serverName ++ }, ++ required_content: content_manager.getRequiredContent(), ++ bounty_mode: false, ++ bounty_value: 0.5, ++ sandbox: false ++ }; ++ else ++ server.beacon = undefined; ++ ++ utils.pushCallback(server, 'onConnect', function(onConnect, client, reconnect) { ++ if (client.rejected) ++ { ++ console.log("Rejected connection from misconfigured client, and shutting down."); ++ server.exit(); ++ } ++ else ++ main.setState(main.gameModes[main.gameMode] || main.states.lobby, client); ++ return onConnect; ++ }); ++ cleanup.push(function() { server.onConnect.pop(); }); ++ ++ if (!main.keep_alive) { ++ var timeout = setTimeout(function() { server.exit(); }, EMPTY_TIMEOUT * 1000); ++ cleanup.push(function() { clearTimeout(timeout); }); ++ } ++}; ++ ++exports.exit = function(newState) { ++ _.forEachRight(cleanup, function(c) { c(); }); ++ cleanup = []; ++ ++ if (server.clients.length && !main.keep_alive) ++ main.shutdownWhenEmpty(); ++ ++ return true; ++}; +diff -Naur server-script.ori/states/lobby.js server-script/states/lobby.js +--- server-script.ori/states/lobby.js 2016-02-07 14:33:04.000000000 +0100 ++++ server-script/states/lobby.js 2016-02-07 16:18:05.520574028 +0100 +@@ -1,1885 +1,1913 @@ +-var main = require('main'); +-var sim_utils = require('sim_utils'); +-var server_utils = require('server_utils'); +-var content_manager = require('content_manager'); +-var utils = require('utils'); +-var bouncer = require('bouncer'); +-var env = require('env'); +-var _ = require('thirdparty/lodash'); +-var commander_manager = require('lobby/commander_manager'); +-var color_manager = require('lobby/color_manager'); +- +-var getAIName = (function () { +- +- var ai_names = _.shuffle(require('ai_names_table').data); /* shuffle returns a new collection */ +- +- return function () { +- var name = ai_names.shift(); +- ai_names.push(name); +- return name; +- } +-})(); +- +-var used_ai_ids = []; +-var last_ai_number = 0; +- +-var getAIId = function () { +- if (used_ai_ids.length) +- return used_ai_ids.pop(); +- else { +- last_ai_number++; +- return '' + last_ai_number; +- } +-} +- +-var returnAIId = function (id) { +- used_ai_ids.push(id); +-} +- +-var commanders = new commander_manager.CommanderManager(); +-var colors = new color_manager.ColorManager(); +- +-var START_GAME_DELAY = 5; // In s. +-var MAX_PLAYERS = 10; +-var MAX_SPECTATORS = 3; +-var MAX_CLIENTS = MAX_PLAYERS + MAX_SPECTATORS; +-var DEFAULT_LOBBY_TAG = ''; +-var DEFAULT_LOBBY_NAME = ''; +-var DEFAULT_GAME_TYPE = 'FreeForAll'; +-var VALID_GAME_TYPES = [DEFAULT_GAME_TYPE, 'TeamArmies', 'VersusAI']; +-var isValidGameType = function (game_type) { +- return VALID_GAME_TYPES.indexOf(game_type) != -1; +-}; +-var isFFAType = function (game_type) { +- return game_type === 'FreeForAll'; +-}; +- +-var DEFAULT_GAME_OPTIONS = { +- dynamic_alliances: false, +- dynamic_alliance_victory: false, +- bounty_mode: false, +- bounty_value: 0.5, +- sandbox: false, +- listen_to_spectators: false, +- game_type: DEFAULT_GAME_TYPE, +- land_anywhere: false, +-}; +- +- +-var alliance_groups = _.range(1, MAX_CLIENTS / 2 + 1); /* 0 indicates no alliance */ +- +-var debugging = false; +- +-function debug_log(object) { +- if (debugging) +- console.log(JSON.stringify(object,null,'\t')); +-} +- +-var client_state = { +- armies: [], +- players: [], +- colors: [], +- system: {}, +- settings: {}, +- control: {} +-}; +- +-/* the lobby stays up after we transition out of the state, so that it can handle login/rejoin attemps +- if a player leaves the lobby, we kill the client (but they can still rejoin); however, if a player +- leaves after the game has moved on to another state (usually due to a disconnect error), +- we don't want to kill the client, since the playing state will setup a disconnect timer. */ +-var hasStartedPlaying = false; +- +-function PlayerModel(client, options) { +- var self = this; +- +- self.client = client; /* data, debugDesc, connected, id, name */ +- try { +- self.client_data = JSON.parse(client.data); +- } catch (error) { +- debug_log("Unable to parse client data for player"); +- debug_log(error); +- self.client_data = null; +- } +- self.creator = !!options.creator; +- self.spectator = !!options.spectator; +- +- self.ai = !!options.ai; +- self.personality = options.personality || ''; +- +- /* for now, only AI have landing policy. values: ['no_restriction', 'on_player_planet', 'off_player_planet'] */ +- self.landingPolicy = (self.ai && options.landing_policy) ? options.landing_policy : 'no_restriction'; +- +- self.commander = commanders.getRandomDefaultCommanderSpec(); +- +- self.ready = self.ai ? true : false; +- self.loading = self.ai ? false : true; +- +- self.armyIndex = -1; +- self.slotIndex = -1; +- +- self.colorIndex = (self.ai || self.spectator) ? [-1, -1] : colors.takeRandomAvailableColor(); +- +- self.economyFactor = _.isFinite(options.economy_factor) ? options.economy_factor : 1.0; +- self.economyFactor = Math.min(Math.max(0.0, self.economyFactor), 5.0); +- +- if (!!options.mod) +- bouncer.addPlayerToModlist(client.id); +- else +- bouncer.removePlayerFromModlist(client.id); +- +- self.nextPrimaryColorIndex = function () { +- if (self.spectator) +- return; +- +- self.returnColorIndex(); +- self.colorIndex = [colors.takeNextAvailableColorIndex(self.colorIndex[0]), 0]; +- }; +- +- self.nextSecondaryColorIndex = function () { +- if (self.spectator) +- return; +- +- var colors = colors.getSecondaryColorsFor(self.colorIndex[0]); +- var max = colors.getNumberOfColors(); +- self.colorIndex[1] = (self.colorIndex[1] + 1) % max; +- } +- +- self.clearColorIndex = function () { +- self.returnColorIndex(); +- self.colorIndex = [-1, -1]; +- }; +- +- self.maybeTakeColorIndex = function () { +- if (self.spectator || self.colorIndex[0] !== -1) +- return; +- self.colorIndex = colors.takeRandomAvailableColor(); +- }; +- +- self.setPrimaryColorIndex = function (index) { +- if (self.spectator) +- return; +- self.colorIndex = [colors.maybeGetNewColorIndex(self.colorIndex[0], index), self.colorIndex[1]]; +- }; +- +- self.setSecondaryColorIndex = function (index) { +- if (self.spectator) +- return; +- +- self.colorIndex = [self.colorIndex[0], index]; +- }; +- +- self.returnColorIndex = function () { +- if (self.colorIndex[0] === -1) +- return; +- +- colors.returnColorIndex(self.colorIndex[0]); +- self.colorIndex = [-1, -1]; +- }; +- +- self.adjustArmyIndexAboveTarget = function (target_index, delta) { +- if (self.armyIndex > target_index) +- self.armyIndex += delta; +- }; +- +- self.adjustSlotIndexAboveTarget = function (target_index, delta) { +- if (self.slotIndex > target_index) +- self.slotIndex += delta; +- }; +- +- self.processRemoveIndex = function (target_index) { +- if (self.armyIndex === target_index) { +- self.armyIndex = -1; +- self.slotIndex = -1; +- } +- else +- self.adjustArmyIndexAboveTarget(target_index, -1); +- }; +- +- self.processRemoveSlotAtIndex = function (target_index, target_slot) { +- if (self.armyIndex === target_index) { +- +- if (self.slotIndex === target_slot) { +- self.armyIndex = -1; +- self.slotIndex = -1; +- } +- else +- self.adjustSlotIndexAboveTarget(target_slot, -1); +- } +- }; +- +- self.setAIPersonality = function (personality) { +- if (!self.ai) +- return; +- +- self.personality = personality; +- }; +- +- self.setAILandingPolicy = function (policy) { +- if (!self.ai) +- return; +- +- self.landingPolicy = policy; +- }; +- +- self.setEconomyFactor = function (value) { +- value = Math.min(Math.max(0.0, value), 5.0); +- self.economyFactor = value; +- }; +- +- self.asJson = function () { +- return { +- name: self.client.name, +- id: self.client.id, +- ai: self.ai, +- personality: self.personality, +- landing_policy: self.landingPolicy, +- economy_factor: self.economyFactor, +- connected: self.ai ? true : self.client.connected, +- creator: self.ai ? false : self.creator, +- mod: self.ai ? false : bouncer.isPlayerMod(self.client.id), +- army_index: self.armyIndex, +- slot_index: self.slotIndex, +- commander: self.commander, +- ready: self.ready, +- loading: self.loading, +- color: colors.getColorFor(self.colorIndex), +- color_index: self.colorIndex[0] +- }; +- }; +- +- self.finalize = function () { +- return { +- name: self.client.name, +- commander: self.commander, +- client: self.client, +- army: self.armyIndex, +- slot: self.slotIndex, +- ai: self.ai, +- personality: self.personality, +- landing_policy: self.landingPolicy +- }; +- }; +- +- self.setCommander = function (new_commander) { +- if (!new_commander || self.commander === new_commander) +- return; +- +- var commanderObject = commanders.getCommanderObjectName(new_commander); +- if (!commanderObject) { +- debug_log("Failed to locate item " + (new_commander)); +- return; +- } +- +- if (!self.client.validateItem(commanderObject)) { +- debug_log("Failed to validate ownership of " + (commanderObject)); +- return; +- } +- +- self.commander = new_commander; +- +- lobbyModel.updatePlayerState(); +- } +-}; +- +-function ArmyModel(options) { +- var self = this; +- +- self.slots = options.slots ? Math.max(options.slots, 1) : 1; +- self.alliance = !!options.alliance; +- self.allianceGroup = 0; /* 0 indicates no alliance */ +- +- self.asJson = function () { +- return { +- slots: self.slots, +- alliance: self.alliance +- }; +- }; +- +- self.finalizeAsConfig = function () { +- var s = []; +- s.length = self.slots; +- +- _.forEach(s, function (element, index) { +- s[index] = 'player'; +- }); +- +- return { +- slots: s, +- alliance_group: self.allianceGroup +- }; +- }; +-}; +- +-function LobbyModel(creator) { +- var self = this; +- +- self.maxNumberOfAllowedPlayers = 1; +- self.players = {}; +- self.armies = []; +- self.system = {}; +- self.minimalSystemDescription = {}; /* system sans custom planet source */ +- self.config = {}; +- self.settings = { +- hidden: true, +- game_options: _.cloneDeep(DEFAULT_GAME_OPTIONS), +- required_content: content_manager.getRequiredContent(), +- }; /* game_mode spectators broadcast_delay private friends public tag */ +- self.control = {}; /* has_first_config starting system_ready sim_ready */ +- +- self.creator = creator; +- +- self.dirty = {}; +- self.allDirty = { +- control: true, +- system: true, +- players: true, +- armies: true, +- color: true, +- settings: true, +- beacon: true +- }; +- // These dirty flags (on the left) will be set when the associated flags (on the right) are set +- self.chainDirty = { +- beacon: ['players', 'armies', 'system', 'settings'], +- color: ['players'] +- }; +- +- // Set a given flag to "dirty" (e.g. self.setDirty({control: true}); ) +- self.setDirty = function(flags) { +- var needsApply = _.isEmpty(self.dirty); +- _.assign(self.dirty, flags); +- +- while (!function() { +- return _.all(self.chainDirty, function(flags, key) { +- if (self.dirty[key]) +- return true; +- var chain = _.pick(self.dirty, flags); +- if (_.isEmpty(chain)) +- return true; +- self.dirty[key] = true; +- return false; +- }); +- }()); +- +- if (needsApply) +- _.delay(function() { self.cleanDirtyFlags(); }); +- }; +- +- // Custom functions for cleaning dirty flags. (broadcast to client for all other flags) +- self.customCleaners = { +- beacon: function () { self.updateBeacon(); } +- }; +- +- // Clean all the dirty flags +- self.cleanDirtyFlags = function () { +- try { +- _.forIn(self.dirty, function(yes, key) { +- if (self.customCleaners.hasOwnProperty(key)) +- self.customCleaners[key](); +- else { +- server.broadcast({ +- message_type: key, +- payload: client_state[key] +- }); +- } +- }); +- } +- catch (e) { +- // Note: Very important that the server "mostly" works if this happens. +- console.error('Lobby unable to clean dirty flags:', e.toString()); +- } +- self.dirty = {}; +- }; +- +- self.isCreator = function (id) { +- return id === self.creator; +- }; +- +- self.playersInArmy = function(army_index) { +- return _.filter(_.values(self.players), function (element){ +- return element.armyIndex === army_index; +- }); +- }; +- +- self.aiCount = function () { +- return _.filter(_.values(self.players), function (element) { return element.ai }).length; +- } +- +- self.totalSlots = function () { +- +- var result = 0; +- _.forEach(self.armies,function (element) { +- result += element.slots; +- }); +- +- return result; +- }; +- +- self.totalCurrentPlayers = function () { +- var total = 0; +- _.forEach(self.armies, function (element, index) { +- total += self.playersInArmy(index).length; +- }); +- return total; +- }; +- +- self.breakArmyIntoAlliances = function (army_index) { +- var army = self.armies[army_index]; +- if (!army) +- return; +- +- var extra = army.slots - 1; +- army.slots = 1; +- var options = army.asJson(); +- +- var group = alliance_groups.splice(0, 1)[0]; +- +- _.invoke(self.players, 'adjustArmyIndexAboveTarget', army_index, extra); +- +- _.times(extra, function (index) { /* split armies */ +- self.armies.splice(army_index, 0, new ArmyModel(options)); /* splice modifies the array */ +- }); +- +- _.forEach(self.playersInArmy(army_index), function (element) { /* add players to new armies */ +- element.armyIndex += element.slotIndex; +- element.slotIndex = 0; +- +- self.armies[element.armyIndex].allianceGroup = group; +- }); +- } +- +- self.finalizeConfig = function () { +- return { +- "armies": _.invoke(self.armies, 'finalizeAsConfig'), +- "system": self.system, +- "enable_lan": true, +- "spectators": 0, +- "password": "", +- "friends": [], +- "blocked": [], +- "public": true, +- "players": self.totalCurrentPlayers(), +- "vs_ai": false, +- "game_options": self.settings.game_options +- }; +- } +- +- self.finalizePlayers = function () { +- var result = {}; +- +- _.forIn(self.players, function (value, key) { +- result[key] = value.finalize() +- }); +- +- return result; +- }; +- +- self.finalizeArmies = function () { +- var result = _.invoke(self.armies, 'finalizeAsConfig'); +- +- _.forIn(self.players, function (element) { +- var army = result[element.armyIndex]; +- if (!army) /* player is spectating */ +- return; +- +- army.slots[element.slotIndex] = element.finalize(); /* insert finalized block { name, commander, client, army, ai } */ +- +- if (element.slotIndex === 0) { /* only use the color for the first player in the army */ +- result[element.armyIndex].color = colors.getColorFor(element.colorIndex); /* insert expanded color */ +- result[element.armyIndex].color_index = element.colorIndex[0]; +- result[element.armyIndex].econ_rate = element.economyFactor; +- } +- +- if (element.ai) { +- result[element.armyIndex].personality = element.personality; +- result[element.armyIndex].landing_policy = element.landingPolicy; +- } +- }); +- +- return result; +- } +- +- self.getFinalData = function () { +- +- while (true) +- { +- var target = _.findIndex(self.armies, function (element) { +- return element.alliance && element.slots > 1; +- }); +- +- if (target === -1) +- break; +- +- self.breakArmyIntoAlliances(target); +- } +- +- _.invoke(self.players, 'maybeTakeColorIndex'); +- +- return { +- game: lobbyModel.finalizeConfig(), +- armies: lobbyModel.finalizeArmies(), +- players: lobbyModel.finalizePlayers(), +- ranked: false +- } +- }; +- +- self.updatePlayerState = function () { +- debug_log('updatePlayerState'); +- var players = _.invoke(self.players, 'asJson'); +- if (_.isEqual(client_state.players, players)) +- return; +- client_state.players = _.cloneDeep(players); +- self.setDirty({players: true}); +- }; +- +- self.updateArmyState = function () { +- debug_log('updateArmyState'); +- var armies = _.invoke(self.armies, 'asJson'); +- if (_.isEqual(client_state.armies, armies)) +- return; +- client_state.armies = _.cloneDeep(armies); +- self.setDirty({ armies: true }); +- }; +- +- self.updateSystemState = function () { +- debug_log('updateSystemState'); +- if (_.isEqual(client_state.system, self.minimalSystemDescription)) +- return; +- client_state.system = _.cloneDeep(self.minimalSystemDescription); +- self.setDirty({system: true}); +- }; +- +- self.changeSystem = function (system) { +- if (_.isEqual(system, self.system)) +- return; +- +- self.changeControl({ system_ready: false, sim_ready: false }); +- self.system = system; +- +- self.minimalSystemDescription = utils.getMinimalSystemDescription(self.system); +- +- self.updateSystemState(); +- +- /* this will take some time. the server will be unresponsive. */ +- sim.shutdown(false); +- sim.systemName = lobbyModel.system.name; +- sim.planets = self.system.planets; +- }; +- +- self.updateColorState = function () { +- debug_log('updateColorState'); +- if (_.isEqual(client_state.colors, colors.colors)) +- return; +- client_state.colors = _.cloneDeep(colors.colors); +- self.setDirty({colors: true}); +- }; +- +- self.updateControlState = function () { +- debug_log('updateControlState'); +- if (_.isEqual(client_state.control, self.control)) +- return; +- client_state.control = _.cloneDeep(self.control); +- self.setDirty({control: true}); +- }; +- +- self.changeControl = function (control /* has_first_config starting system_ready sim_ready */) { +- _.assign(self.control, control); +- self.updateControlState(); +- }; +- +- self.updateSettingsState = function () { +- debug_log('updateSettingsState'); +- if (_.isEqual(client_state.settings, self.settings)) +- return; +- client_state.settings = _.cloneDeep(self.settings); +- +- main.spectators = Number(self.settings.spectators); +- +- self.setDirty({ settings: true }); +- }; +- +- self.changeSettings = function (settings /* game_mode spectators hidden friends public tag */) { +- _.assign(self.settings, settings); +- self.updateSettingsState(); +- }; +- +- self.updateClientState = function () { +- debug_log('updateClientState'); +- self.updatePlayerState(); +- self.updateArmyState(); +- self.updateSystemState(); +- self.updateColorState(); +- self.updateControlState(); +- self.updateSettingsState(); +- }; +- +- self.unreadyAllPlayers = function () { +- debug_log('unreadyAllPlayers'); +- _.forIn(self.players, function (element) { +- if (element.ready && !element.ai) { +- server.broadcastEventMessage(element.client.name, ' is no longer ready.'); +- element.ready = false; +- } +- }); +- +- self.setDirty({ players: true }); +- self.updatePlayerState(); +- }; +- +- self.addPlayersToSlotsIfPossible = function () { +- debug_log('addPlayersToSlotsIfPossible'); +- +- var army_index = 0; +- _.forIn(self.players, function (element, key) { +- +- if (element.armyIndex !== -1 || element.spectator) +- return; +- +- while (army_index < self.armies.length) +- { +- if (self.addPlayerToArmy(key, army_index)) +- break; +- army_index += 1; +- } +- }); +- } +- +- self.addArmy = function (options) { +- if (self.armies.length >= MAX_PLAYERS) +- return; +- +- if (options.slots && options.slots + self.totalSlots() > MAX_PLAYERS) +- return; +- +- self.unreadyAllPlayers(); +- +- self.armies.push(new ArmyModel(options)); +- self.addPlayersToSlotsIfPossible(); +- self.updateArmyState(); +- }; +- +- self.removeArmy = function (army_index) { +- var spares = []; +- +- self.unreadyAllPlayers(); +- +- self.armies.splice(army_index, 1); +- +- _.forEach(self.players, function (element) { +- if (element.ai && element.armyIndex === army_index) +- spares.push(element.client.id) +- }); +- +- _.forEach(spares, self.removePlayer); +- +- _.invoke(self.players, 'processRemoveIndex', army_index); +- +- self.addPlayersToSlotsIfPossible(); +- self.updateArmyState(); +- self.updatePlayerState(); +- }; +- +- self.modifyArmy = function (army_index, options) { +- debug_log('modifyArmy'); +- +- var spares = []; +- +- var army = self.armies[army_index]; +- if (!army) +- return; +- +- var new_options = _.assign(army.asJson(), options); +- +- var ai = !!_.filter(self.playersInArmy(army_index), function (element) { return element.ai }).length; +- if (ai) +- new_options.alliance = true; /* override to prevent shared army with ai */ +- +- if (options.slots && (options.slots - army.slots + self.totalSlots()) > MAX_PLAYERS) +- return; +- +- self.unreadyAllPlayers(); +- +- if (options.slots < army.slots) { +- _.forEach(_.range(options.slots, army.slots), function (index) { +- _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, index); +- +- _.forEach(self.players, function (element) { +- if (element.ai && element.armyIndex === army_index && element.slotIndex == index) +- spares.push(element.client.id) +- }); +- }); +- } +- +- _.forEach(spares, self.removePlayer); +- +- self.armies[army_index] = new ArmyModel(new_options); +- +- self.addPlayersToSlotsIfPossible(); +- +- self.fixColors(); +- +- self.updateArmyState(); +- self.updateColorState(); +- self.updatePlayerState(); +- }; +- +- self.numPlayerSlots = function() { +- return utils.sum(self.armies, function(army) { return army.ai ? 0 : army.slots; }); +- }; +- +- self.numPlayers = function () { +- return _.keys(self.players).length; +- }; +- +- self.updateBeacon = function () { +- debug_log('updateBeacon'); +- var numPlayerSlots = self.numPlayerSlots(); +- server.maxClients = Math.min(MAX_PLAYERS, numPlayerSlots) + Math.min(MAX_SPECTATORS, main.spectators); +- var publish = !server.closed && (self.settings.public || bouncer.getWhitelist().length) && !self.settings.hidden; +- +- if (publish) { +- var full = server.clients.length >= server.maxClients; +- +- var modNames = []; +- var mods = server.getMods(); +- if (mods !== undefined && mods.mounted_mods !== undefined) { +- _.forEach(mods.mounted_mods, function (element) { +- modNames.push(element.display_name); +- }); +- } +- +- var player_names = _.map(_.filter(self.players, { 'spectator': false }), function (player) { return player.client.name; }); +- var spectator_names = _.map(_.filter(self.players, { 'spectator': true }), function (player) { return player.client.name; }); +- +- var mode = DEFAULT_GAME_TYPE; +- if (self.settings.game_options) +- mode = self.settings.game_options.game_type; +- +- server.beacon = { +- uuid: server.uuid(), +- full: full, +- started: self.control.countdown || self.control.starting, +- players: player_names.length, +- creator: self.creatorName(), +- max_players: numPlayerSlots, +- spectators: spectator_names.length, +- max_spectators: main.spectators, +- mode: mode, +- mod_names: modNames, +- cheat_config: main.cheats, +- player_names: player_names, +- spectator_names: spectator_names, +- require_password: !!bouncer.doesGameRequirePassword(), +- whitelist: bouncer.getWhitelist(), +- blacklist: bouncer.getBlacklist(), +- tag: self.settings.tag, +- game: { +- system: self.minimalSystemDescription, +- name: self.settings.game_name +- }, +- required_content: content_manager.getRequiredContent(), +- bounty_mode: self.settings.game_options ? !!self.settings.game_options.bounty_mode : false, +- bounty_value: self.settings.game_options ? self.settings.game_options.bounty_value : 0.5, +- sandbox: self.settings.game_options ? !!self.settings.game_options.sandbox : false +- }; +- } else { +- server.beacon = null; +- } +- }; +- +- self.addPlayer = function (client, options) { +- debug_log('addPlayer'); +- var player = new PlayerModel(client, options); +- self.players[client.id] = player; +- self.updatePlayerState(); +- self.updateColorState(); +- +- if (!options.ai) { +- _.delay(server.broadcastEventMessage, 500, player.client.name, ' joined the lobby.'); +- +- debug_log('Player Stats: '); +- debug_log(player.client.statistics); +- client.incrementStatistic("TestStat_LobbiesJoined", 1); +- } +- }; +- +- self.addAI = function (payload) { +- var player_id = getAIId(); +- var success = false; +- +- if (!payload) +- return; +- +- self.addPlayer({ connected: true, id: player_id, name: getAIName() }, payload.options); +- +- success = self.addPlayerToArmy(player_id, payload.army_index); +- if (!success) +- self.removePlayer(player_id); +- }; +- +- self.creatorName = function () { +- var player = self.players[self.creator]; +- return player ? player.client.name : ''; +- }; +- +- self.chooseNextPlayerAsCreator = function () { +- debug_log('chooseNextPlayerAsCreator'); +- if (_.isEmpty(self.players)) +- return; +- +- var client = _.first(server.clients); +- if (!client) +- return; +- +- var id = client.id; +- self.creator = id; +- +- var player = self.players[id]; +- +- player.creator = true; +- bouncer.addPlayerToModlist(id); +- +- self.updatePlayerState(); +- server.broadcastEventMessage(player.client.name, ' is now the host.'); +- }; +- +- self.removePlayer = function (id) { +- debug_log('removePlayer'); +- +- delete client_state.players[id]; +- var player = self.players[id]; +- +- if (player) { +- self.removePlayerFromArmy(id, { clear_color: true }); +- +- var ai = player.ai; +- if (!ai) +- server.broadcastEventMessage(player.client.name, ' has left the lobby.'); +- +- player.returnColorIndex(); +- delete self.players[id]; +- +- if (!ai) { +- console.log('killing client ' + id); +- player.client.kill(); +- +- // Terminate empty lobbies +- if (!main.keep_alive && !server.connected) { +- sim.shutdown(false); +- server.exit(); +- return; +- } +- +- if (id === self.creator) +- self.chooseNextPlayerAsCreator(); +- } +- +- if (ai) +- returnAIId(id); +- +- self.updatePlayerState(); +- self.updateColorState(); +- } +- }; +- +- self.kickPlayer = function (id) { +- debug_log('kickPlayer'); +- bouncer.addPlayerToBlacklist(id); +- self.removePlayer(id); +- }; +- +- self.addPlayerToArmy = function (player_id, army_index) { +- debug_log('addPlayerToArmy'); +- var player = self.players[player_id]; +- var army = self.armies[army_index]; +- +- var array = self.playersInArmy(army_index); +- var count = array.length; +- +- if (!player || !army || count >= army.slots) +- return false; +- +- if (player.armyIndex === army_index) +- return false; +- +- player.armyIndex = army_index; +- player.slotIndex = count; +- player.spectator = false; +- +- if (player.ai) +- army.alliance = true; /* override to prevent shared army with ai */ +- +- self.fixColors(); +- +- self.updatePlayerState(); +- self.updateArmyState(); +- self.updateColorState(); +- return true; +- }; +- +- self.removePlayerFromArmy = function (player_id, options) { +- debug_log('removePlayerFromArmy'); +- var player = self.players[player_id]; +- +- if (!player) +- return false; +- +- if (options && options.clear_color) +- player.clearColorIndex(); +- +- var army_index = player.armyIndex; +- var slot_index = player.slotIndex; +- +- player.armyIndex = -1; +- player.slotIndex = -1; +- +- if (options && options.set_spectator) +- player.spectator = true; +- +- /* move players down to fill empty slot */ +- _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, slot_index); +- +- self.fixColors(); +- +- self.updatePlayerState(); +- self.updateArmyState(); +- self.updateColorState(); +- +- return true; +- } +- +- /* this checks every army, which is excessive; however, it is very reliable. */ +- self.fixColors = function () { +- _.forEach(self.armies, function (element, index) { +- self.fixColorsForArmy(index); +- }); +- } +- +- /* it would be preferrable to just call this function for each modified army */ +- self.fixColorsForArmy = function (army_index) { +- +- var army = self.armies[army_index]; +- +- if (!army) +- return; +- +- _.forEach(self.playersInArmy(army_index), function (element, index) { +- if (index === 0 || army.alliance) +- element.maybeTakeColorIndex(); +- else +- element.returnColorIndex(); /* only the first player in a shared army gets a color */ +- }); +- +- self.setDirty({ colors: true }); +- }; +- +- self.setPrimaryColorIndex = function (player_id, index, ai) { +- debug_log('{{setPrimaryColorIndex}} ' + index); +- var player = self.players[player_id]; +- +- if (!player || player.ai !== ai || !colors.isValidPrimaryColorIndex(index)) +- return; +- +- player.setPrimaryColorIndex(index); +- +- self.updatePlayerState(); +- self.updateColorState(); +- }; +- +- self.setSecondaryColorIndex = function (player_id, index, ai) { +- debug_log('{{setSecondaryColorIndex}} ' + index); +- var player = self.players[player_id]; +- +- if (!player || player.ai !== ai || !colors.isValidColorPair(player.colorIndex[0], index)) +- return; +- +- player.setSecondaryColorIndex(index); +- +- self.updatePlayerState(); +- self.updateColorState(); +- }; +- +- self.setAIPersonality = function (player_id, personality) { +- debug_log('setAIPersonality'); +- var player = self.players[player_id]; +- +- if (!player || !player.ai) +- return; +- +- player.setAIPersonality(personality); +- +- self.unreadyAllPlayers(); +- self.updatePlayerState(); +- }; +- +- self.setAILandingPolicy = function (player_id, policy) { +- debug_log('setAILandingPolicy'); +- var player = self.players[player_id]; +- +- if (!player || !player.ai) +- return; +- +- player.setAILandingPolicy(policy); +- +- self.unreadyAllPlayers(); +- self.updatePlayerState(); +- }; +- +- self.setEconomyFactor = function (player_id, value) { +- debug_log('setEconomyFactor'); +- var player = self.players[player_id]; +- +- if (!player) +- return; +- +- if (player.economyFactor != value) +- { +- player.setEconomyFactor(value); +- +- self.unreadyAllPlayers(); +- self.updatePlayerState(); +- } +- }; +- +- self.validateSetup = function () { +- debug_log('validateSetup'); +- var totalOpenSlots = self.numPlayerSlots(); +- var totalPlayersInSlots = utils.sum(self.players, function (player) { return player.armyIndex >= 0; }); +- +- debug_log('Validation:'+ totalPlayersInSlots+ '/'+ totalOpenSlots); /* todo: add validation stage to control state */ +- +- if (totalPlayersInSlots !== totalOpenSlots) +- return 'Empty slots encountered'; +- }; +- +- self.gameType = function() { +- if (!self.settings.game_options) +- return null; +- return self.settings.game_options.game_type; +- }; +-}; +- +-var lobbyModel; +- +-var cleanup = []; +-var cleanupOnEntry = []; +- +-function allowChangesFrom(client) { +- if (!client || !lobbyModel.isCreator(client.id)) +- return false; +- if (lobbyModel.control.countdown) +- return false; +- if (lobbyModel.control.starting) +- return false; +- +- return true; +-} +- +-function playerMsg_resetArmies(msg){ +- debug_log('playerMsg_resetArmies'); +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- _.forEach(lobbyModel.players, function (element, key) { +- if (element.ai) +- lobbyModel.removePlayer(key); +- else +- lobbyModel.removePlayerFromArmy(key); +- }); +- +- lobbyModel.armies = []; +- _.forEach(payload, function (element) { +- lobbyModel.addArmy(element); +- }); +- +- lobbyModel.changeControl({ has_first_config: true }); +- +- response.succeed(); +-}; +- +-function playerMsg_addArmy(msg /* client payload */){ +- debug_log('playerMsg_addArmy'); +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!payload.options) +- return response.fail("Not allowed."); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.addArmy(payload.options); +- +- response.succeed(); +-} +- +-function playerMsg_removeArmy(msg){ +- debug_log('playerMsg_removeArmy'); +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.removeArmy(payload.army_index); +- +- response.succeed(); +-} +- +-function playerMsg_addAI(msg) { +- debug_log('playerMsg_addAI'); +- +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.addAI(payload); +- +- response.succeed(); +-} +- +-function playerMsg_modifySystem(msg) { +- debug_log('modifySystem'); +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- var systemValidationResult = sim_utils.validateSystemConfig(msg.payload); +- if (_.isString(systemValidationResult)) { +- debug_log('Invalid system'); +- return response.fail("Invalid system provided - " + systemValidationResult); +- } +- else +- systemValidationResult.then( function () { +- lobbyModel.changeSystem(msg.payload); +- response.succeed(); +- }); +-} +- +-function playerMsg_modifyArmy(msg){ +- debug_log('modifyArmy'); +- var response = server.respond(msg); +- var payload = msg.payload; +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.modifyArmy(payload.army_index, payload.options); +- +- response.succeed(); +-} +- +-function updateBouncer(config) { +- +- if (config.password || bouncer.doesGameRequirePassword()) +- bouncer.setPassword(config.password); +- +- bouncer.clearWhitelist(); +- var hasFriendsList = config.friends && config.friends.length; +- if (hasFriendsList) { +- _.forEach(config.friends, function (element) { bouncer.addPlayerToWhitelist(element); }); +- } +- +- bouncer.clearBlacklist(); +- var hasBlockList = config.blocked && config.blocked.length; +- if (hasBlockList) { +- _.forEach(config.blocked, function (element) { bouncer.addPlayerToBlacklist(element); }); +- } +-} +- +- +-function playerMsg_modifyBouncer(msg) { +- debug_log('playerMsg_modifyBouncer'); +- +- var config = msg.payload; +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- updateBouncer(config); +- +- lobbyModel.setDirty({ beacon: true }); +- +- response.succeed(); +-} +- +-function playerMsg_modifySettings(msg) { +- +- debug_log('playerMsg_modifySettings'); +- var config = msg.payload; +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) { +- lobbyModel.setDirty({ settings: true }); +- return response.fail("Not allowed."); +- } +- +- var game_options = _.cloneDeep(DEFAULT_GAME_OPTIONS); +- +- var settings = { +- spectators: MAX_SPECTATORS, +- hidden: false, +- friends: false, +- public: true, +- tag: DEFAULT_LOBBY_TAG, +- game_name: DEFAULT_LOBBY_NAME, +- required_content: content_manager.getRequiredContent(), +- }; +- +- if (config.game_options) +- game_options.game_type = config.game_options.game_type; +- else if (client_state.settings && client_state.settings.game_options) +- game_options.game_type = client_state.settings.game_options.game_type; +- +- if (!isValidGameType(game_options.game_type)) +- game_options.game_type = DEFAULT_GAME_TYPE; +- +- updateBouncer(config); +- var hasFriendsList = bouncer.getWhitelist().length; +- +- if (config.game_options) +- { +- game_options.land_anywhere = !!config.game_options.land_anywhere; +- +- if (isFFAType(game_options.game_type)) { +- game_options.dynamic_alliances = !!config.game_options.dynamic_alliances; +- if (game_options.dynamic_alliances) +- game_options.dynamic_alliance_victory = !!config.game_options.dynamic_alliance_victory; +- } +- if (config.game_options.bounty_mode) +- game_options.bounty_mode = _.contains(content_manager.getRequiredContent(), 'PAExpansion1') && !!config.game_options.bounty_mode; +- if (config.game_options.bounty_value) +- game_options.bounty_value = config.game_options.bounty_value; +- if (config.game_options.sandbox) +- game_options.sandbox = !!config.game_options.sandbox; +- if (config.game_options.listen_to_spectators) +- game_options.listen_to_spectators = !!config.game_options.listen_to_spectators; +- } +- +- settings.hidden = (!hasFriendsList && !config.public); +- settings.friends = !!hasFriendsList; +- settings.public = (config.public && !hasFriendsList); +- if (config.tag) +- settings.tag = config.tag; +- +- settings.spectators = Math.min(Number(config.spectators), MAX_SPECTATORS); +- +- if (_.isString(config.game_name)) +- settings.game_name = config.game_name.substring(0, Math.min(config.game_name.length, 128)); +- +- _.forEach(_.keys(lobbyModel.settings.game_options), function (key) { +- if (lobbyModel.settings.game_options[key] !== game_options[key]) { +- var name = key.replace(/_/g, ' '); +- var message = name + ' ' + +- (_.isBoolean(game_options[key]) +- ? (!!game_options[key] ? 'enabled' : 'disabled') +- : ('changed')) +- + '.'; +- server.broadcastEventMessage('', message, 'settings'); +- } +- }); +- +- settings.game_options = game_options; +- +- var nameChangeOnly = false; +- +- if (settings.game_name !== lobbyModel.settings.game_name) { +- var currentSettings = _.cloneDeep(lobbyModel.settings); +- delete currentSettings.game_name; +- var newSettings = _.cloneDeep(settings); +- delete newSettings.game_name; +- +- nameChangeOnly = _.isEqual(currentSettings, newSettings); +- } +- +- lobbyModel.changeSettings(settings); +- +- if (!nameChangeOnly) { +- lobbyModel.unreadyAllPlayers(); +- } +- +- response.succeed(); +-} +- +-function maybeStartLandingState() { +- debug_log('maybeStartLandingState'); +- if (!lobbyModel.control.starting || !lobbyModel.control.system_ready || !lobbyModel.control.sim_ready) +- return; +- +- lobbyModel.updateClientState(); +- +- var final_data = lobbyModel.getFinalData(); +- +- try { +- if (server_utils.log_lobby_description) { +- console.log('final lobby data:'); +- console.log(JSON.stringify(final_data, null, '\t')); +- } +- } +- catch (e) { +- console.log('final lobby data: failed.'); // this is *not* expected. +- }; +- +- hasStartedPlaying = true; +- main.setState(main.states.landing, final_data); +-} +- +- +- +-function playerMsg_startCountdown(msg) { +- debug_log('playerMsg_startGame'); +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- var player = lobbyModel.players[msg.client.id]; +- player.ready = true; +- +- function not_ready (){ +- return _.some(lobbyModel.players, function (value) { +- return !value.ready && !value.spectator; +- }); +- } +- +- if (not_ready()) { +- player.ready = false; +- return response.fail("Not ready."); +- +- } +- +- var lobbyValidationResult = lobbyModel.validateSetup(); +- if (lobbyValidationResult) { +- player.ready = false; +- return response.fail("Invalid game setup - " + lobbyValidationResult); +- } +- +- if (!lobbyModel.control.sim_ready) { +- player.ready = false; +- return response.fail("Server is not done gerating planets"); +- } +- +- lobbyModel.updatePlayerState(); +- lobbyModel.changeControl({ countdown: true }); +- +- function startGame() { +- +- server.broadcastEventMessage('', -1, 'countdown'); +- +- server.broadcastEventMessage('', 'Game is starting.'); +- +- lobbyModel.changeControl({ starting: true }); +- maybeStartLandingState(); +- } +- +- var count = _.has(msg, 'countdown') ? msg.countdown : START_GAME_DELAY; +- function countdownToStartGame() { +- server.broadcastEventMessage('', count, 'countdown'); +- count -= 1; +- +- if (count > 0) +- setTimeout(countdownToStartGame, 1000); +- else +- setTimeout(startGame, 1000); +- } +- +- if (lobbyModel.totalCurrentPlayers() < 2) +- startGame(); +- else { +- server.broadcastEventMessage('', 'Game will start in ' + START_GAME_DELAY + ' seconds.'); +- countdownToStartGame(); +- } +- +- response.succeed(); +-} +- +-function playerMsg_setPrimaryColorIndex(msg) { +- debug_log('playerMsg_setPrimaryColorIndex'); +- debug_log(msg); +- var response = server.respond(msg); +- +- lobbyModel.setPrimaryColorIndex(msg.client.id, msg.payload, false); +- response.succeed(); +-} +- +-function playerMsg_setPrimaryColorIndexForAI(msg) { +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.setPrimaryColorIndex(msg.payload.id, msg.payload.color, true); +- response.succeed(); +-} +- +-function playerMsg_setSecondaryColorIndex(msg) { +- debug_log('playerMsg_setSecondaryColorIndex'); +- debug_log(msg); +- var response = server.respond(msg); +- +- lobbyModel.setSecondaryColorIndex(msg.client.id, msg.payload, false); +- response.succeed(); +-} +- +-function playerMsg_setSecondaryColorIndexForAI(msg) { +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.setSecondaryColorIndex(msg.payload.id, msg.payload.color, true); +- response.succeed(); +-} +- +-function playerMsg_setAIPersonality(msg) { +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.setAIPersonality(msg.payload.id, msg.payload.ai_personality); +- response.succeed(); +-} +- +-function playerMsg_setAILandingPolicy(msg) { +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.setAILandingPolicy(msg.payload.id, msg.payload.ai_landing_policy); +- response.succeed(); +-} +- +-function playerMsg_setEconomyFactor(msg) { +- var response = server.respond(msg); +- +- if (!allowChangesFrom(msg.client)) +- return response.fail("Not allowed."); +- +- lobbyModel.setEconomyFactor(msg.payload.id, msg.payload.economy_factor); +- response.succeed(); +-} +- +-function playerMsg_updateCommander(msg) { +- debug_log('playerMsg_updateCommander'); +- var response = server.respond(msg); +- var player = lobbyModel.players[msg.client.id]; +- +- if (!msg.payload || !msg.payload.commander || !player) +- return response.fail("Invalid message"); +- +- player.setCommander(msg.payload.commander); +- +- return response.succeed(); +-} +- +-function playerMsg_joinArmy(msg) { +- debug_log('playerMsg_joinArmy'); +- var response = server.respond(msg); +- var player = lobbyModel.players[msg.client.id]; +- +- if (!msg.payload || !player) +- return response.fail("Invalid message"); +- +- if (msg.payload.commander) +- player.setCommander(msg.payload.commander); +- +- lobbyModel.removePlayerFromArmy(msg.client.id); +- if (lobbyModel.addPlayerToArmy(msg.client.id, msg.payload.army)) { +- server.broadcastEventMessage(player.client.name, ' has joined an army.'); +- response.succeed(); +- } else { +- response.fail("Unable to add player to army"); +- } +-} +- +-function playerMsg_toggleReady(msg) { +- debug_log('playerMsg_toggleReady'); +- +- var response = server.respond(msg); +- var player = lobbyModel.players[msg.client.id]; +- +- if (!player) +- return response.fail("Invalid message"); +- +- if (client_state.control.countdown) +- return response.fail("Cannot change ready after countdown has started."); +- +- player.ready = !player.ready; +- +- lobbyModel.updatePlayerState(); +- +- server.broadcastEventMessage(player.client.name, player.ready ? ' is now ready.' : ' is no longer ready.'); +- +- response.succeed(); +-} +- +-function playerMsg_leaveArmy(msg) { +- debug_log('playerMsg_leaveArmy'); +- var response = server.respond(msg); +- +- var player = lobbyModel.players[msg.client.id]; +- if (!player) +- return; +- +- if (lobbyModel.removePlayerFromArmy(msg.client.id, { clear_color: true, set_spectator: true })) { +- server.broadcastEventMessage(player.client.name, ' is now a spectator.'); +- response.succeed(); +- } else { +- response.fail('Could not remove you from the army'); +- } +-} +- +-function playerMsg_chatMessage(msg) { +- debug_log('playerMsg_chatMessage'); +- var response = server.respond(msg); +- if (!msg.payload || !msg.payload.message) +- return response.fail("Invalid message"); +- server.broadcast({ +- message_type: 'chat_message', +- payload: { +- player_name: msg.client.name, +- message: msg.payload.message +- } +- }); +- response.succeed(); +-} +- +-function playerMsg_leave(msg) { +- debug_log('playerMsg_leave'); +- var response = server.respond(msg); +- +- lobbyModel.removePlayer(msg.client.id); +- +- response.succeed(); +-} +- +-function playerMsg_kick(msg) { +- debug_log('playerMsg_kick'); +- debug_log(msg); +- var response = server.respond(msg); +- +- var id = msg.payload.id; +- var player = lobbyModel.players[id]; +- +- if (!bouncer.isPlayerMod(msg.client.id)) +- return response.fail("Only mods can kick."); +- +- if (bouncer.isPlayerMod(id)) +- return response.fail("Mods cannot be kicked."); +- +- if (!player) +- return response.fail("Already left"); +- +- bouncer.addPlayerToBlacklist(id); +- +- lobbyModel.kickPlayer(id); +- +- response.succeed(); +-} +- +-function playerMsg_promoteToMod(msg) { +- debug_log('playerMsg_promoteToMod'); +- var response = server.respond(msg); +- var id = msg.payload.id; +- var player = lobbyModel.players[id]; +- +- if (!bouncer.isPlayerMod(msg.client.id)) +- return response.fail("Only mods can promote."); +- +- if (!player) +- return response.fail("Player is absent"); +- response.succeed(); +- +- bouncer.addPlayerToModlist(id); +- lobbyModel.updatePlayerState(); +-} +- +-function playerMsg_setLoading(msg) { +- debug_log('playerMsg_setLoading'); +- +- var response = server.respond(msg); +- var player = lobbyModel.players[msg.client.id]; +- +- if (!player) +- return response.fail("Invalid message"); +- +- player.loading = msg.payload.loading; +- +- lobbyModel.updatePlayerState(); +- +- response.succeed(); +-} +- +-function playerMsg_modDataAvailable(msg) { +- debug_log('playerMsg_modDataAvailable'); +- +- var response = server.respond(msg); +- +- if (lobbyModel.creator !== msg.client.id) { +- return response.fail("Mod data can only be provided by lobby creator"); +- } +- +- var auth_token = ""; +- var hasMods = false; +- var mods = server.getMods(); +- if (mods !== undefined) { +- auth_token = mods.auth_token || ""; +- if (mods.mounted_mods !== undefined && mods.mounted_mods.length > 0) { +- hasMods = true; +- } +- } +- if (hasMods) { +- return response.fail("Mod data is already mounted"); +- } +- +- response.succeed({ "auth_token": auth_token }); +-} +- +-function check_cheat(cheatname, callback) { +- file.load('/server-script/modroot/cheat_' + cheatname + '.json', function (data) { +- var cheat_enabled = false; +- if (data !== undefined && data.length > 0) { +- var config = JSON.parse(data); +- if (config.cheat_flags !== undefined) { +- if (config.cheat_flags[cheatname]) { +- main.cheats.cheat_flags[cheatname] = true; +- main.cheats.cheat_flags.any_enabled = true; +- main.cheats.cheat_flags.cheat_mod_enabled = true; +- cheat_enabled = true; +- console.log('CHEATS: Mod enabled cheat: ' + cheatname); +- +- server.broadcast({ +- message_type: 'set_cheat_config', +- payload: main.cheats +- }); +- } +- } +- } +- if (callback !== undefined) { +- callback(cheat_enabled); +- } +- }); +-} +- +-function playerMsg_modDataUpdated(msg) { +- check_cheat('allow_change_vision'); +- check_cheat('allow_change_control'); +- check_cheat('allow_create_unit'); +- check_cheat('allow_mod_data_updates', function (cheat_enabled) { +- if (cheat_enabled) { +- server.disableWriteReplay(); +- } else { +- server.resetModUpdateAuthToken(); +- } +- }); +- +- _.forEach(server.clients, function (client) { +- if (client.id !== msg.client.id) { +- client.downloadModsFromServer(); +- } else { +- client.message({ +- message_type: 'mount_mod_file_data', +- payload: {} +- }); +- } +- }); +-} +- +-function playerMsg_requestCheatConfig(msg) { +- debug_log('playerMsg_requestCheatConfig'); +- +- var response = server.respond(msg); +- +- response.succeed({ "cheat_config": main.cheats }); +-} +- +-function initOwner(owner) { +- debug_log('initOwner'); +- server.maxClients = 1; +- +- if (!owner) { +- var testConfig = _.cloneDeep(require('test').exampleConfig); +- main.setState(main.states.lobby, testConfig); +- return; +- } +- +- bouncer.addPlayerToModlist(owner.id); +- +- var client_data = { uberid: '', password: '', uuid: '' }; +- try { +- client_data = JSON.parse(owner.data); +- bouncer.setUUID(client_data.uuid); +- } +- catch (error) { +- debug_log('js initOwner : unable to parse owner data'); +- } +-} +- +-exports.url = 'coui://ui/main/game/new_game/new_game.html'; +-exports.enter = function (owner) { +- +- _.forEachRight(cleanupOnEntry, function (c) { c(); }); +- +- lobbyModel = new LobbyModel(); +- cleanupOnEntry.push(function () { lobbyModel = undefined; }); +- +- initOwner(owner); +- lobbyModel.changeControl({ has_first_config: false, countdown: false, starting: false, system_ready: false, sim_ready: false }); +- +- utils.pushCallback(sim.planets, 'onReady', function (onReady) { +- debug_log('sim.planets.onReady'); +- lobbyModel.changeControl({ system_ready: true }); +- sim.create(); +- maybeStartLandingState(); +- return onReady; +- }); +- cleanup.push(function () { sim.planets.onReady.pop(); }); +- +- utils.pushCallback(sim, 'onReady', function (onReady) { +- debug_log('sim.onReady'); +- lobbyModel.changeControl({ sim_ready: true }); +- maybeStartLandingState(); +- return onReady; +- }); +- cleanup.push(function () { sim.onReady.pop(); }); +- +- utils.pushCallback(server, 'onConnect', function (onConnect, client, reconnect) { +- debug_log('onConnect'); +- var client_data = { uberid: '', password: '', uuid: '' }; +- try { +- client_data = JSON.parse(client.data); +- debug_log(client); +- } +- catch (e) { +- debug_log('js utils.pushCallback : unable to parse client.data'); +- server.rejectClient(client, 'Bad Client data'); +- return onConnect; +- } +- +- if (!bouncer.isPlayerValid(client_data.uberid, client_data.password, client_data.uuid, lobbyModel.settings.public)) { +- console.log('invalid credentials'); +- server.rejectClient(client, 'Credentials are invalid'); +- return onConnect; +- } +- +- if (!reconnect) { +- var max = Math.min(MAX_CLIENTS, lobbyModel.numPlayerSlots() + main.spectators); +- if (lobbyModel.numPlayers() >= max) { +- console.error('lobby at capacity. client rejected.'); +- server.rejectClient(client, 'No room'); +- return onConnect; +- } +- } +- +- utils.pushCallback(client, 'onDisconnect', function (onDisconnect) { +- if (!hasStartedPlaying) { /* don't kill the client unless we have not started any other states. */ +- console.log('removing disconnected player from the lobby.'); +- lobbyModel.removePlayer(client.id); +- } +- return onDisconnect; +- }); +- cleanup.push(function () { +- if (client.onDisconnect) { +- console.log('remove disconnect handler'); +- client.onDisconnect.pop(); +- } +- /* removePlayer calls client.kill(), which will destroy the onDisconnect handler */ +- }); +- +- var players = _.filter(lobbyModel.players, { 'spectator': false }); +- var options = { mod: bouncer.isPlayerMod(client.id), creator: client.id === lobbyModel.creator }; +- /* force the player to be a spectator */ +- if (players.length >= MAX_PLAYERS) +- options.spectator = true; +- +- if (!lobbyModel.players.hasOwnProperty(client.id)) +- lobbyModel.addPlayer(client, options); +- else +- lobbyModel.updatePlayerState(); +- +- lobbyModel.addPlayersToSlotsIfPossible(); +- +- var player = lobbyModel.players[client.id]; +- if (player.armyIndex === -1) /* make the player a spectator if there is no room */ +- player.spectator = true; +- +- debug_log('calling client.downloadModsFromServer'); +- client.downloadModsFromServer(); +- +- client.message({ +- message_type: 'set_cheat_config', +- payload: main.cheats +- }); +- +- return onConnect; +- }); +- cleanupOnEntry.push(function () { server.onConnect.pop(); }); +- +- lobbyModel.creator = owner.id; +- lobbyModel.addPlayer(owner, { mod: true, creator: true }); +- bouncer.addPlayerToModlist(owner.id); +- +- _.forEach(server.clients, function (client) { +- if (client !== owner) { +- lobbyModel.addPlayer(client, { mod: false, creator: false }); +- } +- }); +- +- var removeHandlers = server.setHandlers({ +- reset_armies: playerMsg_resetArmies, +- add_army: playerMsg_addArmy, +- remove_army: playerMsg_removeArmy, +- add_ai: playerMsg_addAI, +- modify_system: playerMsg_modifySystem, +- modify_army: playerMsg_modifyArmy, +- modify_bouncer: playerMsg_modifyBouncer, +- modify_settings: playerMsg_modifySettings, +- start_game: playerMsg_startCountdown, +- set_primary_color_index: playerMsg_setPrimaryColorIndex, +- set_primary_color_index_for_ai: playerMsg_setPrimaryColorIndexForAI, +- set_secondary_color_index: playerMsg_setSecondaryColorIndex, +- set_secondary_color_index_for_ai: playerMsg_setSecondaryColorIndexForAI, +- set_ai_personality: playerMsg_setAIPersonality, +- set_ai_landing_policy: playerMsg_setAILandingPolicy, +- set_econ_factor: playerMsg_setEconomyFactor, +- join_army: playerMsg_joinArmy, +- toggle_ready: playerMsg_toggleReady, +- leave_army: playerMsg_leaveArmy, +- update_commander: playerMsg_updateCommander, +- chat_message: playerMsg_chatMessage, +- leave: playerMsg_leave, +- kick: playerMsg_kick, +- promote_to_mod: playerMsg_promoteToMod, +- set_loading: playerMsg_setLoading, +- mod_data_available: playerMsg_modDataAvailable, +- mod_data_updated: playerMsg_modDataUpdated, +- request_cheat_config: playerMsg_requestCheatConfig +- }); +- cleanup.push(function () { removeHandlers(); }); +- +- lobbyModel.updateBeacon(); +- +- return client_state; +-}; +- +-exports.exit = function (newState) { +- _.forEachRight(cleanup, function (c) { c(); }); +- cleanup = []; +- +- return true; +-}; +- +-main.gameModes.lobby = exports; +-// This is for backwards compatibility. Game used to ask for 'Config' game mode. +-main.gameModes.Config = exports; ++var main = require('main'); ++var sim_utils = require('sim_utils'); ++var server_utils = require('server_utils'); ++var content_manager = require('content_manager'); ++var utils = require('utils'); ++var bouncer = require('bouncer'); ++var env = require('env'); ++var _ = require('thirdparty/lodash'); ++var commander_manager = require('lobby/commander_manager'); ++var color_manager = require('lobby/color_manager'); ++ ++var getAIName = (function () { ++ ++ var ai_names = _.shuffle(require('ai_names_table').data); /* shuffle returns a new collection */ ++ ++ return function () { ++ var name = ai_names.shift(); ++ ai_names.push(name); ++ return name; ++ } ++})(); ++ ++var used_ai_ids = []; ++var last_ai_number = 0; ++ ++var getAIId = function () { ++ if (used_ai_ids.length) ++ return used_ai_ids.pop(); ++ else { ++ last_ai_number++; ++ return '' + last_ai_number; ++ } ++} ++ ++var returnAIId = function (id) { ++ used_ai_ids.push(id); ++} ++ ++var commanders = new commander_manager.CommanderManager(); ++var colors = new color_manager.ColorManager(); ++ ++var START_GAME_DELAY = 5; // In s. ++var MAX_PLAYERS = main.MAX_PLAYERS; ++var MAX_SPECTATORS = main.MAX_SPECTATORS; ++var MAX_CLIENTS = MAX_PLAYERS + MAX_SPECTATORS; ++var DEFAULT_LOBBY_TAG = ''; ++var DEFAULT_LOBBY_NAME = ''; ++var DEFAULT_GAME_TYPE = 'FreeForAll'; ++var VALID_GAME_TYPES = [DEFAULT_GAME_TYPE, 'TeamArmies', 'VersusAI']; ++var isValidGameType = function (game_type) { ++ return VALID_GAME_TYPES.indexOf(game_type) != -1; ++}; ++var isFFAType = function (game_type) { ++ return game_type === 'FreeForAll'; ++}; ++ ++var DEFAULT_GAME_OPTIONS = { ++ dynamic_alliances: false, ++ dynamic_alliance_victory: false, ++ bounty_mode: false, ++ bounty_value: 0.5, ++ sandbox: false, ++ listen_to_spectators: false, ++ game_type: DEFAULT_GAME_TYPE, ++ land_anywhere: false, ++}; ++ ++ ++var alliance_groups = _.range(1, MAX_CLIENTS / 2 + 1); /* 0 indicates no alliance */ ++ ++var debugging = false; ++ ++function debug_log(object) { ++ if (debugging) ++ console.log(JSON.stringify(object,null,'\t')); ++} ++ ++var client_state = { ++ armies: [], ++ players: [], ++ colors: [], ++ system: {}, ++ settings: {}, ++ control: {} ++}; ++ ++/* the lobby stays up after we transition out of the state, so that it can handle login/rejoin attempts ++ if a player leaves the lobby, we kill the client (but they can still rejoin); however, if a player ++ leaves after the game has moved on to another state (usually due to a disconnect error), ++ we don't want to kill the client, since the playing state will setup a disconnect timer. */ ++var hasStartedPlaying = false; ++ ++function PlayerModel(client, options) { ++ var self = this; ++ ++ self.client = client; /* data, debugDesc, connected, id, name */ ++ try { ++ self.client_data = JSON.parse(client.data); ++ } catch (error) { ++ debug_log("Unable to parse client data for player"); ++ debug_log(error); ++ self.client_data = null; ++ } ++ self.creator = !!options.creator; ++ self.spectator = !!options.spectator; ++ ++ self.ai = !!options.ai; ++ self.personality = options.personality || ''; ++ ++ /* for now, only AI have landing policy. values: ['no_restriction', 'on_player_planet', 'off_player_planet'] */ ++ self.landingPolicy = (self.ai && options.landing_policy) ? options.landing_policy : 'no_restriction'; ++ ++ self.commander = commanders.getRandomDefaultCommanderSpec(); ++ ++ self.ready = self.ai ? true : false; ++ self.loading = self.ai ? false : true; ++ ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ ++ self.colorIndex = (self.ai || self.spectator) ? [-1, -1] : colors.takeRandomAvailableColor(); ++ ++ self.economyFactor = _.isFinite(options.economy_factor) ? options.economy_factor : 1.0; ++ self.economyFactor = Math.min(Math.max(0.0, self.economyFactor), 5.0); ++ ++ if (!!options.mod) ++ bouncer.addPlayerToModlist(client.id); ++ else ++ bouncer.removePlayerFromModlist(client.id); ++ ++ self.nextPrimaryColorIndex = function () { ++ if (self.spectator) ++ return; ++ ++ self.returnColorIndex(); ++ self.colorIndex = [colors.takeNextAvailableColorIndex(self.colorIndex[0]), 0]; ++ }; ++ ++ self.nextSecondaryColorIndex = function () { ++ if (self.spectator) ++ return; ++ ++ var colors = colors.getSecondaryColorsFor(self.colorIndex[0]); ++ var max = colors.getNumberOfColors(); ++ self.colorIndex[1] = (self.colorIndex[1] + 1) % max; ++ } ++ ++ self.clearColorIndex = function () { ++ self.returnColorIndex(); ++ self.colorIndex = [-1, -1]; ++ }; ++ ++ self.maybeTakeColorIndex = function () { ++ if (self.spectator || self.colorIndex[0] !== -1) ++ return; ++ self.colorIndex = colors.takeRandomAvailableColor(); ++ }; ++ ++ self.setPrimaryColorIndex = function (index) { ++ if (self.spectator) ++ return; ++ self.colorIndex = [colors.maybeGetNewColorIndex(self.colorIndex[0], index), self.colorIndex[1]]; ++ }; ++ ++ self.setSecondaryColorIndex = function (index) { ++ if (self.spectator) ++ return; ++ ++ self.colorIndex = [self.colorIndex[0], index]; ++ }; ++ ++ self.returnColorIndex = function () { ++ if (self.colorIndex[0] === -1) ++ return; ++ ++ colors.returnColorIndex(self.colorIndex[0]); ++ self.colorIndex = [-1, -1]; ++ }; ++ ++ self.adjustArmyIndexAboveTarget = function (target_index, delta) { ++ if (self.armyIndex > target_index) ++ self.armyIndex += delta; ++ }; ++ ++ self.adjustSlotIndexAboveTarget = function (target_index, delta) { ++ if (self.slotIndex > target_index) ++ self.slotIndex += delta; ++ }; ++ ++ self.processRemoveIndex = function (target_index) { ++ if (self.armyIndex === target_index) { ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ } ++ else ++ self.adjustArmyIndexAboveTarget(target_index, -1); ++ }; ++ ++ self.processRemoveSlotAtIndex = function (target_index, target_slot) { ++ if (self.armyIndex === target_index) { ++ ++ if (self.slotIndex === target_slot) { ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ } ++ else ++ self.adjustSlotIndexAboveTarget(target_slot, -1); ++ } ++ }; ++ ++ self.setAIPersonality = function (personality) { ++ if (!self.ai) ++ return; ++ ++ self.personality = personality; ++ }; ++ ++ self.setAILandingPolicy = function (policy) { ++ if (!self.ai) ++ return; ++ ++ self.landingPolicy = policy; ++ }; ++ ++ self.setEconomyFactor = function (value) { ++ value = Math.min(Math.max(0.0, value), 5.0); ++ self.economyFactor = value; ++ }; ++ ++ self.asJson = function () { ++ return { ++ name: self.client.name, ++ id: self.client.id, ++ ai: self.ai, ++ personality: self.personality, ++ landing_policy: self.landingPolicy, ++ economy_factor: self.economyFactor, ++ connected: self.ai ? true : self.client.connected, ++ creator: self.ai ? false : self.creator, ++ mod: self.ai ? false : bouncer.isPlayerMod(self.client.id), ++ army_index: self.armyIndex, ++ slot_index: self.slotIndex, ++ commander: self.commander, ++ ready: self.ready, ++ loading: self.loading, ++ color: colors.getColorFor(self.colorIndex), ++ color_index: self.colorIndex[0] ++ }; ++ }; ++ ++ self.finalize = function () { ++ return { ++ name: self.client.name, ++ commander: self.commander, ++ client: self.client, ++ army: self.armyIndex, ++ slot: self.slotIndex, ++ ai: self.ai, ++ personality: self.personality, ++ landing_policy: self.landingPolicy ++ }; ++ }; ++ ++ self.setCommander = function (new_commander) { ++ if (!new_commander || self.commander === new_commander) ++ return; ++ ++ var commanderObject = commanders.getCommanderObjectName(new_commander); ++ if (!commanderObject) { ++ debug_log("Failed to locate item " + (new_commander)); ++ return; ++ } ++ ++ // Do not validate AI commanders ++ ++ if (!self.ai && !self.client.validateItem(commanderObject)) { ++ debug_log("Failed to validate ownership of " + (commanderObject)); ++ return; ++ } ++ ++ self.commander = new_commander; ++ ++ lobbyModel.updatePlayerState(); ++ } ++}; ++ ++function ArmyModel(options) { ++ var self = this; ++ ++ self.slots = options.slots ? Math.max(options.slots, 1) : 1; ++ self.alliance = !!options.alliance; ++ self.allianceGroup = 0; /* 0 indicates no alliance */ ++ ++ self.asJson = function () { ++ return { ++ slots: self.slots, ++ alliance: self.alliance ++ }; ++ }; ++ ++ self.finalizeAsConfig = function () { ++ var s = []; ++ s.length = self.slots; ++ ++ _.forEach(s, function (element, index) { ++ s[index] = 'player'; ++ }); ++ ++ return { ++ slots: s, ++ alliance_group: self.allianceGroup ++ }; ++ }; ++}; ++ ++function LobbyModel(creator) { ++ var self = this; ++ ++ self.maxNumberOfAllowedPlayers = 1; ++ self.players = {}; ++ self.armies = []; ++ self.system = {}; ++ self.minimalSystemDescription = {}; /* system sans custom planet source */ ++ self.config = {}; ++ self.settings = { ++ hidden: true, ++ game_options: _.cloneDeep(DEFAULT_GAME_OPTIONS), ++ required_content: content_manager.getRequiredContent(), ++ }; /* game_mode spectators broadcast_delay private friends public tag */ ++ self.control = {}; /* has_first_config starting system_ready sim_ready */ ++ ++ self.creator = creator; ++ ++ self.dirty = {}; ++ self.allDirty = { ++ control: true, ++ system: true, ++ players: true, ++ armies: true, ++ color: true, ++ settings: true, ++ beacon: true ++ }; ++ // These dirty flags (on the left) will be set when the associated flags (on the right) are set ++ self.chainDirty = { ++ beacon: ['players', 'armies', 'system', 'settings'], ++ color: ['players'] ++ }; ++ ++ // Set a given flag to "dirty" (e.g. self.setDirty({control: true}); ) ++ self.setDirty = function(flags) { ++ var needsApply = _.isEmpty(self.dirty); ++ _.assign(self.dirty, flags); ++ ++ while (!function() { ++ return _.all(self.chainDirty, function(flags, key) { ++ if (self.dirty[key]) ++ return true; ++ var chain = _.pick(self.dirty, flags); ++ if (_.isEmpty(chain)) ++ return true; ++ self.dirty[key] = true; ++ return false; ++ }); ++ }()); ++ ++ if (needsApply) ++ _.delay(function() { self.cleanDirtyFlags(); }); ++ }; ++ ++ // Custom functions for cleaning dirty flags. (broadcast to client for all other flags) ++ self.customCleaners = { ++ beacon: function () { self.updateBeacon(); } ++ }; ++ ++ // Clean all the dirty flags ++ self.cleanDirtyFlags = function () { ++ try { ++ _.forIn(self.dirty, function(yes, key) { ++ if (self.customCleaners.hasOwnProperty(key)) ++ self.customCleaners[key](); ++ else { ++ server.broadcast({ ++ message_type: key, ++ payload: client_state[key] ++ }); ++ } ++ }); ++ } ++ catch (e) { ++ // Note: Very important that the server "mostly" works if this happens. ++ console.error('Lobby unable to clean dirty flags:', e.toString()); ++ } ++ self.dirty = {}; ++ }; ++ ++ self.isCreator = function (id) { ++ return id === self.creator; ++ }; ++ ++ self.playersInArmy = function(army_index) { ++ return _.filter(_.values(self.players), function (element){ ++ return element.armyIndex === army_index; ++ }); ++ }; ++ ++ self.aiCount = function () { ++ return _.filter(_.values(self.players), function (element) { return element.ai }).length; ++ } ++ ++ self.totalSlots = function () { ++ ++ var result = 0; ++ _.forEach(self.armies,function (element) { ++ result += element.slots; ++ }); ++ ++ return result; ++ }; ++ ++ self.totalCurrentPlayers = function () { ++ var total = 0; ++ _.forEach(self.armies, function (element, index) { ++ total += self.playersInArmy(index).length; ++ }); ++ return total; ++ }; ++ ++ self.breakArmyIntoAlliances = function (army_index) { ++ var army = self.armies[army_index]; ++ if (!army) ++ return; ++ ++ var extra = army.slots - 1; ++ army.slots = 1; ++ var options = army.asJson(); ++ ++ var group = alliance_groups.splice(0, 1)[0]; ++ ++ _.invoke(self.players, 'adjustArmyIndexAboveTarget', army_index, extra); ++ ++ _.times(extra, function (index) { /* split armies */ ++ self.armies.splice(army_index, 0, new ArmyModel(options)); /* splice modifies the array */ ++ }); ++ ++ _.forEach(self.playersInArmy(army_index), function (element) { /* add players to new armies */ ++ element.armyIndex += element.slotIndex; ++ element.slotIndex = 0; ++ ++ self.armies[element.armyIndex].allianceGroup = group; ++ }); ++ } ++ ++ self.finalizeConfig = function () { ++ return { ++ "armies": _.invoke(self.armies, 'finalizeAsConfig'), ++ "system": self.system, ++ "enable_lan": true, ++ "spectators": 0, ++ "password": "", ++ "friends": [], ++ "blocked": [], ++ "public": true, ++ "players": self.totalCurrentPlayers(), ++ "vs_ai": false, ++ "game_options": self.settings.game_options ++ }; ++ } ++ ++ self.finalizePlayers = function () { ++ var result = {}; ++ ++ _.forIn(self.players, function (value, key) { ++ result[key] = value.finalize() ++ }); ++ ++ return result; ++ }; ++ ++ self.finalizeArmies = function () { ++ var result = _.invoke(self.armies, 'finalizeAsConfig'); ++ ++ _.forIn(self.players, function (element) { ++ var army = result[element.armyIndex]; ++ if (!army) /* player is spectating */ ++ return; ++ ++ army.slots[element.slotIndex] = element.finalize(); /* insert finalized block { name, commander, client, army, ai } */ ++ ++ if (element.slotIndex === 0) { /* only use the color for the first player in the army */ ++ result[element.armyIndex].color = colors.getColorFor(element.colorIndex); /* insert expanded color */ ++ result[element.armyIndex].color_index = element.colorIndex[0]; ++ result[element.armyIndex].econ_rate = element.economyFactor; ++ } ++ ++ if (element.ai) { ++ result[element.armyIndex].personality = element.personality; ++ result[element.armyIndex].landing_policy = element.landingPolicy; ++ } ++ }); ++ ++ return result; ++ } ++ ++ self.getFinalData = function () { ++ ++ while (true) ++ { ++ var target = _.findIndex(self.armies, function (element) { ++ return element.alliance && element.slots > 1; ++ }); ++ ++ if (target === -1) ++ break; ++ ++ self.breakArmyIntoAlliances(target); ++ } ++ ++ _.invoke(self.players, 'maybeTakeColorIndex'); ++ ++ return { ++ game: lobbyModel.finalizeConfig(), ++ armies: lobbyModel.finalizeArmies(), ++ players: lobbyModel.finalizePlayers(), ++ ranked: false ++ } ++ }; ++ ++ self.updatePlayerState = function () { ++ debug_log('updatePlayerState'); ++ var players = _.invoke(self.players, 'asJson'); ++ if (_.isEqual(client_state.players, players)) ++ return; ++ client_state.players = _.cloneDeep(players); ++ self.setDirty({players: true}); ++ }; ++ ++ self.updateArmyState = function () { ++ debug_log('updateArmyState'); ++ var armies = _.invoke(self.armies, 'asJson'); ++ if (_.isEqual(client_state.armies, armies)) ++ return; ++ client_state.armies = _.cloneDeep(armies); ++ self.setDirty({ armies: true }); ++ }; ++ ++ self.updateSystemState = function () { ++ debug_log('updateSystemState'); ++ if (_.isEqual(client_state.system, self.minimalSystemDescription)) ++ return; ++ client_state.system = _.cloneDeep(self.minimalSystemDescription); ++ self.setDirty({system: true}); ++ }; ++ ++ self.changeSystem = function (system) { ++ if (_.isEqual(system, self.system)) ++ return; ++ ++ self.changeControl({ system_ready: false, sim_ready: false }); ++ self.system = system; ++ ++ self.minimalSystemDescription = utils.getMinimalSystemDescription(self.system); ++ ++ self.updateSystemState(); ++ ++ /* this will take some time. the server will be unresponsive. */ ++ sim.shutdown(false); ++ sim.systemName = lobbyModel.system.name; ++ sim.planets = self.system.planets; ++ }; ++ ++ self.updateColorState = function () { ++ debug_log('updateColorState'); ++ if (_.isEqual(client_state.colors, colors.colors)) ++ return; ++ client_state.colors = _.cloneDeep(colors.colors); ++ self.setDirty({colors: true}); ++ }; ++ ++ self.updateControlState = function () { ++ debug_log('updateControlState'); ++ if (_.isEqual(client_state.control, self.control)) ++ return; ++ client_state.control = _.cloneDeep(self.control); ++ self.setDirty({control: true}); ++ }; ++ ++ self.changeControl = function (control /* has_first_config starting system_ready sim_ready */) { ++ _.assign(self.control, control); ++ self.updateControlState(); ++ }; ++ ++ self.updateSettingsState = function () { ++ debug_log('updateSettingsState'); ++ if (_.isEqual(client_state.settings, self.settings)) ++ return; ++ client_state.settings = _.cloneDeep(self.settings); ++ ++ main.spectators = Number(self.settings.spectators); ++ ++ self.setDirty({ settings: true }); ++ }; ++ ++ self.changeSettings = function (settings /* game_mode spectators hidden friends public tag */) { ++ _.assign(self.settings, settings); ++ self.updateSettingsState(); ++ }; ++ ++ self.updateClientState = function () { ++ debug_log('updateClientState'); ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateSystemState(); ++ self.updateColorState(); ++ self.updateControlState(); ++ self.updateSettingsState(); ++ }; ++ ++ self.unreadyAllPlayers = function () { ++ debug_log('unreadyAllPlayers'); ++ _.forIn(self.players, function (element) { ++ if (element.ready && !element.ai) { ++ server.broadcastEventMessage(element.client.name, ' is no longer ready.'); ++ element.ready = false; ++ } ++ }); ++ ++ self.setDirty({ players: true }); ++ self.updatePlayerState(); ++ }; ++ ++ self.addPlayersToSlotsIfPossible = function () { ++ debug_log('addPlayersToSlotsIfPossible'); ++ ++ var army_index = 0; ++ _.forIn(self.players, function (element, key) { ++ ++ if (element.armyIndex !== -1 || element.spectator) ++ return; ++ ++ while (army_index < self.armies.length) ++ { ++ if (self.addPlayerToArmy(key, army_index)) ++ break; ++ army_index += 1; ++ } ++ }); ++ } ++ ++ self.addArmy = function (options) { ++ if (self.armies.length >= MAX_PLAYERS) ++ return; ++ ++ if (options.slots && options.slots + self.totalSlots() > MAX_PLAYERS) ++ return; ++ ++ self.unreadyAllPlayers(); ++ ++ self.armies.push(new ArmyModel(options)); ++ self.addPlayersToSlotsIfPossible(); ++ self.updateArmyState(); ++ }; ++ ++ self.removeArmy = function (army_index) { ++ var spares = []; ++ ++ self.unreadyAllPlayers(); ++ ++ self.armies.splice(army_index, 1); ++ ++ _.forEach(self.players, function (element) { ++ if (element.ai && element.armyIndex === army_index) ++ spares.push(element.client.id) ++ }); ++ ++ _.forEach(spares, self.removePlayer); ++ ++ _.invoke(self.players, 'processRemoveIndex', army_index); ++ ++ self.addPlayersToSlotsIfPossible(); ++ self.updateArmyState(); ++ self.updatePlayerState(); ++ }; ++ ++ self.modifyArmy = function (army_index, options) { ++ debug_log('modifyArmy'); ++ ++ var spares = []; ++ ++ var army = self.armies[army_index]; ++ if (!army) ++ return; ++ ++ var new_options = _.assign(army.asJson(), options); ++ ++ var ai = !!_.filter(self.playersInArmy(army_index), function (element) { return element.ai }).length; ++ if (ai) ++ new_options.alliance = true; /* override to prevent shared army with ai */ ++ ++ if (options.slots && (options.slots - army.slots + self.totalSlots()) > MAX_PLAYERS) ++ return; ++ ++ self.unreadyAllPlayers(); ++ ++ if (options.slots < army.slots) { ++ _.forEach(_.range(options.slots, army.slots), function (index) { ++ _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, index); ++ ++ _.forEach(self.players, function (element) { ++ if (element.ai && element.armyIndex === army_index && element.slotIndex == index) ++ spares.push(element.client.id) ++ }); ++ }); ++ } ++ ++ _.forEach(spares, self.removePlayer); ++ ++ self.armies[army_index] = new ArmyModel(new_options); ++ ++ self.addPlayersToSlotsIfPossible(); ++ ++ self.fixColors(); ++ ++ self.updateArmyState(); ++ self.updateColorState(); ++ self.updatePlayerState(); ++ }; ++ ++ self.numPlayerSlots = function() { ++ return utils.sum(self.armies, function(army) { return army.ai ? 0 : army.slots; }); ++ }; ++ ++ self.numPlayers = function () { ++ return _.keys(self.players).length; ++ }; ++ ++ self.updateBeacon = function () { ++ debug_log('updateBeacon'); ++ var numPlayerSlots = self.numPlayerSlots(); ++ server.maxClients = Math.min(MAX_PLAYERS, numPlayerSlots) + Math.min(MAX_SPECTATORS, main.spectators); ++ var publish = !server.closed && (self.settings.public || bouncer.getWhitelist().length) && !self.settings.hidden; ++ ++ if (publish) { ++ var full = server.clients.length >= server.maxClients; ++ ++ var modNames = []; ++ var mods = server.getMods(); ++ if (mods !== undefined && mods.mounted_mods !== undefined) { ++ _.forEach(mods.mounted_mods, function (element) { ++ modNames.push(element.display_name); ++ }); ++ } ++ ++ var player_names = _.map(_.filter(self.players, { 'spectator': false }), function (player) { return player.client.name; }); ++ var spectator_names = _.map(_.filter(self.players, { 'spectator': true }), function (player) { return player.client.name; }); ++ ++ var mode = DEFAULT_GAME_TYPE; ++ if (self.settings.game_options) ++ mode = self.settings.game_options.game_type; ++ ++ server.beacon = { ++ uuid: server.uuid(), ++ full: full, ++ started: self.control.countdown || self.control.starting, ++ players: player_names.length, ++ creator: self.creatorName(), ++ max_players: numPlayerSlots, ++ spectators: spectator_names.length, ++ max_spectators: main.spectators, ++ mode: mode, ++ mod_names: modNames, ++ cheat_config: main.cheats, ++ player_names: player_names, ++ spectator_names: spectator_names, ++ require_password: !!bouncer.doesGameRequirePassword(), ++ whitelist: bouncer.getWhitelist(), ++ blacklist: bouncer.getBlacklist(), ++ tag: self.settings.tag, ++ game: { ++ system: self.minimalSystemDescription, ++ name: self.settings.game_name ++ }, ++ required_content: content_manager.getRequiredContent(), ++ bounty_mode: self.settings.game_options ? !!self.settings.game_options.bounty_mode : false, ++ bounty_value: self.settings.game_options ? self.settings.game_options.bounty_value : 0.5, ++ sandbox: self.settings.game_options ? !!self.settings.game_options.sandbox : false ++ }; ++ } else { ++ server.beacon = null; ++ } ++ }; ++ ++ self.addPlayer = function (client, options) { ++ debug_log('addPlayer'); ++ var player = new PlayerModel(client, options); ++ self.players[client.id] = player; ++ self.updatePlayerState(); ++ self.updateColorState(); ++ ++ if (!options.ai) { ++ _.delay(server.broadcastEventMessage, 500, player.client.name, ' joined the lobby.'); ++ ++ debug_log('Player Stats: '); ++ debug_log(player.client.statistics); ++ client.incrementStatistic("TestStat_LobbiesJoined", 1); ++ } ++ }; ++ ++ self.addAI = function (payload) { ++ var player_id = getAIId(); ++ var success = false; ++ ++ if (!payload) ++ return; ++ ++ self.addPlayer({ connected: true, id: player_id, name: getAIName() }, payload.options); ++ ++ success = self.addPlayerToArmy(player_id, payload.army_index); ++ if (!success) ++ self.removePlayer(player_id); ++ }; ++ ++ self.creatorName = function () { ++ var player = self.players[self.creator]; ++ return player ? player.client.name : ''; ++ }; ++ ++ self.chooseNextPlayerAsCreator = function () { ++ debug_log('chooseNextPlayerAsCreator'); ++ if (_.isEmpty(self.players)) ++ return; ++ ++ var client = _.first(server.clients); ++ if (!client) ++ return; ++ ++ var id = client.id; ++ self.creator = id; ++ ++ var player = self.players[id]; ++ ++ player.creator = true; ++ bouncer.addPlayerToModlist(id); ++ ++ self.updatePlayerState(); ++ server.broadcastEventMessage(player.client.name, ' is now the host.'); ++ }; ++ ++ self.removePlayer = function (id) { ++ debug_log('removePlayer'); ++ ++ delete client_state.players[id]; ++ var player = self.players[id]; ++ ++ if (player) { ++ self.removePlayerFromArmy(id, { clear_color: true }); ++ ++ var ai = player.ai; ++ if (!ai) ++ server.broadcastEventMessage(player.client.name, ' has left the lobby.'); ++ ++ player.returnColorIndex(); ++ delete self.players[id]; ++ ++ if (!ai) { ++ console.log('killing client ' + id); ++ player.client.kill(); ++ ++ // Terminate empty lobbies ++ if (!main.keep_alive && !server.connected) { ++ sim.shutdown(false); ++ server.exit(); ++ return; ++ } ++ ++ if (id === self.creator) ++ self.chooseNextPlayerAsCreator(); ++ } ++ ++ if (ai) ++ returnAIId(id); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ } ++ }; ++ ++ self.kickPlayer = function (id) { ++ debug_log('kickPlayer'); ++ bouncer.addPlayerToBlacklist(id); ++ self.removePlayer(id); ++ }; ++ ++ self.addPlayerToArmy = function (player_id, army_index) { ++ debug_log('addPlayerToArmy'); ++ var player = self.players[player_id]; ++ var army = self.armies[army_index]; ++ ++ var array = self.playersInArmy(army_index); ++ var count = array.length; ++ ++ if (!player || !army || count >= army.slots) ++ return false; ++ ++ if (player.armyIndex === army_index) ++ return false; ++ ++ player.armyIndex = army_index; ++ player.slotIndex = count; ++ player.spectator = false; ++ ++ if (player.ai) ++ army.alliance = true; /* override to prevent shared army with ai */ ++ ++ self.fixColors(); ++ ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateColorState(); ++ return true; ++ }; ++ ++ self.removePlayerFromArmy = function (player_id, options) { ++ debug_log('removePlayerFromArmy'); ++ var player = self.players[player_id]; ++ ++ if (!player) ++ return false; ++ ++ if (options && options.clear_color) ++ player.clearColorIndex(); ++ ++ var army_index = player.armyIndex; ++ var slot_index = player.slotIndex; ++ ++ player.armyIndex = -1; ++ player.slotIndex = -1; ++ ++ if (options && options.set_spectator) ++ player.spectator = true; ++ ++ /* move players down to fill empty slot */ ++ _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, slot_index); ++ ++ self.fixColors(); ++ ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateColorState(); ++ ++ return true; ++ } ++ ++ /* this checks every army, which is excessive; however, it is very reliable. */ ++ self.fixColors = function () { ++ _.forEach(self.armies, function (element, index) { ++ self.fixColorsForArmy(index); ++ }); ++ } ++ ++ /* it would be preferrable to just call this function for each modified army */ ++ self.fixColorsForArmy = function (army_index) { ++ ++ var army = self.armies[army_index]; ++ ++ if (!army) ++ return; ++ ++ _.forEach(self.playersInArmy(army_index), function (element, index) { ++ if (index === 0 || army.alliance) ++ element.maybeTakeColorIndex(); ++ else ++ element.returnColorIndex(); /* only the first player in a shared army gets a color */ ++ }); ++ ++ self.setDirty({ colors: true }); ++ }; ++ ++ self.setPrimaryColorIndex = function (player_id, index, ai) { ++ debug_log('{{setPrimaryColorIndex}} ' + index); ++ var player = self.players[player_id]; ++ ++ if (!player || player.ai !== ai || !colors.isValidPrimaryColorIndex(index)) ++ return; ++ ++ player.setPrimaryColorIndex(index); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ }; ++ ++ self.setSecondaryColorIndex = function (player_id, index, ai) { ++ debug_log('{{setSecondaryColorIndex}} ' + index); ++ var player = self.players[player_id]; ++ ++ if (!player || player.ai !== ai || !colors.isValidColorPair(player.colorIndex[0], index)) ++ return; ++ ++ player.setSecondaryColorIndex(index); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ }; ++ ++ self.setAIPersonality = function (player_id, personality) { ++ debug_log('setAIPersonality'); ++ var player = self.players[player_id]; ++ ++ if (!player || !player.ai) ++ return; ++ ++ player.setAIPersonality(personality); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ }; ++ ++ self.setAILandingPolicy = function (player_id, policy) { ++ debug_log('setAILandingPolicy'); ++ var player = self.players[player_id]; ++ ++ if (!player || !player.ai) ++ return; ++ ++ player.setAILandingPolicy(policy); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ }; ++ ++ self.setAICommander = function (player_id, commander) { ++ var player = self.players[player_id]; ++ ++ if (!player || !player.ai) ++ return; ++ ++ player.setCommander(commander); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ }; ++ ++ self.setEconomyFactor = function (player_id, value) { ++ debug_log('setEconomyFactor'); ++ var player = self.players[player_id]; ++ ++ if (!player) ++ return; ++ ++ if (player.economyFactor != value) ++ { ++ player.setEconomyFactor(value); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ } ++ }; ++ ++ self.validateSetup = function () { ++ debug_log('validateSetup'); ++ var totalOpenSlots = self.numPlayerSlots(); ++ var totalPlayersInSlots = utils.sum(self.players, function (player) { return player.armyIndex >= 0; }); ++ ++ debug_log('Validation:'+ totalPlayersInSlots+ '/'+ totalOpenSlots); /* todo: add validation stage to control state */ ++ ++ if (totalPlayersInSlots !== totalOpenSlots) ++ return 'Empty slots encountered'; ++ }; ++ ++ self.gameType = function() { ++ if (!self.settings.game_options) ++ return null; ++ return self.settings.game_options.game_type; ++ }; ++}; ++ ++var lobbyModel; ++ ++var cleanup = []; ++var cleanupOnEntry = []; ++ ++function allowChangesFrom(client) { ++ if (!client || !lobbyModel.isCreator(client.id)) ++ return false; ++ if (lobbyModel.control.countdown) ++ return false; ++ if (lobbyModel.control.starting) ++ return false; ++ ++ return true; ++} ++ ++function playerMsg_resetArmies(msg){ ++ debug_log('playerMsg_resetArmies'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ _.forEach(lobbyModel.players, function (element, key) { ++ if (element.ai) ++ lobbyModel.removePlayer(key); ++ else ++ lobbyModel.removePlayerFromArmy(key); ++ }); ++ ++ lobbyModel.armies = []; ++ _.forEach(payload, function (element) { ++ lobbyModel.addArmy(element); ++ }); ++ ++ lobbyModel.changeControl({ has_first_config: true }); ++ ++ response.succeed(); ++}; ++ ++function playerMsg_addArmy(msg /* client payload */){ ++ debug_log('playerMsg_addArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!payload.options) ++ return response.fail("Not allowed."); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.addArmy(payload.options); ++ ++ response.succeed(); ++} ++ ++function playerMsg_removeArmy(msg){ ++ debug_log('playerMsg_removeArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.removeArmy(payload.army_index); ++ ++ response.succeed(); ++} ++ ++function playerMsg_addAI(msg) { ++ debug_log('playerMsg_addAI'); ++ ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.addAI(payload); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modifySystem(msg) { ++ debug_log('modifySystem'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ var systemValidationResult = sim_utils.validateSystemConfig(msg.payload); ++ if (_.isString(systemValidationResult)) { ++ debug_log('Invalid system'); ++ return response.fail("Invalid system provided - " + systemValidationResult); ++ } ++ else ++ systemValidationResult.then( function () { ++ lobbyModel.changeSystem(msg.payload); ++ response.succeed(); ++ }); ++} ++ ++function playerMsg_modifyArmy(msg){ ++ debug_log('modifyArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.modifyArmy(payload.army_index, payload.options); ++ ++ response.succeed(); ++} ++ ++function updateBouncer(config) { ++ ++ if (config.password || bouncer.doesGameRequirePassword()) ++ bouncer.setPassword(config.password); ++ ++ bouncer.clearWhitelist(); ++ var hasFriendsList = config.friends && config.friends.length; ++ if (hasFriendsList) { ++ _.forEach(config.friends, function (element) { bouncer.addPlayerToWhitelist(element); }); ++ } ++ ++ bouncer.clearBlacklist(); ++ var hasBlockList = config.blocked && config.blocked.length; ++ if (hasBlockList) { ++ _.forEach(config.blocked, function (element) { bouncer.addPlayerToBlacklist(element); }); ++ } ++} ++ ++ ++function playerMsg_modifyBouncer(msg) { ++ debug_log('playerMsg_modifyBouncer'); ++ ++ var config = msg.payload; ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ updateBouncer(config); ++ ++ lobbyModel.setDirty({ beacon: true }); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modifySettings(msg) { ++ ++ debug_log('playerMsg_modifySettings'); ++ var config = msg.payload; ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) { ++ lobbyModel.setDirty({ settings: true }); ++ return response.fail("Not allowed."); ++ } ++ ++ var game_options = _.cloneDeep(DEFAULT_GAME_OPTIONS); ++ ++ var settings = { ++ max_spectators: MAX_SPECTATORS, ++ max_players: MAX_PLAYERS, ++ spectators: MAX_SPECTATORS, ++ hidden: false, ++ friends: false, ++ public: true, ++ tag: DEFAULT_LOBBY_TAG, ++ game_name: DEFAULT_LOBBY_NAME, ++ required_content: content_manager.getRequiredContent(), ++ }; ++ ++ if (config.game_options) ++ game_options.game_type = config.game_options.game_type; ++ else if (client_state.settings && client_state.settings.game_options) ++ game_options.game_type = client_state.settings.game_options.game_type; ++ ++ if (!isValidGameType(game_options.game_type)) ++ game_options.game_type = DEFAULT_GAME_TYPE; ++ ++ updateBouncer(config); ++ var hasFriendsList = bouncer.getWhitelist().length; ++ ++ if (config.game_options) ++ { ++ game_options.land_anywhere = !!config.game_options.land_anywhere; ++ ++ if (isFFAType(game_options.game_type)) { ++ game_options.dynamic_alliances = !!config.game_options.dynamic_alliances; ++ if (game_options.dynamic_alliances) ++ game_options.dynamic_alliance_victory = !!config.game_options.dynamic_alliance_victory; ++ } ++ if (config.game_options.bounty_mode) ++ game_options.bounty_mode = _.contains(content_manager.getRequiredContent(), 'PAExpansion1') && !!config.game_options.bounty_mode; ++ if (config.game_options.bounty_value) ++ game_options.bounty_value = config.game_options.bounty_value; ++ if (config.game_options.sandbox) ++ game_options.sandbox = !!config.game_options.sandbox; ++ if (config.game_options.listen_to_spectators) ++ game_options.listen_to_spectators = !!config.game_options.listen_to_spectators; ++ } ++ ++ settings.hidden = (!hasFriendsList && !config.public); ++ settings.friends = !!hasFriendsList; ++ settings.public = (config.public && !hasFriendsList); ++ if (config.tag) ++ settings.tag = config.tag; ++ ++ settings.spectators = Math.min(Number(config.spectators), MAX_SPECTATORS); ++ ++ if (_.isString(config.game_name)) ++ settings.game_name = config.game_name.substring(0, Math.min(config.game_name.length, 128)); ++ ++ _.forEach(_.keys(lobbyModel.settings.game_options), function (key) { ++ if (lobbyModel.settings.game_options[key] !== game_options[key]) { ++ var name = key.replace(/_/g, ' '); ++ var message = name + ' ' + ++ (_.isBoolean(game_options[key]) ++ ? (!!game_options[key] ? 'enabled' : 'disabled') ++ : ('changed')) ++ + '.'; ++ server.broadcastEventMessage('', message, 'settings'); ++ } ++ }); ++ ++ settings.game_options = game_options; ++ ++ var nameChangeOnly = false; ++ ++ if (settings.game_name !== lobbyModel.settings.game_name) { ++ var currentSettings = _.cloneDeep(lobbyModel.settings); ++ delete currentSettings.game_name; ++ var newSettings = _.cloneDeep(settings); ++ delete newSettings.game_name; ++ ++ nameChangeOnly = _.isEqual(currentSettings, newSettings); ++ } ++ ++ lobbyModel.changeSettings(settings); ++ ++ if (!nameChangeOnly) { ++ lobbyModel.unreadyAllPlayers(); ++ } ++ ++ response.succeed(); ++} ++ ++function maybeStartLandingState() { ++ debug_log('maybeStartLandingState'); ++ if (!lobbyModel.control.starting || !lobbyModel.control.system_ready || !lobbyModel.control.sim_ready) ++ return; ++ ++ lobbyModel.updateClientState(); ++ ++ var final_data = lobbyModel.getFinalData(); ++ ++ try { ++ if (server_utils.log_lobby_description) { ++ console.log('final lobby data:'); ++ console.log(JSON.stringify(final_data, null, '\t')); ++ } ++ } ++ catch (e) { ++ console.log('final lobby data: failed.'); // this is *not* expected. ++ }; ++ ++ hasStartedPlaying = true; ++ main.setState(main.states.landing, final_data); ++} ++ ++ ++ ++function playerMsg_startCountdown(msg) { ++ debug_log('playerMsg_startGame'); ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ var player = lobbyModel.players[msg.client.id]; ++ player.ready = true; ++ ++ function not_ready (){ ++ return _.some(lobbyModel.players, function (value) { ++ return !value.ready && !value.spectator; ++ }); ++ } ++ ++ if (not_ready()) { ++ player.ready = false; ++ return response.fail("Not ready."); ++ ++ } ++ ++ var lobbyValidationResult = lobbyModel.validateSetup(); ++ if (lobbyValidationResult) { ++ player.ready = false; ++ return response.fail("Invalid game setup - " + lobbyValidationResult); ++ } ++ ++ if (!lobbyModel.control.sim_ready) { ++ player.ready = false; ++ return response.fail("Server is not done gerating planets"); ++ } ++ ++ lobbyModel.updatePlayerState(); ++ lobbyModel.changeControl({ countdown: true }); ++ ++ function startGame() { ++ ++ server.broadcastEventMessage('', -1, 'countdown'); ++ ++ server.broadcastEventMessage('', 'Game is starting.'); ++ ++ lobbyModel.changeControl({ starting: true }); ++ maybeStartLandingState(); ++ } ++ ++ var count = _.has(msg, 'countdown') ? msg.countdown : START_GAME_DELAY; ++ function countdownToStartGame() { ++ server.broadcastEventMessage('', count, 'countdown'); ++ count -= 1; ++ ++ if (count > 0) ++ setTimeout(countdownToStartGame, 1000); ++ else ++ setTimeout(startGame, 1000); ++ } ++ ++ if (lobbyModel.totalCurrentPlayers() < 2) ++ startGame(); ++ else { ++ server.broadcastEventMessage('', 'Game will start in ' + START_GAME_DELAY + ' seconds.'); ++ countdownToStartGame(); ++ } ++ ++ response.succeed(); ++} ++ ++function playerMsg_setPrimaryColorIndex(msg) { ++ debug_log('playerMsg_setPrimaryColorIndex'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ lobbyModel.setPrimaryColorIndex(msg.client.id, msg.payload, false); ++ response.succeed(); ++} ++ ++function playerMsg_setPrimaryColorIndexForAI(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setPrimaryColorIndex(msg.payload.id, msg.payload.color, true); ++ response.succeed(); ++} ++ ++function playerMsg_setSecondaryColorIndex(msg) { ++ debug_log('playerMsg_setSecondaryColorIndex'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ lobbyModel.setSecondaryColorIndex(msg.client.id, msg.payload, false); ++ response.succeed(); ++} ++ ++function playerMsg_setSecondaryColorIndexForAI(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setSecondaryColorIndex(msg.payload.id, msg.payload.color, true); ++ response.succeed(); ++} ++ ++function playerMsg_setAIPersonality(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setAIPersonality(msg.payload.id, msg.payload.ai_personality); ++ response.succeed(); ++} ++ ++function playerMsg_setAILandingPolicy(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setAILandingPolicy(msg.payload.id, msg.payload.ai_landing_policy); ++ response.succeed(); ++} ++ ++function playerMsg_setAICommander(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setAICommander(msg.payload.id, msg.payload.ai_commander); ++ ++ response.succeed(); ++} ++ ++function playerMsg_setEconomyFactor(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setEconomyFactor(msg.payload.id, msg.payload.economy_factor); ++ response.succeed(); ++} ++ ++function playerMsg_updateCommander(msg) { ++ debug_log('playerMsg_updateCommander'); ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!msg.payload || !msg.payload.commander || !player) ++ return response.fail("Invalid message"); ++ ++ player.setCommander(msg.payload.commander); ++ ++ return response.succeed(); ++} ++ ++function playerMsg_joinArmy(msg) { ++ debug_log('playerMsg_joinArmy'); ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!msg.payload || !player) ++ return response.fail("Invalid message"); ++ ++ if (msg.payload.commander) ++ player.setCommander(msg.payload.commander); ++ ++ lobbyModel.removePlayerFromArmy(msg.client.id); ++ if (lobbyModel.addPlayerToArmy(msg.client.id, msg.payload.army)) { ++ server.broadcastEventMessage(player.client.name, ' has joined an army.'); ++ response.succeed(); ++ } else { ++ response.fail("Unable to add player to army"); ++ } ++} ++ ++function playerMsg_toggleReady(msg) { ++ debug_log('playerMsg_toggleReady'); ++ ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!player) ++ return response.fail("Invalid message"); ++ ++ if (client_state.control.countdown) ++ return response.fail("Cannot change ready after countdown has started."); ++ ++ player.ready = !player.ready; ++ ++ lobbyModel.updatePlayerState(); ++ ++ server.broadcastEventMessage(player.client.name, player.ready ? ' is now ready.' : ' is no longer ready.'); ++ ++ response.succeed(); ++} ++ ++function playerMsg_leaveArmy(msg) { ++ debug_log('playerMsg_leaveArmy'); ++ var response = server.respond(msg); ++ ++ var player = lobbyModel.players[msg.client.id]; ++ if (!player) ++ return; ++ ++ if (lobbyModel.removePlayerFromArmy(msg.client.id, { clear_color: true, set_spectator: true })) { ++ server.broadcastEventMessage(player.client.name, ' is now a spectator.'); ++ response.succeed(); ++ } else { ++ response.fail('Could not remove you from the army'); ++ } ++} ++ ++function playerMsg_chatMessage(msg) { ++ debug_log('playerMsg_chatMessage'); ++ var response = server.respond(msg); ++ if (!msg.payload || !msg.payload.message) ++ return response.fail("Invalid message"); ++ server.broadcast({ ++ message_type: 'chat_message', ++ payload: { ++ player_name: msg.client.name, ++ message: msg.payload.message ++ } ++ }); ++ response.succeed(); ++} ++ ++function playerMsg_leave(msg) { ++ debug_log('playerMsg_leave'); ++ var response = server.respond(msg); ++ ++ lobbyModel.removePlayer(msg.client.id); ++ ++ response.succeed(); ++} ++ ++function playerMsg_kick(msg) { ++ debug_log('playerMsg_kick'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ var id = msg.payload.id; ++ var player = lobbyModel.players[id]; ++ ++ if (!bouncer.isPlayerMod(msg.client.id)) ++ return response.fail("Only mods can kick."); ++ ++ if (bouncer.isPlayerMod(id)) ++ return response.fail("Mods cannot be kicked."); ++ ++ if (!player) ++ return response.fail("Already left"); ++ ++ bouncer.addPlayerToBlacklist(id); ++ ++ lobbyModel.kickPlayer(id); ++ ++ response.succeed(); ++} ++ ++function playerMsg_promoteToMod(msg) { ++ debug_log('playerMsg_promoteToMod'); ++ var response = server.respond(msg); ++ var id = msg.payload.id; ++ var player = lobbyModel.players[id]; ++ ++ if (!bouncer.isPlayerMod(msg.client.id)) ++ return response.fail("Only mods can promote."); ++ ++ if (!player) ++ return response.fail("Player is absent"); ++ response.succeed(); ++ ++ bouncer.addPlayerToModlist(id); ++ lobbyModel.updatePlayerState(); ++} ++ ++function playerMsg_setLoading(msg) { ++ debug_log('playerMsg_setLoading'); ++ ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!player) ++ return response.fail("Invalid message"); ++ ++ player.loading = msg.payload.loading; ++ ++ lobbyModel.updatePlayerState(); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modDataAvailable(msg) { ++ debug_log('playerMsg_modDataAvailable'); ++ ++ var response = server.respond(msg); ++ ++ if (lobbyModel.creator !== msg.client.id) { ++ return response.fail("Mod data can only be provided by lobby creator"); ++ } ++ ++ var auth_token = ""; ++ var hasMods = false; ++ var mods = server.getMods(); ++ if (mods !== undefined) { ++ auth_token = mods.auth_token || ""; ++ if (mods.mounted_mods !== undefined && mods.mounted_mods.length > 0) { ++ hasMods = true; ++ } ++ } ++ if (hasMods) { ++ return response.fail("Mod data is already mounted"); ++ } ++ ++ response.succeed({ "auth_token": auth_token }); ++} ++ ++function check_cheat(cheatname, callback) { ++ file.load('/server-script/modroot/cheat_' + cheatname + '.json', function (data) { ++ var cheat_enabled = false; ++ if (data !== undefined && data.length > 0) { ++ var config = JSON.parse(data); ++ if (config.cheat_flags !== undefined) { ++ if (config.cheat_flags[cheatname]) { ++ main.cheats.cheat_flags[cheatname] = true; ++ main.cheats.cheat_flags.any_enabled = true; ++ main.cheats.cheat_flags.cheat_mod_enabled = true; ++ cheat_enabled = true; ++ console.log('CHEATS: Mod enabled cheat: ' + cheatname); ++ ++ server.broadcast({ ++ message_type: 'set_cheat_config', ++ payload: main.cheats ++ }); ++ } ++ } ++ } ++ if (callback !== undefined) { ++ callback(cheat_enabled); ++ } ++ }); ++} ++ ++function playerMsg_modDataUpdated(msg) { ++ check_cheat('allow_change_vision'); ++ check_cheat('allow_change_control'); ++ check_cheat('allow_create_unit'); ++ check_cheat('allow_mod_data_updates', function (cheat_enabled) { ++ if (cheat_enabled) { ++ server.disableWriteReplay(); ++ } else { ++ server.resetModUpdateAuthToken(); ++ } ++ }); ++ ++ _.forEach(server.clients, function (client) { ++ if (client.id !== msg.client.id) { ++ client.downloadModsFromServer(); ++ } else { ++ client.message({ ++ message_type: 'mount_mod_file_data', ++ payload: {} ++ }); ++ } ++ }); ++} ++ ++function playerMsg_requestCheatConfig(msg) { ++ debug_log('playerMsg_requestCheatConfig'); ++ ++ var response = server.respond(msg); ++ ++ response.succeed({ "cheat_config": main.cheats }); ++} ++ ++function initOwner(owner) { ++ debug_log('initOwner'); ++ server.maxClients = 1; ++ ++ if (!owner) { ++ var testConfig = _.cloneDeep(require('test').exampleConfig); ++ main.setState(main.states.lobby, testConfig); ++ return; ++ } ++ ++ bouncer.addPlayerToModlist(owner.id); ++ ++ var client_data = { uberid: '', password: '', uuid: '' }; ++ try { ++ client_data = JSON.parse(owner.data); ++ bouncer.setUUID(client_data.uuid); ++ } ++ catch (error) { ++ debug_log('js initOwner : unable to parse owner data'); ++ } ++} ++ ++exports.url = 'coui://ui/main/game/new_game/new_game.html'; ++exports.enter = function (owner) { ++ ++ _.forEachRight(cleanupOnEntry, function (c) { c(); }); ++ ++ lobbyModel = new LobbyModel(); ++ cleanupOnEntry.push(function () { lobbyModel = undefined; }); ++ ++ initOwner(owner); ++ lobbyModel.changeControl({ has_first_config: false, countdown: false, starting: false, system_ready: false, sim_ready: false }); ++ ++ utils.pushCallback(sim.planets, 'onReady', function (onReady) { ++ debug_log('sim.planets.onReady'); ++ lobbyModel.changeControl({ system_ready: true }); ++ sim.create(); ++ maybeStartLandingState(); ++ return onReady; ++ }); ++ cleanup.push(function () { sim.planets.onReady.pop(); }); ++ ++ utils.pushCallback(sim, 'onReady', function (onReady) { ++ debug_log('sim.onReady'); ++ lobbyModel.changeControl({ sim_ready: true }); ++ maybeStartLandingState(); ++ return onReady; ++ }); ++ cleanup.push(function () { sim.onReady.pop(); }); ++ ++ utils.pushCallback(server, 'onConnect', function (onConnect, client, reconnect) { ++ debug_log('onConnect'); ++ var client_data = { uberid: '', password: '', uuid: '' }; ++ try { ++ client_data = JSON.parse(client.data); ++ debug_log(client); ++ } ++ catch (e) { ++ debug_log('js utils.pushCallback : unable to parse client.data'); ++ server.rejectClient(client, 'Bad Client data'); ++ return onConnect; ++ } ++ ++ if (!bouncer.isPlayerValid(client_data.uberid, client_data.password, client_data.uuid, lobbyModel.settings.public)) { ++ console.log('invalid credentials'); ++ server.rejectClient(client, 'Credentials are invalid'); ++ return onConnect; ++ } ++ ++ if (!reconnect) { ++ var max = Math.min(MAX_CLIENTS, lobbyModel.numPlayerSlots() + main.spectators); ++ if (lobbyModel.numPlayers() >= max) { ++ console.error('lobby at capacity. client rejected.'); ++ server.rejectClient(client, 'No room'); ++ return onConnect; ++ } ++ } ++ ++ utils.pushCallback(client, 'onDisconnect', function (onDisconnect) { ++ if (!hasStartedPlaying) { /* don't kill the client unless we have not started any other states. */ ++ console.log('removing disconnected player from the lobby.'); ++ lobbyModel.removePlayer(client.id); ++ } ++ return onDisconnect; ++ }); ++ cleanup.push(function () { ++ if (client.onDisconnect) { ++ console.log('remove disconnect handler'); ++ client.onDisconnect.pop(); ++ } ++ /* removePlayer calls client.kill(), which will destroy the onDisconnect handler */ ++ }); ++ ++ var players = _.filter(lobbyModel.players, { 'spectator': false }); ++ var options = { mod: bouncer.isPlayerMod(client.id), creator: client.id === lobbyModel.creator }; ++ /* force the player to be a spectator */ ++ if (players.length >= MAX_PLAYERS) ++ options.spectator = true; ++ ++ if (!lobbyModel.players.hasOwnProperty(client.id)) ++ lobbyModel.addPlayer(client, options); ++ else ++ lobbyModel.updatePlayerState(); ++ ++ lobbyModel.addPlayersToSlotsIfPossible(); ++ ++ var player = lobbyModel.players[client.id]; ++ if (player.armyIndex === -1) /* make the player a spectator if there is no room */ ++ player.spectator = true; ++ ++ debug_log('calling client.downloadModsFromServer'); ++ client.downloadModsFromServer(); ++ ++ client.message({ ++ message_type: 'set_cheat_config', ++ payload: main.cheats ++ }); ++ ++ return onConnect; ++ }); ++ cleanupOnEntry.push(function () { server.onConnect.pop(); }); ++ ++ lobbyModel.creator = owner.id; ++ lobbyModel.addPlayer(owner, { mod: true, creator: true }); ++ bouncer.addPlayerToModlist(owner.id); ++ ++ _.forEach(server.clients, function (client) { ++ if (client !== owner) { ++ lobbyModel.addPlayer(client, { mod: false, creator: false }); ++ } ++ }); ++ ++ var removeHandlers = server.setHandlers({ ++ reset_armies: playerMsg_resetArmies, ++ add_army: playerMsg_addArmy, ++ remove_army: playerMsg_removeArmy, ++ add_ai: playerMsg_addAI, ++ modify_system: playerMsg_modifySystem, ++ modify_army: playerMsg_modifyArmy, ++ modify_bouncer: playerMsg_modifyBouncer, ++ modify_settings: playerMsg_modifySettings, ++ start_game: playerMsg_startCountdown, ++ set_primary_color_index: playerMsg_setPrimaryColorIndex, ++ set_primary_color_index_for_ai: playerMsg_setPrimaryColorIndexForAI, ++ set_secondary_color_index: playerMsg_setSecondaryColorIndex, ++ set_secondary_color_index_for_ai: playerMsg_setSecondaryColorIndexForAI, ++ set_ai_personality: playerMsg_setAIPersonality, ++ set_ai_landing_policy: playerMsg_setAILandingPolicy, ++ set_ai_commander: playerMsg_setAICommander, ++ set_econ_factor: playerMsg_setEconomyFactor, ++ join_army: playerMsg_joinArmy, ++ toggle_ready: playerMsg_toggleReady, ++ leave_army: playerMsg_leaveArmy, ++ update_commander: playerMsg_updateCommander, ++ chat_message: playerMsg_chatMessage, ++ leave: playerMsg_leave, ++ kick: playerMsg_kick, ++ promote_to_mod: playerMsg_promoteToMod, ++ set_loading: playerMsg_setLoading, ++ mod_data_available: playerMsg_modDataAvailable, ++ mod_data_updated: playerMsg_modDataUpdated, ++ request_cheat_config: playerMsg_requestCheatConfig ++ }); ++ cleanup.push(function () { removeHandlers(); }); ++ ++ lobbyModel.updateBeacon(); ++ ++ return client_state; ++}; ++ ++exports.exit = function (newState) { ++ _.forEachRight(cleanup, function (c) { c(); }); ++ cleanup = []; ++ ++ return true; ++}; ++ ++main.gameModes.lobby = exports; ++// This is for backwards compatibility. Game used to ask for 'Config' game mode. ++main.gameModes.Config = exports; +diff -Naur server-script.ori/states/lobby.js.bkp server-script/states/lobby.js.bkp +--- server-script.ori/states/lobby.js.bkp 1970-01-01 01:00:00.000000000 +0100 ++++ server-script/states/lobby.js.bkp 2016-02-06 19:41:37.000000000 +0100 +@@ -0,0 +1,1885 @@ ++var main = require('main'); ++var sim_utils = require('sim_utils'); ++var server_utils = require('server_utils'); ++var content_manager = require('content_manager'); ++var utils = require('utils'); ++var bouncer = require('bouncer'); ++var env = require('env'); ++var _ = require('thirdparty/lodash'); ++var commander_manager = require('lobby/commander_manager'); ++var color_manager = require('lobby/color_manager'); ++ ++var getAIName = (function () { ++ ++ var ai_names = _.shuffle(require('ai_names_table').data); /* shuffle returns a new collection */ ++ ++ return function () { ++ var name = ai_names.shift(); ++ ai_names.push(name); ++ return name; ++ } ++})(); ++ ++var used_ai_ids = []; ++var last_ai_number = 0; ++ ++var getAIId = function () { ++ if (used_ai_ids.length) ++ return used_ai_ids.pop(); ++ else { ++ last_ai_number++; ++ return '' + last_ai_number; ++ } ++} ++ ++var returnAIId = function (id) { ++ used_ai_ids.push(id); ++} ++ ++var commanders = new commander_manager.CommanderManager(); ++var colors = new color_manager.ColorManager(); ++ ++var START_GAME_DELAY = 5; // In s. ++var MAX_PLAYERS = 10; ++var MAX_SPECTATORS = 3; ++var MAX_CLIENTS = MAX_PLAYERS + MAX_SPECTATORS; ++var DEFAULT_LOBBY_TAG = ''; ++var DEFAULT_LOBBY_NAME = ''; ++var DEFAULT_GAME_TYPE = 'FreeForAll'; ++var VALID_GAME_TYPES = [DEFAULT_GAME_TYPE, 'TeamArmies', 'VersusAI']; ++var isValidGameType = function (game_type) { ++ return VALID_GAME_TYPES.indexOf(game_type) != -1; ++}; ++var isFFAType = function (game_type) { ++ return game_type === 'FreeForAll'; ++}; ++ ++var DEFAULT_GAME_OPTIONS = { ++ dynamic_alliances: false, ++ dynamic_alliance_victory: false, ++ bounty_mode: false, ++ bounty_value: 0.5, ++ sandbox: false, ++ listen_to_spectators: false, ++ game_type: DEFAULT_GAME_TYPE, ++ land_anywhere: false, ++}; ++ ++ ++var alliance_groups = _.range(1, MAX_CLIENTS / 2 + 1); /* 0 indicates no alliance */ ++ ++var debugging = false; ++ ++function debug_log(object) { ++ if (debugging) ++ console.log(JSON.stringify(object,null,'\t')); ++} ++ ++var client_state = { ++ armies: [], ++ players: [], ++ colors: [], ++ system: {}, ++ settings: {}, ++ control: {} ++}; ++ ++/* the lobby stays up after we transition out of the state, so that it can handle login/rejoin attemps ++ if a player leaves the lobby, we kill the client (but they can still rejoin); however, if a player ++ leaves after the game has moved on to another state (usually due to a disconnect error), ++ we don't want to kill the client, since the playing state will setup a disconnect timer. */ ++var hasStartedPlaying = false; ++ ++function PlayerModel(client, options) { ++ var self = this; ++ ++ self.client = client; /* data, debugDesc, connected, id, name */ ++ try { ++ self.client_data = JSON.parse(client.data); ++ } catch (error) { ++ debug_log("Unable to parse client data for player"); ++ debug_log(error); ++ self.client_data = null; ++ } ++ self.creator = !!options.creator; ++ self.spectator = !!options.spectator; ++ ++ self.ai = !!options.ai; ++ self.personality = options.personality || ''; ++ ++ /* for now, only AI have landing policy. values: ['no_restriction', 'on_player_planet', 'off_player_planet'] */ ++ self.landingPolicy = (self.ai && options.landing_policy) ? options.landing_policy : 'no_restriction'; ++ ++ self.commander = commanders.getRandomDefaultCommanderSpec(); ++ ++ self.ready = self.ai ? true : false; ++ self.loading = self.ai ? false : true; ++ ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ ++ self.colorIndex = (self.ai || self.spectator) ? [-1, -1] : colors.takeRandomAvailableColor(); ++ ++ self.economyFactor = _.isFinite(options.economy_factor) ? options.economy_factor : 1.0; ++ self.economyFactor = Math.min(Math.max(0.0, self.economyFactor), 5.0); ++ ++ if (!!options.mod) ++ bouncer.addPlayerToModlist(client.id); ++ else ++ bouncer.removePlayerFromModlist(client.id); ++ ++ self.nextPrimaryColorIndex = function () { ++ if (self.spectator) ++ return; ++ ++ self.returnColorIndex(); ++ self.colorIndex = [colors.takeNextAvailableColorIndex(self.colorIndex[0]), 0]; ++ }; ++ ++ self.nextSecondaryColorIndex = function () { ++ if (self.spectator) ++ return; ++ ++ var colors = colors.getSecondaryColorsFor(self.colorIndex[0]); ++ var max = colors.getNumberOfColors(); ++ self.colorIndex[1] = (self.colorIndex[1] + 1) % max; ++ } ++ ++ self.clearColorIndex = function () { ++ self.returnColorIndex(); ++ self.colorIndex = [-1, -1]; ++ }; ++ ++ self.maybeTakeColorIndex = function () { ++ if (self.spectator || self.colorIndex[0] !== -1) ++ return; ++ self.colorIndex = colors.takeRandomAvailableColor(); ++ }; ++ ++ self.setPrimaryColorIndex = function (index) { ++ if (self.spectator) ++ return; ++ self.colorIndex = [colors.maybeGetNewColorIndex(self.colorIndex[0], index), self.colorIndex[1]]; ++ }; ++ ++ self.setSecondaryColorIndex = function (index) { ++ if (self.spectator) ++ return; ++ ++ self.colorIndex = [self.colorIndex[0], index]; ++ }; ++ ++ self.returnColorIndex = function () { ++ if (self.colorIndex[0] === -1) ++ return; ++ ++ colors.returnColorIndex(self.colorIndex[0]); ++ self.colorIndex = [-1, -1]; ++ }; ++ ++ self.adjustArmyIndexAboveTarget = function (target_index, delta) { ++ if (self.armyIndex > target_index) ++ self.armyIndex += delta; ++ }; ++ ++ self.adjustSlotIndexAboveTarget = function (target_index, delta) { ++ if (self.slotIndex > target_index) ++ self.slotIndex += delta; ++ }; ++ ++ self.processRemoveIndex = function (target_index) { ++ if (self.armyIndex === target_index) { ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ } ++ else ++ self.adjustArmyIndexAboveTarget(target_index, -1); ++ }; ++ ++ self.processRemoveSlotAtIndex = function (target_index, target_slot) { ++ if (self.armyIndex === target_index) { ++ ++ if (self.slotIndex === target_slot) { ++ self.armyIndex = -1; ++ self.slotIndex = -1; ++ } ++ else ++ self.adjustSlotIndexAboveTarget(target_slot, -1); ++ } ++ }; ++ ++ self.setAIPersonality = function (personality) { ++ if (!self.ai) ++ return; ++ ++ self.personality = personality; ++ }; ++ ++ self.setAILandingPolicy = function (policy) { ++ if (!self.ai) ++ return; ++ ++ self.landingPolicy = policy; ++ }; ++ ++ self.setEconomyFactor = function (value) { ++ value = Math.min(Math.max(0.0, value), 5.0); ++ self.economyFactor = value; ++ }; ++ ++ self.asJson = function () { ++ return { ++ name: self.client.name, ++ id: self.client.id, ++ ai: self.ai, ++ personality: self.personality, ++ landing_policy: self.landingPolicy, ++ economy_factor: self.economyFactor, ++ connected: self.ai ? true : self.client.connected, ++ creator: self.ai ? false : self.creator, ++ mod: self.ai ? false : bouncer.isPlayerMod(self.client.id), ++ army_index: self.armyIndex, ++ slot_index: self.slotIndex, ++ commander: self.commander, ++ ready: self.ready, ++ loading: self.loading, ++ color: colors.getColorFor(self.colorIndex), ++ color_index: self.colorIndex[0] ++ }; ++ }; ++ ++ self.finalize = function () { ++ return { ++ name: self.client.name, ++ commander: self.commander, ++ client: self.client, ++ army: self.armyIndex, ++ slot: self.slotIndex, ++ ai: self.ai, ++ personality: self.personality, ++ landing_policy: self.landingPolicy ++ }; ++ }; ++ ++ self.setCommander = function (new_commander) { ++ if (!new_commander || self.commander === new_commander) ++ return; ++ ++ var commanderObject = commanders.getCommanderObjectName(new_commander); ++ if (!commanderObject) { ++ debug_log("Failed to locate item " + (new_commander)); ++ return; ++ } ++ ++ if (!self.client.validateItem(commanderObject)) { ++ debug_log("Failed to validate ownership of " + (commanderObject)); ++ return; ++ } ++ ++ self.commander = new_commander; ++ ++ lobbyModel.updatePlayerState(); ++ } ++}; ++ ++function ArmyModel(options) { ++ var self = this; ++ ++ self.slots = options.slots ? Math.max(options.slots, 1) : 1; ++ self.alliance = !!options.alliance; ++ self.allianceGroup = 0; /* 0 indicates no alliance */ ++ ++ self.asJson = function () { ++ return { ++ slots: self.slots, ++ alliance: self.alliance ++ }; ++ }; ++ ++ self.finalizeAsConfig = function () { ++ var s = []; ++ s.length = self.slots; ++ ++ _.forEach(s, function (element, index) { ++ s[index] = 'player'; ++ }); ++ ++ return { ++ slots: s, ++ alliance_group: self.allianceGroup ++ }; ++ }; ++}; ++ ++function LobbyModel(creator) { ++ var self = this; ++ ++ self.maxNumberOfAllowedPlayers = 1; ++ self.players = {}; ++ self.armies = []; ++ self.system = {}; ++ self.minimalSystemDescription = {}; /* system sans custom planet source */ ++ self.config = {}; ++ self.settings = { ++ hidden: true, ++ game_options: _.cloneDeep(DEFAULT_GAME_OPTIONS), ++ required_content: content_manager.getRequiredContent(), ++ }; /* game_mode spectators broadcast_delay private friends public tag */ ++ self.control = {}; /* has_first_config starting system_ready sim_ready */ ++ ++ self.creator = creator; ++ ++ self.dirty = {}; ++ self.allDirty = { ++ control: true, ++ system: true, ++ players: true, ++ armies: true, ++ color: true, ++ settings: true, ++ beacon: true ++ }; ++ // These dirty flags (on the left) will be set when the associated flags (on the right) are set ++ self.chainDirty = { ++ beacon: ['players', 'armies', 'system', 'settings'], ++ color: ['players'] ++ }; ++ ++ // Set a given flag to "dirty" (e.g. self.setDirty({control: true}); ) ++ self.setDirty = function(flags) { ++ var needsApply = _.isEmpty(self.dirty); ++ _.assign(self.dirty, flags); ++ ++ while (!function() { ++ return _.all(self.chainDirty, function(flags, key) { ++ if (self.dirty[key]) ++ return true; ++ var chain = _.pick(self.dirty, flags); ++ if (_.isEmpty(chain)) ++ return true; ++ self.dirty[key] = true; ++ return false; ++ }); ++ }()); ++ ++ if (needsApply) ++ _.delay(function() { self.cleanDirtyFlags(); }); ++ }; ++ ++ // Custom functions for cleaning dirty flags. (broadcast to client for all other flags) ++ self.customCleaners = { ++ beacon: function () { self.updateBeacon(); } ++ }; ++ ++ // Clean all the dirty flags ++ self.cleanDirtyFlags = function () { ++ try { ++ _.forIn(self.dirty, function(yes, key) { ++ if (self.customCleaners.hasOwnProperty(key)) ++ self.customCleaners[key](); ++ else { ++ server.broadcast({ ++ message_type: key, ++ payload: client_state[key] ++ }); ++ } ++ }); ++ } ++ catch (e) { ++ // Note: Very important that the server "mostly" works if this happens. ++ console.error('Lobby unable to clean dirty flags:', e.toString()); ++ } ++ self.dirty = {}; ++ }; ++ ++ self.isCreator = function (id) { ++ return id === self.creator; ++ }; ++ ++ self.playersInArmy = function(army_index) { ++ return _.filter(_.values(self.players), function (element){ ++ return element.armyIndex === army_index; ++ }); ++ }; ++ ++ self.aiCount = function () { ++ return _.filter(_.values(self.players), function (element) { return element.ai }).length; ++ } ++ ++ self.totalSlots = function () { ++ ++ var result = 0; ++ _.forEach(self.armies,function (element) { ++ result += element.slots; ++ }); ++ ++ return result; ++ }; ++ ++ self.totalCurrentPlayers = function () { ++ var total = 0; ++ _.forEach(self.armies, function (element, index) { ++ total += self.playersInArmy(index).length; ++ }); ++ return total; ++ }; ++ ++ self.breakArmyIntoAlliances = function (army_index) { ++ var army = self.armies[army_index]; ++ if (!army) ++ return; ++ ++ var extra = army.slots - 1; ++ army.slots = 1; ++ var options = army.asJson(); ++ ++ var group = alliance_groups.splice(0, 1)[0]; ++ ++ _.invoke(self.players, 'adjustArmyIndexAboveTarget', army_index, extra); ++ ++ _.times(extra, function (index) { /* split armies */ ++ self.armies.splice(army_index, 0, new ArmyModel(options)); /* splice modifies the array */ ++ }); ++ ++ _.forEach(self.playersInArmy(army_index), function (element) { /* add players to new armies */ ++ element.armyIndex += element.slotIndex; ++ element.slotIndex = 0; ++ ++ self.armies[element.armyIndex].allianceGroup = group; ++ }); ++ } ++ ++ self.finalizeConfig = function () { ++ return { ++ "armies": _.invoke(self.armies, 'finalizeAsConfig'), ++ "system": self.system, ++ "enable_lan": true, ++ "spectators": 0, ++ "password": "", ++ "friends": [], ++ "blocked": [], ++ "public": true, ++ "players": self.totalCurrentPlayers(), ++ "vs_ai": false, ++ "game_options": self.settings.game_options ++ }; ++ } ++ ++ self.finalizePlayers = function () { ++ var result = {}; ++ ++ _.forIn(self.players, function (value, key) { ++ result[key] = value.finalize() ++ }); ++ ++ return result; ++ }; ++ ++ self.finalizeArmies = function () { ++ var result = _.invoke(self.armies, 'finalizeAsConfig'); ++ ++ _.forIn(self.players, function (element) { ++ var army = result[element.armyIndex]; ++ if (!army) /* player is spectating */ ++ return; ++ ++ army.slots[element.slotIndex] = element.finalize(); /* insert finalized block { name, commander, client, army, ai } */ ++ ++ if (element.slotIndex === 0) { /* only use the color for the first player in the army */ ++ result[element.armyIndex].color = colors.getColorFor(element.colorIndex); /* insert expanded color */ ++ result[element.armyIndex].color_index = element.colorIndex[0]; ++ result[element.armyIndex].econ_rate = element.economyFactor; ++ } ++ ++ if (element.ai) { ++ result[element.armyIndex].personality = element.personality; ++ result[element.armyIndex].landing_policy = element.landingPolicy; ++ } ++ }); ++ ++ return result; ++ } ++ ++ self.getFinalData = function () { ++ ++ while (true) ++ { ++ var target = _.findIndex(self.armies, function (element) { ++ return element.alliance && element.slots > 1; ++ }); ++ ++ if (target === -1) ++ break; ++ ++ self.breakArmyIntoAlliances(target); ++ } ++ ++ _.invoke(self.players, 'maybeTakeColorIndex'); ++ ++ return { ++ game: lobbyModel.finalizeConfig(), ++ armies: lobbyModel.finalizeArmies(), ++ players: lobbyModel.finalizePlayers(), ++ ranked: false ++ } ++ }; ++ ++ self.updatePlayerState = function () { ++ debug_log('updatePlayerState'); ++ var players = _.invoke(self.players, 'asJson'); ++ if (_.isEqual(client_state.players, players)) ++ return; ++ client_state.players = _.cloneDeep(players); ++ self.setDirty({players: true}); ++ }; ++ ++ self.updateArmyState = function () { ++ debug_log('updateArmyState'); ++ var armies = _.invoke(self.armies, 'asJson'); ++ if (_.isEqual(client_state.armies, armies)) ++ return; ++ client_state.armies = _.cloneDeep(armies); ++ self.setDirty({ armies: true }); ++ }; ++ ++ self.updateSystemState = function () { ++ debug_log('updateSystemState'); ++ if (_.isEqual(client_state.system, self.minimalSystemDescription)) ++ return; ++ client_state.system = _.cloneDeep(self.minimalSystemDescription); ++ self.setDirty({system: true}); ++ }; ++ ++ self.changeSystem = function (system) { ++ if (_.isEqual(system, self.system)) ++ return; ++ ++ self.changeControl({ system_ready: false, sim_ready: false }); ++ self.system = system; ++ ++ self.minimalSystemDescription = utils.getMinimalSystemDescription(self.system); ++ ++ self.updateSystemState(); ++ ++ /* this will take some time. the server will be unresponsive. */ ++ sim.shutdown(false); ++ sim.systemName = lobbyModel.system.name; ++ sim.planets = self.system.planets; ++ }; ++ ++ self.updateColorState = function () { ++ debug_log('updateColorState'); ++ if (_.isEqual(client_state.colors, colors.colors)) ++ return; ++ client_state.colors = _.cloneDeep(colors.colors); ++ self.setDirty({colors: true}); ++ }; ++ ++ self.updateControlState = function () { ++ debug_log('updateControlState'); ++ if (_.isEqual(client_state.control, self.control)) ++ return; ++ client_state.control = _.cloneDeep(self.control); ++ self.setDirty({control: true}); ++ }; ++ ++ self.changeControl = function (control /* has_first_config starting system_ready sim_ready */) { ++ _.assign(self.control, control); ++ self.updateControlState(); ++ }; ++ ++ self.updateSettingsState = function () { ++ debug_log('updateSettingsState'); ++ if (_.isEqual(client_state.settings, self.settings)) ++ return; ++ client_state.settings = _.cloneDeep(self.settings); ++ ++ main.spectators = Number(self.settings.spectators); ++ ++ self.setDirty({ settings: true }); ++ }; ++ ++ self.changeSettings = function (settings /* game_mode spectators hidden friends public tag */) { ++ _.assign(self.settings, settings); ++ self.updateSettingsState(); ++ }; ++ ++ self.updateClientState = function () { ++ debug_log('updateClientState'); ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateSystemState(); ++ self.updateColorState(); ++ self.updateControlState(); ++ self.updateSettingsState(); ++ }; ++ ++ self.unreadyAllPlayers = function () { ++ debug_log('unreadyAllPlayers'); ++ _.forIn(self.players, function (element) { ++ if (element.ready && !element.ai) { ++ server.broadcastEventMessage(element.client.name, ' is no longer ready.'); ++ element.ready = false; ++ } ++ }); ++ ++ self.setDirty({ players: true }); ++ self.updatePlayerState(); ++ }; ++ ++ self.addPlayersToSlotsIfPossible = function () { ++ debug_log('addPlayersToSlotsIfPossible'); ++ ++ var army_index = 0; ++ _.forIn(self.players, function (element, key) { ++ ++ if (element.armyIndex !== -1 || element.spectator) ++ return; ++ ++ while (army_index < self.armies.length) ++ { ++ if (self.addPlayerToArmy(key, army_index)) ++ break; ++ army_index += 1; ++ } ++ }); ++ } ++ ++ self.addArmy = function (options) { ++ if (self.armies.length >= MAX_PLAYERS) ++ return; ++ ++ if (options.slots && options.slots + self.totalSlots() > MAX_PLAYERS) ++ return; ++ ++ self.unreadyAllPlayers(); ++ ++ self.armies.push(new ArmyModel(options)); ++ self.addPlayersToSlotsIfPossible(); ++ self.updateArmyState(); ++ }; ++ ++ self.removeArmy = function (army_index) { ++ var spares = []; ++ ++ self.unreadyAllPlayers(); ++ ++ self.armies.splice(army_index, 1); ++ ++ _.forEach(self.players, function (element) { ++ if (element.ai && element.armyIndex === army_index) ++ spares.push(element.client.id) ++ }); ++ ++ _.forEach(spares, self.removePlayer); ++ ++ _.invoke(self.players, 'processRemoveIndex', army_index); ++ ++ self.addPlayersToSlotsIfPossible(); ++ self.updateArmyState(); ++ self.updatePlayerState(); ++ }; ++ ++ self.modifyArmy = function (army_index, options) { ++ debug_log('modifyArmy'); ++ ++ var spares = []; ++ ++ var army = self.armies[army_index]; ++ if (!army) ++ return; ++ ++ var new_options = _.assign(army.asJson(), options); ++ ++ var ai = !!_.filter(self.playersInArmy(army_index), function (element) { return element.ai }).length; ++ if (ai) ++ new_options.alliance = true; /* override to prevent shared army with ai */ ++ ++ if (options.slots && (options.slots - army.slots + self.totalSlots()) > MAX_PLAYERS) ++ return; ++ ++ self.unreadyAllPlayers(); ++ ++ if (options.slots < army.slots) { ++ _.forEach(_.range(options.slots, army.slots), function (index) { ++ _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, index); ++ ++ _.forEach(self.players, function (element) { ++ if (element.ai && element.armyIndex === army_index && element.slotIndex == index) ++ spares.push(element.client.id) ++ }); ++ }); ++ } ++ ++ _.forEach(spares, self.removePlayer); ++ ++ self.armies[army_index] = new ArmyModel(new_options); ++ ++ self.addPlayersToSlotsIfPossible(); ++ ++ self.fixColors(); ++ ++ self.updateArmyState(); ++ self.updateColorState(); ++ self.updatePlayerState(); ++ }; ++ ++ self.numPlayerSlots = function() { ++ return utils.sum(self.armies, function(army) { return army.ai ? 0 : army.slots; }); ++ }; ++ ++ self.numPlayers = function () { ++ return _.keys(self.players).length; ++ }; ++ ++ self.updateBeacon = function () { ++ debug_log('updateBeacon'); ++ var numPlayerSlots = self.numPlayerSlots(); ++ server.maxClients = Math.min(MAX_PLAYERS, numPlayerSlots) + Math.min(MAX_SPECTATORS, main.spectators); ++ var publish = !server.closed && (self.settings.public || bouncer.getWhitelist().length) && !self.settings.hidden; ++ ++ if (publish) { ++ var full = server.clients.length >= server.maxClients; ++ ++ var modNames = []; ++ var mods = server.getMods(); ++ if (mods !== undefined && mods.mounted_mods !== undefined) { ++ _.forEach(mods.mounted_mods, function (element) { ++ modNames.push(element.display_name); ++ }); ++ } ++ ++ var player_names = _.map(_.filter(self.players, { 'spectator': false }), function (player) { return player.client.name; }); ++ var spectator_names = _.map(_.filter(self.players, { 'spectator': true }), function (player) { return player.client.name; }); ++ ++ var mode = DEFAULT_GAME_TYPE; ++ if (self.settings.game_options) ++ mode = self.settings.game_options.game_type; ++ ++ server.beacon = { ++ uuid: server.uuid(), ++ full: full, ++ started: self.control.countdown || self.control.starting, ++ players: player_names.length, ++ creator: self.creatorName(), ++ max_players: numPlayerSlots, ++ spectators: spectator_names.length, ++ max_spectators: main.spectators, ++ mode: mode, ++ mod_names: modNames, ++ cheat_config: main.cheats, ++ player_names: player_names, ++ spectator_names: spectator_names, ++ require_password: !!bouncer.doesGameRequirePassword(), ++ whitelist: bouncer.getWhitelist(), ++ blacklist: bouncer.getBlacklist(), ++ tag: self.settings.tag, ++ game: { ++ system: self.minimalSystemDescription, ++ name: self.settings.game_name ++ }, ++ required_content: content_manager.getRequiredContent(), ++ bounty_mode: self.settings.game_options ? !!self.settings.game_options.bounty_mode : false, ++ bounty_value: self.settings.game_options ? self.settings.game_options.bounty_value : 0.5, ++ sandbox: self.settings.game_options ? !!self.settings.game_options.sandbox : false ++ }; ++ } else { ++ server.beacon = null; ++ } ++ }; ++ ++ self.addPlayer = function (client, options) { ++ debug_log('addPlayer'); ++ var player = new PlayerModel(client, options); ++ self.players[client.id] = player; ++ self.updatePlayerState(); ++ self.updateColorState(); ++ ++ if (!options.ai) { ++ _.delay(server.broadcastEventMessage, 500, player.client.name, ' joined the lobby.'); ++ ++ debug_log('Player Stats: '); ++ debug_log(player.client.statistics); ++ client.incrementStatistic("TestStat_LobbiesJoined", 1); ++ } ++ }; ++ ++ self.addAI = function (payload) { ++ var player_id = getAIId(); ++ var success = false; ++ ++ if (!payload) ++ return; ++ ++ self.addPlayer({ connected: true, id: player_id, name: getAIName() }, payload.options); ++ ++ success = self.addPlayerToArmy(player_id, payload.army_index); ++ if (!success) ++ self.removePlayer(player_id); ++ }; ++ ++ self.creatorName = function () { ++ var player = self.players[self.creator]; ++ return player ? player.client.name : ''; ++ }; ++ ++ self.chooseNextPlayerAsCreator = function () { ++ debug_log('chooseNextPlayerAsCreator'); ++ if (_.isEmpty(self.players)) ++ return; ++ ++ var client = _.first(server.clients); ++ if (!client) ++ return; ++ ++ var id = client.id; ++ self.creator = id; ++ ++ var player = self.players[id]; ++ ++ player.creator = true; ++ bouncer.addPlayerToModlist(id); ++ ++ self.updatePlayerState(); ++ server.broadcastEventMessage(player.client.name, ' is now the host.'); ++ }; ++ ++ self.removePlayer = function (id) { ++ debug_log('removePlayer'); ++ ++ delete client_state.players[id]; ++ var player = self.players[id]; ++ ++ if (player) { ++ self.removePlayerFromArmy(id, { clear_color: true }); ++ ++ var ai = player.ai; ++ if (!ai) ++ server.broadcastEventMessage(player.client.name, ' has left the lobby.'); ++ ++ player.returnColorIndex(); ++ delete self.players[id]; ++ ++ if (!ai) { ++ console.log('killing client ' + id); ++ player.client.kill(); ++ ++ // Terminate empty lobbies ++ if (!main.keep_alive && !server.connected) { ++ sim.shutdown(false); ++ server.exit(); ++ return; ++ } ++ ++ if (id === self.creator) ++ self.chooseNextPlayerAsCreator(); ++ } ++ ++ if (ai) ++ returnAIId(id); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ } ++ }; ++ ++ self.kickPlayer = function (id) { ++ debug_log('kickPlayer'); ++ bouncer.addPlayerToBlacklist(id); ++ self.removePlayer(id); ++ }; ++ ++ self.addPlayerToArmy = function (player_id, army_index) { ++ debug_log('addPlayerToArmy'); ++ var player = self.players[player_id]; ++ var army = self.armies[army_index]; ++ ++ var array = self.playersInArmy(army_index); ++ var count = array.length; ++ ++ if (!player || !army || count >= army.slots) ++ return false; ++ ++ if (player.armyIndex === army_index) ++ return false; ++ ++ player.armyIndex = army_index; ++ player.slotIndex = count; ++ player.spectator = false; ++ ++ if (player.ai) ++ army.alliance = true; /* override to prevent shared army with ai */ ++ ++ self.fixColors(); ++ ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateColorState(); ++ return true; ++ }; ++ ++ self.removePlayerFromArmy = function (player_id, options) { ++ debug_log('removePlayerFromArmy'); ++ var player = self.players[player_id]; ++ ++ if (!player) ++ return false; ++ ++ if (options && options.clear_color) ++ player.clearColorIndex(); ++ ++ var army_index = player.armyIndex; ++ var slot_index = player.slotIndex; ++ ++ player.armyIndex = -1; ++ player.slotIndex = -1; ++ ++ if (options && options.set_spectator) ++ player.spectator = true; ++ ++ /* move players down to fill empty slot */ ++ _.invoke(self.players, 'processRemoveSlotAtIndex', army_index, slot_index); ++ ++ self.fixColors(); ++ ++ self.updatePlayerState(); ++ self.updateArmyState(); ++ self.updateColorState(); ++ ++ return true; ++ } ++ ++ /* this checks every army, which is excessive; however, it is very reliable. */ ++ self.fixColors = function () { ++ _.forEach(self.armies, function (element, index) { ++ self.fixColorsForArmy(index); ++ }); ++ } ++ ++ /* it would be preferrable to just call this function for each modified army */ ++ self.fixColorsForArmy = function (army_index) { ++ ++ var army = self.armies[army_index]; ++ ++ if (!army) ++ return; ++ ++ _.forEach(self.playersInArmy(army_index), function (element, index) { ++ if (index === 0 || army.alliance) ++ element.maybeTakeColorIndex(); ++ else ++ element.returnColorIndex(); /* only the first player in a shared army gets a color */ ++ }); ++ ++ self.setDirty({ colors: true }); ++ }; ++ ++ self.setPrimaryColorIndex = function (player_id, index, ai) { ++ debug_log('{{setPrimaryColorIndex}} ' + index); ++ var player = self.players[player_id]; ++ ++ if (!player || player.ai !== ai || !colors.isValidPrimaryColorIndex(index)) ++ return; ++ ++ player.setPrimaryColorIndex(index); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ }; ++ ++ self.setSecondaryColorIndex = function (player_id, index, ai) { ++ debug_log('{{setSecondaryColorIndex}} ' + index); ++ var player = self.players[player_id]; ++ ++ if (!player || player.ai !== ai || !colors.isValidColorPair(player.colorIndex[0], index)) ++ return; ++ ++ player.setSecondaryColorIndex(index); ++ ++ self.updatePlayerState(); ++ self.updateColorState(); ++ }; ++ ++ self.setAIPersonality = function (player_id, personality) { ++ debug_log('setAIPersonality'); ++ var player = self.players[player_id]; ++ ++ if (!player || !player.ai) ++ return; ++ ++ player.setAIPersonality(personality); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ }; ++ ++ self.setAILandingPolicy = function (player_id, policy) { ++ debug_log('setAILandingPolicy'); ++ var player = self.players[player_id]; ++ ++ if (!player || !player.ai) ++ return; ++ ++ player.setAILandingPolicy(policy); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ }; ++ ++ self.setEconomyFactor = function (player_id, value) { ++ debug_log('setEconomyFactor'); ++ var player = self.players[player_id]; ++ ++ if (!player) ++ return; ++ ++ if (player.economyFactor != value) ++ { ++ player.setEconomyFactor(value); ++ ++ self.unreadyAllPlayers(); ++ self.updatePlayerState(); ++ } ++ }; ++ ++ self.validateSetup = function () { ++ debug_log('validateSetup'); ++ var totalOpenSlots = self.numPlayerSlots(); ++ var totalPlayersInSlots = utils.sum(self.players, function (player) { return player.armyIndex >= 0; }); ++ ++ debug_log('Validation:'+ totalPlayersInSlots+ '/'+ totalOpenSlots); /* todo: add validation stage to control state */ ++ ++ if (totalPlayersInSlots !== totalOpenSlots) ++ return 'Empty slots encountered'; ++ }; ++ ++ self.gameType = function() { ++ if (!self.settings.game_options) ++ return null; ++ return self.settings.game_options.game_type; ++ }; ++}; ++ ++var lobbyModel; ++ ++var cleanup = []; ++var cleanupOnEntry = []; ++ ++function allowChangesFrom(client) { ++ if (!client || !lobbyModel.isCreator(client.id)) ++ return false; ++ if (lobbyModel.control.countdown) ++ return false; ++ if (lobbyModel.control.starting) ++ return false; ++ ++ return true; ++} ++ ++function playerMsg_resetArmies(msg){ ++ debug_log('playerMsg_resetArmies'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ _.forEach(lobbyModel.players, function (element, key) { ++ if (element.ai) ++ lobbyModel.removePlayer(key); ++ else ++ lobbyModel.removePlayerFromArmy(key); ++ }); ++ ++ lobbyModel.armies = []; ++ _.forEach(payload, function (element) { ++ lobbyModel.addArmy(element); ++ }); ++ ++ lobbyModel.changeControl({ has_first_config: true }); ++ ++ response.succeed(); ++}; ++ ++function playerMsg_addArmy(msg /* client payload */){ ++ debug_log('playerMsg_addArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!payload.options) ++ return response.fail("Not allowed."); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.addArmy(payload.options); ++ ++ response.succeed(); ++} ++ ++function playerMsg_removeArmy(msg){ ++ debug_log('playerMsg_removeArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.removeArmy(payload.army_index); ++ ++ response.succeed(); ++} ++ ++function playerMsg_addAI(msg) { ++ debug_log('playerMsg_addAI'); ++ ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.addAI(payload); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modifySystem(msg) { ++ debug_log('modifySystem'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ var systemValidationResult = sim_utils.validateSystemConfig(msg.payload); ++ if (_.isString(systemValidationResult)) { ++ debug_log('Invalid system'); ++ return response.fail("Invalid system provided - " + systemValidationResult); ++ } ++ else ++ systemValidationResult.then( function () { ++ lobbyModel.changeSystem(msg.payload); ++ response.succeed(); ++ }); ++} ++ ++function playerMsg_modifyArmy(msg){ ++ debug_log('modifyArmy'); ++ var response = server.respond(msg); ++ var payload = msg.payload; ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.modifyArmy(payload.army_index, payload.options); ++ ++ response.succeed(); ++} ++ ++function updateBouncer(config) { ++ ++ if (config.password || bouncer.doesGameRequirePassword()) ++ bouncer.setPassword(config.password); ++ ++ bouncer.clearWhitelist(); ++ var hasFriendsList = config.friends && config.friends.length; ++ if (hasFriendsList) { ++ _.forEach(config.friends, function (element) { bouncer.addPlayerToWhitelist(element); }); ++ } ++ ++ bouncer.clearBlacklist(); ++ var hasBlockList = config.blocked && config.blocked.length; ++ if (hasBlockList) { ++ _.forEach(config.blocked, function (element) { bouncer.addPlayerToBlacklist(element); }); ++ } ++} ++ ++ ++function playerMsg_modifyBouncer(msg) { ++ debug_log('playerMsg_modifyBouncer'); ++ ++ var config = msg.payload; ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ updateBouncer(config); ++ ++ lobbyModel.setDirty({ beacon: true }); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modifySettings(msg) { ++ ++ debug_log('playerMsg_modifySettings'); ++ var config = msg.payload; ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) { ++ lobbyModel.setDirty({ settings: true }); ++ return response.fail("Not allowed."); ++ } ++ ++ var game_options = _.cloneDeep(DEFAULT_GAME_OPTIONS); ++ ++ var settings = { ++ spectators: MAX_SPECTATORS, ++ hidden: false, ++ friends: false, ++ public: true, ++ tag: DEFAULT_LOBBY_TAG, ++ game_name: DEFAULT_LOBBY_NAME, ++ required_content: content_manager.getRequiredContent(), ++ }; ++ ++ if (config.game_options) ++ game_options.game_type = config.game_options.game_type; ++ else if (client_state.settings && client_state.settings.game_options) ++ game_options.game_type = client_state.settings.game_options.game_type; ++ ++ if (!isValidGameType(game_options.game_type)) ++ game_options.game_type = DEFAULT_GAME_TYPE; ++ ++ updateBouncer(config); ++ var hasFriendsList = bouncer.getWhitelist().length; ++ ++ if (config.game_options) ++ { ++ game_options.land_anywhere = !!config.game_options.land_anywhere; ++ ++ if (isFFAType(game_options.game_type)) { ++ game_options.dynamic_alliances = !!config.game_options.dynamic_alliances; ++ if (game_options.dynamic_alliances) ++ game_options.dynamic_alliance_victory = !!config.game_options.dynamic_alliance_victory; ++ } ++ if (config.game_options.bounty_mode) ++ game_options.bounty_mode = _.contains(content_manager.getRequiredContent(), 'PAExpansion1') && !!config.game_options.bounty_mode; ++ if (config.game_options.bounty_value) ++ game_options.bounty_value = config.game_options.bounty_value; ++ if (config.game_options.sandbox) ++ game_options.sandbox = !!config.game_options.sandbox; ++ if (config.game_options.listen_to_spectators) ++ game_options.listen_to_spectators = !!config.game_options.listen_to_spectators; ++ } ++ ++ settings.hidden = (!hasFriendsList && !config.public); ++ settings.friends = !!hasFriendsList; ++ settings.public = (config.public && !hasFriendsList); ++ if (config.tag) ++ settings.tag = config.tag; ++ ++ settings.spectators = Math.min(Number(config.spectators), MAX_SPECTATORS); ++ ++ if (_.isString(config.game_name)) ++ settings.game_name = config.game_name.substring(0, Math.min(config.game_name.length, 128)); ++ ++ _.forEach(_.keys(lobbyModel.settings.game_options), function (key) { ++ if (lobbyModel.settings.game_options[key] !== game_options[key]) { ++ var name = key.replace(/_/g, ' '); ++ var message = name + ' ' + ++ (_.isBoolean(game_options[key]) ++ ? (!!game_options[key] ? 'enabled' : 'disabled') ++ : ('changed')) ++ + '.'; ++ server.broadcastEventMessage('', message, 'settings'); ++ } ++ }); ++ ++ settings.game_options = game_options; ++ ++ var nameChangeOnly = false; ++ ++ if (settings.game_name !== lobbyModel.settings.game_name) { ++ var currentSettings = _.cloneDeep(lobbyModel.settings); ++ delete currentSettings.game_name; ++ var newSettings = _.cloneDeep(settings); ++ delete newSettings.game_name; ++ ++ nameChangeOnly = _.isEqual(currentSettings, newSettings); ++ } ++ ++ lobbyModel.changeSettings(settings); ++ ++ if (!nameChangeOnly) { ++ lobbyModel.unreadyAllPlayers(); ++ } ++ ++ response.succeed(); ++} ++ ++function maybeStartLandingState() { ++ debug_log('maybeStartLandingState'); ++ if (!lobbyModel.control.starting || !lobbyModel.control.system_ready || !lobbyModel.control.sim_ready) ++ return; ++ ++ lobbyModel.updateClientState(); ++ ++ var final_data = lobbyModel.getFinalData(); ++ ++ try { ++ if (server_utils.log_lobby_description) { ++ console.log('final lobby data:'); ++ console.log(JSON.stringify(final_data, null, '\t')); ++ } ++ } ++ catch (e) { ++ console.log('final lobby data: failed.'); // this is *not* expected. ++ }; ++ ++ hasStartedPlaying = true; ++ main.setState(main.states.landing, final_data); ++} ++ ++ ++ ++function playerMsg_startCountdown(msg) { ++ debug_log('playerMsg_startGame'); ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ var player = lobbyModel.players[msg.client.id]; ++ player.ready = true; ++ ++ function not_ready (){ ++ return _.some(lobbyModel.players, function (value) { ++ return !value.ready && !value.spectator; ++ }); ++ } ++ ++ if (not_ready()) { ++ player.ready = false; ++ return response.fail("Not ready."); ++ ++ } ++ ++ var lobbyValidationResult = lobbyModel.validateSetup(); ++ if (lobbyValidationResult) { ++ player.ready = false; ++ return response.fail("Invalid game setup - " + lobbyValidationResult); ++ } ++ ++ if (!lobbyModel.control.sim_ready) { ++ player.ready = false; ++ return response.fail("Server is not done gerating planets"); ++ } ++ ++ lobbyModel.updatePlayerState(); ++ lobbyModel.changeControl({ countdown: true }); ++ ++ function startGame() { ++ ++ server.broadcastEventMessage('', -1, 'countdown'); ++ ++ server.broadcastEventMessage('', 'Game is starting.'); ++ ++ lobbyModel.changeControl({ starting: true }); ++ maybeStartLandingState(); ++ } ++ ++ var count = _.has(msg, 'countdown') ? msg.countdown : START_GAME_DELAY; ++ function countdownToStartGame() { ++ server.broadcastEventMessage('', count, 'countdown'); ++ count -= 1; ++ ++ if (count > 0) ++ setTimeout(countdownToStartGame, 1000); ++ else ++ setTimeout(startGame, 1000); ++ } ++ ++ if (lobbyModel.totalCurrentPlayers() < 2) ++ startGame(); ++ else { ++ server.broadcastEventMessage('', 'Game will start in ' + START_GAME_DELAY + ' seconds.'); ++ countdownToStartGame(); ++ } ++ ++ response.succeed(); ++} ++ ++function playerMsg_setPrimaryColorIndex(msg) { ++ debug_log('playerMsg_setPrimaryColorIndex'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ lobbyModel.setPrimaryColorIndex(msg.client.id, msg.payload, false); ++ response.succeed(); ++} ++ ++function playerMsg_setPrimaryColorIndexForAI(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setPrimaryColorIndex(msg.payload.id, msg.payload.color, true); ++ response.succeed(); ++} ++ ++function playerMsg_setSecondaryColorIndex(msg) { ++ debug_log('playerMsg_setSecondaryColorIndex'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ lobbyModel.setSecondaryColorIndex(msg.client.id, msg.payload, false); ++ response.succeed(); ++} ++ ++function playerMsg_setSecondaryColorIndexForAI(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setSecondaryColorIndex(msg.payload.id, msg.payload.color, true); ++ response.succeed(); ++} ++ ++function playerMsg_setAIPersonality(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setAIPersonality(msg.payload.id, msg.payload.ai_personality); ++ response.succeed(); ++} ++ ++function playerMsg_setAILandingPolicy(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setAILandingPolicy(msg.payload.id, msg.payload.ai_landing_policy); ++ response.succeed(); ++} ++ ++function playerMsg_setEconomyFactor(msg) { ++ var response = server.respond(msg); ++ ++ if (!allowChangesFrom(msg.client)) ++ return response.fail("Not allowed."); ++ ++ lobbyModel.setEconomyFactor(msg.payload.id, msg.payload.economy_factor); ++ response.succeed(); ++} ++ ++function playerMsg_updateCommander(msg) { ++ debug_log('playerMsg_updateCommander'); ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!msg.payload || !msg.payload.commander || !player) ++ return response.fail("Invalid message"); ++ ++ player.setCommander(msg.payload.commander); ++ ++ return response.succeed(); ++} ++ ++function playerMsg_joinArmy(msg) { ++ debug_log('playerMsg_joinArmy'); ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!msg.payload || !player) ++ return response.fail("Invalid message"); ++ ++ if (msg.payload.commander) ++ player.setCommander(msg.payload.commander); ++ ++ lobbyModel.removePlayerFromArmy(msg.client.id); ++ if (lobbyModel.addPlayerToArmy(msg.client.id, msg.payload.army)) { ++ server.broadcastEventMessage(player.client.name, ' has joined an army.'); ++ response.succeed(); ++ } else { ++ response.fail("Unable to add player to army"); ++ } ++} ++ ++function playerMsg_toggleReady(msg) { ++ debug_log('playerMsg_toggleReady'); ++ ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!player) ++ return response.fail("Invalid message"); ++ ++ if (client_state.control.countdown) ++ return response.fail("Cannot change ready after countdown has started."); ++ ++ player.ready = !player.ready; ++ ++ lobbyModel.updatePlayerState(); ++ ++ server.broadcastEventMessage(player.client.name, player.ready ? ' is now ready.' : ' is no longer ready.'); ++ ++ response.succeed(); ++} ++ ++function playerMsg_leaveArmy(msg) { ++ debug_log('playerMsg_leaveArmy'); ++ var response = server.respond(msg); ++ ++ var player = lobbyModel.players[msg.client.id]; ++ if (!player) ++ return; ++ ++ if (lobbyModel.removePlayerFromArmy(msg.client.id, { clear_color: true, set_spectator: true })) { ++ server.broadcastEventMessage(player.client.name, ' is now a spectator.'); ++ response.succeed(); ++ } else { ++ response.fail('Could not remove you from the army'); ++ } ++} ++ ++function playerMsg_chatMessage(msg) { ++ debug_log('playerMsg_chatMessage'); ++ var response = server.respond(msg); ++ if (!msg.payload || !msg.payload.message) ++ return response.fail("Invalid message"); ++ server.broadcast({ ++ message_type: 'chat_message', ++ payload: { ++ player_name: msg.client.name, ++ message: msg.payload.message ++ } ++ }); ++ response.succeed(); ++} ++ ++function playerMsg_leave(msg) { ++ debug_log('playerMsg_leave'); ++ var response = server.respond(msg); ++ ++ lobbyModel.removePlayer(msg.client.id); ++ ++ response.succeed(); ++} ++ ++function playerMsg_kick(msg) { ++ debug_log('playerMsg_kick'); ++ debug_log(msg); ++ var response = server.respond(msg); ++ ++ var id = msg.payload.id; ++ var player = lobbyModel.players[id]; ++ ++ if (!bouncer.isPlayerMod(msg.client.id)) ++ return response.fail("Only mods can kick."); ++ ++ if (bouncer.isPlayerMod(id)) ++ return response.fail("Mods cannot be kicked."); ++ ++ if (!player) ++ return response.fail("Already left"); ++ ++ bouncer.addPlayerToBlacklist(id); ++ ++ lobbyModel.kickPlayer(id); ++ ++ response.succeed(); ++} ++ ++function playerMsg_promoteToMod(msg) { ++ debug_log('playerMsg_promoteToMod'); ++ var response = server.respond(msg); ++ var id = msg.payload.id; ++ var player = lobbyModel.players[id]; ++ ++ if (!bouncer.isPlayerMod(msg.client.id)) ++ return response.fail("Only mods can promote."); ++ ++ if (!player) ++ return response.fail("Player is absent"); ++ response.succeed(); ++ ++ bouncer.addPlayerToModlist(id); ++ lobbyModel.updatePlayerState(); ++} ++ ++function playerMsg_setLoading(msg) { ++ debug_log('playerMsg_setLoading'); ++ ++ var response = server.respond(msg); ++ var player = lobbyModel.players[msg.client.id]; ++ ++ if (!player) ++ return response.fail("Invalid message"); ++ ++ player.loading = msg.payload.loading; ++ ++ lobbyModel.updatePlayerState(); ++ ++ response.succeed(); ++} ++ ++function playerMsg_modDataAvailable(msg) { ++ debug_log('playerMsg_modDataAvailable'); ++ ++ var response = server.respond(msg); ++ ++ if (lobbyModel.creator !== msg.client.id) { ++ return response.fail("Mod data can only be provided by lobby creator"); ++ } ++ ++ var auth_token = ""; ++ var hasMods = false; ++ var mods = server.getMods(); ++ if (mods !== undefined) { ++ auth_token = mods.auth_token || ""; ++ if (mods.mounted_mods !== undefined && mods.mounted_mods.length > 0) { ++ hasMods = true; ++ } ++ } ++ if (hasMods) { ++ return response.fail("Mod data is already mounted"); ++ } ++ ++ response.succeed({ "auth_token": auth_token }); ++} ++ ++function check_cheat(cheatname, callback) { ++ file.load('/server-script/modroot/cheat_' + cheatname + '.json', function (data) { ++ var cheat_enabled = false; ++ if (data !== undefined && data.length > 0) { ++ var config = JSON.parse(data); ++ if (config.cheat_flags !== undefined) { ++ if (config.cheat_flags[cheatname]) { ++ main.cheats.cheat_flags[cheatname] = true; ++ main.cheats.cheat_flags.any_enabled = true; ++ main.cheats.cheat_flags.cheat_mod_enabled = true; ++ cheat_enabled = true; ++ console.log('CHEATS: Mod enabled cheat: ' + cheatname); ++ ++ server.broadcast({ ++ message_type: 'set_cheat_config', ++ payload: main.cheats ++ }); ++ } ++ } ++ } ++ if (callback !== undefined) { ++ callback(cheat_enabled); ++ } ++ }); ++} ++ ++function playerMsg_modDataUpdated(msg) { ++ check_cheat('allow_change_vision'); ++ check_cheat('allow_change_control'); ++ check_cheat('allow_create_unit'); ++ check_cheat('allow_mod_data_updates', function (cheat_enabled) { ++ if (cheat_enabled) { ++ server.disableWriteReplay(); ++ } else { ++ server.resetModUpdateAuthToken(); ++ } ++ }); ++ ++ _.forEach(server.clients, function (client) { ++ if (client.id !== msg.client.id) { ++ client.downloadModsFromServer(); ++ } else { ++ client.message({ ++ message_type: 'mount_mod_file_data', ++ payload: {} ++ }); ++ } ++ }); ++} ++ ++function playerMsg_requestCheatConfig(msg) { ++ debug_log('playerMsg_requestCheatConfig'); ++ ++ var response = server.respond(msg); ++ ++ response.succeed({ "cheat_config": main.cheats }); ++} ++ ++function initOwner(owner) { ++ debug_log('initOwner'); ++ server.maxClients = 1; ++ ++ if (!owner) { ++ var testConfig = _.cloneDeep(require('test').exampleConfig); ++ main.setState(main.states.lobby, testConfig); ++ return; ++ } ++ ++ bouncer.addPlayerToModlist(owner.id); ++ ++ var client_data = { uberid: '', password: '', uuid: '' }; ++ try { ++ client_data = JSON.parse(owner.data); ++ bouncer.setUUID(client_data.uuid); ++ } ++ catch (error) { ++ debug_log('js initOwner : unable to parse owner data'); ++ } ++} ++ ++exports.url = 'coui://ui/main/game/new_game/new_game.html'; ++exports.enter = function (owner) { ++ ++ _.forEachRight(cleanupOnEntry, function (c) { c(); }); ++ ++ lobbyModel = new LobbyModel(); ++ cleanupOnEntry.push(function () { lobbyModel = undefined; }); ++ ++ initOwner(owner); ++ lobbyModel.changeControl({ has_first_config: false, countdown: false, starting: false, system_ready: false, sim_ready: false }); ++ ++ utils.pushCallback(sim.planets, 'onReady', function (onReady) { ++ debug_log('sim.planets.onReady'); ++ lobbyModel.changeControl({ system_ready: true }); ++ sim.create(); ++ maybeStartLandingState(); ++ return onReady; ++ }); ++ cleanup.push(function () { sim.planets.onReady.pop(); }); ++ ++ utils.pushCallback(sim, 'onReady', function (onReady) { ++ debug_log('sim.onReady'); ++ lobbyModel.changeControl({ sim_ready: true }); ++ maybeStartLandingState(); ++ return onReady; ++ }); ++ cleanup.push(function () { sim.onReady.pop(); }); ++ ++ utils.pushCallback(server, 'onConnect', function (onConnect, client, reconnect) { ++ debug_log('onConnect'); ++ var client_data = { uberid: '', password: '', uuid: '' }; ++ try { ++ client_data = JSON.parse(client.data); ++ debug_log(client); ++ } ++ catch (e) { ++ debug_log('js utils.pushCallback : unable to parse client.data'); ++ server.rejectClient(client, 'Bad Client data'); ++ return onConnect; ++ } ++ ++ if (!bouncer.isPlayerValid(client_data.uberid, client_data.password, client_data.uuid, lobbyModel.settings.public)) { ++ console.log('invalid credentials'); ++ server.rejectClient(client, 'Credentials are invalid'); ++ return onConnect; ++ } ++ ++ if (!reconnect) { ++ var max = Math.min(MAX_CLIENTS, lobbyModel.numPlayerSlots() + main.spectators); ++ if (lobbyModel.numPlayers() >= max) { ++ console.error('lobby at capacity. client rejected.'); ++ server.rejectClient(client, 'No room'); ++ return onConnect; ++ } ++ } ++ ++ utils.pushCallback(client, 'onDisconnect', function (onDisconnect) { ++ if (!hasStartedPlaying) { /* don't kill the client unless we have not started any other states. */ ++ console.log('removing disconnected player from the lobby.'); ++ lobbyModel.removePlayer(client.id); ++ } ++ return onDisconnect; ++ }); ++ cleanup.push(function () { ++ if (client.onDisconnect) { ++ console.log('remove disconnect handler'); ++ client.onDisconnect.pop(); ++ } ++ /* removePlayer calls client.kill(), which will destroy the onDisconnect handler */ ++ }); ++ ++ var players = _.filter(lobbyModel.players, { 'spectator': false }); ++ var options = { mod: bouncer.isPlayerMod(client.id), creator: client.id === lobbyModel.creator }; ++ /* force the player to be a spectator */ ++ if (players.length >= MAX_PLAYERS) ++ options.spectator = true; ++ ++ if (!lobbyModel.players.hasOwnProperty(client.id)) ++ lobbyModel.addPlayer(client, options); ++ else ++ lobbyModel.updatePlayerState(); ++ ++ lobbyModel.addPlayersToSlotsIfPossible(); ++ ++ var player = lobbyModel.players[client.id]; ++ if (player.armyIndex === -1) /* make the player a spectator if there is no room */ ++ player.spectator = true; ++ ++ debug_log('calling client.downloadModsFromServer'); ++ client.downloadModsFromServer(); ++ ++ client.message({ ++ message_type: 'set_cheat_config', ++ payload: main.cheats ++ }); ++ ++ return onConnect; ++ }); ++ cleanupOnEntry.push(function () { server.onConnect.pop(); }); ++ ++ lobbyModel.creator = owner.id; ++ lobbyModel.addPlayer(owner, { mod: true, creator: true }); ++ bouncer.addPlayerToModlist(owner.id); ++ ++ _.forEach(server.clients, function (client) { ++ if (client !== owner) { ++ lobbyModel.addPlayer(client, { mod: false, creator: false }); ++ } ++ }); ++ ++ var removeHandlers = server.setHandlers({ ++ reset_armies: playerMsg_resetArmies, ++ add_army: playerMsg_addArmy, ++ remove_army: playerMsg_removeArmy, ++ add_ai: playerMsg_addAI, ++ modify_system: playerMsg_modifySystem, ++ modify_army: playerMsg_modifyArmy, ++ modify_bouncer: playerMsg_modifyBouncer, ++ modify_settings: playerMsg_modifySettings, ++ start_game: playerMsg_startCountdown, ++ set_primary_color_index: playerMsg_setPrimaryColorIndex, ++ set_primary_color_index_for_ai: playerMsg_setPrimaryColorIndexForAI, ++ set_secondary_color_index: playerMsg_setSecondaryColorIndex, ++ set_secondary_color_index_for_ai: playerMsg_setSecondaryColorIndexForAI, ++ set_ai_personality: playerMsg_setAIPersonality, ++ set_ai_landing_policy: playerMsg_setAILandingPolicy, ++ set_econ_factor: playerMsg_setEconomyFactor, ++ join_army: playerMsg_joinArmy, ++ toggle_ready: playerMsg_toggleReady, ++ leave_army: playerMsg_leaveArmy, ++ update_commander: playerMsg_updateCommander, ++ chat_message: playerMsg_chatMessage, ++ leave: playerMsg_leave, ++ kick: playerMsg_kick, ++ promote_to_mod: playerMsg_promoteToMod, ++ set_loading: playerMsg_setLoading, ++ mod_data_available: playerMsg_modDataAvailable, ++ mod_data_updated: playerMsg_modDataUpdated, ++ request_cheat_config: playerMsg_requestCheatConfig ++ }); ++ cleanup.push(function () { removeHandlers(); }); ++ ++ lobbyModel.updateBeacon(); ++ ++ return client_state; ++}; ++ ++exports.exit = function (newState) { ++ _.forEachRight(cleanup, function (c) { c(); }); ++ cleanup = []; ++ ++ return true; ++}; ++ ++main.gameModes.lobby = exports; ++// This is for backwards compatibility. Game used to ask for 'Config' game mode. ++main.gameModes.Config = exports; +diff -Naur server-script.ori/utils.js server-script/utils.js +--- server-script.ori/utils.js 2016-02-07 14:27:12.000000000 +0100 ++++ server-script/utils.js 2016-02-06 20:38:00.182512814 +0100 +@@ -1,68 +1,73 @@ +-var _ = require('thirdparty/lodash'); +- +-// TODO: 12/16/2014 - This module requires using an exports object. Adding +-// extra members in this module without using a new object exposes some bug in +-// the scripting environment implementation, and nothing will get exported. +-// We don't have time to figure out why right now. +-exports = { +- pushCallback: function(obj, name, fn) { +- var oldCallback = obj[name]; +- var wrapper = function() { +- var oldResult = oldCallback ? oldCallback.apply(this, arguments) : undefined; +- var args = [oldResult]; +- for (var a = 0; a < arguments.length; ++a) +- args.push(arguments[a]); +- return fn.apply(this, args); +- }; +- wrapper.pop = function() { +- obj[name] = oldCallback; +- }; +- obj[name] = wrapper; +- }, +- +- // Convenience wrapper for _.reduce. Counts callbacks that return "true", or +- // sums a number per item in the collection. +- sum: function(collection, callback) { +- return _.reduce(collection, function(sum, item) { +- var itemResult = callback(item); +- var itemCount; +- if (typeof itemResult === 'number') +- itemCount = itemResult; +- else +- itemCount = (itemResult ? 1 : 0); +- return sum + itemCount; +- }, 0); +- }, +- +- getMinimalSystemDescription: function (system) { +- if (!system) +- return system; +- +- var copy = _.omit(system, 'planets'); +- copy.planets = _.map(system.planets, function (element) { +- return _.omit(element, ['planetCSG', 'landing_zones', 'metal_spots', 'source']); +- }); +- +- return copy; +- }, +- +- // Modulo division, which is not what % does in JS. +- modulo: function(n, m) { return ((n % m) + m) % m; } +-}; +- +-var random_characters = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'; +- +-var randomString = function(length) { +- var result = ''; +- _.times(length, function () { +- var number = Math.floor(Math.random() * random_characters.length); +- result += random_characters.charAt(number); +- }); +- return result; +-} +- +-exports.randomString = randomString; +- +-exports.createUUIDString = function() { +- return randomString(32); ++var _ = require('thirdparty/lodash'); ++ ++// TODO: 12/16/2014 - This module requires using an exports object. Adding ++// extra members in this module without using a new object exposes some bug in ++// the scripting environment implementation, and nothing will get exported. ++// We don't have time to figure out why right now. ++exports = { ++ pushCallback: function(obj, name, fn) { ++ var oldCallback = obj[name]; ++ var wrapper = function() { ++ var oldResult = oldCallback ? oldCallback.apply(this, arguments) : undefined; ++ var args = [oldResult]; ++ for (var a = 0; a < arguments.length; ++a) ++ args.push(arguments[a]); ++ return fn.apply(this, args); ++ }; ++ wrapper.pop = function() { ++ obj[name] = oldCallback; ++ }; ++ obj[name] = wrapper; ++ }, ++ ++ // Convenience wrapper for _.reduce. Counts callbacks that return "true", or ++ // sums a number per item in the collection. ++ sum: function(collection, callback) { ++ return _.reduce(collection, function(sum, item) { ++ var itemResult = callback(item); ++ var itemCount; ++ if (typeof itemResult === 'number') ++ itemCount = itemResult; ++ else ++ itemCount = (itemResult ? 1 : 0); ++ return sum + itemCount; ++ }, 0); ++ }, ++ ++ getMinimalSystemDescription: function (system) { ++ if (!system) ++ return system; ++ ++ var copy = _.omit(system, 'planets'); ++ copy.planets = _.map(system.planets, function (element) { ++ var summary = _.omit(element, ['planetCSG', 'landing_zones', 'metal_spots', 'source']); ++ ++ summary.metal_spots_count = element.metal_spots ? element.metal_spots.length : 0; ++ summary.planetCSG_count = element.planetCSG ? element.planetCSG.length : 0; ++ summary.landing_zones_count = element.landing_zones ? ( element.landing_zones.list ? element.landing_zones.list.length : element.landing_zones.length ) : 0; ++ return summary; ++ }); ++ ++ return copy; ++ }, ++ ++ // Modulo division, which is not what % does in JS. ++ modulo: function(n, m) { return ((n % m) + m) % m; } ++}; ++ ++var random_characters = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'; ++ ++var randomString = function(length) { ++ var result = ''; ++ _.times(length, function () { ++ var number = Math.floor(Math.random() * random_characters.length); ++ result += random_characters.charAt(number); ++ }); ++ return result; ++} ++ ++exports.randomString = randomString; ++ ++exports.createUUIDString = function() { ++ return randomString(32); + } +\ No newline at end of file diff --git a/update_conf_file.sh b/update_conf_file.sh index 72e91bc..2ec5e5c 100644 --- a/update_conf_file.sh +++ b/update_conf_file.sh @@ -2,5 +2,12 @@ # Update configuration files +# Introduce modification from mikeyh : https://forums.uberent.com/threads/wip-dedicated-servers.65077/page-6#post-1124347 +cd ./.local/Uber\ Entertainment/Planetary\ Annihilation/stable/media/server-script && patch -p1 < /home/pa/mikeyh_lobby.patch +cd /home/pa +# Update lobby to add beacon sed -i.bkp -f update_lobby.js.sed ./.local/Uber\ Entertainment/Planetary\ Annihilation/stable/media/server-script/states/lobby.js +# Update MAX_PLAYERS and MAX_SPECTATORS parameters +sed -i.bkp -f update_main.js.sed ./.local/Uber\ Entertainment/Planetary\ Annihilation/stable/media/server-script/main.js +# Update MAX_PLANETS parameters sed -i.bkp -f update_sim_utils.js.sed ./.local/Uber\ Entertainment/Planetary\ Annihilation/stable/media/server-script/sim_utils.js diff --git a/update_lobby.js.sed b/update_lobby.js.sed index e7e4bf2..06a3a49 100644 --- a/update_lobby.js.sed +++ b/update_lobby.js.sed @@ -5,7 +5,3 @@ /^var getAIName/ i } /^var getAIName/ i }, 5000); /^var getAIName/ i \ - -# Update parameters -s/var MAX_PLAYERS = [0-9]\+/var MAX_PLAYERS = 16/ -s/var MAX_SPECTATORS = [0-9]\+/var MAX_SPECTATORS = 5/ diff --git a/update_main.js.sed b/update_main.js.sed new file mode 100644 index 0000000..00e2200 --- /dev/null +++ b/update_main.js.sed @@ -0,0 +1,3 @@ +# Update parameters +s/exports\.MAX_PLAYERS = [0-9]\+/exports.MAX_PLAYERS = 16/ +s/exports\.MAX_SPECTATORS = [0-9]\+/exports.MAX_SPECTATORS = 5/