4648 lines
145 KiB
Diff
4648 lines
145 KiB
Diff
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/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/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
|