Clarify camp companion role labels

This commit is contained in:
2026-07-05 06:22:43 +09:00
parent 5e79db2153
commit e3d92689b9

View File

@@ -586,6 +586,12 @@ const sortieFormationSlotDefinitions: Omit<SortieFormationSlot, 'unitNames'>[] =
{ role: 'support', label: '후원', description: '원거리, 책략, 보급 지원' },
{ role: 'reserve', label: '예비', description: '빈틈 보강과 공명 연결' }
];
const campCompanionRoleDefinitions: Omit<SortieFormationSlot, 'unitNames'>[] = [
{ role: 'front', label: '대표', description: '대화의 중심과 군영 결정을 맡음' },
{ role: 'flank', label: '현장', description: '다음 장면의 현장 판단과 증언 담당' },
{ role: 'support', label: '보좌', description: '문서, 보급, 책략 기록을 보탬' },
{ role: 'reserve', label: '기록', description: '남은 기록과 공명 정리 담당' }
];
const defaultSortieRule: SortieRuleDefinition = {
maxUnits: maxSortieUnits,
@@ -12640,11 +12646,11 @@ export class CampScene extends Phaser.Scene {
this.trackSortie(this.add.text(slotX + 8, slotY + 5, `${index + 1}`, this.textStyle(10, labelColor, true))).setDepth(depth + 1);
if (unit && role) {
this.renderSortieBattleUiIcon(this.formationRoleIconKey(role), slotX + 28, slotY + slotHeight / 2, 18, depth + 1, 0.96);
const roleLabel = this.sortieFormationRoleLabel(role);
const roleLabel = this.sortieFormationRoleLabel(role, hasBattle);
this.trackSortie(this.add.text(slotX + 42, slotY + 4, this.compactText(unit.name, 8), this.textStyle(11, '#f2e3bf', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(slotX + 42, slotY + 17, roleLabel, this.textStyle(8, '#9fb0bf', true))).setDepth(depth + 1);
} else {
this.trackSortie(this.add.text(slotX + 28, slotY + 8, '빈 슬롯', this.textStyle(10, '#7f8994', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(slotX + 28, slotY + 8, hasBattle ? '빈 슬롯' : '빈 동행', this.textStyle(10, '#7f8994', true))).setDepth(depth + 1);
}
});
}
@@ -12652,18 +12658,20 @@ export class CampScene extends Phaser.Scene {
private renderSortieRoleQuickButtons(x: number, y: number, width: number, height: number, depth: number) {
const focusedUnit = this.sortieFocusedUnit();
const enabled = Boolean(focusedUnit && this.isSortieSelected(focusedUnit.id));
const hasBattle = Boolean(this.nextSortieScenario());
const gap = 6;
const buttonWidth = Math.floor((width - gap * (sortieFormationSlotDefinitions.length - 1)) / sortieFormationSlotDefinitions.length);
sortieFormationSlotDefinitions.forEach((slot, index) => {
const buttonX = x + index * (buttonWidth + gap);
const active = Boolean(focusedUnit && this.sortieFormationRole(focusedUnit) === slot.role);
const roleCopy = this.sortieFormationRoleCopy(slot.role, hasBattle);
const bg = this.trackSortie(this.add.rectangle(buttonX, y, buttonWidth, height, active ? 0x263a2d : 0x111922, enabled ? 0.94 : 0.58));
bg.setOrigin(0);
bg.setDepth(depth);
bg.setStrokeStyle(1, active ? palette.green : 0x53606c, active ? 0.78 : 0.36);
bg.setInteractive({ useHandCursor: true });
bg.on('pointerdown', () => this.assignFocusedSortieRole(slot.role));
this.trackSortie(this.add.text(buttonX + buttonWidth / 2, y + 5, slot.label, this.textStyle(9, active ? '#a8ffd0' : enabled ? '#c8d2dd' : '#77818c', true))).setOrigin(0.5, 0).setDepth(depth + 1);
this.trackSortie(this.add.text(buttonX + buttonWidth / 2, y + 5, roleCopy.label, this.textStyle(9, active ? '#a8ffd0' : enabled ? '#c8d2dd' : '#77818c', true))).setOrigin(0.5, 0).setDepth(depth + 1);
});
}
@@ -12716,7 +12724,7 @@ export class CampScene extends Phaser.Scene {
const availability = this.sortieUnitAvailability(unit);
const recommendation = this.sortieRecommendation(unit.id);
const hasBattle = Boolean(this.nextSortieScenario());
const roleLabel = this.sortieFormationRoleLabel(this.sortieFormationRole(unit));
const roleLabel = this.sortieFormationRoleLabel(this.sortieFormationRole(unit), hasBattle);
const terrainScore = this.sortieTerrainScore(unit, this.nextSortieScenario());
const terrainGrade = this.sortieTerrainGrade(terrainScore);
const statusColor = status.tone === 'disabled' ? '#77818c' : status.tone === 'selected' ? '#a8ffd0' : status.tone === 'recommended' ? '#ffdf7b' : '#9fb0bf';
@@ -12778,7 +12786,7 @@ export class CampScene extends Phaser.Scene {
? '출전 등록'
: '동행 등록';
this.renderSortiePanelButton(actionLabel, x + 18, y + height - 28, 108, 22, availability.available, selected, () => this.toggleSortieUnit(unit.id), depth + 1);
this.trackSortie(this.add.text(x + 140, y + height - 24, `${roleLabel} · 역할은 중앙 버튼으로 변경`, this.textStyle(10, selected ? '#a8ffd0' : '#9fb0bf', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 140, y + height - 24, hasBattle ? `${roleLabel} · 역할은 중앙 버튼으로 변경` : `${roleLabel} · 의정 역할은 중앙 버튼으로 변경`, this.textStyle(10, selected ? '#a8ffd0' : '#9fb0bf', true))).setDepth(depth + 1);
}
private renderSortieBattleUiIcon(iconKey: BattleUiIconKey, x: number, y: number, size: number, depth: number, alpha = 1) {
@@ -12832,8 +12840,13 @@ export class CampScene extends Phaser.Scene {
return 'focus';
}
private sortieFormationRoleLabel(role: SortieFormationRole) {
return sortieFormationSlotDefinitions.find((slot) => slot.role === role)?.label ?? '예비';
private sortieFormationRoleCopy(role: SortieFormationRole, hasBattle = Boolean(this.nextSortieScenario())) {
const definitions = hasBattle ? sortieFormationSlotDefinitions : campCompanionRoleDefinitions;
return definitions.find((slot) => slot.role === role) ?? definitions[definitions.length - 1];
}
private sortieFormationRoleLabel(role: SortieFormationRole, hasBattle = Boolean(this.nextSortieScenario())) {
return this.sortieFormationRoleCopy(role, hasBattle).label;
}
private sortieRecommendationHint(recommendation: SortieRecommendation, unit?: UnitData, maxReasonLength = 20) {
@@ -13190,8 +13203,8 @@ export class CampScene extends Phaser.Scene {
frame.setStrokeStyle(1, palette.blue, 0.48);
if (!scenario) {
this.trackSortie(this.add.text(x + 14, y + 22, '전투 배치 없음', this.textStyle(12, '#d8b15f', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 14, y + 40, '다음 장면에 함께할 동행 명단을 확인합니다.', this.textStyle(11, '#9fb0bf'))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 14, y + 20, '군영 동행 확인', this.textStyle(12, '#d8b15f', true))).setDepth(depth + 1);
this.trackSortie(this.add.text(x + 14, y + 38, '전투 배치 없이 의정에 함께할 무장을 정리합니다.', this.textStyle(11, '#9fb0bf'))).setDepth(depth + 1);
return;
}
@@ -13303,8 +13316,9 @@ export class CampScene extends Phaser.Scene {
},
{ front: 0, flank: 0, support: 0, reserve: 0 } as Record<SortieFormationRole, number>
);
const hasBattle = Boolean(scenario);
const formationSlots = sortieFormationSlotDefinitions.map((slot) => ({
...slot,
...this.sortieFormationRoleCopy(slot.role, hasBattle),
unitNames: selectedUnits.filter((unit) => this.sortieFormationRole(unit) === slot.role).map((unit) => unit.name)
}));
const warnings: string[] = [];
@@ -13319,21 +13333,36 @@ export class CampScene extends Phaser.Scene {
if (missingClassRecommendations.length > 0) {
warnings.push(`추천 병종 ${missingClassRecommendations.map((entry) => entry.label).join(', ')} 부족.`);
}
if (roleCounts.front <= 0) {
warnings.push('전열 담당이 없어 길목을 버티기 어렵습니다.');
}
if (roleCounts.flank <= 0) {
warnings.push('돌파 담당이 없어 추격과 측면 압박이 느릴 수 있습니다.');
}
if (roleCounts.support <= 0) {
warnings.push('후원 담당이 없어 회복, 보급, 원거리 견제가 약합니다.');
if (hasBattle) {
if (roleCounts.front <= 0) {
warnings.push('전열 담당이 없어 길목을 버티기 어렵습니다.');
}
if (roleCounts.flank <= 0) {
warnings.push('돌파 담당이 없어 추격과 측면 압박이 느릴 수 있습니다.');
}
if (roleCounts.support <= 0) {
warnings.push('후원 담당이 없어 회복, 보급, 원거리 견제가 약합니다.');
}
} else {
if (roleCounts.front <= 0) {
warnings.push('대표 동행이 없어 의정의 중심이 약합니다.');
}
if (roleCounts.flank <= 0) {
warnings.push('현장 증언을 맡을 동행이 부족합니다.');
}
if (roleCounts.support <= 0) {
warnings.push('보좌 기록을 맡을 동행이 부족합니다.');
}
}
if (activeBondCount <= 0 && selectedUnits.length > 1) {
warnings.push('함께하는 공명 조합이 없습니다.');
}
if (terrainScore > 0 && terrainScore < 98) {
if (hasBattle && terrainScore > 0 && terrainScore < 98) {
warnings.push('선택 무장의 평균 지형 적성이 낮습니다.');
}
const roleLine = hasBattle
? `전열 ${roleCounts.front} · 돌파 ${roleCounts.flank} · 후원 ${roleCounts.support} · 예비 ${roleCounts.reserve}`
: `대표 ${roleCounts.front} · 현장 ${roleCounts.flank} · 보좌 ${roleCounts.support} · 기록 ${roleCounts.reserve}`;
return {
selectedCount: selectedUnits.length,
@@ -13346,7 +13375,7 @@ export class CampScene extends Phaser.Scene {
reserveCount: reserveUnits.length,
recommendedSelectedCount,
recommendedTotal: recommended.length,
deploymentLine: `전열 ${roleCounts.front} · 돌파 ${roleCounts.flank} · 후원 ${roleCounts.support} · 예비 ${roleCounts.reserve}`,
deploymentLine: roleLine,
recommendationLine: recommended.length > 0
? `추천 무장 ${recommendedSelectedCount}/${recommended.length} · 병종 ${this.sortieRecommendedClassLine(scenario)}`
: `추천 병종 ${this.sortieRecommendedClassLine(scenario)}`,
@@ -13366,7 +13395,7 @@ export class CampScene extends Phaser.Scene {
reserveTrainingBondPreview: reserveFocus.bondExpGained,
objectiveLine: scenario ? `${scenario.title} · ${scenario.victoryConditionLabel}` : `${flow.title} · 군영 의정`,
formationSlots,
warningLine: warnings[0] ?? '배치 균형 양호',
warningLine: warnings[0] ?? (hasBattle ? '배치 균형 양호' : '동행 균형 양호'),
warnings
};
}
@@ -13477,7 +13506,10 @@ export class CampScene extends Phaser.Scene {
);
const names = unitIds.slice(0, 3).map((id) => this.unitName(id)).join(', ');
const extra = unitIds.length > 3 ? `${unitIds.length - 3}` : '';
return `${hasBattle ? '추천 편성' : '추천 동행'} 적용 · ${names}${extra} · 전열 ${roleCounts.front} / 돌파 ${roleCounts.flank} / 후원 ${roleCounts.support}`;
const roleLine = hasBattle
? `전열 ${roleCounts.front} / 돌파 ${roleCounts.flank} / 후원 ${roleCounts.support}`
: `대표 ${roleCounts.front} / 현장 ${roleCounts.flank} / 보좌 ${roleCounts.support}`;
return `${hasBattle ? '추천 편성' : '추천 동행'} 적용 · ${names}${extra} · ${roleLine}`;
}
private sortieTerrainScore(unit: UnitData, scenario: BattleScenarioDefinition | undefined) {