Exemple #1
0
/**
 * Generic constructor for both instantiating an existing game
 * character (from JSON data), as well as creating a new one.
 *
 * @param {object} [data] initialization values (properties are
 *        shallow-copied into the instance)
 * @constructor
 * @augments Bag
 * @mixes PlayerApi
 */
function Player(data) {
	Player.super_.call(this, data);
	utils.addNonEnumerable(this, 'session');
	utils.addNonEnumerable(this, 'active', false);
	utils.addNonEnumerable(this, 'changes', []);
	utils.addNonEnumerable(this, 'anncs', []);
	utils.addNonEnumerable(this, 'msgCache', []);
	// convert selected properties to "Property" instances (works with simple
	// int values as well as serialized Property instances)
	for (var group in PROPS) {
		if (!this[group]) this[group] = {};
		for (var key in PROPS[group]) {
			if (PROPS[group][key] === 'property') {
				this[group][key] = new Prop(key, this[group][key]);
			}
			else if (PROPS[group][key] === 'object') {
				if (!this[group][key]) {
					this[group][key] = {};
				}
				for (var subkey in this[group][key]) {
					var propGroup = this[group][key];
					propGroup[subkey] = new Prop(subkey, propGroup[subkey]);
				}
			}
		}
	}
}
/**
 * Generic constructor for both instantiating an existing location
 * (from JSON data), and creating a new location.
 *
 * @param {object} [data] initialization values (properties are
 *        shallow-copied into the location object)
 * @param {Geo} [geo] geometry data (optional, for testing); if
 *        `undefined`, the respective `Geo` object is loaded from
 *        persistence
 * @constructor
 * @augments GameObject
 * @mixes LocationApi
 */
function Location(data, geo) {
	data = data || {};
	if (!data.tsid) {
		if (geo) {
			data.tsid = geo.getLocTsid();
		}
		else {
			data.tsid = rpc.makeLocalTsid(this.TSID_INITIAL_LOCATION);
		}
	}
	Location.super_.call(this, data);
	// initialize items and players, convert to IdObjRefMap
	this.players = new IdObjRefMap(this.players);
	this.items = new IdObjRefMap(this.items);
	// convert neighbor list to OrderedHash
	if (this.neighbors) {
		this.neighbors = new OrderedHash(this.neighbors);
	}
	// initialize geometry
	utils.addNonEnumerable(this, 'geometry');
	utils.addNonEnumerable(this, 'clientGeometry');
	utils.addNonEnumerable(this, 'geo');
	var geoData = geo || pers.get(this.getGeoTsid(), true);
	assert(_.isObject(geoData), 'no geometry data for ' + this);
	if (rpc.isLocal(this)) {
		this.updateGeo(geoData);
	}
}
/**
 * Generic constructor for both instantiating an existing location
 * (from JSON data), and creating a new location.
 *
 * @param {object} [data] initialization values (properties are
 *        shallow-copied into the location object)
 * @param {Geo} [geo] geometry data (optional, for testing); if
 *        `undefined`, the respective `Geo` object is loaded from
 *        persistence
 * @constructor
 * @augments GameObject
 * @mixes LocationApi
 */
function Location(data, geo) {
	data = data || {};
	if (!data.tsid) {
		if (geo) {
			data.tsid = geo.getLocTsid();
		}
		else {
			data.tsid = rpc.makeLocalTsid(Location.prototype.TSID_INITIAL);
		}
	}
	Location.super_.call(this, data);
	// initialize items and players, convert to IdObjRefMap
	if (!this.players || this.players instanceof Array) {
		this.players = utils.arrayToHash(this.players);
	}
	this.players = new IdObjRefMap(this.players);
	if (!this.items || this.items instanceof Array) {
		this.items = utils.arrayToHash(this.items);
	}
	this.items = new IdObjRefMap(this.items);
	// convert neighbor list to OrderedHash
	if (this.neighbors) {
		this.neighbors = new OrderedHash(this.neighbors);
	}
	// initialize geometry
	utils.addNonEnumerable(this, 'geometry');
	utils.addNonEnumerable(this, 'clientGeometry');
	utils.addNonEnumerable(this, 'geo');
	var geoData = geo || pers.get(this.getGeoTsid(), true);
	assert(typeof geoData === 'object', 'no geometry data for ' + this);
	this.updateGeo(geoData);
}
/**
 * Generic constructor for both instantiating an existing game object
 * (from JSON data), and creating a new object.
 *
 * @param {object} [data] initialization values (properties are
 *        shallow-copied into the game object)
 * @constructor
 * @mixes GameObjectApi
 */
function GameObject(data) {
	if (!data) data = {};
	// initialize TSID/class ID (use deprecated properties if necessary, and
	// keep them as non-enumerable so they are available, but not persisted)
	this.tsid = data.tsid || data.id || utils.makeTsid(this.TSID_INITIAL,
		config.getGsid());
	utils.addNonEnumerable(this, 'id', this.tsid);  // deprecated
	if (data.class_tsid || data.class_id) {
		this.class_tsid = data.class_tsid || data.class_id;
		utils.addNonEnumerable(this, 'class_id', this.class_tsid);  // deprecated
	}
	// add non-enumerable internal properties
	utils.addNonEnumerable(this, '__isGO', true);
	utils.addNonEnumerable(this, 'deleted', false);
	utils.addNonEnumerable(this, 'stale', false);
	// copy supplied data
	orProxy.copyOwnProps(data, this);
	this.ts = this.ts || Date.now();
	if (!this.gsTimers) this.gsTimers = {};
	utils.makeNonEnumerable(this, 'gsTimers');
}
/**
 * If Slack integration is configured for the given group, this
 * modifies its function reponsible for generating the list of chat
 * participants (i.e. the output of `/who`) to include Slack users.
 * @private
 */
function patchGroup(group) {
	if (!groupToChannel[group.tsid]) return;
	// monkey patch the function generating /who output
	var orig = group.chat_get_roster_msg;
	utils.addNonEnumerable(group, 'chat_get_roster_msg',
		function chat_get_roster_msg() {  // eslint-disable-line camelcase
			var channel = groupToChannel[group.tsid];
			var roster = utils.shallowCopy(group.chat_roster);
			for (var i = 0; i < channel.members.length; i++) {
				var user = slack.dataStore.getUserById(channel.members[i]);
				if (user.presence === 'active' && user.id !== slack.activeUserId) {
					roster[user.id] = {label: user.name + ' (Slack)'};
				}
			}
			return orig.call({chat_roster: roster});
		}
	);
}
/**
 * A class for integer properties of game objects that provides atomic
 * manipulations on their values.
 *
 * @param {string} label name of the property
 * @param {number|object} data either just a numeric initial value, or
 *        an object containing extended configuration like:
 *        ```{val: 3, bottom: -3, top: 8000}```
 * @constructor
 * @mixes PropertyApi
 */
function Property(label, data) {
	this.label = label;
	if (data === undefined) data = {};
	if (_.isNumber(data)) {
		this.value = Math.round(data);
	}
	else if (_.isObject(data)) {
		this.value = _.isNumber(data.value) ? Math.round(data.value) : 0;
	}
	else {
		this.value = 0;
	}
	this.setLimits(
		_.isNumber(data.bottom) ? data.bottom : this.value,
		_.isNumber(data.top) ? data.top : this.value);
	// add a flag that indicates whether an update for this property's value
	// needs to be sent to the client
	utils.addNonEnumerable(this, 'changed', false);
}
/**
 * Handles incoming messages from Slack. Figures out if the message is
 * something we're interested in (regular message sent to one of the
 * connected groups), and forwards it to the respective group if so.
 * @private
 */
function onSlackMessage(msg) {
	log.trace({data: msg}, 'incoming Slack message');
	if (msg.type !== 'message') return;  // ignore meta stuff
	// is this a connected group managed by this GS instance?
	var groupTsid = channelToGroup[msg.channel];
	if (!groupTsid) return;
	// special handling for edits/removals of earlier messages
	if (msg.subtype === 'message_deleted') return;
	if (msg.subtype === 'message_changed' && msg.message) {
		msg = msg.message;  // for edits, message prop seems to contain the actual payload
		msg.text = '[EDIT] ' + msg.text;
	}
	// prepare message payload
	var user = slack.dataStore.getUserById(msg.user);
	if (!user) {
		log.error({data: msg}, 'could not retrieve Slack user');
		return;
	}
	if (user.id === slack.activeUserId) {
		// don't echo!
		return;
	}
	var out = {
		type: 'pc_groups_chat',
		tsid: groupTsid,
		pc: {
			// unique pseudo TSID so client assigns different colors:
			tsid: 'PSLACK' + user.id,
			label: user.name,
		},
		txt: processMsgText(msg.text),
	};
	utils.addNonEnumerable(out, 'fromSlack', true);
	dispatchToGroup(out, function cb(err) {
		if (err) {
			log.error({err: err, data: msg},
				'error dispatching group chat message from Slack');
		}
	});
}