123
This commit is contained in:
119
public/game.js
119
public/game.js
@ -411,32 +411,139 @@
|
||||
const attackerWrap = document.querySelector(`[data-minion-id="${lastLog.attackerMinionId}"]`);
|
||||
const targetWrap = document.querySelector(`[data-minion-id="${lastLog.targetMinionId}"]`);
|
||||
if (!attackerWrap || !targetWrap) return false;
|
||||
attackerWrap.classList.add('combat-lunge');
|
||||
|
||||
// Вычисляем позиции для точного перемещения карты
|
||||
const attackerRect = attackerWrap.getBoundingClientRect();
|
||||
const targetRect = targetWrap.getBoundingClientRect();
|
||||
|
||||
// Вычисляем смещение
|
||||
const dx = targetRect.left + targetRect.width / 2 - (attackerRect.left + attackerRect.width / 2);
|
||||
const dy = targetRect.top + targetRect.height / 2 - (attackerRect.top + attackerRect.height / 2);
|
||||
|
||||
// Сохраняем начальную позицию
|
||||
const startX = attackerRect.left;
|
||||
const startY = attackerRect.top;
|
||||
const startWidth = attackerRect.width;
|
||||
const startHeight = attackerRect.height;
|
||||
|
||||
// Устанавливаем fixed positioning для наложения
|
||||
attackerWrap.style.position = 'fixed';
|
||||
attackerWrap.style.left = startX + 'px';
|
||||
attackerWrap.style.top = startY + 'px';
|
||||
attackerWrap.style.width = startWidth + 'px';
|
||||
attackerWrap.style.height = startHeight + 'px';
|
||||
attackerWrap.style.zIndex = '1000';
|
||||
attackerWrap.style.transition = 'none';
|
||||
|
||||
// Добавляем класс для анимации
|
||||
attackerWrap.classList.add('combat-lunge-overlay');
|
||||
targetWrap.classList.add('combat-hit');
|
||||
|
||||
// Устанавливаем CSS переменные для перемещения
|
||||
attackerWrap.style.setProperty('--attack-dx', dx + 'px');
|
||||
attackerWrap.style.setProperty('--attack-dy', dy + 'px');
|
||||
|
||||
if (lastLog.damage) showDamageEffect(targetWrap, lastLog.damage, false);
|
||||
if (typeof window.Sounds !== 'undefined') window.Sounds.attack();
|
||||
if (lastLog.attackerDied) {
|
||||
|
||||
// Анимация удара
|
||||
setTimeout(() => {
|
||||
targetWrap.classList.add('combat-hit-impact');
|
||||
}, 200);
|
||||
|
||||
// Возврат карты и очистка
|
||||
setTimeout(() => {
|
||||
// Если атакующий не умер, возвращаем карту на место
|
||||
if (!lastLog.attackerDied) {
|
||||
attackerWrap.classList.remove('combat-lunge-overlay');
|
||||
attackerWrap.style.position = '';
|
||||
attackerWrap.style.left = '';
|
||||
attackerWrap.style.top = '';
|
||||
attackerWrap.style.width = '';
|
||||
attackerWrap.style.height = '';
|
||||
attackerWrap.style.zIndex = '';
|
||||
attackerWrap.style.setProperty('--attack-dx', '');
|
||||
attackerWrap.style.setProperty('--attack-dy', '');
|
||||
attackerWrap.style.transition = '';
|
||||
} else {
|
||||
// Если атакующий умер, сразу применяем анимацию смерти
|
||||
attackerWrap.classList.remove('combat-lunge-overlay');
|
||||
attackerWrap.style.position = '';
|
||||
attackerWrap.style.left = '';
|
||||
attackerWrap.style.top = '';
|
||||
attackerWrap.style.width = '';
|
||||
attackerWrap.style.height = '';
|
||||
attackerWrap.style.zIndex = '';
|
||||
attackerWrap.style.setProperty('--attack-dx', '');
|
||||
attackerWrap.style.setProperty('--attack-dy', '');
|
||||
attackerWrap.style.transition = '';
|
||||
attackerWrap.classList.add('combat-death');
|
||||
showDamageEffect(attackerWrap, '💀', false);
|
||||
}
|
||||
|
||||
if (lastLog.targetDied) {
|
||||
targetWrap.classList.add('combat-death');
|
||||
showDamageEffect(targetWrap, '💀', false);
|
||||
}
|
||||
|
||||
targetWrap.classList.remove('combat-hit-impact');
|
||||
}, 500);
|
||||
|
||||
combatTimeout = setTimeout(function () {
|
||||
combatTimeout = null;
|
||||
renderBoards(state);
|
||||
bindGameEvents(state);
|
||||
}, 600);
|
||||
return true;
|
||||
} else if (lastLog.type === 'attackHero' && lastLog.toPlayer !== undefined) {
|
||||
} else if (lastLog.type === 'attackHero' && lastLog.toPlayer !== undefined && lastLog.attackerMinionId) {
|
||||
const attackerWrap = document.querySelector(`[data-minion-id="${lastLog.attackerMinionId}"]`);
|
||||
const targetPlayer = state.players[lastLog.toPlayer];
|
||||
if (targetPlayer) {
|
||||
if (attackerWrap && targetPlayer) {
|
||||
const heroEl = document.querySelector(`[data-player-index="${lastLog.toPlayer}"].hero-target, .opponent-block[data-opponent-index="${lastLog.toPlayer}"] .drop-target-hero`);
|
||||
if (heroEl) {
|
||||
heroEl.classList.add('hero-damage-flash');
|
||||
// Вычисляем позиции для атаки по герою
|
||||
const attackerRect = attackerWrap.getBoundingClientRect();
|
||||
const heroRect = heroEl.getBoundingClientRect();
|
||||
|
||||
const dx = heroRect.left + heroRect.width / 2 - (attackerRect.left + attackerRect.width / 2);
|
||||
const dy = heroRect.top + heroRect.height / 2 - (attackerRect.top + attackerRect.height / 2);
|
||||
|
||||
const startX = attackerRect.left;
|
||||
const startY = attackerRect.top;
|
||||
const startWidth = attackerRect.width;
|
||||
const startHeight = attackerRect.height;
|
||||
|
||||
// Устанавливаем fixed positioning
|
||||
attackerWrap.style.position = 'fixed';
|
||||
attackerWrap.style.left = startX + 'px';
|
||||
attackerWrap.style.top = startY + 'px';
|
||||
attackerWrap.style.width = startWidth + 'px';
|
||||
attackerWrap.style.height = startHeight + 'px';
|
||||
attackerWrap.style.zIndex = '1000';
|
||||
attackerWrap.style.transition = 'none';
|
||||
|
||||
attackerWrap.classList.add('combat-lunge-overlay');
|
||||
attackerWrap.style.setProperty('--attack-dx', dx + 'px');
|
||||
attackerWrap.style.setProperty('--attack-dy', dy + 'px');
|
||||
|
||||
heroEl.classList.add('hero-damage-flash', 'combat-hit-impact');
|
||||
if (lastLog.damage) showDamageEffect(heroEl, lastLog.damage, true);
|
||||
setTimeout(() => heroEl.classList.remove('hero-damage-flash'), 500);
|
||||
if (typeof window.Sounds !== 'undefined') window.Sounds.attack();
|
||||
|
||||
setTimeout(() => {
|
||||
attackerWrap.classList.remove('combat-lunge-overlay');
|
||||
attackerWrap.style.position = '';
|
||||
attackerWrap.style.left = '';
|
||||
attackerWrap.style.top = '';
|
||||
attackerWrap.style.width = '';
|
||||
attackerWrap.style.height = '';
|
||||
attackerWrap.style.zIndex = '';
|
||||
attackerWrap.style.setProperty('--attack-dx', '');
|
||||
attackerWrap.style.setProperty('--attack-dy', '');
|
||||
attackerWrap.style.transition = '';
|
||||
|
||||
heroEl.classList.remove('hero-damage-flash', 'combat-hit-impact');
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@ -842,6 +842,48 @@ html, body {
|
||||
35% { transform: translate(var(--lunge-dx, 30px), var(--lunge-dy, -20px)) scale(1.15); filter: brightness(1.4); }
|
||||
100% { transform: translate(0, 0) scale(1); filter: brightness(1); }
|
||||
}
|
||||
|
||||
/* Новая анимация: карта накладывается на цель */
|
||||
.card-wrap.combat-lunge-overlay {
|
||||
animation: combatLungeOverlay 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-wrap.combat-lunge-overlay .card {
|
||||
box-shadow: 0 0 40px rgba(255, 200, 0, 0.6), 0 0 80px rgba(255, 100, 0, 0.4);
|
||||
}
|
||||
|
||||
@keyframes combatLungeOverlay {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1) rotate(0deg);
|
||||
filter: brightness(1);
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
transform: translate(calc(var(--attack-dx, 0) * 0.6), calc(var(--attack-dy, 0) * 0.6)) scale(1.15) rotate(-3deg);
|
||||
filter: brightness(1.4) drop-shadow(0 0 25px rgba(255, 200, 0, 0.7));
|
||||
}
|
||||
45% {
|
||||
transform: translate(var(--attack-dx, 0), var(--attack-dy, 0)) scale(1.3) rotate(0deg);
|
||||
filter: brightness(2) drop-shadow(0 0 40px rgba(255, 100, 0, 1));
|
||||
z-index: 1001;
|
||||
}
|
||||
50% {
|
||||
transform: translate(var(--attack-dx, 0), var(--attack-dy, 0)) scale(1.28) rotate(1deg);
|
||||
filter: brightness(1.9) drop-shadow(0 0 35px rgba(255, 150, 0, 0.95));
|
||||
}
|
||||
55% {
|
||||
transform: translate(var(--attack-dx, 0), var(--attack-dy, 0)) scale(1.25) rotate(-1deg);
|
||||
filter: brightness(1.7) drop-shadow(0 0 30px rgba(255, 120, 0, 0.9));
|
||||
}
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1) rotate(0deg);
|
||||
filter: brightness(1);
|
||||
z-index: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card-wrap.combat-hit .card {
|
||||
animation: combatHit 0.45s ease-out forwards;
|
||||
}
|
||||
@ -851,6 +893,66 @@ html, body {
|
||||
40% { transform: scale(0.95); filter: brightness(1.2); }
|
||||
100% { transform: scale(1); filter: brightness(1); }
|
||||
}
|
||||
|
||||
/* Эффект удара - карта трясётся и подсвечивается */
|
||||
.card-wrap.combat-hit-impact {
|
||||
animation: combatHitImpact 0.35s ease-out forwards;
|
||||
}
|
||||
|
||||
.card-wrap.combat-hit-impact .card {
|
||||
box-shadow: 0 0 30px rgba(255, 50, 50, 0.8), 0 0 60px rgba(255, 100, 100, 0.5);
|
||||
}
|
||||
|
||||
@keyframes combatHitImpact {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1) rotate(0deg);
|
||||
filter: brightness(1);
|
||||
}
|
||||
8% {
|
||||
transform: translate(-4px, -3px) scale(1.08) rotate(-3deg);
|
||||
filter: brightness(2.8) saturate(1.3);
|
||||
}
|
||||
16% {
|
||||
transform: translate(4px, 3px) scale(1.08) rotate(3deg);
|
||||
filter: brightness(3) saturate(1.4);
|
||||
}
|
||||
24% {
|
||||
transform: translate(-3px, 2px) scale(1.05) rotate(-2deg);
|
||||
filter: brightness(2.5) saturate(1.2);
|
||||
}
|
||||
32% {
|
||||
transform: translate(3px, -2px) scale(1.05) rotate(2deg);
|
||||
filter: brightness(2.7) saturate(1.3);
|
||||
}
|
||||
40% {
|
||||
transform: translate(-2px, 1px) scale(1.03) rotate(-1deg);
|
||||
filter: brightness(2.2) saturate(1.1);
|
||||
}
|
||||
48% {
|
||||
transform: translate(2px, -1px) scale(1.03) rotate(1deg);
|
||||
filter: brightness(2.4) saturate(1.2);
|
||||
}
|
||||
56% {
|
||||
transform: translate(-1px, 0) scale(1.02) rotate(-0.5deg);
|
||||
filter: brightness(2) saturate(1.05);
|
||||
}
|
||||
64% {
|
||||
transform: translate(1px, 0) scale(1.02) rotate(0.5deg);
|
||||
filter: brightness(1.8) saturate(1.1);
|
||||
}
|
||||
72% {
|
||||
transform: translate(0, 0) scale(1.01) rotate(0deg);
|
||||
filter: brightness(1.5) saturate(1.05);
|
||||
}
|
||||
80% {
|
||||
transform: translate(0, 0) scale(1.005) rotate(0deg);
|
||||
filter: brightness(1.2) saturate(1.02);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1) rotate(0deg);
|
||||
filter: brightness(1) saturate(1);
|
||||
}
|
||||
}
|
||||
.card-wrap.combat-death .card {
|
||||
animation: combatDeath 0.4s ease-in forwards;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user