Field/FieldManager/FieldManagerAnim.js

/**
* Показывает козырные карты.
* @param {CardInfo[]} cardsInfo информация о картах
* @param {string}     pid       id игрока с наименьшей картой
*
* @return {number} Время показа карт.
*/
FieldManager.prototype.showTrumpCards = function(cardsInfo, pid){
	
	for(var ci = 0; ci < cardsInfo.length; ci++){
		var c = cardsInfo[ci];
		var card = cardManager.cards[c.cid];

		if(!card){
			console.error('Field manager: Card', c.cid, 'not found');
			connection.server.reconnect();
			return;
		}

		if(c.field == pid){
			card.setHighlight(true, ui.colors.green);
		}

		card.raised = true;

		if(card.field.id != gameInfo.field){	
			card.presetValue(c.suit, c.value);	
		}
		card.field.placeCards(null, BRING_TO_TOP_ON.INIT, true);
	}
};

/**
* Прячет козырные карты.
* @param {CardInfo[]} cardsInfo информация о картах
*/
FieldManager.prototype.hideTrumpCards = function(cardsInfo){
	if(!cardsInfo || !cardsInfo.length){
		return;
	}
		
	for(var ci = 0; ci < cardsInfo.length; ci++){
		var c = cardsInfo[ci];
		var card = cardManager.cards[c.cid];	

		if(!card){
			console.error('Field manager: Card', c.cid, 'not found');
			connection.server.reconnect();
			continue;
		}

		card.raised = false;
		card.setHighlight(false);

		if(!card.field){
			continue;
		}

		if(card.field.id != gameInfo.pid){					
			card.presetValue(null, null);
		}
		card.field.placeCards(null, BRING_TO_TOP_ON.INIT, true);
	}			
};


/**
* Анимирует перемешивание карт, добавляя карты в колоду по окончании анимации.
* Также анимирует появления полей игроков и подсвечивает поля стола.
* @param {CardInfo[]} cardsInfo информация о картах
*
* @return {number} Время анимации.
*/
FieldManager.prototype.fancyShuffleCards = function(seq, cardsInfo, trumpSuit){

	var	playerField = this.fields[game.pid];
	// если поле игрока будет анимироваться, нужно это учесть в радиусе вращения карт
	var animOffset = playerField._entranceTween ? playerField.area.height : 0;
	var gameSpeed = game.speed; // анимация будет проходить с этим множителем

	// this was a mistake
	var duration = 500/gameSpeed,	// время передвижения карты в позицию из которой они будут вращаться
		interval = 15/gameSpeed,	// интервал между началом передвижения карт в эту позицию
		interval2 = 50/gameSpeed;	// интервал между началом движения карт в колоду

	var offset = game.scale.cellHeight*2 + game.scale.gridOffset.y,		// отступ сверху до верхней границы вращения
		maxHeight = game.screenWidth - game.scale.cellWidth*4,
		height = playerField.y - animOffset - offset;	// высота ограничивающая вращение
	if(height > maxHeight){
		height = maxHeight;
		offset = game.screenHeight/2 - offset - height/2;
	}
	var hx = game.screenWidth/2,						// позиция по горизонтали вокруг которой вращаются карты
		cx = hx + height/2 - skinManager.skin.width,	// позиция по горизонтали откуда начнут вращаться карты
		cy = height/2 + offset;							// позиция по вертикали откуда начнут вращаться карты и вокруг которой вращаются карты

	var shuffledCardsInfo = shuffleArray(cardsInfo.slice()),	// перемешанный массив с информацией о картах
		len = cardsInfo.length;	

	// Используем хвост карты в анимации с измененными свойствами
	var trail = cardControl.trail;
	cardControl.trailReset();
	var trailLifespan = 1000/gameSpeed;

	var tableLitTime = 500;		// Время остановки на подсвеченном столе
	var tableLightOutTime = 500;	// Время отключения подсветки стола
	// Минимальное время до перемещения карт в колоду
	var minTime = interval * len + duration + 500/gameSpeed;
	// Полное время анимации
	var totalTime = minTime + len*interval2 + trailLifespan + tableLitTime + tableLightOutTime;

	// Поля, которые нужно будет анимировать
	var fields = [].concat(
		playerField,
		this.opponents,
		this.fields.DISCARD_PILE
	);
	// Поля стола, которые будут подсвечены
	var tables = this.builder.tableOrder.map(function(i){
		return this.table[i];
	}, this);
	var timeForTables = trailLifespan,		// Время подсветки стола
		timePerField = (totalTime - timeForTables - 1000)/fields.length,	// Время между анимацией двух полей
		timePerTable = timeForTables/tables.length;					// Время между подсветкой двух полей стола
	// Анимируем поля с задержкой асинхронно остальной анимации
	fields.forEach(function(f, i){
		f.animateAppearance(timePerField*i);
	});

	// Передвигает карту из перемешанного массива в точку начала вращения, запускает вращение
	// и планирует перемещение карты из неперемешанного массива в колоду
	function revolveCard(i, lastStep){
		var c = cardManager.cards[shuffledCardsInfo[i].cid]; // перемешанная карта		
		var rc = cardManager.cards[cardsInfo[i].cid];		 // неперемешанная карта
		var info = cardsInfo[i];
		var delay = interval*i;		// задержка до перемешения в точку врашения
		var da = (-0.009 + 0.005*Math.random())*gameSpeed;	// скорость вращения

		c.presetValue(null, 0);
		c.moveTo(cx, cy, duration*gameSpeed, delay*gameSpeed, false, true, BRING_TO_TOP_ON.START, Phaser.Easing.Cubic.In);
		c.revolveAround(hx, cy, da);

		return lastStep.then(function(){
			rc.stopRevolving();
			var fieldId = info.field;
			this.moveCards(this.fields[fieldId], [info]);
		}, interval2, this); 
	}

	/*
	*  Ход анимации: 
	*  Карты перемещаются в точку начала вращения с interval, где они вращаются
	*  В это же время анимируются поля игроков
	*  Как только первая карта достигла точки вращения, запускается эмиттер
	*  Спустя секунду как все карты достигли точки вращения, они начинают перемещаться в колоду с interval2
	*  Как только последняя карта отправилась в колоду, выключается эмиттер
	*  Здесь заканчивается анимация полей игроков
	*  После этого подсвечиваются и выключаются поля стола
	*  Конец анимации, ресет эмиттера
	*/

	seq.append(function(seq){
		// Меняем свойства хвоста карты, ставим его в центр вращения 
		// и планируем запуск сразу как первая карта достигнет точки вращения
		var lastStep = seq.append(function(){
			trail.x = hx;
			trail.y = height/2 + offset;
			trail.lifespan = trailLifespan;
			trail.interval = 10;

			trail.maxParticles = Math.ceil(trail.lifespan / trail.interval);
			trail.makeParticles(skinManager.skin.trailName, [0, 1, 2, 3]);

			// Добавляем хвост к фону, чтобы он был за картами
			ui.background.add(trail);

			trail.width = trail.height = height/2 - skinManager.skin.width*1.5;
			trail.start(false, trail.lifespan, trail.interval);
		}, minTime - duration);

		// Запускаем перемещение и вращение карт, планируем перемешения в колоду
		for(var i = 0; i < cardsInfo.length; i++){
			lastStep = revolveCard.call(this, i, lastStep);
		}

		lastStep = lastStep.then(function(){
			// выключаем эмиттер хвоста карты
			trail.on = false;

			// Добавляем колоде текстуру, обозначающую текущую козырную масть
			if(trumpSuit || trumpSuit === 0){
				this.setTrumpSuit(trumpSuit);
			}
		}, 0, this);

		// Подсвечиваем стол
		tables.forEach(function(t, i){
			if(i == tables.length - 1){
				timePerTable += tableLitTime;
			}
			lastStep = lastStep.then(function(){
				t.setOwnPlayability(true);
			}, timePerTable);
		});

		// Выключаем подсветку стола
		timePerTable = tableLightOutTime/tables.length;
		tables.forEach(function(t, i){
			lastStep = lastStep.then(function(){
				t.setOwnPlayability(false);
			}, timePerTable);
		});

		// Завершаем анимацию и ресетим хвост карты
		lastStep.then(function(){
			fields.forEach(function(f){
				f.endAnimation();
			});
			cardControl.trailReset();
		});

	}, duration, this);
};

/**
* Убирает визуальный замок с поля.
* @param {string}  id            id поля
* @param {boolean} [noAnimation] отключает анимацию
*/
FieldManager.prototype.unlockField = function(seq, id, noAnimation){
	var field = this.fields[id];
	if(!field || !field.icon){
		console.error('Field manager: cannot unlock field', id, ', no such field or field has no icon');
		return;
	}
	var icon = field.icon;
	
	if(game.paused || noAnimation){
		field.icon.destroy();
		field.icon = null;
		return;
	}

	var lockDelay = 100/game.speed,
		spinDelay = 300/game.speed,
		spinTime = 1000/game.speed;

	var tween = game.add.tween(icon);	

	seq.append(function(){
		icon.visible = true;
		field.iconStyle.shouldHide = false;
		icon.alpha = 1;
		field.setOwnHighlight(true);
		tween.to({alpha: 0, angle: 720}, spinTime - lockDelay, Phaser.Easing.Quadratic.In, false, spinDelay);
		tween.start();	
	}, lockDelay)
	.then(function(seq){
		icon.loadTexture('unlock');
	}, spinTime + spinDelay - lockDelay)
	.then(function(seq){
		tween.stop();
		icon.destroy();
		field.icon = null;
	}, lockDelay)
	.then(function(){
		if(!field.playable){
			field.setOwnHighlight(false);
		}
	});
};

/**
* Анимирует конец игры в зависимости от результатов.
* @param  {object} results результаты игры
* @param  {object} seq     последовательность, в которую будут добавлены анимации
*/
FieldManager.prototype.animateGameEnd = function(results, seq){
	var discard = fieldManager.fields.DISCARD_PILE,
		dummy = fieldManager.fields.dummy, 
		won = results && results.winners && ~results.winners.indexOf(game.pid),
		lost = results && results.loser && results.loser == game.pid,
		delay = 0;
	
	if(!discard || !dummy){
		return;
	}

	var cards = discard.cards.slice();
	delay = dummy.queueCards(cards, BRING_TO_TOP_ON.START_ALL);	
	delay += cardManager.defaultMoveTime;

	// Перемещаем карты в поле по центру и стартуем анимацию
	seq.append(function(seq){

		discard.removeAllCards();
		dummy.placeQueuedCards();
		if(won){
			return;
		}

		// Анимируем не выигрыш
		seq.abort();
		seq.append(delay - cardManager.defaultMoveTime)
			.then(function(seq, sync){
				if(lost){
					ui.announcer.newMessage('You lost');
				}
				else{
					ui.announcer.newMessage('Better luck next time');
				}

				fieldManager.resetFields();
				cardManager.enablePhysics(true);

				if(!sync){
					game.shake(15, 800, 20, 50);
				}

				ui.menus.endGame.fadeIn();
				ui.layers.hideLayer(ui.actionButtons, true);
			});

	}, delay/game.speed)
	.then(function(){

		// Анимируем выигрыш
		ui.announcer.newMessage('YOU WON!');
		ui.menus.endGame.fadeIn();
		ui.layers.hideLayer(ui.actionButtons, true);

		for(var ci = 0; ci < dummy.cards.length; ci++){
			var card = dummy.cards[ci],
				x = card.sprite.x + card.x + (10 * (ci - dummy.cards.length / 2)),
				time = Math.random() * cardManager.defaultMoveTime + 500;
			card.moveTo(x, -200, time, 0, false, true);
			card.rotateTo(Math.random() * 360 - 180, time);
		}

		cards = dummy.cards.slice();
		fieldManager.resetFields();
		cardManager.enablePhysics(false, dummy.cards);

	}, cardManager.defaultMoveTime + 500)
	.then(function(){

		// Запускаем эмиттер карт
		for(var ci = 0; ci < cards.length; ci++){
			var card = cards[ci];
			card.field = null;
			card.destroy(0, true);
		}

		cardEmitter.start(300, 500, 100, false, 100, 10);

	}, 500);
};

/**
* Анимирует выход противника из игры и его замену ботом.
* @param  {object} action информация об игроках
* @param  {object} seq    последовательность, в которую будут добавлены анимации
*/
FieldManager.prototype.animatePlayerConcede = function(action, seq){
	var player = gameInfo.getPlayer(action.pid);
	ui.eventFeed.newMessage(player.name + ' conceded', 2000);
	player.name = action.name;
	var field = fieldManager.fields[action.pid];
	var duration = field.moveTime/game.speed;
	seq.append(function(){		
		field.badge.visible = false;		
		field.badge.updatePosition();
		field.setupAnimatedAppearance();
	}, duration, 300)
	.then(function(){
		field.badge.visible = true;		
		field.animateAppearance();
	}, duration*2)
	.then(function(){
		field.endAnimation();
	});
};