Card/CardControl/CardControl.js

/**
* Модуль, отвечающий за перетаскивание карт.  
* Обрабатывает клики по картам, перемещение карт по экрану игроком, перемещение карт между полями игроком,
* а также хвост карты при перемещении игроком.  
* Основные элементы: `{@link CardControl#card|card}, {@link CardControl#trail|pointer}, {@link CardControl#trail|trail}, {@link CardControl#trailDefaultBase|trailDefaultBase}`.
* @class
*/
var CardControl = function(inDebugMode){

	/**
	* Находится ли контроллер в дебаг режиме.
	* @type {boolean}
	*/
	this.inDebugMode = inDebugMode || false;

	/**
	* Контролируемая карта.
	* @type {Card}
	*/
	this.card = null;

	/**
	* Указатель, "держущий" карту
	* @type {Phaser.Pointer}
	*/
	this.pointer = null;

	/**
	* Хвост карты.
	* Представляет из себя эмиттер партиклей в виде иконок масти карты.
	* @type {Phaser.Particles.Arcade.Emitter}
	*/
	this.trail = null;

	/**
	* База хвоста карты, когда он не прикреплен к карте.
	* @type {external:Phaser.Group}
	*/
	this.trailDefaultBase = null;
	
	/**
	* Нужно ли прикрепить хвост к текущей карте.
	* @type {Boolean}
	*/
	this._trailShouldReappend = false;

	/** 
	* Время сдвига центра карты к указателю.
	* @type {Number}
	*/
	this.cardShiftDuration = 100;
	/**
	* Время возвращения карты на свою базу.
	* @type {Number}
	*/
	this.cardReturnTime = 200;
	/**
	* Время между кликами по карте, когда она будет поднята вторым кликом.
	* @type {Number}
	*/
	this.cardClickMaxDelay = 200;
	/**
	* На сколько должна быть свдвинута карта, чтобы было заметно покачивание
	* и чтобы хвост уменьшил пространство, на котором спавнятся партикли.
	* @type {Number}
	*/
	this.cardMoveThreshold = 2;

	/**
	* Максимальный угол покачивания карты при движении.
	* @type {Number}
	*/
	this.cardMaxMoveAngle = 30;

	/**
	* История инерции карты.
	* @type {Array}
	*/
	this._inertiaHistory = [];

	/**
	* Оповещатель о выборе карт.
	* @type {CardPickNotifier}
	*/
	this.pickNotifier = new CardPickNotifier();
};

/** Инициалищирует модуль - создает хвост карты и группу для него. */
CardControl.prototype.initialize = function(){
	this.trail = game.add.emitter(0, 0);
	this.trailDefaultBase = game.add.group();
	this.trail.makeParticles(skinManager.skin.trailName, 0);
	this.trailDefaultBase.name = 'trail';
	this.trailReset();
};

/**
* Обрабатывает нажатие на карту.
* @param {Card}           card    карта
* @param {Phaser.Pointer} pointer указатель, нажавший на карту
*/
CardControl.prototype.cardClick = function(card, pointer){
	if(pointer.button == 1 || pointer.button == 4){
		console.log(card);
	}

	if(!card.draggable || this.card && this.card != card || !this.card && card.field && !card.field.interactible){
		return;
	}

	if(this.inDebugMode){
		console.log('Card control: Clicked', card.id);
	}

	if(this.card){
		this.cardPutDown();
	}
	else{
		this.cardPickup(card, pointer);
	}
};

/**
* Обрабатывает поднятие кнопки после нажатия на карту.
* @param {Card} card карта
*/
CardControl.prototype.cardUnclick = function(card){
	if(!this.card || this.card != card){
		return;
	}

	if(this.inDebugMode){
		console.log('Card control: Unclicked', card.id);
	}

	if(!this.pointer.withinGame){
		this.cardReturn();
	}
	else if(!this._cardPointerInbound() || !this.cardClickTimer || !this.pointer.isMouse || cardManager.physicsEnabled && this.card.sprite.body){
		this.cardPutDown();
	}
};


/** 
* Проверка нажатия на базу карты.
*/
CardControl.prototype._cardPointerInbound = function(){
	var width = this.card.field ? skinManager.skin.width*(1 + this.card.field.style.scaleDiff) : skinManager.skin.width,
		height = this.card.field ? skinManager.skin.height*(1 + this.card.field.style.scaleDiff) : skinManager.skin.height;
	return Phaser.Rectangle.containsRaw(
		this.card.x - width / 2,
		this.card.y - height / 2,
		width,
		height,
		this.pointer.x,
		this.pointer.y
	);
};

/**
* Проверка корректности позиции карты (возащает false или поля).
*/
CardControl.prototype._cardOnValidField = function(){
	if(!this.card.playable){
		return false;
	}

	var fields = fieldManager.forEachField(function(field, si){
		if(field.playable && field.cardIsInside(this.card, false)){
			return field;
		}
	}, this);
	if(!fields.length){
		fields = fieldManager.forEachField(function(field, si){
			if(field.playable && field.cardIsInside(this.card, false, true)){
				return field;
			}
		}, this);
	}
	if(fields.length){
		return fields;
	}
	return false;
};

//@include:CardControlAction
//@include:CardControlCard
//@include:CardControlTrail

/** Обновляет контролируемую карту и ее хвост. */
CardControl.prototype.update = function(){
	var shouldUpdateTrail = this._updateCard();
	if(shouldUpdateTrail && !this._trailShouldReappend){
		this._trailSpawnParticle();
	}
	else if(!shouldUpdateTrail){
		this._trailShouldReappend = false;
	}
	this._updateTrail();
};

/**
* Ресет контроллера
* @param {string} [reason] Причина ресета для дебага.
*/
CardControl.prototype.reset = function(reason){

	if(this.inDebugMode){
		console.log('Card control: Reset' + (reason ? ': ' + reason : ''));
	}

	this.trailReset(true);
	this.card = null;
	this.pointer = null;
};

//@include:CardControlDebug