disconnect: function(){
		Zkl.disconnect();
		this.state.connection = ServerCommon.ConnectionState.DISCONNECTED;
		if (this.underlyingStore) {
			this.stopListeningTo(this.underlyingStore);
			this.underlyingStore.dispose();
			this.underlyingStore = null;
		}
		this.triggerSync();
	},
	set: function(key, val){
		this[key] = val;
		Zkl.saveConfig(_.reduce(
			_.flatten(_.map(this.settings, _.keys)),
			function(acc, key){
				acc[key] = this[key];
				return acc;
			}.bind(this),
			{}
		));
		this.trigger(key);
	},
	init: function(){
		// An object describing the possible settings. The actual values
		// are stored in this, not in this.settings.
		this.settings = {
			"Game": {
				safeMode: { val: false, name: 'Run in safe mode', desc: 'Try this if you get crashes.', type: 'bool' },
				windowedMode: { val: false, name: 'Run in windowed mode instead of fullscreen', type: 'bool' },
				resolutionWidth: { val: null, name: 'Screen resolution width', desc: 'Leave empty for default.', type: 'int' },
				resolutionHeight: { val: null, name: 'Screen resolution height', desc: 'Leave empty for default.', type: 'int' },
				playTitleMusic: { val: true, name: 'Play music in menu', type: 'bool' },
			},
			"Login": {
				name: { val: '', name: 'Login', type: 'text' },
				password: { val: '', name: 'Password', type: 'password' },
				autoConnect: { val: true, name: 'Connect on launch', type: 'bool' },
				useZkServer: { val: true, name: 'Use Zero-K server', type: 'bool' },
			},
			"Chat": {
				autoJoin: { val: '', name: 'Autojoin channels/private messages', type: 'list', desc: 'Can be changed by pressing the heart button in chat. Channels are prefixed with #.' },
			},
			"Games to show in battle list": {
				selectedEvo: { val: false, name: 'Evolution RTS', type: 'bool' },
				selectedZk: { val: true, name: 'Zero-K', type: 'bool' },
				selectedBa: { val: false, name: 'Balanced Annihilation', type: 'bool' },
				selectedTa: { val: false, name: 'Tech Annihilation', type: 'bool' },
				selectedXta: { val: false, name: 'XTA', type: 'bool' },
				selectedNota: { val: false, name: 'NOTA', type: 'bool' },
				selectedJauria: { val: false, name: 'JauriaRTS', type: 'bool' },
				selectedS44: { val: false, name: 'Spring 1944', type: 'bool' },
				selectedIw: { val: false, name: 'Imperial Winter', type: 'bool' },
			},
			"Advanced": {
				lobbyServer: { val: '', name: 'Custom lobby server', type: 'text', desc: 'Use lobby.zero-k.info for Zero-K server, leave empty for default.' },
				springHome: { val: '', name: 'Installation directory', desc: 'Requires a restart after changing. Leave empty for default.', type: 'text' },
				springCommandPrefix: { val: '', name: 'Spring command prefix', desc: 'You can set this to optirun or primusrun if you use those.', type: 'text' },
				dumpNetwork: { val: false, name: 'Dump network traffic', type: 'bool', desc: 'Dumps all traffic to the log (that includes your password!). Only useful for debugging.' },
			},
		};
		_.forIn(this.settings, function(vals){
			_.extend(this, _.mapValues(vals, 'val'));
		}.bind(this));
		// This is rather hacky in that it relies on the runApp timeout in
		// zkwl.jsx to make sure the settings are read by the time the other
		// stores are created (otherwise the stores would get the default
		// settings instead of the saved config).
		Zkl.readConfig(function(config){
			if (config)
				_.extend(this, config);
		}.bind(this));
	},
	connectInternal: function(registering){
		if (this.state.connection !== ServerCommon.ConnectionState.DISCONNECTED)
			this.disconnect();

		var host = Settings.lobbyServer.split(':')[0] ||
			Settings.useZkServer && 'lobby.zero-k.info' || 'lobby.springrts.com';
		var port = Settings.lobbyServer.split(':')[1] || '8200';
		Zkl.connect(host, port);

		this.state = ServerCommon.getClearState();
		this.state.socket = this.socket;
		this.state.connection = ServerCommon.ConnectionState.CONNECTING;
		this.state.registering = registering;
		this.triggerSync();
	},
	sendRaw: function(data){
		Zkl.sendLobbyMessage(data);
	},