feat: complete legacy game screen parity
This commit is contained in:
@@ -457,7 +457,7 @@ onMounted(() => {
|
||||
.log-block {
|
||||
border: 1px solid #666;
|
||||
padding: 0;
|
||||
background: #111;
|
||||
background: #000;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ onMounted(() => {
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #666;
|
||||
color: orange;
|
||||
background: #252525;
|
||||
background: #000;
|
||||
font-size: 1.3em;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -479,6 +479,11 @@ onMounted(() => {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.log-line :deep(.hidden_but_copyable) {
|
||||
color: transparent !important;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 2px 8px;
|
||||
color: #999;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import MapViewer from '../components/main/MapViewer.vue';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.world.getGlobalInfo.query>>;
|
||||
type Layout = Awaited<ReturnType<typeof trpc.world.getMapLayout.query>>;
|
||||
@@ -14,17 +15,9 @@ const goBack = () => router.push('/');
|
||||
const state = (value: number) => ({ 0: '★', 1: '▲', 2: '', 7: '@' })[value] ?? 'ㆍ';
|
||||
const stateClass = (value: number) => `state-${value}`;
|
||||
const nationMap = computed(() => new Map(data.value?.nations.map((nation) => [nation.id, nation]) ?? []));
|
||||
const isBrightColor = (color: string): boolean => {
|
||||
const normalized = color.trim().replace(/^#/u, '');
|
||||
if (!/^[0-9a-f]{6}$/iu.test(normalized)) return false;
|
||||
const red = Number.parseInt(normalized.slice(0, 2), 16);
|
||||
const green = Number.parseInt(normalized.slice(2, 4), 16);
|
||||
const blue = Number.parseInt(normalized.slice(4, 6), 16);
|
||||
return red * 0.299 + green * 0.587 + blue * 0.114 > 170;
|
||||
};
|
||||
const nationNameStyle = (color: string) => ({
|
||||
backgroundColor: color,
|
||||
color: isBrightColor(color) ? '#000' : '#fff',
|
||||
color: legacyNationTextColor(color),
|
||||
});
|
||||
onMounted(async () => {
|
||||
try {
|
||||
@@ -55,7 +48,7 @@ onMounted(async () => {
|
||||
v-for="nation in data.nations"
|
||||
:key="nation.id"
|
||||
class="vertical"
|
||||
:style="{ backgroundColor: nation.color }"
|
||||
:style="nationNameStyle(nation.color)"
|
||||
>
|
||||
{{ nation.name }}
|
||||
</th>
|
||||
@@ -63,7 +56,7 @@ onMounted(async () => {
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="me in data.nations" :key="me.id">
|
||||
<th :style="{ backgroundColor: me.color }">{{ me.name }}</th>
|
||||
<th :style="nationNameStyle(me.color)">{{ me.name }}</th>
|
||||
<td
|
||||
v-for="you in data.nations"
|
||||
:key="you.id"
|
||||
@@ -93,7 +86,7 @@ onMounted(async () => {
|
||||
<strong>{{ conflict.cityName }}</strong>
|
||||
<div>
|
||||
<div v-for="(percent, id) in conflict.nations" :key="id" class="conflict-row">
|
||||
<span :style="{ backgroundColor: nationMap.get(Number(id))?.color }">{{
|
||||
<span :style="nationNameStyle(nationMap.get(Number(id))?.color ?? '#000000')">{{
|
||||
nationMap.get(Number(id))?.name
|
||||
}}</span
|
||||
><em>{{ percent.toFixed(1) }}%</em
|
||||
|
||||
@@ -199,6 +199,9 @@ const specialNameMap = computed(() => {
|
||||
}
|
||||
return map;
|
||||
});
|
||||
const selectedSpecialWarInfo = computed(
|
||||
() => status.value?.availableSpecialWar.find((entry) => entry.key === nextSpecialKey.value)?.info ?? ''
|
||||
);
|
||||
|
||||
const buffCost = (key: BuffKey, target: number): number => {
|
||||
const points = status.value?.inheritConst.inheritBuffPoints ?? [0, 0, 0, 0, 0, 0];
|
||||
@@ -493,7 +496,11 @@ onMounted(() => {
|
||||
</select>
|
||||
</div>
|
||||
<small
|
||||
>{{ specialNameMap.get(nextSpecialKey) }} 특기를 다음에 얻도록 지정합니다.<br /><b
|
||||
><span v-if="selectedSpecialWarInfo" class="special-description">{{
|
||||
selectedSpecialWarInfo
|
||||
}}</span
|
||||
><br v-if="selectedSpecialWarInfo" />{{ specialNameMap.get(nextSpecialKey) }} 특기를 다음에
|
||||
얻도록 지정합니다.<br /><b
|
||||
>필요 포인트: {{ status.inheritConst.inheritSpecificSpecialPoint }}</b
|
||||
></small
|
||||
>
|
||||
|
||||
@@ -65,6 +65,18 @@ const nationAccess = computed(() => ({
|
||||
}));
|
||||
const nationColor = computed(() => nation.value?.color ?? '#000000');
|
||||
const voteActive = computed(() => Boolean(frontStatus.value?.latestVote));
|
||||
const formatRecord = (entry: { text: string; createdAt?: string | Date }, appendTime = false): string => {
|
||||
if (!appendTime || /\d{2}:\d{2}\s*$/u.test(entry.text)) return formatLog(entry.text);
|
||||
const parsed = entry.createdAt ? new Date(entry.createdAt) : null;
|
||||
if (!parsed || Number.isNaN(parsed.getTime())) return formatLog(entry.text);
|
||||
const time = new Intl.DateTimeFormat('ko-KR', {
|
||||
timeZone: 'Asia/Seoul',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
}).format(parsed);
|
||||
return formatLog(`${entry.text} ${time}`);
|
||||
};
|
||||
|
||||
let surveyNoticeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
watch(surveyNotice, (notice) => {
|
||||
@@ -151,7 +163,9 @@ watch(
|
||||
>
|
||||
실시간 동기화: {{ realtimeLabel }}
|
||||
</button>
|
||||
<button class="game-shell__action game-shell__action--navigation" type="button" @click="loadMainData">갱 신</button>
|
||||
<button class="game-shell__action game-shell__action--navigation" type="button" @click="loadMainData">
|
||||
갱 신
|
||||
</button>
|
||||
<button class="game-shell__action" type="button" @click="moveLobby">로비로</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -241,7 +255,7 @@ watch(
|
||||
v-for="entry in globalRecords"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry)"
|
||||
/>
|
||||
<div v-if="globalRecords.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -255,7 +269,7 @@ watch(
|
||||
v-for="entry in generalRecords"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry, true)"
|
||||
/>
|
||||
<div v-if="generalRecords.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -269,7 +283,7 @@ watch(
|
||||
v-for="entry in worldHistory"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry)"
|
||||
/>
|
||||
<div v-if="worldHistory.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -350,7 +364,7 @@ watch(
|
||||
v-for="entry in globalRecords"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry)"
|
||||
/>
|
||||
<div v-if="globalRecords.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -364,7 +378,7 @@ watch(
|
||||
v-for="entry in generalRecords"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry, true)"
|
||||
/>
|
||||
<div v-if="generalRecords.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -378,7 +392,7 @@ watch(
|
||||
v-for="entry in worldHistory"
|
||||
:key="entry.id"
|
||||
class="record-line"
|
||||
v-html="formatLog(entry.text)"
|
||||
v-html="formatRecord(entry)"
|
||||
/>
|
||||
<div v-if="worldHistory.length === 0" class="record-empty">기록이 없습니다.</div>
|
||||
</div>
|
||||
@@ -624,7 +638,6 @@ button {
|
||||
|
||||
.desktop-message-panel {
|
||||
grid-column: 1 / -1;
|
||||
height: 1377.5px;
|
||||
}
|
||||
|
||||
.common-menu-middle {
|
||||
@@ -654,6 +667,11 @@ button {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.record-line :deep(.hidden_but_copyable) {
|
||||
color: transparent !important;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.record-empty {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
@@ -386,20 +386,49 @@ onMounted(() => {
|
||||
<dt>경험/공헌</dt>
|
||||
<dd>{{ data.general.experience }} / {{ data.general.dedication }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>성격/특기</dt>
|
||||
<dd>
|
||||
{{ data.general.traits?.personal ?? '-' }} /
|
||||
{{ data.general.traits?.specialWar ?? '-' }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>나이/다음턴</dt>
|
||||
<dd>{{ data.general.age ?? '-' }}세 / {{ data.general.turnTime?.slice(11, 16) ?? '-' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div v-if="data" class="legacy-general-details">
|
||||
<div>
|
||||
명망 <strong>약간 ({{ data.general.experience }})</strong> · 계급
|
||||
<strong>약간 ({{ data.general.dedication }})</strong>
|
||||
명망
|
||||
<strong
|
||||
>Lv {{ data.general.progression?.experienceLevel ?? 0 }} ({{
|
||||
data.general.experience
|
||||
}})</strong
|
||||
>
|
||||
· 계급
|
||||
<strong
|
||||
>Lv {{ data.general.progression?.dedicationLevel ?? 0 }} ({{
|
||||
data.general.dedication
|
||||
}})</strong
|
||||
>
|
||||
</div>
|
||||
<div>전투 0 · 계략 0 · 사관 7년</div>
|
||||
<div>승률 0% · 승리 0 · 패배 0</div>
|
||||
<div>살상률 0% · 사살 0 · 피살 0</div>
|
||||
<div class="dexterity-title">숙련도</div>
|
||||
<div>보병 0.0K · 궁병 0.0K · 기병 0.0K · 귀병 0.0K · 차병 0.0K</div>
|
||||
<div>
|
||||
병종 {{ data.general.crew ? '보병' : '-' }} · 부상 {{ data.general.injury }} · 부대 - · 벌점 -
|
||||
보병 {{ data.general.progression?.dex?.[0] ?? 0 }} · 궁병
|
||||
{{ data.general.progression?.dex?.[1] ?? 0 }} · 기병
|
||||
{{ data.general.progression?.dex?.[2] ?? 0 }} · 귀병
|
||||
{{ data.general.progression?.dex?.[3] ?? 0 }} · 차병
|
||||
{{ data.general.progression?.dex?.[4] ?? 0 }}
|
||||
</div>
|
||||
<div>
|
||||
병종 {{ data.general.crewTypeId || '-' }} · 내정특기
|
||||
{{ data.general.traits?.specialDomestic ?? '-' }} · 부상 {{ data.general.injury }} · 부대
|
||||
{{ data.general.troopId || '-' }} · 벌점 {{ penalties.length || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getNpcColor } from '../utils/npcColor';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { cityLevelMap, regionMap } from '../utils/nationFormat';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
@@ -149,7 +150,14 @@ onMounted(async () => {
|
||||
>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="10" class="city-title" :style="{ backgroundColor: data?.nation.color }">
|
||||
<td
|
||||
colspan="10"
|
||||
class="city-title"
|
||||
:style="{
|
||||
backgroundColor: data?.nation.color,
|
||||
color: legacyNationTextColor(data?.nation.color ?? '#000000'),
|
||||
}"
|
||||
>
|
||||
【 {{ regionMap[city.region] }} | {{ cityLevelMap[city.level] }} 】
|
||||
<span :class="{ capital: city.id === data?.nation.capitalCityId }">{{
|
||||
city.id === data?.nation.capitalCityId ? `[${city.name}]` : city.name
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { formatLog } from '../utils/formatLog';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
type Result = Awaited<ReturnType<typeof trpc.nation.getNationInfo.query>>;
|
||||
@@ -35,7 +36,11 @@ onMounted(async () => {
|
||||
<table v-if="data" class="legacy-table info-table legacy-bg2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="8" class="nation-title" :style="{ backgroundColor: data.nation.color }">
|
||||
<td
|
||||
colspan="8"
|
||||
class="nation-title"
|
||||
:style="{ backgroundColor: data.nation.color, color: legacyNationTextColor(data.nation.color) }"
|
||||
>
|
||||
【{{ data.nation.name }}】
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from 'vue-router';
|
||||
import { resolveGeneralIconBackgroundImage } from '../utils/generalIcon';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { cityLevelMap, formatOfficerLevelText, getNationChiefLevel, regionMap } from '../utils/nationFormat';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
|
||||
type PersonnelResponse = Awaited<ReturnType<typeof trpc.nation.getPersonnelInfo.query>>;
|
||||
type GeneralEntry = PersonnelResponse['generals'][number];
|
||||
@@ -213,7 +214,10 @@ onMounted(() => void loadPersonnel());
|
||||
<td
|
||||
class="nation-heading"
|
||||
colspan="6"
|
||||
:style="{ color: '#fff', backgroundColor: data.nation.color }"
|
||||
:style="{
|
||||
color: legacyNationTextColor(data.nation.color),
|
||||
backgroundColor: data.nation.color,
|
||||
}"
|
||||
>
|
||||
【 {{ data.nation.name }} 】
|
||||
</td>
|
||||
@@ -415,10 +419,22 @@ onMounted(() => void loadPersonnel());
|
||||
<td colspan="5" class="region-heading">【 {{ regionMap[city.region] ?? '-' }} 】</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="nation-city" :style="{ backgroundColor: data.nation.color }">
|
||||
<td
|
||||
class="nation-city"
|
||||
:style="{
|
||||
backgroundColor: data.nation.color,
|
||||
color: legacyNationTextColor(data.nation.color),
|
||||
}"
|
||||
>
|
||||
【{{ cityLevelMap[city.level] ?? '-' }}】
|
||||
</td>
|
||||
<td class="nation-city city-name" :style="{ backgroundColor: data.nation.color }">
|
||||
<td
|
||||
class="nation-city city-name"
|
||||
:style="{
|
||||
backgroundColor: data.nation.color,
|
||||
color: legacyNationTextColor(data.nation.color),
|
||||
}"
|
||||
>
|
||||
{{ city.name }}
|
||||
</td>
|
||||
<td
|
||||
|
||||
@@ -3,6 +3,8 @@ import { computed, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { resolveDiplomacyInfo } from '../utils/diplomacy';
|
||||
import { legacyNationTextColor } from '../utils/legacyNationColor';
|
||||
import LegacyHtmlEditor from '../components/ui/LegacyHtmlEditor.vue';
|
||||
|
||||
type StratFinanResponse = Awaited<ReturnType<typeof trpc.nation.getStratFinan.query>>;
|
||||
type NationEntry = StratFinanResponse['nationsList'][number];
|
||||
@@ -194,7 +196,9 @@ onMounted(() => void loadStratFinan());
|
||||
<div>종료 시점</div>
|
||||
</div>
|
||||
<div v-for="nation in nationsList" :key="nation.id" class="diplomacy-row">
|
||||
<div :style="{ backgroundColor: nation.color }">{{ nation.name }}</div>
|
||||
<div :style="{ backgroundColor: nation.color, color: legacyNationTextColor(nation.color) }">
|
||||
{{ nation.name }}
|
||||
</div>
|
||||
<div>{{ formatNumber(nation.power) }}</div>
|
||||
<div>{{ formatNumber(nation.generalCount) }}</div>
|
||||
<div>{{ formatNumber(nation.cityCount) }}</div>
|
||||
@@ -245,7 +249,7 @@ onMounted(() => void loadStratFinan());
|
||||
</span>
|
||||
</header>
|
||||
<div v-if="!editingNationMsg" class="message-preview" v-html="nationMsg || '내용 없음'" />
|
||||
<textarea v-else v-model="nationMsgDraft" aria-label="국가 방침" maxlength="16384" />
|
||||
<LegacyHtmlEditor v-else v-model="nationMsgDraft" :max-length="16384" />
|
||||
</section>
|
||||
<section id="scout-message-form" class="message-form">
|
||||
<header class="green-header">
|
||||
@@ -279,7 +283,7 @@ onMounted(() => void loadStratFinan());
|
||||
</header>
|
||||
<div class="scout-limit">870px x 200px를 넘어서는 내용은 표시되지 않습니다.</div>
|
||||
<div v-if="!editingScoutMsg" class="message-preview scout-preview" v-html="scoutMsg || '내용 없음'" />
|
||||
<textarea v-else v-model="scoutMsgDraft" class="scout-editor" aria-label="임관 권유" maxlength="1000" />
|
||||
<LegacyHtmlEditor v-else v-model="scoutMsgDraft" :max-length="1000" />
|
||||
</section>
|
||||
|
||||
<div class="finance-title">예산&정책</div>
|
||||
|
||||
Reference in New Issue
Block a user