Improve battle status effect UI
This commit is contained in:
@@ -1336,6 +1336,7 @@ type CombatResult = CombatPreview & {
|
||||
defenderGrowth: EquipmentGrowthResult;
|
||||
bondExp: number;
|
||||
bondGrowth: BondGrowthResult[];
|
||||
statusApplied?: BattleStatusState;
|
||||
counter?: CombatResult;
|
||||
};
|
||||
|
||||
@@ -1352,6 +1353,9 @@ type BattleUsable = {
|
||||
attackBonus?: number;
|
||||
hitBonus?: number;
|
||||
duration?: number;
|
||||
statusEffect?: BattleStatusKind;
|
||||
statusDuration?: number;
|
||||
statusPower?: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
@@ -1397,6 +1401,16 @@ type SupportResult = {
|
||||
equipmentGrowth: EquipmentGrowthResult;
|
||||
};
|
||||
|
||||
type BattleStatusKind = 'burn' | 'confusion';
|
||||
|
||||
type BattleStatusState = {
|
||||
unitId: string;
|
||||
kind: BattleStatusKind;
|
||||
label: string;
|
||||
turns: number;
|
||||
power: number;
|
||||
};
|
||||
|
||||
type BattleBuffState = {
|
||||
unitId: string;
|
||||
label: string;
|
||||
@@ -1412,6 +1426,7 @@ type UnitEffectSummary = {
|
||||
value: string;
|
||||
tone: number;
|
||||
turns?: number;
|
||||
polarity?: 'buff' | 'debuff' | 'terrain';
|
||||
};
|
||||
|
||||
type PreviewModifierSummary = {
|
||||
@@ -1471,6 +1486,7 @@ type BattleSaveState = {
|
||||
bonds: BondState[];
|
||||
itemStocks?: Record<string, Record<string, number>>;
|
||||
battleBuffs?: BattleBuffState[];
|
||||
battleStatuses?: BattleStatusState[];
|
||||
battleStats?: Record<string, UnitBattleStats>;
|
||||
triggeredBattleEvents?: string[];
|
||||
};
|
||||
@@ -1521,6 +1537,9 @@ const usableCatalog: Record<string, BattleUsable> = {
|
||||
power: 12,
|
||||
accuracyBonus: 6,
|
||||
criticalBonus: 0,
|
||||
statusEffect: 'burn',
|
||||
statusDuration: 2,
|
||||
statusPower: 4,
|
||||
description: '적 한 부대에 책략 피해를 준다.'
|
||||
},
|
||||
roar: {
|
||||
@@ -1533,6 +1552,9 @@ const usableCatalog: Record<string, BattleUsable> = {
|
||||
power: 9,
|
||||
accuracyBonus: 8,
|
||||
criticalBonus: 4,
|
||||
statusEffect: 'confusion',
|
||||
statusDuration: 1,
|
||||
statusPower: 1,
|
||||
description: '가까운 적을 위압해 피해를 준다.'
|
||||
},
|
||||
bean: {
|
||||
@@ -3011,6 +3033,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
private bondStates = new Map<string, BondState>();
|
||||
private itemStocks = new Map<string, Map<string, number>>();
|
||||
private battleBuffs = new Map<string, BattleBuffState>();
|
||||
private battleStatuses = new Map<string, BattleStatusState[]>();
|
||||
private attackIntents: AttackIntent[] = [];
|
||||
private battleLog: string[] = [];
|
||||
private enemyHomeTiles = new Map<string, { x: number; y: number }>();
|
||||
@@ -3068,6 +3091,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.bondStates = this.createBondStates();
|
||||
this.itemStocks = this.createItemStocks();
|
||||
this.battleBuffs.clear();
|
||||
this.battleStatuses.clear();
|
||||
this.applyDebugStatusUiSetup();
|
||||
this.enemyHomeTiles = this.createEnemyHomeTiles();
|
||||
this.battleStats = this.createBattleStats();
|
||||
this.triggeredBattleEvents.clear();
|
||||
@@ -3259,7 +3284,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private applyDebugBattleSetup() {
|
||||
if (this.debugBattleSetupKey() !== 'attack-preview') {
|
||||
const setupKey = this.debugBattleSetupKey();
|
||||
if (setupKey !== 'attack-preview' && setupKey !== 'status-ui') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3279,6 +3305,34 @@ export class BattleScene extends Phaser.Scene {
|
||||
target.hp = Math.max(1, target.hp);
|
||||
}
|
||||
|
||||
private applyDebugStatusUiSetup() {
|
||||
if (this.debugBattleSetupKey() !== 'status-ui') {
|
||||
return;
|
||||
}
|
||||
|
||||
const zhangFei = battleUnits.find((unit) => unit.id === 'zhang-fei' && unit.hp > 0);
|
||||
if (zhangFei) {
|
||||
this.battleBuffs.set(zhangFei.id, {
|
||||
unitId: zhangFei.id,
|
||||
label: '술',
|
||||
turns: 2,
|
||||
attackBonus: 3,
|
||||
hitBonus: 6,
|
||||
criticalBonus: 10
|
||||
});
|
||||
}
|
||||
|
||||
const guanYu = battleUnits.find((unit) => unit.id === 'guan-yu' && unit.hp > 0);
|
||||
if (guanYu) {
|
||||
this.applyBattleStatus(guanYu, 'burn', 2, 4);
|
||||
}
|
||||
|
||||
const target = battleUnits.find((unit) => unit.id === 'rebel-a' && unit.hp > 0) ?? battleUnits.find((unit) => unit.faction === 'enemy' && unit.hp > 0);
|
||||
if (target) {
|
||||
this.applyBattleStatus(target, 'confusion', 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private debugBattleSetupKey() {
|
||||
if (!this.debugToolsEnabled() || typeof window === 'undefined') {
|
||||
return undefined;
|
||||
@@ -6650,6 +6704,14 @@ export class BattleScene extends Phaser.Scene {
|
||||
});
|
||||
}
|
||||
}
|
||||
this.unitStatuses(preview.attacker).forEach((status) => {
|
||||
summaries.push({
|
||||
icon: this.statusEffectIcon(status.kind),
|
||||
label: status.label,
|
||||
value: `${this.statusEffectShortText(status)} · ${status.turns}턴`,
|
||||
tone: this.statusEffectTone(status.kind)
|
||||
});
|
||||
});
|
||||
if (buff) {
|
||||
summaries.push({
|
||||
icon: 'focus',
|
||||
@@ -8623,6 +8685,15 @@ export class BattleScene extends Phaser.Scene {
|
||||
const bondExp = bondGrowth.reduce((total, growth) => total + growth.amount, 0);
|
||||
|
||||
defender.hp = Math.max(0, defender.hp - damage);
|
||||
const statusApplied =
|
||||
hit && defender.hp > 0 && usable?.statusEffect
|
||||
? this.applyBattleStatus(
|
||||
defender,
|
||||
usable.statusEffect,
|
||||
usable.statusDuration ?? usable.duration ?? 1,
|
||||
usable.statusPower ?? this.defaultStatusPower(usable.statusEffect)
|
||||
)
|
||||
: undefined;
|
||||
this.recordCombatStats(attacker, defender, damage, defender.hp <= 0 && previousDefenderHp > 0);
|
||||
this.faceUnitToward(attacker, defender);
|
||||
if (hit) {
|
||||
@@ -8651,7 +8722,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
attackerGrowth,
|
||||
defenderGrowth: armorGrowth,
|
||||
bondExp,
|
||||
bondGrowth
|
||||
bondGrowth,
|
||||
statusApplied
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8947,7 +9019,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
const critical = result.critical ? '치명 ' : '';
|
||||
const defeated = result.defeated ? ' · 퇴각' : '';
|
||||
return `${critical}피해 ${result.damage}${defeated}`;
|
||||
const status = result.statusApplied ? ` · ${this.statusEffectMapText(result.statusApplied)}` : '';
|
||||
return `${critical}피해 ${result.damage}${defeated}${status}`;
|
||||
}
|
||||
|
||||
private combatResultHeadline(result: CombatResult) {
|
||||
@@ -8961,7 +9034,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
: `${damage} 피해`;
|
||||
const hpLine = result.hit ? ` · HP ${result.previousDefenderHp}→${result.defender.hp}` : '';
|
||||
const defeated = result.defeated ? ' · 퇴각' : '';
|
||||
return `${result.attacker.name} ${this.combatActionName(result)} → ${result.defender.name}: ${outcome}${hpLine}${defeated}`;
|
||||
const status = result.statusApplied ? ` · ${this.statusEffectMapText(result.statusApplied)}` : '';
|
||||
return `${result.attacker.name} ${this.combatActionName(result)} → ${result.defender.name}: ${outcome}${hpLine}${defeated}${status}`;
|
||||
}
|
||||
|
||||
private combatResolutionLine(result: CombatResult) {
|
||||
@@ -8969,7 +9043,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
const actual = this.combatDamageValue(result);
|
||||
const verdict = result.hit ? `${successLabel} ${result.hitRate}% 통과` : `${successLabel} ${result.hitRate}% 실패`;
|
||||
const critical = result.hit ? (result.critical ? '치명 발생' : `치명 ${result.criticalRate}% 미발동`) : '치명 없음';
|
||||
return `판정: ${verdict} · ${critical} · 예측 ${result.expectedDamage} / 실제 ${actual}`;
|
||||
const status = result.statusApplied ? ` · 상태 ${this.statusEffectMapText(result.statusApplied)} 적용` : '';
|
||||
return `판정: ${verdict} · ${critical} · 예측 ${result.expectedDamage} / 실제 ${actual}${status}`;
|
||||
}
|
||||
|
||||
private formatCombatOutcome(result: CombatResult) {
|
||||
@@ -9744,6 +9819,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
})),
|
||||
itemStocks: this.serializeItemStocks(),
|
||||
battleBuffs: Array.from(this.battleBuffs.values()).map((buff) => ({ ...buff })),
|
||||
battleStatuses: this.serializeBattleStatuses(),
|
||||
battleStats: this.serializeBattleStats(),
|
||||
triggeredBattleEvents: Array.from(this.triggeredBattleEvents)
|
||||
};
|
||||
@@ -9781,6 +9857,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
);
|
||||
this.itemStocks = this.deserializeItemStocks(state.itemStocks);
|
||||
this.battleBuffs = new Map((state.battleBuffs ?? []).map((buff) => [buff.unitId, { ...buff }]));
|
||||
this.battleStatuses = this.deserializeBattleStatuses(state.battleStatuses);
|
||||
this.battleStats = this.deserializeBattleStats(state.battleStats);
|
||||
this.triggeredBattleEvents = new Set(state.triggeredBattleEvents ?? []);
|
||||
|
||||
@@ -9851,6 +9928,20 @@ export class BattleScene extends Phaser.Scene {
|
||||
);
|
||||
}
|
||||
|
||||
private serializeBattleStatuses() {
|
||||
return Array.from(this.battleStatuses.values()).flatMap((statuses) => statuses.map((status) => ({ ...status })));
|
||||
}
|
||||
|
||||
private deserializeBattleStatuses(savedStatuses?: BattleStatusState[]) {
|
||||
const statuses = new Map<string, BattleStatusState[]>();
|
||||
(savedStatuses ?? []).forEach((status) => {
|
||||
const unitStatuses = statuses.get(status.unitId) ?? [];
|
||||
unitStatuses.push({ ...status });
|
||||
statuses.set(status.unitId, unitStatuses);
|
||||
});
|
||||
return statuses;
|
||||
}
|
||||
|
||||
private serializeBattleStats() {
|
||||
return Object.fromEntries(Array.from(this.battleStats.entries()).map(([unitId, stats]) => [unitId, { ...stats }]));
|
||||
}
|
||||
@@ -9902,8 +9993,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.updateTurnText();
|
||||
this.renderBattleSpeedControl();
|
||||
this.updateObjectiveTracker();
|
||||
const statusMessage = this.tickBattleStatuses('enemy');
|
||||
if (this.battleOutcome || this.resolveBattleOutcomeIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
const recoveryMessage = this.applyTerrainRecovery('enemy');
|
||||
this.renderSituationPanel(['아군 턴을 종료했습니다.', recoveryMessage, '적군이 행동을 시작합니다.'].filter(Boolean).join('\n'));
|
||||
this.renderSituationPanel(['아군 턴을 종료했습니다.', statusMessage, recoveryMessage, '적군이 행동을 시작합니다.'].filter(Boolean).join('\n'));
|
||||
void this.runEnemyTurn();
|
||||
}
|
||||
|
||||
@@ -12366,6 +12461,10 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.turnNumber += 1;
|
||||
const statusMessage = this.tickBattleStatuses('ally');
|
||||
if (this.battleOutcome || this.resolveBattleOutcomeIfNeeded()) {
|
||||
return;
|
||||
}
|
||||
const expiredBuffMessage = this.tickBattleBuffs();
|
||||
this.activeFaction = 'ally';
|
||||
this.fastForwardHeld = false;
|
||||
@@ -12378,15 +12477,23 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.updateObjectiveTracker();
|
||||
this.checkBattleEvents();
|
||||
this.centerCameraOnAllyLine();
|
||||
const turnMessage = [`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, '행동할 장수를 선택하세요.'].filter(Boolean).join('\n');
|
||||
const turnMessage = [`${this.turnNumber}턴 아군 차례입니다.`, statusMessage, expiredBuffMessage, recoveryMessage, '행동할 장수를 선택하세요.']
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
this.renderRosterPanel('ally', turnMessage);
|
||||
|
||||
const firstUnit = this.firstActionableAlly();
|
||||
if (firstUnit) {
|
||||
this.renderSituationPanel([`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, `다음 행동: ${firstUnit.name}`].filter(Boolean).join('\n'));
|
||||
this.renderSituationPanel(
|
||||
[`${this.turnNumber}턴 아군 차례입니다.`, statusMessage, expiredBuffMessage, recoveryMessage, `다음 행동: ${firstUnit.name}`]
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
);
|
||||
this.focusActionableAlly(
|
||||
firstUnit,
|
||||
[`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, '이동할 파란 칸을 선택하세요.'].filter(Boolean).join('\n')
|
||||
[`${this.turnNumber}턴 아군 차례입니다.`, statusMessage, expiredBuffMessage, recoveryMessage, '이동할 파란 칸을 선택하세요.']
|
||||
.filter(Boolean)
|
||||
.join('\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -12861,15 +12968,144 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private attackBuff(unit: UnitData) {
|
||||
return this.battleBuffs.get(unit.id)?.attackBonus ?? 0;
|
||||
return (this.battleBuffs.get(unit.id)?.attackBonus ?? 0) - this.statusAttackPenalty(unit);
|
||||
}
|
||||
|
||||
private hitBuff(unit: UnitData) {
|
||||
return this.battleBuffs.get(unit.id)?.hitBonus ?? 0;
|
||||
return (this.battleBuffs.get(unit.id)?.hitBonus ?? 0) - this.statusHitPenalty(unit);
|
||||
}
|
||||
|
||||
private criticalBuff(unit: UnitData) {
|
||||
return this.battleBuffs.get(unit.id)?.criticalBonus ?? 0;
|
||||
return (this.battleBuffs.get(unit.id)?.criticalBonus ?? 0) - this.statusCriticalPenalty(unit);
|
||||
}
|
||||
|
||||
private unitStatuses(unit: UnitData) {
|
||||
return this.battleStatuses.get(unit.id) ?? [];
|
||||
}
|
||||
|
||||
private hasBattleStatus(unit: UnitData, kind: BattleStatusKind) {
|
||||
return this.unitStatuses(unit).some((status) => status.kind === kind && status.turns > 0);
|
||||
}
|
||||
|
||||
private statusAttackPenalty(unit: UnitData) {
|
||||
return this.hasBattleStatus(unit, 'confusion') ? 2 : 0;
|
||||
}
|
||||
|
||||
private statusHitPenalty(unit: UnitData) {
|
||||
return this.hasBattleStatus(unit, 'confusion') ? 12 : 0;
|
||||
}
|
||||
|
||||
private statusCriticalPenalty(unit: UnitData) {
|
||||
return this.hasBattleStatus(unit, 'confusion') ? 6 : 0;
|
||||
}
|
||||
|
||||
private statusEffectLabel(kind: BattleStatusKind) {
|
||||
return kind === 'burn' ? '화상' : '혼란';
|
||||
}
|
||||
|
||||
private statusEffectIcon(kind: BattleStatusKind): BattleUiIconKey {
|
||||
return kind === 'burn' ? 'fire' : 'confusion';
|
||||
}
|
||||
|
||||
private statusEffectTone(kind: BattleStatusKind) {
|
||||
return kind === 'burn' ? 0xd8732c : 0xb36dff;
|
||||
}
|
||||
|
||||
private statusEffectShortText(status: BattleStatusState) {
|
||||
if (status.kind === 'burn') {
|
||||
return `지속 -${status.power}`;
|
||||
}
|
||||
return '공-2 명-12 치-6';
|
||||
}
|
||||
|
||||
private statusEffectMapText(status?: BattleStatusState) {
|
||||
return status ? `${status.label} ${status.turns}턴` : '';
|
||||
}
|
||||
|
||||
private defaultStatusPower(kind: BattleStatusKind) {
|
||||
return kind === 'burn' ? 4 : 1;
|
||||
}
|
||||
|
||||
private applyBattleStatus(unit: UnitData, kind: BattleStatusKind, turns = 1, power = this.defaultStatusPower(kind)) {
|
||||
const label = this.statusEffectLabel(kind);
|
||||
const unitStatuses = [...this.unitStatuses(unit)];
|
||||
const existing = unitStatuses.find((status) => status.kind === kind);
|
||||
const nextStatus: BattleStatusState = {
|
||||
unitId: unit.id,
|
||||
kind,
|
||||
label,
|
||||
turns: Math.max(existing?.turns ?? 0, turns),
|
||||
power: Math.max(existing?.power ?? 0, power)
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
existing.turns = nextStatus.turns;
|
||||
existing.power = nextStatus.power;
|
||||
existing.label = nextStatus.label;
|
||||
} else {
|
||||
unitStatuses.push(nextStatus);
|
||||
}
|
||||
|
||||
this.battleStatuses.set(unit.id, unitStatuses);
|
||||
return { ...nextStatus };
|
||||
}
|
||||
|
||||
private tickBattleStatuses(faction: ActiveFaction) {
|
||||
const messages: string[] = [];
|
||||
const units = battleUnits.filter((unit) => unit.faction === faction && unit.hp > 0);
|
||||
|
||||
units.forEach((unit) => {
|
||||
const statuses = this.unitStatuses(unit);
|
||||
if (statuses.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const remaining: BattleStatusState[] = [];
|
||||
for (const status of statuses) {
|
||||
if (status.kind === 'burn' && unit.hp > 0) {
|
||||
const previousHp = unit.hp;
|
||||
const damage = Math.min(unit.hp, Math.max(1, status.power));
|
||||
unit.hp = Math.max(0, unit.hp - damage);
|
||||
messages.push(`${unit.name} ${status.label}: 지속 피해 ${damage} · HP ${previousHp}→${unit.hp}`);
|
||||
if (unit.hp <= 0) {
|
||||
this.applyDefeatedStyle(unit);
|
||||
} else {
|
||||
this.syncUnitMotion(unit);
|
||||
}
|
||||
}
|
||||
|
||||
if (unit.hp <= 0) {
|
||||
messages.push(`${unit.name} 전투 불능`);
|
||||
break;
|
||||
}
|
||||
|
||||
const nextTurns = status.turns - 1;
|
||||
if (nextTurns > 0 && unit.hp > 0) {
|
||||
remaining.push({ ...status, turns: nextTurns });
|
||||
} else {
|
||||
messages.push(`${unit.name} ${status.label} 해제`);
|
||||
}
|
||||
|
||||
if (unit.hp <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining.length > 0) {
|
||||
this.battleStatuses.set(unit.id, remaining);
|
||||
} else {
|
||||
this.battleStatuses.delete(unit.id);
|
||||
}
|
||||
});
|
||||
|
||||
if (messages.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.updateMiniMap();
|
||||
const message = `상태효과 변화\n${messages.slice(0, 4).join('\n')}${messages.length > 4 ? `\n외 ${messages.length - 4}건` : ''}`;
|
||||
this.pushBattleLog(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
private tickBattleBuffs() {
|
||||
@@ -13440,6 +13676,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private applyDefeatedStyle(unit: UnitData, view = this.unitViews.get(unit.id)) {
|
||||
this.battleBuffs.delete(unit.id);
|
||||
this.battleStatuses.delete(unit.id);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
@@ -13536,13 +13774,19 @@ export class BattleScene extends Phaser.Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [
|
||||
this.combatMapDamageTitle(result),
|
||||
`HP ${result.previousDefenderHp}→${result.defender.hp}`,
|
||||
this.statusEffectMapText(result.statusApplied)
|
||||
].filter(Boolean);
|
||||
|
||||
this.showMapResultPopup(
|
||||
result.defender,
|
||||
[this.combatMapDamageTitle(result), `HP ${result.previousDefenderHp}→${result.defender.hp}`],
|
||||
lines,
|
||||
result.critical ? '#ff8f5f' : '#ffdf7b',
|
||||
'#220909',
|
||||
result.critical ? 20 : 18,
|
||||
result.critical ? 34 : 30
|
||||
lines.length > 2 ? 38 : result.critical ? 34 : 30
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13746,6 +13990,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
private battleLogVisual(entry: string): { icon: BattleUiIconKey; tone: number } {
|
||||
const { text } = this.battleLogDisplayParts(entry);
|
||||
if (text.includes('화상') || text.includes('지속 피해')) {
|
||||
return { icon: 'fire', tone: 0xd8732c };
|
||||
}
|
||||
if (text.includes('혼란') || text.includes('위압')) {
|
||||
return { icon: 'confusion', tone: 0xb36dff };
|
||||
}
|
||||
if (text.includes('반격')) {
|
||||
return { icon: 'counter', tone: 0xb64a45 };
|
||||
}
|
||||
@@ -13764,7 +14014,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
if (text.includes('회복') || text.includes('병력') || text.includes('거점')) {
|
||||
return { icon: 'heal', tone: 0x59d18c };
|
||||
}
|
||||
if (text.includes('강화') || text.includes('상태효과')) {
|
||||
if (text.includes('강화') || text.includes('상태효과') || text.includes('해제') || text.includes('만료')) {
|
||||
return { icon: 'focus', tone: palette.gold };
|
||||
}
|
||||
if (text.includes('이동') || text.includes('접근')) {
|
||||
@@ -14416,7 +14666,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}));
|
||||
|
||||
const effectTop = top + (hasEffects ? 302 : 326);
|
||||
const equipmentTop = hasEffects ? top + 350 : effectTop;
|
||||
const equipmentTop = hasEffects ? top + 360 : effectTop;
|
||||
if (hasEffects) {
|
||||
this.renderUnitEffectCards(effects, left, effectTop, width);
|
||||
}
|
||||
@@ -14441,13 +14691,24 @@ export class BattleScene extends Phaser.Scene {
|
||||
terrainRule.moraleBonus ? `사기 +${terrainRule.moraleBonus}` : ''
|
||||
].filter(Boolean);
|
||||
|
||||
this.unitStatuses(unit).forEach((status) => {
|
||||
effects.push({
|
||||
icon: this.statusEffectIcon(status.kind),
|
||||
label: status.label,
|
||||
value: this.statusEffectShortText(status),
|
||||
tone: this.statusEffectTone(status.kind),
|
||||
turns: status.turns,
|
||||
polarity: 'debuff'
|
||||
});
|
||||
});
|
||||
if (buff) {
|
||||
effects.push({
|
||||
icon: 'focus',
|
||||
label: buff.label,
|
||||
value: this.battleBuffShortText(buff),
|
||||
value: this.supportBuffCompactText(buff),
|
||||
tone: palette.gold,
|
||||
turns: buff.turns
|
||||
turns: buff.turns,
|
||||
polarity: 'buff'
|
||||
});
|
||||
}
|
||||
if (turnStartParts.length > 0) {
|
||||
@@ -14455,7 +14716,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
icon: 'terrain',
|
||||
label: `${terrainRule.label} 턴 시작`,
|
||||
value: turnStartParts.join(' / '),
|
||||
tone: 0x59d18c
|
||||
tone: 0x59d18c,
|
||||
polarity: 'terrain'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14463,38 +14725,40 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private renderUnitEffectCards(effects: UnitEffectSummary[], x: number, y: number, width: number) {
|
||||
const visible = effects.slice(0, 2);
|
||||
const visible = effects.slice(0, 3);
|
||||
const gap = 6;
|
||||
const cardWidth = (width - gap) / 2;
|
||||
const cardHeight = 50;
|
||||
const cardWidth = (width - gap * (visible.length - 1)) / Math.max(1, visible.length);
|
||||
visible.forEach((effect, index) => {
|
||||
const cardX = x + index * (cardWidth + gap);
|
||||
const bg = this.trackSideObject(this.add.rectangle(cardX, y, cardWidth, 42, 0x101820, 0.97));
|
||||
const fill = effect.polarity === 'debuff' ? 0x1d1516 : effect.polarity === 'terrain' ? 0x101d18 : 0x101820;
|
||||
const bg = this.trackSideObject(this.add.rectangle(cardX, y, cardWidth, cardHeight, fill, 0.98));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, effect.tone, 0.62);
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(cardX + 20, y + 21, 32, 32, 0x0a0f14, 0.92));
|
||||
bg.setStrokeStyle(1, effect.tone, effect.polarity === 'debuff' ? 0.78 : 0.62);
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(cardX + 23, y + 25, 36, 36, 0x0a0f14, 0.94));
|
||||
iconFrame.setStrokeStyle(1, effect.tone, 0.56);
|
||||
this.trackSideIcon(cardX + 20, y + 21, effect.icon, 30);
|
||||
this.trackSideObject(this.add.text(cardX + 42, y + 4, effect.label, {
|
||||
this.trackSideIcon(cardX + 23, y + 25, effect.icon, 34);
|
||||
this.trackSideObject(this.add.text(cardX + 47, y + 6, effect.label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#9fb0bf',
|
||||
fontSize: '11px',
|
||||
color: effect.polarity === 'debuff' ? '#ffb9a8' : '#9fb0bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
fixedWidth: Math.max(36, cardWidth - 56)
|
||||
}));
|
||||
this.trackSideObject(this.add.text(cardX + 42, y + 20, this.truncateUiText(effect.value, 15), {
|
||||
this.trackSideObject(this.add.text(cardX + 47, y + 25, this.truncateUiText(effect.value, 15), {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
fontSize: '13px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
fixedWidth: Math.max(36, cardWidth - 56)
|
||||
}));
|
||||
|
||||
if (effect.turns) {
|
||||
const badge = this.trackSideObject(this.add.rectangle(cardX + cardWidth - 18, y + 14, 26, 18, 0x211624, 0.96));
|
||||
const badge = this.trackSideObject(this.add.rectangle(cardX + cardWidth - 20, y + 16, 30, 19, 0x211624, 0.96));
|
||||
badge.setStrokeStyle(1, effect.tone, 0.78);
|
||||
const turnText = this.trackSideObject(this.add.text(cardX + cardWidth - 18, y + 14, `${effect.turns}`, {
|
||||
const turnText = this.trackSideObject(this.add.text(cardX + cardWidth - 20, y + 16, `${effect.turns}T`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
fontSize: '10px',
|
||||
color: '#fff2b8',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
@@ -14909,6 +15173,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
audio: soundDirector.getDebugState(),
|
||||
itemStocks: this.serializeItemStocks(),
|
||||
battleBuffs: Array.from(this.battleBuffs.values()).map((buff) => ({ ...buff })),
|
||||
battleStatuses: this.serializeBattleStatuses(),
|
||||
treasureEffectPreviews: this.debugTreasureEffectPreviews(),
|
||||
combatMechanicsProbe: this.debugCombatMechanicsProbe(),
|
||||
units: battleUnits.map((unit) => ({
|
||||
|
||||
Reference in New Issue
Block a user