Improve battle status effect visibility
This commit is contained in:
@@ -1398,6 +1398,14 @@ type UnitEffectSummary = {
|
||||
label: string;
|
||||
value: string;
|
||||
tone: number;
|
||||
turns?: number;
|
||||
};
|
||||
|
||||
type PreviewModifierSummary = {
|
||||
icon: BattleUiIconKey;
|
||||
label: string;
|
||||
value: string;
|
||||
tone: number;
|
||||
};
|
||||
|
||||
type SavedUnitState = Pick<UnitData, 'id' | 'level' | 'exp' | 'hp' | 'maxHp' | 'attack' | 'move' | 'x' | 'y'> & {
|
||||
@@ -5691,67 +5699,101 @@ export class BattleScene extends Phaser.Scene {
|
||||
return parts.join(' / ') || '보정 없음';
|
||||
}
|
||||
|
||||
private battleBuffBonusText(buff: BattleBuffState) {
|
||||
private battleBuffShortText(buff: BattleBuffState) {
|
||||
const parts = [
|
||||
buff.attackBonus > 0 ? `공격 +${buff.attackBonus}` : '',
|
||||
buff.hitBonus > 0 ? `명중 +${buff.hitBonus}` : '',
|
||||
buff.criticalBonus > 0 ? `치명 +${buff.criticalBonus}` : ''
|
||||
buff.attackBonus > 0 ? `공+${buff.attackBonus}` : '',
|
||||
buff.hitBonus > 0 ? `명+${buff.hitBonus}` : '',
|
||||
buff.criticalBonus > 0 ? `치+${buff.criticalBonus}` : ''
|
||||
].filter(Boolean);
|
||||
return `${parts.join(' / ') || '효과 유지'} · ${buff.turns}턴`;
|
||||
return `${parts.join(' ') || '유지'} · ${buff.turns}턴`;
|
||||
}
|
||||
|
||||
private previewModifierLabels(preview: CombatPreview) {
|
||||
const labels: string[] = [];
|
||||
private previewModifierSummaries(preview: CombatPreview): PreviewModifierSummary[] {
|
||||
const summaries: PreviewModifierSummary[] = [];
|
||||
const buff = this.battleBuffs.get(preview.attacker.id);
|
||||
const equipmentBonusCount = [
|
||||
preview.equipmentDamageBonus,
|
||||
preview.equipmentDamageReduction,
|
||||
preview.equipmentHitBonus,
|
||||
preview.equipmentCriticalBonus
|
||||
].filter((bonus) => bonus > 0).length;
|
||||
const equipmentText = this.equipmentPreviewBonusText(preview);
|
||||
const terrainParts = [
|
||||
preview.terrainDefenseBonus > 0 ? `방+${preview.terrainDefenseBonus}` : '',
|
||||
preview.terrainHitPenalty > 0 ? `명-${preview.terrainHitPenalty}` : ''
|
||||
].filter(Boolean);
|
||||
|
||||
if (buff) {
|
||||
labels.push(`${buff.label} ${this.battleBuffBonusText(buff)}`);
|
||||
summaries.push({
|
||||
icon: 'focus',
|
||||
label: buff.label,
|
||||
value: this.battleBuffShortText(buff),
|
||||
tone: palette.gold
|
||||
});
|
||||
}
|
||||
if (preview.bondDamageBonus > 0 && preview.bondLabel) {
|
||||
labels.push(`공명 피해 +${preview.bondDamageBonus}%`);
|
||||
summaries.push({
|
||||
icon: 'leadership',
|
||||
label: '공명',
|
||||
value: `피해 +${preview.bondDamageBonus}%`,
|
||||
tone: 0x83d6ff
|
||||
});
|
||||
}
|
||||
if (equipmentBonusCount > 0) {
|
||||
labels.push(`장비 보정 ${equipmentBonusCount}개`);
|
||||
if (equipmentText !== '보정 없음') {
|
||||
summaries.push({
|
||||
icon: 'mastery',
|
||||
label: '장비',
|
||||
value: equipmentText,
|
||||
tone: 0xd8b15f
|
||||
});
|
||||
}
|
||||
if (preview.terrainDefenseBonus > 0) {
|
||||
labels.push(`${preview.terrainLabel} 방어 +${preview.terrainDefenseBonus}`);
|
||||
}
|
||||
if (preview.terrainHitPenalty > 0) {
|
||||
labels.push(`지형 명중 -${preview.terrainHitPenalty}`);
|
||||
if (terrainParts.length > 0) {
|
||||
summaries.push({
|
||||
icon: 'terrain',
|
||||
label: preview.terrainLabel,
|
||||
value: terrainParts.join(' / '),
|
||||
tone: 0x59d18c
|
||||
});
|
||||
}
|
||||
|
||||
return labels.length > 0 ? labels : ['추가 보정 없음'];
|
||||
return summaries;
|
||||
}
|
||||
|
||||
private supportPreviewModifierLabels(preview: SupportPreview) {
|
||||
private supportPreviewModifierSummaries(preview: SupportPreview): PreviewModifierSummary[] {
|
||||
if (preview.usable.effect === 'heal') {
|
||||
const baseBonus = preview.healAmount - preview.usable.power;
|
||||
return [
|
||||
`${this.usableChannelLabel(preview.usable)} 회복 ${preview.healAmount}`,
|
||||
baseBonus > 0 ? `능력/장비 +${baseBonus}` : '추가 보정 없음'
|
||||
{
|
||||
icon: this.usablePowerIcon(preview.usable),
|
||||
label: this.usableChannelLabel(preview.usable),
|
||||
value: `회복 +${preview.healAmount}`,
|
||||
tone: 0x59d18c
|
||||
},
|
||||
{
|
||||
icon: preview.usable.command === 'strategy' ? 'intelligence' : 'accessory',
|
||||
label: baseBonus > 0 ? '보정' : '기본',
|
||||
value: baseBonus > 0 ? `+${baseBonus}` : '추가 없음',
|
||||
tone: baseBonus > 0 ? palette.gold : 0x647485
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`공격 +${preview.attackBonus}`,
|
||||
`명중 +${preview.hitBonus}`,
|
||||
`치명 +${preview.criticalBonus}`,
|
||||
`${preview.duration}턴 유지`
|
||||
{
|
||||
icon: 'focus',
|
||||
label: preview.usable.name,
|
||||
value: `공+${preview.attackBonus} 명+${preview.hitBonus}`,
|
||||
tone: palette.gold
|
||||
},
|
||||
{
|
||||
icon: 'critical',
|
||||
label: '치명',
|
||||
value: `+${preview.criticalBonus}`,
|
||||
tone: 0xd8732c
|
||||
},
|
||||
{
|
||||
icon: 'leadership',
|
||||
label: '지속',
|
||||
value: `${preview.duration}턴`,
|
||||
tone: 0x83d6ff
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private compactModifierText(labels: string[], limit = 2, maxLength = 34) {
|
||||
const text = labels.slice(0, limit).join(' · ');
|
||||
const suffix = labels.length > limit ? ` · 외 ${labels.length - limit}` : '';
|
||||
return this.truncateUiText(`${text}${suffix}`, maxLength);
|
||||
}
|
||||
|
||||
private truncateUiText(text: string, maxLength: number) {
|
||||
return text.length > maxLength ? `${text.slice(0, Math.max(0, maxLength - 3))}...` : text;
|
||||
}
|
||||
@@ -5798,7 +5840,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
const { panelX, panelWidth } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const height = locked ? 396 : 360;
|
||||
const height = locked ? 438 : 402;
|
||||
const y = Math.max(this.sideContentTop(), Math.min(this.sideContentTop() + 328, this.sideContentBottom(10) - height));
|
||||
const accent = this.previewAccentColor(preview);
|
||||
const defenderClass = getUnitClass(preview.defender.classKey);
|
||||
@@ -5809,7 +5851,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
const sourceLabel = preview.usable ? this.usableChannelLabel(preview.usable) : attackerEquipmentLabel;
|
||||
const sourceName = preview.usable?.name ?? attackerWeapon.name;
|
||||
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 0.98));
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 1));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, accent, 0.78);
|
||||
|
||||
@@ -5859,16 +5901,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
);
|
||||
|
||||
this.renderPreviewForecastRow(preview, left + 10, y + 264, width - 20, accent);
|
||||
this.renderPreviewModifierStrip(
|
||||
left + 10,
|
||||
y + 300,
|
||||
width - 20,
|
||||
'mastery',
|
||||
this.compactModifierText(this.previewModifierLabels(preview), 2, 36),
|
||||
accent
|
||||
);
|
||||
this.renderPreviewModifierCards(left + 10, y + 300, width - 20, this.previewModifierSummaries(preview), accent);
|
||||
|
||||
const badgeY = y + 328;
|
||||
const badgeY = y + 370;
|
||||
const maxBadgeRight = left + width - 10;
|
||||
let badgeX = left + 10;
|
||||
badgeX = this.renderPreviewBadge(badgeX, badgeY, this.actionIcon(preview), this.previewActionTypeLabel(preview), maxBadgeRight);
|
||||
@@ -5881,7 +5916,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.renderPreviewBadge(detailBadgeX, badgeY, 'mastery', '장비 보정', maxBadgeRight);
|
||||
}
|
||||
if (locked) {
|
||||
this.renderPreviewConfirmBar(left + 10, y + 360, width - 20, accent, `${preview.defender.name} 선택됨`);
|
||||
this.renderPreviewConfirmBar(left + 10, y + 402, width - 20, accent, `${preview.defender.name} 선택됨`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5889,11 +5924,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
const { panelX, panelWidth } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const height = locked ? 394 : 360;
|
||||
const height = locked ? 438 : 402;
|
||||
const y = Math.max(this.sideContentTop(), Math.min(this.sideContentTop() + 344, this.sideContentBottom(10) - height));
|
||||
const accent = this.usableAccentColor(preview.usable);
|
||||
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 0.98));
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 1));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, accent, 0.78);
|
||||
|
||||
@@ -5966,16 +6001,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.renderSupportForecastRow(preview, left + 10, y + 264, width - 20, accent);
|
||||
this.renderPreviewModifierStrip(
|
||||
left + 10,
|
||||
y + 300,
|
||||
width - 20,
|
||||
preview.usable.effect === 'heal' ? this.usablePowerIcon(preview.usable) : 'leadership',
|
||||
this.compactModifierText(this.supportPreviewModifierLabels(preview)),
|
||||
accent
|
||||
);
|
||||
this.renderPreviewModifierCards(left + 10, y + 300, width - 20, this.supportPreviewModifierSummaries(preview), accent);
|
||||
|
||||
const badgeY = y + 328;
|
||||
const badgeY = y + 370;
|
||||
const maxBadgeRight = left + width - 10;
|
||||
let badgeX = left + 10;
|
||||
badgeX = this.renderPreviewBadge(badgeX, badgeY, this.usableIcon(preview.usable), this.usableEffectLabel(preview.usable), maxBadgeRight);
|
||||
@@ -5985,7 +6013,72 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.renderPreviewBadge(detailBadgeX, badgeY, 'leadership', `${preview.duration}턴`, maxBadgeRight);
|
||||
}
|
||||
if (locked) {
|
||||
this.renderPreviewConfirmBar(left + 10, y + 360, width - 20, accent, `${preview.target.name} 선택됨`);
|
||||
this.renderPreviewConfirmBar(left + 10, y + 402, width - 20, accent, `${preview.target.name} 선택됨`);
|
||||
}
|
||||
}
|
||||
|
||||
private renderPreviewModifierCards(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
summaries: PreviewModifierSummary[],
|
||||
accent: number
|
||||
) {
|
||||
const visible = summaries.slice(0, 3);
|
||||
if (visible.length === 0) {
|
||||
const row = this.trackSideObject(this.add.rectangle(x, y, width, 58, 0x0b1118, 0.94));
|
||||
row.setOrigin(0);
|
||||
row.setStrokeStyle(1, accent, 0.44);
|
||||
this.trackSideIcon(x + 24, y + 29, 'success', 34);
|
||||
this.trackSideObject(this.add.text(x + 50, y + 10, '보정 출처', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(x + 50, y + 30, '추가 보정 없음', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '14px',
|
||||
color: '#d4dce6',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const gap = 6;
|
||||
const cardWidth = (width - gap * 2) / 3;
|
||||
visible.forEach((summary, index) => {
|
||||
const cardX = x + index * (cardWidth + gap);
|
||||
const card = this.trackSideObject(this.add.rectangle(cardX, y, cardWidth, 58, 0x0b1118, 0.96));
|
||||
card.setOrigin(0);
|
||||
card.setStrokeStyle(1, summary.tone, 0.62);
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(cardX + 21, y + 29, 38, 38, 0x16212d, 0.92));
|
||||
iconFrame.setStrokeStyle(1, summary.tone, 0.5);
|
||||
this.trackSideIcon(cardX + 21, y + 29, summary.icon, 34);
|
||||
this.trackSideObject(this.add.text(cardX + 44, y + 7, summary.label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
}));
|
||||
this.trackSideObject(this.add.text(cardX + 44, y + 24, this.truncateUiText(summary.value, 14), {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
}));
|
||||
});
|
||||
|
||||
if (summaries.length > visible.length) {
|
||||
const extraText = this.trackSideObject(this.add.text(x + width - 8, y + 4, `+${summaries.length - visible.length}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
extraText.setOrigin(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6239,26 +6332,6 @@ export class BattleScene extends Phaser.Scene {
|
||||
buffText.setOrigin(1, 0);
|
||||
}
|
||||
|
||||
private renderPreviewModifierStrip(x: number, y: number, width: number, icon: BattleUiIconKey, text: string, accent: number) {
|
||||
const row = this.trackSideObject(this.add.rectangle(x, y, width, 24, 0x101820, 0.96));
|
||||
row.setOrigin(0);
|
||||
row.setStrokeStyle(1, accent, 0.42);
|
||||
this.trackSideIcon(x + 15, y + 12, icon, 20);
|
||||
this.trackSideObject(this.add.text(x + 32, y + 6, '보정', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(x + 68, y + 5, text, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: text === '추가 보정 없음' ? '#aeb7c2' : '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 76
|
||||
}));
|
||||
}
|
||||
|
||||
private renderPreviewBadge(x: number, y: number, icon: BattleUiIconKey, label: string, maxRight?: number) {
|
||||
const width = Phaser.Math.Clamp(label.length * 7 + 48, 76, 146);
|
||||
if (maxRight && x + width > maxRight) {
|
||||
@@ -10822,7 +10895,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.turnNumber += 1;
|
||||
this.tickBattleBuffs();
|
||||
const expiredBuffMessage = this.tickBattleBuffs();
|
||||
this.activeFaction = 'ally';
|
||||
this.fastForwardHeld = false;
|
||||
this.actedUnitIds.clear();
|
||||
@@ -10834,13 +10907,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.updateObjectiveTracker();
|
||||
this.checkBattleEvents();
|
||||
this.centerCameraOnAllyLine();
|
||||
const turnMessage = [`${this.turnNumber}턴 아군 차례입니다.`, recoveryMessage, '행동할 장수를 선택하세요.'].filter(Boolean).join('\n');
|
||||
const turnMessage = [`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, '행동할 장수를 선택하세요.'].filter(Boolean).join('\n');
|
||||
this.renderRosterPanel('ally', turnMessage);
|
||||
|
||||
const firstUnit = this.firstActionableAlly();
|
||||
if (firstUnit) {
|
||||
this.renderSituationPanel(`${this.turnNumber}턴 아군 차례입니다.\n다음 행동: ${firstUnit.name}`);
|
||||
this.focusActionableAlly(firstUnit, [`${this.turnNumber}턴 아군 차례입니다.`, recoveryMessage, '이동할 파란 칸을 선택하세요.'].filter(Boolean).join('\n'));
|
||||
this.renderSituationPanel([`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, `다음 행동: ${firstUnit.name}`].filter(Boolean).join('\n'));
|
||||
this.focusActionableAlly(
|
||||
firstUnit,
|
||||
[`${this.turnNumber}턴 아군 차례입니다.`, expiredBuffMessage, recoveryMessage, '이동할 파란 칸을 선택하세요.'].filter(Boolean).join('\n')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11326,12 +11402,22 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private tickBattleBuffs() {
|
||||
const expired: string[] = [];
|
||||
this.battleBuffs.forEach((buff, unitId) => {
|
||||
buff.turns -= 1;
|
||||
if (buff.turns <= 0) {
|
||||
this.battleBuffs.delete(unitId);
|
||||
expired.push(`${this.unitName(unitId)} ${buff.label}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (expired.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const message = `상태효과 만료\n${expired.slice(0, 3).join('\n')}${expired.length > 3 ? `\n외 ${expired.length - 3}명` : ''}`;
|
||||
this.pushBattleLog(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
private cancelAttackTargeting() {
|
||||
@@ -12629,19 +12715,21 @@ export class BattleScene extends Phaser.Scene {
|
||||
`${this.directionSymbol(direction)} ${this.unitDirectionLabel(direction)}`
|
||||
);
|
||||
|
||||
const statTop = top + 164;
|
||||
const effects = this.unitEffectSummaries(unit);
|
||||
const hasEffects = effects.length > 0;
|
||||
const statTop = top + (hasEffects ? 154 : 164);
|
||||
const statGap = hasEffects ? 21 : 24;
|
||||
statLabels.forEach((stat, index) => {
|
||||
this.renderStatRow(stat.key, stat.label, unit.stats[stat.key], left, statTop + index * 24, width);
|
||||
this.renderStatRow(stat.key, stat.label, unit.stats[stat.key], left, statTop + index * statGap, width);
|
||||
});
|
||||
|
||||
const effects = this.unitEffectSummaries(unit);
|
||||
const effectText = effects.length > 0 ? ` / 효과 ${this.compactUnitEffectText(effects)}` : '';
|
||||
const positionText = `위치 ${unit.x + 1}, ${unit.y + 1} · ${terrainRule.label} ${terrainRating}% · 이동 ${unit.move}${acted ? ' · 행동완료' : ''}${effectText}`;
|
||||
const statusPanel = this.trackSideObject(this.add.rectangle(left, top + 288, width, 32, 0x101820, 0.9));
|
||||
const positionText = `위치 ${unit.x + 1}, ${unit.y + 1} · ${terrainRule.label} ${terrainRating}% · 이동 ${unit.move}${acted ? ' · 행동완료' : ''}`;
|
||||
const statusTop = top + (hasEffects ? 264 : 288);
|
||||
const statusPanel = this.trackSideObject(this.add.rectangle(left, statusTop, width, 32, 0x101820, 0.9));
|
||||
statusPanel.setOrigin(0);
|
||||
statusPanel.setStrokeStyle(1, 0x40515e, 0.5);
|
||||
this.trackSideIcon(left + 17, top + 304, effects[0]?.icon ?? (acted ? 'counter' : this.unitMoveIcon(unit)), 25);
|
||||
this.trackSideObject(this.add.text(left + 36, top + 294, positionText, {
|
||||
this.trackSideIcon(left + 17, statusTop + 16, acted ? 'counter' : this.unitMoveIcon(unit), 25);
|
||||
this.trackSideObject(this.add.text(left + 36, statusTop + 6, positionText, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: acted ? '#bdbdbd' : '#9fb0bf',
|
||||
@@ -12649,8 +12737,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
lineSpacing: 2
|
||||
}));
|
||||
|
||||
const equipmentTop = top + 326;
|
||||
this.renderEquipmentSummary(unit, left, equipmentTop, width);
|
||||
const effectTop = top + (hasEffects ? 302 : 326);
|
||||
const equipmentTop = hasEffects ? top + 350 : effectTop;
|
||||
if (hasEffects) {
|
||||
this.renderUnitEffectCards(effects, left, effectTop, width);
|
||||
}
|
||||
this.renderEquipmentSummary(unit, left, equipmentTop, width, hasEffects);
|
||||
if (message) {
|
||||
this.renderPanelMessage(message, left, equipmentTop + 156, width, 42);
|
||||
}
|
||||
@@ -12668,10 +12760,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
if (buff) {
|
||||
effects.push({
|
||||
icon: 'leadership',
|
||||
icon: 'focus',
|
||||
label: buff.label,
|
||||
value: this.battleBuffBonusText(buff),
|
||||
tone: palette.gold
|
||||
value: this.battleBuffShortText(buff),
|
||||
tone: palette.gold,
|
||||
turns: buff.turns
|
||||
});
|
||||
}
|
||||
if (turnStartParts.length > 0) {
|
||||
@@ -12686,80 +12779,127 @@ export class BattleScene extends Phaser.Scene {
|
||||
return effects;
|
||||
}
|
||||
|
||||
private compactUnitEffectText(effects: UnitEffectSummary[]) {
|
||||
return this.compactModifierText(
|
||||
effects.map((effect) => `${effect.label}: ${effect.value}`),
|
||||
2
|
||||
);
|
||||
private renderUnitEffectCards(effects: UnitEffectSummary[], x: number, y: number, width: number) {
|
||||
const visible = effects.slice(0, 2);
|
||||
const gap = 6;
|
||||
const cardWidth = (width - gap) / 2;
|
||||
visible.forEach((effect, index) => {
|
||||
const cardX = x + index * (cardWidth + gap);
|
||||
const bg = this.trackSideObject(this.add.rectangle(cardX, y, cardWidth, 42, 0x101820, 0.97));
|
||||
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));
|
||||
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, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
}));
|
||||
this.trackSideObject(this.add.text(cardX + 42, y + 20, this.truncateUiText(effect.value, 15), {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: cardWidth - 52
|
||||
}));
|
||||
|
||||
if (effect.turns) {
|
||||
const badge = this.trackSideObject(this.add.rectangle(cardX + cardWidth - 18, y + 14, 26, 18, 0x211624, 0.96));
|
||||
badge.setStrokeStyle(1, effect.tone, 0.78);
|
||||
const turnText = this.trackSideObject(this.add.text(cardX + cardWidth - 18, y + 14, `${effect.turns}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#fff2b8',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
turnText.setOrigin(0.5);
|
||||
}
|
||||
});
|
||||
|
||||
if (effects.length > visible.length) {
|
||||
const extraText = this.trackSideObject(this.add.text(x + width - 8, y + 4, `+${effects.length - visible.length}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
extraText.setOrigin(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private renderEquipmentSummary(unit: UnitData, x: number, y: number, width: number) {
|
||||
this.trackSideIcon(x + 15, y + 12, 'mastery', 30);
|
||||
private renderEquipmentSummary(unit: UnitData, x: number, y: number, width: number, compact = false) {
|
||||
this.trackSideIcon(x + 15, y + 12, 'mastery', compact ? 25 : 30);
|
||||
this.trackSideObject(this.add.text(x + 32, y, '장비 / 숙련도', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '17px',
|
||||
fontSize: compact ? '14px' : '17px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
|
||||
equipmentSlots.forEach((slot, index) => {
|
||||
this.renderEquipmentRow(unit, slot, x, y + 28 + index * 40, width);
|
||||
const rowTop = y + (compact ? 20 : 28) + index * (compact ? 30 : 40);
|
||||
this.renderEquipmentRow(unit, slot, x, rowTop, width, compact);
|
||||
});
|
||||
}
|
||||
|
||||
private renderEquipmentRow(unit: UnitData, slot: EquipmentSlot, x: number, y: number, width: number) {
|
||||
private renderEquipmentRow(unit: UnitData, slot: EquipmentSlot, x: number, y: number, width: number, compact = false) {
|
||||
const state = unit.equipment[slot];
|
||||
const item = getItem(state.itemId);
|
||||
const next = equipmentExpToNext(state.level);
|
||||
const isTreasure = item.rank === 'treasure';
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, 38, isTreasure ? 0x1f2430 : 0x101820, isTreasure ? 0.98 : 0.95));
|
||||
const rowHeight = compact ? 28 : 38;
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, rowHeight, isTreasure ? 0x1f2430 : 0x101820, isTreasure ? 0.98 : 0.95));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, isTreasure ? palette.gold : 0x53606c, isTreasure ? 0.66 : 0.5);
|
||||
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + 23, y + 19, 36, 36, 0x0a0f14, 0.92));
|
||||
const iconSize = compact ? 26 : 36;
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + (compact ? 19 : 21), y + rowHeight / 2, iconSize, iconSize, 0x0a0f14, 0.92));
|
||||
iconFrame.setStrokeStyle(1, isTreasure ? palette.gold : 0x53606c, isTreasure ? 0.78 : 0.54);
|
||||
|
||||
this.trackSideIcon(x + 23, y + 19, this.itemIcon(item, slot), 34);
|
||||
this.trackSideIcon(x + (compact ? 19 : 21), y + rowHeight / 2, this.itemIcon(item, slot), compact ? 24 : 34);
|
||||
|
||||
this.trackSideObject(this.add.text(x + 48, y + 3, equipmentSlotLabels[slot], {
|
||||
this.trackSideObject(this.add.text(x + 40, y + (compact ? 1 : 3), equipmentSlotLabels[slot], {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
fontSize: compact ? '10px' : '11px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
const bonusText = this.itemBonusText(item);
|
||||
this.trackSideObject(this.add.text(x + 86, y + 3, bonusText, {
|
||||
this.trackSideObject(this.add.text(x + 78, y + (compact ? 1 : 3), bonusText, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
fontSize: compact ? '10px' : '11px',
|
||||
color: '#aeb7c2',
|
||||
fixedWidth: width - 170
|
||||
}));
|
||||
this.trackSideObject(this.add.text(x + 48, y + 17, item.name, {
|
||||
this.trackSideObject(this.add.text(x + 40, y + (compact ? 12 : 17), item.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
fontSize: compact ? '12px' : '15px',
|
||||
color: isTreasure ? '#f4dfad' : '#d4dce6',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 150
|
||||
}));
|
||||
|
||||
const levelText = this.trackSideObject(this.add.text(x + width - 10, y + 5, `Lv ${state.level}`, {
|
||||
const levelText = this.trackSideObject(this.add.text(x + width - 10, y + (compact ? 2 : 5), `Lv ${state.level}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
fontSize: compact ? '12px' : '15px',
|
||||
color: isTreasure ? '#f2e3bf' : '#d4dce6',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
levelText.setOrigin(1, 0);
|
||||
|
||||
this.trackSideIcon(x + width - 76, y + 27, 'mastery', 20);
|
||||
const expText = this.trackSideObject(this.add.text(x + width - 10, y + 24, `${state.exp}/${next}`, {
|
||||
this.trackSideIcon(x + width - 76, y + (compact ? 20 : 27), 'mastery', compact ? 16 : 20);
|
||||
const expText = this.trackSideObject(this.add.text(x + width - 10, y + (compact ? 17 : 24), `${state.exp}/${next}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
fontSize: compact ? '10px' : '11px',
|
||||
color: '#c8d2dd',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
expText.setOrigin(1, 0);
|
||||
|
||||
this.drawGauge(x + 48, y + 32, width - 132, 5, state.exp / next, isTreasure ? 0xd8b15f : 0x58aee0);
|
||||
this.drawGauge(x + 40, y + (compact ? 24 : 32), width - 124, compact ? 4 : 5, state.exp / next, isTreasure ? 0xd8b15f : 0x58aee0);
|
||||
}
|
||||
|
||||
private createBattleUiIcon(x: number, y: number, icon: BattleUiIconKey, size = 22) {
|
||||
|
||||
Reference in New Issue
Block a user