Align battle map art and bond effects
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 1.6 MiB |
@@ -24,15 +24,15 @@ export type UnitClassDefinition = {
|
||||
};
|
||||
|
||||
export const terrainRules: Record<TerrainType, TerrainRule> = {
|
||||
plain: { label: '평지', color: 0x6d8056, alpha: 0.04, moveCost: 1, defenseBonus: 0 },
|
||||
road: { label: '길', color: 0xb08b55, alpha: 0.1, moveCost: 1, defenseBonus: 0 },
|
||||
forest: { label: '숲', color: 0x355f42, alpha: 0.17, moveCost: 2, defenseBonus: 10 },
|
||||
hill: { label: '언덕', color: 0x82724e, alpha: 0.15, moveCost: 2, defenseBonus: 8 },
|
||||
village: { label: '마을', color: 0x8d6f4b, alpha: 0.2, moveCost: 1, defenseBonus: 12, recoveryHp: 8, moraleBonus: 3 },
|
||||
fort: { label: '요새', color: 0x735c46, alpha: 0.22, moveCost: 1, defenseBonus: 18, recoveryHp: 10, moraleBonus: 4 },
|
||||
camp: { label: '진영', color: 0x4f6a73, alpha: 0.17, moveCost: 1, defenseBonus: 15, recoveryHp: 6, moraleBonus: 4 },
|
||||
river: { label: '강', color: 0x2c6687, alpha: 0.24, moveCost: 99, defenseBonus: -4, passable: false },
|
||||
cliff: { label: '절벽', color: 0x39342e, alpha: 0.28, moveCost: 99, defenseBonus: 20, passable: false }
|
||||
plain: { label: '평지', color: 0x6d8056, alpha: 0.01, moveCost: 1, defenseBonus: 0 },
|
||||
road: { label: '길', color: 0xb08b55, alpha: 0.015, moveCost: 1, defenseBonus: 0 },
|
||||
forest: { label: '숲', color: 0x355f42, alpha: 0.025, moveCost: 2, defenseBonus: 10 },
|
||||
hill: { label: '언덕', color: 0x82724e, alpha: 0.025, moveCost: 2, defenseBonus: 8 },
|
||||
village: { label: '마을', color: 0x8d6f4b, alpha: 0.03, moveCost: 1, defenseBonus: 12, recoveryHp: 8, moraleBonus: 3 },
|
||||
fort: { label: '요새', color: 0x735c46, alpha: 0.035, moveCost: 1, defenseBonus: 18, recoveryHp: 10, moraleBonus: 4 },
|
||||
camp: { label: '진영', color: 0x4f6a73, alpha: 0.03, moveCost: 1, defenseBonus: 15, recoveryHp: 6, moraleBonus: 4 },
|
||||
river: { label: '강', color: 0x2c6687, alpha: 0.035, moveCost: 99, defenseBonus: -4, passable: false },
|
||||
cliff: { label: '절벽', color: 0x39342e, alpha: 0.045, moveCost: 99, defenseBonus: 20, passable: false }
|
||||
};
|
||||
|
||||
export const unitClasses: Record<UnitClassKey, UnitClassDefinition> = {
|
||||
|
||||
@@ -188,6 +188,8 @@ type CombatPreview = {
|
||||
attacker: UnitData;
|
||||
defender: UnitData;
|
||||
damage: number;
|
||||
bondDamageBonus: number;
|
||||
bondLabel?: string;
|
||||
hitRate: number;
|
||||
criticalRate: number;
|
||||
counterAvailable: boolean;
|
||||
@@ -1595,9 +1597,10 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
private renderAttackPreview(preview: CombatPreview) {
|
||||
const counter = preview.counterAvailable ? ' / 반격 가능' : '';
|
||||
const bond = preview.bondDamageBonus > 0 && preview.bondLabel ? `\n공명 ${preview.bondLabel} 피해 +${preview.bondDamageBonus}%` : '';
|
||||
this.renderUnitDetail(
|
||||
preview.defender,
|
||||
`${preview.attacker.name} ${commandLabels[preview.action]} 예측\n피해 ${preview.damage} / 명중 ${preview.hitRate}% / 치명 ${preview.criticalRate}%\n지형 ${preview.terrainLabel}${counter}`
|
||||
`${preview.attacker.name} ${commandLabels[preview.action]} 예측\n피해 ${preview.damage} / 명중 ${preview.hitRate}% / 치명 ${preview.criticalRate}%\n지형 ${preview.terrainLabel}${counter}${bond}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2018,8 +2021,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
const terrainRule = getTerrainRule(terrain);
|
||||
const attackPower = this.actionPower(attacker, action, usable);
|
||||
const defensePower = this.actionDefense(defender, action, terrainRule.defenseBonus);
|
||||
const bondBonus = attacker.faction === 'ally' ? this.strongestBondBonus(attacker.id).damageBonus : 0;
|
||||
const damage = Phaser.Math.Clamp(Math.round((attackPower - defensePower) * (1 + bondBonus / 100)), 3, defender.maxHp);
|
||||
const bondBonus = attacker.faction === 'ally' ? this.strongestBondBonus(attacker.id) : { damageBonus: 0, chainRate: 0 };
|
||||
const damage = Phaser.Math.Clamp(Math.round((attackPower - defensePower) * (1 + bondBonus.damageBonus / 100)), 3, defender.maxHp);
|
||||
const defenderTerrainRating = getUnitClass(defender.classKey).terrainRatings[terrain] ?? 100;
|
||||
const terrainHitPenalty = Math.floor(terrainRule.defenseBonus / 2) + Math.max(0, Math.floor((defenderTerrainRating - 100) / 4));
|
||||
const hitRate = Phaser.Math.Clamp(
|
||||
@@ -2049,6 +2052,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
attacker,
|
||||
defender,
|
||||
damage,
|
||||
bondDamageBonus: bondBonus.damageBonus,
|
||||
bondLabel: bondBonus.label,
|
||||
hitRate,
|
||||
criticalRate,
|
||||
counterAvailable: this.canCounterAttack(defender, attacker, action),
|
||||
@@ -2223,9 +2228,10 @@ export class BattleScene extends Phaser.Scene {
|
||||
private formatCombatResult(result: CombatResult) {
|
||||
const outcome = this.formatCombatOutcome(result);
|
||||
const defeated = result.defeated ? `\n${result.defender.name} 퇴각!` : `\n${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
|
||||
const bondBonus = result.bondDamageBonus > 0 && result.bondLabel ? `\n공명 효과 ${result.bondLabel}: 피해 +${result.bondDamageBonus}%` : '';
|
||||
const bond = result.bondExp > 0 ? `\n공명 경험치 +${result.bondExp}` : '';
|
||||
const counter = result.counter ? `\n${this.formatCounterLine(result.counter)}` : '';
|
||||
return `${result.attacker.name} ${result.usable?.name ?? commandLabels[result.action]}\n${outcome}${defeated}\n장수 경험치 +${result.characterGrowth.amount}\n${this.formatEquipmentGrowth(result.attackerGrowth)}\n${this.formatEquipmentGrowth(result.defenderGrowth)}${bond}${counter}`;
|
||||
return `${result.attacker.name} ${result.usable?.name ?? commandLabels[result.action]}\n${outcome}${defeated}${bondBonus}\n장수 경험치 +${result.characterGrowth.amount}\n${this.formatEquipmentGrowth(result.attackerGrowth)}\n${this.formatEquipmentGrowth(result.defenderGrowth)}${bond}${counter}`;
|
||||
}
|
||||
|
||||
private formatCombatOutcome(result: CombatResult) {
|
||||
@@ -2902,6 +2908,10 @@ export class BattleScene extends Phaser.Scene {
|
||||
attackerStatus.expFill.setScale(result.characterGrowth.previousExp / result.characterGrowth.next, 1);
|
||||
attackerStatus.expText.setText(`${result.characterGrowth.previousExp} / ${result.characterGrowth.next}`);
|
||||
|
||||
if (result.bondDamageBonus > 0 && result.bondLabel) {
|
||||
await this.showBondCombatEffect(result, left + panelWidth / 2, top + 106, attackerX, groundY - 70, depth + 15);
|
||||
}
|
||||
|
||||
await this.delay(180);
|
||||
await this.playCombatActionMotion(result, attackerSprite, defenderSprite, attackerX, defenderX, groundY, depth + 10, defenderDirection);
|
||||
if (result.hit) {
|
||||
@@ -3490,6 +3500,44 @@ export class BattleScene extends Phaser.Scene {
|
||||
return container;
|
||||
}
|
||||
|
||||
private showBondCombatEffect(result: CombatResult, textX: number, textY: number, auraX: number, auraY: number, depth: number) {
|
||||
if (!result.bondLabel || result.bondDamageBonus <= 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
soundDirector.playEffect('strategy-cast', { volume: 0.22, rate: 1.08, stopAfterMs: 720 });
|
||||
|
||||
const aura = this.trackCombatObject(this.add.circle(auraX, auraY, 42));
|
||||
aura.setStrokeStyle(4, 0xffdf7b, 0.88);
|
||||
aura.setDepth(depth);
|
||||
|
||||
const inner = this.trackCombatObject(this.add.circle(auraX, auraY, 24, 0xffdf7b, 0.18));
|
||||
inner.setDepth(depth - 1);
|
||||
|
||||
const spark = this.trackCombatObject(this.add.star(auraX, auraY, 6, 8, 24, 0xfff0b8, 0.86));
|
||||
spark.setDepth(depth + 1);
|
||||
|
||||
const text = this.trackCombatObject(this.add.text(textX, textY, `공명 효과 ${result.bondLabel}\n피해 +${result.bondDamageBonus}%`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '18px',
|
||||
color: '#fff2b8',
|
||||
fontStyle: '700',
|
||||
align: 'center',
|
||||
stroke: '#2b1606',
|
||||
strokeThickness: 4,
|
||||
lineSpacing: 2
|
||||
}));
|
||||
text.setOrigin(0.5);
|
||||
text.setDepth(depth + 2);
|
||||
|
||||
this.tweens.add({ targets: aura, scale: 1.7, alpha: 0, duration: 560, ease: 'Sine.easeOut' });
|
||||
this.tweens.add({ targets: inner, scale: 1.35, alpha: 0.04, duration: 560, ease: 'Sine.easeOut' });
|
||||
this.tweens.add({ targets: spark, angle: 180, scale: 1.22, duration: 560, ease: 'Sine.easeInOut' });
|
||||
this.tweens.add({ targets: text, y: textY - 8, scale: 1.05, duration: 180, yoyo: true, ease: 'Sine.easeOut' });
|
||||
|
||||
return this.delay(580);
|
||||
}
|
||||
|
||||
private showCombatImpact(result: CombatResult, x: number, y: number, depth: number) {
|
||||
soundDirector.playEffect('combat-impact', { volume: result.critical ? 0.62 : result.action === 'strategy' ? 0.3 : 0.48, stopAfterMs: 650 });
|
||||
|
||||
@@ -4006,10 +4054,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
if (!strongest) {
|
||||
return { damageBonus: 0, chainRate: 0 };
|
||||
}
|
||||
const [first, second] = strongest.unitIds.map((id) => this.unitName(id));
|
||||
|
||||
return {
|
||||
damageBonus: Math.floor(strongest.level / 20) * 3,
|
||||
chainRate: strongest.level >= 70 ? 18 : strongest.level >= 50 ? 8 : 0
|
||||
chainRate: strongest.level >= 70 ? 18 : strongest.level >= 50 ? 8 : 0,
|
||||
label: `${strongest.title}: ${first}·${second}`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user