ActionHandler/ActionHandler.js

/**
* Модуль отвечает за обработку действий и оповещений от сервера.
* Подсвечивает возможные действия и управляет кнопкой действия и таймером хода.
* @class
*/
var ActionHandler = function(){

	/**
	* Каналы с действиями.
	* @type {Object}
	*/
	this.channels = {};

	/**
	* Сохраненные возможные действия.
	* @type {object}
	*/
	this.possibleActions = null;

	/**
	* Кнопка действия.
	* @type {UI.Button}
	*/
	this.actionButton = null;

	/**
	* Менеджер последовательностей игровых действий и анимаций.
	* @type {Sequencer}
	*/
	this.sequencer = new Sequencer(connection.server.sendResponse);
};

/**
* Добавляет канал для управление игрой с сервера.
* @param {string}           name               уникальное имя канала
* @param {CHANNEL_TYPE}     type               тип канала
* @param {string}           [state]            Состояние игры, в котором она должна быть для выполнения действий этого канала.
*                                              Если действие выполняется в неверном состоянии, игра будет переведена в верное состояние.  
*                                              Если не указать, действие будет выполняться в любом состоянии.
* @param {object<function>} [reactions]        объект с функциями, соответствующие типу действий от сервера
* @param {array}            [additionalStates] Дополнительные состояния игры, в которых действия этого канала не могут выполняться,
*                                              но в которых эти действия могут быть приняты. Избавляется от выведения предупреждения
*                                              при получении действия в другом канале, нежеле указаном в `state`.
*/
ActionHandler.prototype.addChannel = function(name, type, state, reactions, additionalStates){
	if(this.channels[name]){
		console.error('ActionHandler: channel already exists', name);
	}
	var channel = {};
	channel.name = name;
	channel.type = type;
	channel.state = state;
	channel.reactions = reactions || null;
	channel.additionalStates = additionalStates || [];
	this.channels[name] = channel;
};

/**
* Выполняет действие.
* @param {object} action         действие
* @param {string} action.type    тип действия по которому будет вызвана соответствующая функция из.
* @param {string} action.channel канал, по которому будет вызвано действие
*
* @return {number} Время выполнения действия.
*/
ActionHandler.prototype.executeAction = function(action){
	
	if(!action){
		console.error('Action handler: no action recieved');
		return;
	}

	// Находим указанный канал
	var channel = this.channels[action.channel];

	if(!channel){
		console.error('Action handler: channel not found', action.channel, action);
		return;
	}

	var reaction = null;
	var context = null;

	// Устанавливаем ожидание от сервера в соответствии с типом сообщения
	switch(channel.type){

		// В случае принятия действий для игрока, указываем действие и контекст сразу
		case CHANNEL_TYPE.USER_INVOLVED:
		connection.serverWaiting = false;
		reaction = this.handlePossibleActions;
		context = this;
		break;

		case CHANNEL_TYPE.RESPOND:
		if(!action.noResponse){
			connection.serverWaiting = true;
		}
		else if(!action.noInterrupt){
			connection.serverWaiting = false;
		}
		break;

		case CHANNEL_TYPE.INTERRUPT:
		if(!action.noInterrupt){
			connection.serverWaiting = false;
		}
		break;

		case CHANNEL_TYPE.NO_ACTION:
		break;

		default:
		console.error('ActionHandler: invalid channel type', channel.type, channel, action);
		return;
	}

	// Находим указанное действие
	if(!reaction){
		reaction = channel.reactions[action.type];
	}
	if(!reaction){
		console.warn('Action handler: unknown action type', action.channel, action.type, action);
		return;
	}
	if(!context){
		context = channel.reactions;
	}

	// Завершаем все действия, если новое действие этого требует
	if(gameInfo.simulating || action.instant){
		this.sequencer.finish();
	}

	// Если действие должно выполняться в определенном состоянии игры, добавляем его в очередь действий
	if(channel.state){
		this.sequencer.queueUp(function(seq, sync){

			// Переключаем состояние, если текущее не совпадает с указанным
			if(channel.state != game.state.currentSync){

				// Выводим предупреждение, если действие не должно приходить в текущем состоянии
				if(!~channel.additionalStates.indexOf(game.state.currentSync)){
					console.warn('Action handler: wrong game state', game.state.currentSync, action.channel, channel.state, action);
				}

				// Переключем состояние без завершения очереди действий
				game.state.change(channel.state, false);
			}

			// Выполняем действие
			return reaction.call(this, action, seq, sync);
		}, null, context);
	}
	else{
		// Иначе просто выполняем действие
		reaction.call(context, action);
	}
};

/**
* Позволяет игроку выбрать действие из списка при помощи интеракции с игрой.
* @param {object} actions 	возможные действия
* @param {number} time 	время, до которого необходимо выбрать действие
* @param {number} timeSent время в которое действия были отправлены с сервера
* @param {string} turnStage текущая стадия хода
*/
ActionHandler.prototype.handlePossibleActions = function(action, seq){

	// Включаем таймер, если игрок может что-то делать
	if(action.actions.length){
		var time = action.time - Date.now();
		if(time){
			ui.rope.start(time - 1000);
		}
	}

	// Обновляем статусы игроков
	if(action.roles){
		gameInfo.updateTurnInfo(action.roles, action.turnIndex, action.turnStage, action.actions.length !== 0, seq);
		fieldManager.updateBadges();
	}

	// Подсвечиваем возможные действия
	this.highlightPossibleActions(action.actions);
};

/**
* Подсвечивает карты, которыми можно ходить и активирует кнопку действия.
* @param {object} [actions] возможные действия
*/
ActionHandler.prototype.highlightPossibleActions = function(actions){
	if(!actions && !this.possibleActions){
		return;
	}

	if(actions){
		this.possibleActions = actions;
	}
	else{
		actions = this.possibleActions;
	}

	if(!fieldManager.networkCreated){
		console.error('Action handler: field network hasn\'t been created');
		return;
	}

	gameInfo.applyInteractivity(actions, this.actionButton);
};

/** Убирает все возможные действия */
ActionHandler.prototype.resetActions = function(){
	this.possibleActions = null;
	this.actionButton.serverAction = null;
	this.actionButton.changeStyle(0);
};

/** Убирает все возможные действия и ресетит связанные с ними элементы игры */
ActionHandler.prototype.reset = function(){
	this.resetActions();
	fieldManager.resetHighlights();	
	this.actionButton.disable();
	this.actionButton.changeStyle(0);
	ui.rope.stop();
};

/** Убирает определенные действия из `possibleActions` в соответствии с `turnStage`. */
ActionHandler.prototype.removeActionsWith = function(card, field, doneAction){
	if(!this.possibleActions){
		return;
	}
	var fieldStatuses = gameInfo.getFieldStatuses();
	for(var i = this.possibleActions.length - 1; i >= 0; i--){
		var action = this.possibleActions[i];
		if(gameInfo.shouldDeleteAction(action, card, field, doneAction, fieldStatuses)){
			this.possibleActions.splice(i, 1);
		}
	}
};


//@include:reactPrimary
//@include:reactSecondary
//@include:reactExtra
//@include:reactQueue
//@include:reactMenu
//@include:reactSystem