파일 이식(0.31.1)

This commit is contained in:
2022-07-08 00:11:41 +09:00
parent db08cec2cb
commit 96d76a06ef
217 changed files with 29966 additions and 2 deletions
@@ -0,0 +1,39 @@
<template>
<div v-for="(cityList, distance) in distanceList" :key="distance">
{{ distance }} 떨어진 도시:
<template v-for="(cityID, key) in cityList" :key="key">
<template v-if="key !== 0"> , </template>
<a
:style="{ color: colorMap[distance as keyof typeof colorMap] ?? undefined, textDecoration:'underline' }"
@click="$emit('selected', cityID)"
>{{ citiesMap.get(cityID)?.name }}</a
>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
export default defineComponent({
props: {
distanceList: {
type: Object as PropType<Record<number, number[]>>,
required: true,
},
citiesMap: {
type: Object as PropType<Map<number, { name: string }>>,
required: true,
},
},
emits: ["selected"],
data() {
return {
colorMap: {
1: "magenta",
2: "orange",
3: "yellow",
},
};
},
});
</script>
+112
View File
@@ -0,0 +1,112 @@
<template>
<div class="crewTypeItem crewTypeSubGrid text-center s-border-b">
<div
class="crewTypeImg"
:style="{
background: '#222222 no-repeat center',
backgroundImage: `url('${crewType.img}')`,
backgroundSize: '64px',
outline: 'solid 1px gray',
}"
/>
<div
:style="{
backgroundColor: crewType.notAvailable ? 'red' : crewType.reqTech == 0 ? 'green' : 'limegreen',
height: '100%',
}"
class="d-grid crewTypeName"
>
<div style="margin: auto">{{ crewType.name }}</div>
</div>
<div>{{ crewType.attack }}</div>
<div>{{ crewType.defence }}</div>
<div>{{ crewType.speed }}</div>
<div>{{ crewType.avoid }}</div>
<div>{{ crewType.baseCost.toFixed(1) }}</div>
<div>{{ crewType.baseRice.toFixed(1) }}</div>
<div class="crewTypePanel">
<b-button-group>
<b-button class="py-1" variant="dark" @click="beHalf">절반</b-button>
<b-button class="py-1" variant="dark" @click="beFilled">채우기</b-button>
<b-button class="py-1" variant="dark" @click="beFull">가득</b-button>
</b-button-group>
<div class="row">
<div class="col mx-2">
<div class="input-group my-0">
<span class="input-group-text py-1">병력</span>
<input v-model.number="amount" type="number" class="form-control py-1 f_tnum px-0 text-end" min="1" />
<span class="input-group-text py-1 f_tnum">00</span>
<span
class="input-group-text py-1 f_tnum"
style="text-align: right; min-width: 10ch; color: #303030; background-color: #ddd"
>
<div style="margin-left: auto">
{{ Math.ceil(amount * crewType.baseCost * goldCoeff).toLocaleString() }}
</div>
</span>
</div>
</div>
</div>
</div>
<div class="crewTypeBtn d-grid">
<b-button variant="primary" @click="doSubmit">{{ commandName }}</b-button>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="crewTypeInfo text-start" v-html="crewType.info.join('<br>')" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import VueTypes from "vue-types";
export default defineComponent({
props: {
crewType: VueTypes.object.isRequired,
leadership: VueTypes.number.isRequired,
commandName: VueTypes.string.isRequired,
currentCrewType: VueTypes.number.def(-1),
crew: VueTypes.number.def(0),
goldCoeff: VueTypes.number.isRequired,
},
emits: ["submitOutput", "update:amount"],
setup(props, { emit }) {
const amount = ref(0);
function beHalf() {
amount.value = Math.ceil(props.leadership * 0.5);
}
function beFilled() {
if (props.crewType.id == props.currentCrewType) {
amount.value = Math.max(1, props.leadership - Math.floor(props.crew / 100));
} else {
amount.value = props.leadership;
}
}
function beFull() {
amount.value = Math.floor(props.leadership * 1.2);
}
function doSubmit(e: Event) {
emit("submitOutput", e, amount.value, props.crewType.id);
}
beFilled();
return {
amount,
beHalf,
beFilled,
beFull,
doSubmit,
};
},
watch: {
amount(val: number) {
this.$emit("update:amount", val);
},
},
});
</script>
+97
View File
@@ -0,0 +1,97 @@
<template>
<TopBackBar :title="commandName" />
<div v-if="!available건국" class="bg0"> 이상 건국은 불가능합니다.</div>
<div v-else class="bg0">
<div>현재 도시에서 나라를 세웁니다. , 소도시에서만 가능합니다.</div>
<ul>
<li v-for="nationType in nationTypes" :key="nationType.type" class="row">
<div class="col-2 col-md-1">- {{ nationType.name }}</div>
<div class="col-4 col-md-2">
: <span style="color: cyan">{{ nationType.pros }}</span
>,
</div>
<div class="col-4 col-md-2">
<span style="color: magenta">{{ nationType.cons }}</span>
</div>
</li>
</ul>
<div class="row">
<div class="col-4 col-md-2">국명 : <b-form-input v-model="destNationName" maxlength="18" /></div>
<div class="col-3 col-md-2">색상 : <ColorSelect v-model="selectedColorID" :colors="colors" /></div>
<div class="col-3 col-md-2">
<label>성향 :</label>
<b-form-select v-model="selectedNationType" :options="nationTypesOption" />
</div>
<div class="col-2 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import ColorSelect from "@/processing/SelectColor.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import type { procNationTypeList } from "../processingRes";
declare const commandName: string;
declare const procRes: {
available건국: boolean;
colors: string[];
nationTypes: procNationTypeList;
};
export default defineComponent({
components: {
ColorSelect,
TopBackBar,
BottomBar,
},
setup() {
const destNationName = ref("");
const selectedColorID = ref(0);
const selectedNationType = ref(Object.values(procRes.nationTypes)[0].type);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
colorType: selectedColorID.value,
nationName: destNationName.value,
nationType: selectedNationType.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const nationTypesOption: { html: string; value: string }[] = [];
for (const nationType of Object.values(procRes.nationTypes)) {
nationTypesOption.push({
html: nationType.name,
value: nationType.type,
});
}
return {
available건국: procRes.available건국,
selectedColorID,
selectedNationType,
colors: procRes.colors,
nationTypes: procRes.nationTypes,
nationTypesOption,
destNationName,
commandName,
submit,
};
},
});
</script>
@@ -0,0 +1,73 @@
<template>
<TopBackBar :title="commandName" />
<div class="bg0">
<div>자신의 군량을 사거나 팝니다.<br /></div>
<div class="row">
<div class="col-2 col-md-1">
군량을 :
<b-button-group>
<b-button :pressed="buyRice" @click="buyRice = true"> </b-button>
<b-button :pressed="!buyRice" @click="buyRice = false"> </b-button>
</b-button-group>
</div>
<div class="col-7 col-md-4">
금액 :
<SelectAmount v-model="amount" :amountGuide="amountGuide" :maxAmount="maxAmount" :minAmount="minAmount" />
</div>
<div class="col-3 col-md-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectAmount from "@/processing/SelectAmount.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
declare const commandName: string;
declare const procRes: {
minAmount: number;
maxAmount: number;
amountGuide: number[];
};
export default defineComponent({
components: {
SelectAmount,
TopBackBar,
BottomBar,
},
setup() {
const amount = ref(1000);
const buyRice = ref(true);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amount: amount.value,
buyRice: buyRice.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
buyRice,
amount,
minAmount: ref(procRes.minAmount),
maxAmount: ref(procRes.maxAmount),
amountGuide: procRes.amountGuide,
commandName,
submit,
};
},
});
</script>
+97
View File
@@ -0,0 +1,97 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" />
<div class="bg0">
<div>
재야나 타국의 장수를 등용합니다.<br />
서신은 개인 메세지로 전달됩니다.<br />
등용할 장수를 목록에서 선택하세요.<br />
</div>
<div class="row">
<div class="col-12 col-md-6">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:generals="generalList"
:groupByNation="nationList"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectGeneral from "@/processing/SelectGeneral.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
type procNationItem,
type procNationList,
} from "../processingRes";
import { getNpcColor } from "@/common_legacy";
declare const commandName: string;
declare const procRes: {
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
nationList: procNationList;
};
export default defineComponent({
components: {
SelectGeneral,
TopBackBar,
BottomBar,
},
setup() {
const generalList = convertGeneralList(procRes.generalsKey, procRes.generals);
const selectedGeneralID = ref(generalList[0].no);
function textHelpGeneral(gen: procGeneralItem): string {
const nameColor = getNpcColor(gen.npc);
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
return name;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
return {
searchable: getProcSearchable(),
selectedGeneralID,
generalList,
nationList,
commandName,
textHelpGeneral,
submit,
};
},
});
</script>
+91
View File
@@ -0,0 +1,91 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" />
<div class="bg0">
<div v-if="commandName == '등용'">
재야나 타국의 장수를 등용합니다.<br />
서신은 개인 메세지로 전달됩니다.<br />
등용할 장수를 목록에서 선택하세요.<br />
</div>
<div v-if="commandName == '선양'">
군주의 자리를 다른 장수에게 물려줍니다.<br />
장수를 선택하세요.<br />
</div>
<div class="row">
<div class="col-9 col-md-4">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:generals="generalList"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-3 col-md-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectGeneral from "@/processing/SelectGeneral.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
} from "../processingRes";
import { getNpcColor } from "@/common_legacy";
declare const commandName: string;
declare const procRes: {
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
};
export default defineComponent({
components: {
SelectGeneral,
TopBackBar,
BottomBar,
},
setup() {
const generalList = convertGeneralList(procRes.generalsKey, procRes.generals);
const selectedGeneralID = ref(generalList[0].no);
function textHelpGeneral(gen: procGeneralItem): string {
const nameColor = getNpcColor(gen.npc);
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
return `${name} (${gen.leadership}/${gen.strength}/${gen.intel})`;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
searchable: getProcSearchable(),
selectedGeneralID,
generalList,
commandName,
textHelpGeneral,
submit,
};
},
});
</script>
@@ -0,0 +1,234 @@
<template>
<TopBackBar :title="commandName" />
<div class="bg0">
<div>본인의 특정 병종 숙련을 40% 줄이고, 줄어든 숙련 9/10(90%p) 다른 병종 숙련으로 전환합니다.</div>
<div class="row">
<div class="col-4 col-md-2">
감소 대상 숙련 :
<b-form-select v-model="srcArmTypeID">
<b-form-select-option v-for="[armType, dexInfo] in dexFullInfo" :key="armType" :value="armType">
{{ dexInfo.name }} (<span :style="{ color: dexInfo.currentInfo.color }">{{ dexInfo.currentInfo.name }}</span
>)
</b-form-select-option>
</b-form-select>
</div>
<div class="col-4 col-md-2">
전환 대상 숙련 :
<b-form-select v-model="destArmTypeID">
<b-form-select-option v-for="[armType, dexInfo] in dexFullInfo" :key="armType" :value="armType">
{{ dexInfo.name }} (<span :style="{ color: dexInfo.currentInfo.color }">{{ dexInfo.currentInfo.name }}</span
>)
</b-form-select-option>
</b-form-select>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
<div
:style="{
display: 'grid',
gridTemplateColumns: '3ch 1ch 2ch 10ch 1ch 3ch 1ch 2ch 10ch 1ch',
}"
>
<div>{{ unwrap(dexFullInfo.get(srcArmTypeID)).name }}</div>
<div class="text-end">[</div>
<div :style="`color:${unwrap(dexFullInfo.get(srcArmTypeID)).currentInfo.color}`">
{{ unwrap(dexFullInfo.get(srcArmTypeID)).currentInfo.name }}
</div>
<div class="f_tnum text-end">
{{ convNumberFormat(unwrap(dexFullInfo.get(srcArmTypeID)).currentInfo.amount) }}
</div>
<div>]</div>
<div class="text-center"></div>
<div class="text-end">[</div>
<div :style="`color:${unwrap(dexFullInfo.get(srcArmTypeID)).decreasedInfo.color}`">
{{ unwrap(dexFullInfo.get(srcArmTypeID)).decreasedInfo.name }}
</div>
<div class="f_tnum text-end">
{{ convNumberFormat(unwrap(dexFullInfo.get(srcArmTypeID)).decreasedInfo.amount) }}
</div>
<div>]</div>
</div>
<div
:style="{
display: 'grid',
gridTemplateColumns: '3ch 1ch 2ch 10ch 1ch 3ch 1ch 2ch 10ch 1ch',
}"
>
<div>{{ unwrap(dexFullInfo.get(destArmTypeID)).name }}</div>
<div class="text-end">[</div>
<template v-if="srcArmTypeID == destArmTypeID">
<div :style="`color:${unwrap(dexFullInfo.get(destArmTypeID)).decreasedInfo.color}`">
{{ unwrap(dexFullInfo.get(destArmTypeID)).decreasedInfo.name }}
</div>
<div class="f_tnum text-end">
{{ convNumberFormat(unwrap(dexFullInfo.get(destArmTypeID)).decreasedInfo.amount) }}
</div>
</template>
<template v-else>
<div :style="`color:${unwrap(dexFullInfo.get(destArmTypeID)).currentInfo.color}`">
{{ unwrap(dexFullInfo.get(destArmTypeID)).currentInfo.name }}
</div>
<div class="f_tnum text-end">
{{ convNumberFormat(unwrap(dexFullInfo.get(destArmTypeID)).currentInfo.amount) }}
</div>
</template>
<div>]</div>
<div class="text-center"></div>
<div class="text-end">[</div>
<div :style="`color:${unwrap(unwrap(dexFullInfo.get(destArmTypeID)).afterInfo.get(srcArmTypeID)).color}`">
{{ unwrap(unwrap(dexFullInfo.get(destArmTypeID)).afterInfo.get(srcArmTypeID)).name }}
</div>
<div class="f_tnum text-end">
{{ convNumberFormat(unwrap(unwrap(dexFullInfo.get(destArmTypeID)).afterInfo.get(srcArmTypeID)).amount) }}
</div>
<div>]</div>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
declare const commandName: string;
type dexInfo = {
amount: number;
color: string;
name: string;
};
declare const procRes: {
ownDexList: {
armType: number;
name: string;
amount: number;
}[];
dexLevelList: dexInfo[];
decreaseCoeff: number;
convertCoeff: number;
};
export default defineComponent({
components: {
TopBackBar,
BottomBar,
},
setup() {
const srcArmTypeID = ref(procRes.ownDexList[0].armType);
const destArmTypeID = ref(procRes.ownDexList[0].armType);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
srcArmType: srcArmTypeID.value,
destArmType: destArmTypeID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
function getDexCall(dex: number): { color: string; name: string } {
if (dex < 0) {
throw `올바르지 않은 수치: ${dex}`;
}
let color = "";
let name = "";
for (const nextDexLevel of procRes.dexLevelList) {
if (dex < nextDexLevel.amount) {
break;
}
color = nextDexLevel.color;
name = nextDexLevel.name;
}
return {
color,
name,
};
}
const dexFullInfo = new Map<
number,
{
armType: number;
name: string;
amount: number;
decresedAmount: number;
currentInfo: dexInfo;
decreasedInfo: dexInfo;
afterInfo: Map<number, dexInfo>;
}
>();
for (const dexItem of procRes.ownDexList) {
const amount = dexItem.amount;
const currentInfo = { ...getDexCall(amount), amount };
const decresedAmount = amount * procRes.decreaseCoeff;
const decresedAfterAmount = amount - decresedAmount;
const decreasedInfo = {
...getDexCall(decresedAfterAmount),
amount: decresedAfterAmount,
};
dexFullInfo.set(dexItem.armType, {
...dexItem,
decresedAmount,
currentInfo,
decreasedInfo,
afterInfo: new Map(),
});
}
for (const [armType, dexItem] of dexFullInfo.entries()) {
for (const [fromArmType, fromDexItem] of dexFullInfo.entries()) {
let afterAmount = fromDexItem.decresedAmount * procRes.convertCoeff;
if (armType != fromArmType) {
afterAmount += dexItem.amount;
} else {
afterAmount += dexItem.decresedAmount;
}
dexItem.afterInfo.set(fromArmType, {
amount: afterAmount,
...getDexCall(afterAmount),
});
}
}
function convDexFormat(value: dexInfo): string {
const amount = convNumberFormat(value.amount);
return `<span class="f_tnum" style="color:${value.color}">${value.name}</span>,${"\xa0".repeat(
Math.max(0, 3 - value.name.length)
)} ${amount}`;
}
function convNumberFormat(value: number): string {
return Math.floor(value).toLocaleString();
}
return {
unwrap,
...procRes,
srcArmTypeID,
destArmTypeID,
dexFullInfo,
getDexCall,
commandName,
submit,
convDexFormat,
convNumberFormat,
};
},
});
</script>
+184
View File
@@ -0,0 +1,184 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" />
<div class="bg0">
<div>
국가에 임관합니다.
<br>
이미 임관/등용되었던 국가는 다시 임관할 없습니다.
<br>
바로 군주의 위치로 이동합니다.
<br>
임관할 국가를 목록에서 선택하세요.
<br>
</div>
<div class="row">
<div class="col-6 col-md-3">
국가 :
<SelectNation v-model="selectedNationID" :nations="nationList" :searchable="searchable" />
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
<div class="nation-list">
<div class="nation-header nation-row bg1 center">
<div>국가명</div>
<div>임관권유문</div>
<div class="zoom-toggle d-grid">
<b-button
v-model="toggleZoom"
:pressed="toggleZoom"
:variant="toggleZoom ? 'info' : 'secondary'"
@click="toggleZoom = !toggleZoom"
>
{{ toggleZoom ? "작게 보기" : "크게 보기" }}
</b-button>
</div>
</div>
<div
v-for="[, nation] in nationList"
:key="nation.id"
:class="['nation-row', 's-border-b', toggleZoom ? 'on-zoom' : 'on-fit']"
@click="selectedNationID = nation.id"
>
<div
:style="{
backgroundColor: nation.color,
color: isBrightColor(nation.color) ? 'black' : 'white',
fontSize: '1.3em',
}"
class="d-grid"
>
<div class="align-self-center center">{{ nation.name }}</div>
</div>
<div class="nation-scout-plate align-self-center">
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="nation-scout-msg" v-html="nation.scoutMsg" />
</div>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectNation from "@/processing/SelectNation.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { type procNationItem, type procNationList, getProcSearchable } from "../processingRes";
import { isBrightColor } from "@/util/isBrightColor";
declare const commandName: string;
declare const procRes: {
nationList: procNationList;
};
export default defineComponent({
components: {
SelectNation,
TopBackBar,
BottomBar,
},
setup() {
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
const toggleZoom = ref(true);
const selectedNationID = ref(procRes.nationList[0].id);
function selectedNation(nationID: number) {
selectedNationID.value = nationID;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destNationID: selectedNationID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
searchable: getProcSearchable(),
nationList: ref(nationList),
selectedNationID,
commandName,
toggleZoom,
isBrightColor,
selectedNation,
submit,
};
},
});
</script>
<style lang="scss" scoped>
@import "@scss/common/break_500px.scss";
@include media-1000px {
.nation-list .nation-row {
display: grid;
grid-template-columns: 130px 870px;
}
.zoom-toggle {
display: none;
}
.zoom-toggle > * {
display: none;
}
}
@include media-500px {
.nation-list .nation-row.nation-header {
display: grid;
grid-template-columns: 3fr 1fr;
grid-template-rows: 1fr 1fr;
.zoom-toggle {
grid-column: 2/3;
grid-row: 1/3;
}
}
.nation-list .nation-row {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr minmax(1fr, calc(200px * 500 / 870));
}
.on-fit {
.nation-scout-plate {
max-height: calc(200px * 500 / 870);
overflow: hidden;
}
.nation-scout-msg {
width: 870px;
transform-origin: 0px 0px;
transform: scale(calc(500 / 870));
}
}
.on-zoom {
.nation-scout-plate {
max-height: 200px;
overflow-y: hidden;
overflow-x: auto;
}
.nation-scout-msg {
max-width: 870px;
}
}
}
</style>
@@ -0,0 +1,205 @@
<template>
<!-- eslint-disable vue/no-v-html -->
<TopBackBar v-model:searchable="searchable" :title="commandName" />
<div class="bg0">
<div>
장비를 구입하거나 매각합니다.
<br>
현재 구입 불가능한 것은 <span style="color: red">붉은색</span>으로
표시됩니다.
<br>
현재 도시 치안 : {{ citySecu.toLocaleString() }} &nbsp;&nbsp;&nbsp;현재
자금 : {{ gold.toLocaleString() }}
<br>
</div>
<div class="row">
<div class="col-8 col-md-4">
장비:
<v-multiselect
v-model="selectedItemObj"
class="selectedItemObj"
:allow-empty="false"
:options="forFind"
:group-select="false"
group-values="values"
group-label="category"
label="searchText"
track-by="simpleName"
:show-labels="false"
select-label="선택(엔터)"
select-group-label
selected-label="선택됨"
deselect-label="해제(엔터)"
deselect-group-label
placeholder="아이템 선택"
:max-height="400"
:searchable="searchable"
>
<template #option="props">
<div
v-if="props.option.html"
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
v-html="
`${props.option.html} ${
props.option.notAvailable ? '(불가)' : ''
}`
"
/>
<div
v-else-if="props.option.simpleName"
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
>
{{ props.option.simpleName }}
{{ props.option.notAvailable ? "(불가)" : undefined }}
</div>
</template>
<template #singleLabel="props">
[{{ ItemTypeNameMap[props.option.type as keyof typeof ItemTypeNameMap] }}]
{{ props.option.simpleName }}
</template>
</v-multiselect>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
<div v-if="selectedItemObj.obj.id != NoneValue" class="row">
<div class="col-4 col-md-2 align-self-center text-center">
{{ selectedItemObj.obj.name }}
</div>
<div class="col" v-html="selectedItemObj.obj.info" />
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import { entriesWithType } from "@util/entriesWithType";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
getProcSearchable,
type procItemList,
type procItemType,
} from "../processingRes";
import {
type ItemTypeKey,
ItemTypeNameMap,
NoneValue,
type ValuesOf,
} from "@/defs";
import { convertSearch초성 } from "@/util/convertSearch초성";
declare const commandName: string;
declare const procRes: {
citySecu: number;
gold: number;
itemList: procItemList;
ownItem: Record<ItemTypeKey, procItemType>;
};
type selectItemKey = {
type: ItemTypeKey;
id: string;
html: string;
simpleName: string;
searchText: string;
notAvailable?: boolean;
obj: procItemType;
};
export default defineComponent({
components: {
TopBackBar,
BottomBar,
},
setup() {
const forFind: {
category: ValuesOf<typeof ItemTypeNameMap> | "판매";
values: selectItemKey[];
}[] = [];
//판매 처리
const forSell: typeof forFind[0] = {
category: "소유 물품 판매",
values: [],
};
for (const [type, ownItem] of entriesWithType(procRes.ownItem)) {
const typeName = ItemTypeNameMap[type];
const itemNameHelp =
ownItem.id == NoneValue
? ""
: ` [${ownItem.name}, ${(ownItem.cost / 2).toLocaleString()}]`;
forSell.values.push({
type,
id: NoneValue,
html: `${typeName} 판매${itemNameHelp}`,
simpleName: `${ownItem.id == NoneValue ? typeName : ownItem.name} 판매`,
searchText: convertSearch초성(typeName).join("|"),
notAvailable: ownItem.id == NoneValue,
obj: ownItem,
});
}
forFind.push(forSell);
const selectedItemObj = ref<selectItemKey>(forSell.values[0]);
for (const [type, itemSubList] of entriesWithType(procRes.itemList)) {
const values: selectItemKey[] = [];
const forBuy: typeof forFind[0] = {
category: `${ItemTypeNameMap[type]} 구매`,
values,
};
for (const itemObj of itemSubList.values) {
values.push({
type,
id: itemObj.id,
html: `${
itemObj.name
} 구매 [${itemObj.cost.toLocaleString()}금, 필요 치안 ${itemObj.reqSecu.toLocaleString()}]`,
simpleName: `${itemObj.name} 구매`,
searchText: convertSearch초성(itemObj.name).join("|"),
notAvailable:
itemObj.reqSecu > procRes.citySecu || procRes.gold < itemObj.cost,
obj: itemObj,
});
}
forFind.push(forBuy);
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
itemType: selectedItemObj.value.type,
itemCode: selectedItemObj.value.id,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
...procRes,
searchable: getProcSearchable(),
forFind,
NoneValue,
ItemTypeNameMap,
selectedItemObj,
commandName,
submit,
};
},
});
</script>
@@ -0,0 +1,214 @@
<template>
<TopBackBar
v-model:searchable="searchable"
:title="commandName"
/>
<div class="bg0">
<div>
장수를 따라 임관합니다.<br>
이미 임관/등용되었던 국가는 다시 임관할 없습니다.<br>
바로 군주의 위치로 이동합니다.<br>
임관할 국가를 목록에서 선택하세요.<br>
</div>
<div class="row">
<div class="col-8 col-md-4">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:generals="generalList"
:groupByNation="nationList"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button
variant="primary"
@click="submit"
>
{{ commandName }}
</b-button>
</div>
</div>
<div class="nation-list">
<div class="nation-header nation-row bg1 center">
<div>국가명</div>
<div>임관권유문</div>
<div class="zoom-toggle d-grid">
<b-button
v-model="toggleZoom"
:pressed="toggleZoom"
:variant="toggleZoom ? 'info' : 'secondary'"
@click="toggleZoom = !toggleZoom"
>
{{ toggleZoom ? "작게 보기" : "크게 보기" }}
</b-button>
</div>
</div>
<div
v-for="[, nation] in nationList"
:key="nation.id"
:class="['nation-row', 's-border-b', toggleZoom ? 'on-zoom' : 'on-fit']"
>
<div
:style="{
backgroundColor: nation.color,
color: isBrightColor(nation.color) ? 'black' : 'white',
fontSize: '1.3em',
}"
class="d-grid"
>
<div class="align-self-center center">
{{ nation.name }}
</div>
</div>
<div class="nation-scout-plate align-self-center">
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="nation-scout-msg" v-html="nation.scoutMsg" />
</div>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectGeneral from "@/processing/SelectGeneral.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
type procNationItem,
type procNationList,
} from "../processingRes";
import { getNpcColor } from "@/common_legacy";
import { isBrightColor } from "@/util/isBrightColor";
declare const commandName: string;
declare const procRes: {
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
nationList: procNationList;
};
export default defineComponent({
components: {
SelectGeneral,
TopBackBar,
BottomBar,
},
setup() {
const generalList = convertGeneralList(
procRes.generalsKey,
procRes.generals
);
const toggleZoom = ref(true);
const selectedGeneralID = ref(generalList[0].no);
function textHelpGeneral(gen: procGeneralItem): string {
const nameColor = getNpcColor(gen.npc);
const name = nameColor
? `<span style="color:${nameColor}">${gen.name}</span>`
: gen.name;
return name;
}
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
searchable: getProcSearchable(),
nationList: ref(nationList),
selectedGeneralID,
generalList,
commandName,
toggleZoom,
isBrightColor,
textHelpGeneral,
submit,
};
},
});
</script>
<style lang="scss" scoped>
@import "@scss/common/break_500px.scss";
@include media-1000px {
.nation-list .nation-row {
display: grid;
grid-template-columns: 130px 870px;
}
.zoom-toggle {
display: none;
}
.zoom-toggle > * {
display: none;
}
}
@include media-500px {
.nation-list .nation-row.nation-header {
display: grid;
grid-template-columns: 3fr 1fr;
grid-template-rows: 1fr 1fr;
.zoom-toggle {
grid-column: 2/3;
grid-row: 1/3;
}
}
.nation-list .nation-row {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr minmax(1fr, calc(200px * 500 / 870));
}
.on-fit {
.nation-scout-plate {
max-height: calc(200px * 500 / 870);
overflow: hidden;
}
.nation-scout-msg {
width: 870px;
transform-origin: 0px 0px;
transform: scale(calc(500 / 870));
}
}
.on-zoom {
.nation-scout-plate {
max-height: 200px;
overflow-y: hidden;
overflow-x: auto;
}
.nation-scout-msg {
max-width: 870px;
}
}
}
</style>
+419
View File
@@ -0,0 +1,419 @@
<template>
<TopBackBar :title="commandName" />
<div class="bg0">
<div>
병사를 모집합니다.
<template v-if="commandName == '징병'">
훈련과 사기치는 낮지만 가격이 저렴합니다.<br>
</template>
<template v-else-if="commandName == '모병'">
훈련과 사기치는 높지만 자금이 많이 듭니다.<br>
</template>
가능한 수보다 많게 입력하면 가능한 최대 병사를 모집합니다.<br>
이미 병사가 있는 경우 추가 {{ commandName }}되며, 병종이 다를경우는 기존의
병사는 소집해제됩니다. <br>
현재 {{ commandName }} 가능한 병종은
<span style="color: green">녹색</span>으로 표시되며, 현재
{{ commandName }} 가능한 특수병종은
<span style="color: limegreen">초록색</span>으로 표시됩니다.
</div>
<div
ref="defaultTarget"
class="crewTypeList"
>
<div class="listFront">
<div class="row gx-0 bg0">
<div class="col-12 col-md-12 d-flex align-items-center">
<div
v-if="commandName == '모병'"
class="text-center w-100"
>
모병은 가격 2배의 자금이 소요됩니다.<br>
</div>
</div>
</div>
<div class="row text-center bg2 gx-0">
<div class="col-4 col-md-2">
현재 기술력 : {{ techLevel }}등급
</div>
<div class="col-4 col-md-2">
현재 통솔 :
<span
:style="{
color: leadership < fullLeadership ? 'red' : undefined,
}"
>{{ leadership }}</span>
</div>
<div class="col-4 col-md-2">
최대 통솔 : {{ fullLeadership }}
</div>
<div class="col-4 col-md-2">
현재 병종 : {{ crewTypeMap?.get(currentCrewType)?.name }}
</div>
<div class="col-4 col-md-2">
현재 병사 : {{ crew.toLocaleString() }}
</div>
<div class="col-4 col-md-2">
현재 자금 : {{ gold.toLocaleString() }}
</div>
</div>
<div class="miniCrewPanel center bg0">
<div
class="crewTypeImg"
:style="{
background: '#222222 no-repeat center',
backgroundImage: `url('${destCrewType.img}')`,
backgroundSize: '64px',
outline: 'solid 1px gray',
height: '64px',
}"
/>
<div
:style="{
backgroundColor:
(destCrewType.notAvailable
? 'red'
: destCrewType.reqTech == 0
? 'green'
: 'limegreen') + ' !important',
height: '100%',
}"
class="d-grid"
>
<div style="margin: auto">
{{ destCrewType.name }}
</div>
</div>
<div />
<div class="crewTypePanel">
<b-button-group>
<b-button
class="py-1"
variant="dark"
@click="beHalf"
>
절반
</b-button><b-button
class="py-1"
variant="dark"
@click="beFilled"
>
채우기
</b-button><b-button
class="py-1"
variant="dark"
@click="beFull"
>
가득
</b-button>
</b-button-group>
<div class="row">
<div class="col mx-2">
<div class="input-group my-0">
<span class="input-group-text py-1">병력</span>
<input
v-model.number="amount"
type="number"
class="form-control py-1 f_tnum px-0 text-end"
min="1"
>
<span class="input-group-text py-1 f_tnum">00</span>
<span
class="input-group-text py-1 f_tnum"
style="
text-align: right;
min-width: 10ch;
color: #303030;
background-color: #ddd;
"
><div style="margin-left: auto">
{{
Math.ceil(
amount * destCrewType.baseCost * goldCoeff
).toLocaleString()
}}
</div></span>
</div>
</div>
</div>
</div>
<div />
<b-button
variant="primary"
@click="submit"
>
{{
commandName
}}
</b-button>
</div>
<div class="listHeader crewTypeSubGrid text-center bg1">
<div class="crewTypeImg">
사진
</div>
<div class="crewTypeName">
병종
</div>
<div>공격</div>
<div>방어</div>
<div>기동</div>
<div>회피</div>
<div>가격</div>
<div>군량</div>
<div class="crewTypePanel">
병사
</div>
<div class="crewTypeBtn">
행동
</div>
<div class="crewTypeInfo">
특징
</div>
</div>
</div>
<div class="listMain">
<template
v-for="armCrewType in armCrewTypes"
:key="armCrewType.armType"
>
<div class="s-border-b row gx-0">
<div
class="col-7 col-md-10 align-self-center px-3"
style="font-size: 1.3em"
>
{{ armCrewType.armName }} 계열
</div>
<div class="col-5 col-md-2 d-grid">
<b-button
:variant="
showNotAvailable.get(armCrewType.armType) ? 'warning' : 'dark'
"
:pressed="showNotAvailable.get(armCrewType.armType)"
class="btn-sm"
@click="toggleShowNotAvailable(armCrewType.armType)"
>
{{
showNotAvailable.get(armCrewType.armType)
? "선택 할 수 있는 병종만 보기"
: "선택 할 수 없는 병종도 보기"
}}
</b-button>
</div>
</div>
<template
v-for="crewType in armCrewType.values"
:key="crewType.id"
>
<CrewTypeItem
v-if="
showNotAvailable.get(armCrewType.armType) ||
!crewType.notAvailable
"
:crewType="crewType"
:leadership="fullLeadership"
:commandName="commandName"
:currentCrewType="currentCrewType"
:crew="crew"
:goldCoeff="goldCoeff"
@submitOutput="trySubmit"
@click="
destCrewType = unwrap(crewTypeMap.get(crewType.id));
beFilled();
"
/>
</template>
</template>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import CrewTypeItem from "@/processing/CrewTypeItem.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import type { procArmTypeItem, procCrewTypeItem } from "../processingRes";
declare const commandName: string;
declare const procRes: {
relYear: number;
year: number;
tech: number;
techLevel: number;
startYear: number;
goldCoeff: number;
leadership: number;
fullLeadership: number;
armCrewTypes: procArmTypeItem[];
currentCrewType: number;
crew: number;
gold: number;
};
export default defineComponent({
components: {
CrewTypeItem,
TopBackBar,
BottomBar,
},
setup() {
const amount = ref(procRes.fullLeadership - Math.floor(procRes.crew / 100));
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amount: amount.value * 100,
crewType: destCrewType.value.id,
},
});
unwrap(e.target).dispatchEvent(event);
}
const showNotAvailable = ref(new Map<number, boolean>());
const crewTypeMap = new Map<number, procCrewTypeItem>();
for (const armType of procRes.armCrewTypes) {
showNotAvailable.value.set(armType.armType, false);
for (const crewType of armType.values) {
crewTypeMap.set(crewType.id, crewType);
}
}
const destCrewType = ref(unwrap(crewTypeMap.get(procRes.currentCrewType)));
function beHalf() {
amount.value = Math.ceil(procRes.fullLeadership * 0.5);
}
function beFilled() {
if (destCrewType.value.id == procRes.currentCrewType) {
amount.value = Math.max(
1,
procRes.fullLeadership - Math.floor(procRes.crew / 100)
);
} else {
amount.value = procRes.fullLeadership;
}
}
function beFull() {
amount.value = Math.floor(procRes.fullLeadership * 1.2);
}
function trySubmit(e: Event, inAmount: number, inCrewType: number) {
e.preventDefault();
amount.value = inAmount;
destCrewType.value = unwrap(crewTypeMap.get(inCrewType));
void submit(e);
}
function toggleShowNotAvailable(armType: number) {
showNotAvailable.value.set(
armType,
!(showNotAvailable.value.get(armType) ?? 0)
);
}
return {
destCrewType,
amount,
showNotAvailable,
...procRes,
crewTypeMap,
commandName,
beHalf,
beFilled,
beFull,
submit,
toggleShowNotAvailable,
trySubmit,
unwrap,
};
},
});
</script>
<style lang="scss">
@import "@scss/common/break_500px.scss";
.crewTypeSubGrid {
display: grid;
align-items: center;
}
.crewTypeItem .crewTypeImg {
height: 64px;
}
.crewTypeInfo {
padding-left: 0.5em;
padding-right: 0.5em;
}
@include media-1000px {
.crewTypeSubGrid {
grid-template-columns: 64px 1.5fr 1fr 1fr 1fr 1fr 1fr 1fr 250px 1.5fr 270px;
}
.miniCrewPanel {
display: none;
}
.only500pxMode {
display: none;
}
}
@include media-500px {
.only1000pxMode {
display: none !important;
}
.listFront {
position: sticky;
top: 0px;
}
.crewTypeSubGrid {
grid-template-columns: 64px 1.5fr 1fr 1fr 1fr 270px;
grid-template-rows: 1fr 1fr;
}
.crewTypeImg {
grid-column: 1 / 2;
grid-row: 1 / 3;
}
.crewTypeName {
grid-row: 1/ 3;
}
.crewTypeInfo {
grid-column: -2 / -1;
grid-row: 1 / 3;
}
.crewTypePanel {
display: none;
}
.crewTypeBtn {
display: none;
}
.crewTypeBtn > button {
display: none;
}
.miniCrewPanel .crewTypePanel {
display: block;
}
.miniCrewPanel {
display: grid;
grid-template-columns: 64px 1.5fr 0.5fr 270px 0.5fr 2fr;
grid-template-rows: 64px;
align-items: center;
}
}
</style>
<style scoped>
</style>
+94
View File
@@ -0,0 +1,94 @@
<template>
<TopBackBar :title="commandName" />
<div class="bg0">
<div>
자신의 자금이나 군량을 국가 재산으로 헌납합니다.
</div>
<div class="row">
<div class="col-2 col-md-1">
자원 :
<b-button-group>
<b-button
:pressed="isGold"
@click="isGold = true"
>
</b-button>
<b-button
:pressed="!isGold"
@click="isGold = false"
>
</b-button>
</b-button-group>
</div>
<div class="col-7 col-md-4">
금액 :
<SelectAmount
v-model="amount"
:amountGuide="amountGuide"
:maxAmount="maxAmount"
:minAmount="minAmount"
/>
</div>
<div class="col-3 col-md-2 d-grid">
<b-button
variant="primary"
@click="submit"
>
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" />
</template>
<script lang="ts">
import SelectAmount from "@/processing/SelectAmount.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
declare const commandName: string;
declare const procRes: {
minAmount: number;
maxAmount: number;
amountGuide: number[];
};
export default defineComponent({
components: {
SelectAmount,
TopBackBar,
BottomBar,
},
setup() {
const amount = ref(1000);
const isGold = ref(true);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amount: amount.value,
isGold: isGold.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
amount,
isGold,
minAmount: ref(procRes.minAmount),
maxAmount: ref(procRes.maxAmount),
amountGuide: procRes.amountGuide,
commandName,
submit,
};
},
});
</script>
+44
View File
@@ -0,0 +1,44 @@
import { default as che_건국 } from "./che_건국.vue";
import { default as che_군량매매 } from "./che_군량매매.vue";
import { default as che_등용 } from "./che_등용.vue";
import { default as che_선양 } from "./che_선양.vue";
import { default as che_숙련전환 } from "./che_숙련전환.vue";
import { default as che_임관 } from "./che_임관.vue";
import { default as che_장비매매 } from "./che_장비매매.vue";
import { default as che_장수대상임관 } from "./che_장수대상임관.vue";
import { default as che_징병 } from "./che_징병.vue";
import { default as che_헌납 } from "./che_헌납.vue";
import { default as ProcessCity } from "../ProcessCity.vue";
import { default as ProcessGeneralAmount } from "../ProcessGeneralAmount.vue";
//TODO: 자주 쓰는 녀석들은 Slot으로 변경
export const commandMap: Record<string, typeof ProcessCity> = {
che_강행: ProcessCity,
che_군량매매,
che_건국,
che_등용,
che_모병: che_징병,
che_선동: ProcessCity,
che_선양,
che_숙련전환,
che_이동: ProcessCity,
che_임관,
che_장비매매,
che_장수대상임관,
che_징병,
che_증여: ProcessGeneralAmount,
che_첩보: ProcessCity,
che_출병: ProcessCity,
che_탈취: ProcessCity,
che_파괴: ProcessCity,
che_화계: ProcessCity,
che_헌납,
}
/*
- 항목들
고유 양식 - 장비매매
*/
@@ -0,0 +1,72 @@
<template>
<TopBackBar
:title="commandName"
type="chief"
/>
<div class="bg0">
<div>
국기를 변경합니다. 1 가능합니다.<br>
</div>
<div class="row">
<div class="col-6 col-md-3">
색상 :
<ColorSelect
v-model="selectedColorID"
:colors="colors"
/>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar
:title="commandName"
type="chief"
/>
</template>
<script lang="ts">
import ColorSelect from "@/processing/SelectColor.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
declare const commandName: string;
declare const procRes: {
colors: string[],
};
export default defineComponent({
components: {
ColorSelect,
TopBackBar,
BottomBar,
},
setup() {
const selectedColorID = ref(0);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
colorType: selectedColorID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
selectedColorID,
colors: procRes.colors,
commandName,
submit,
};
},
});
</script>
@@ -0,0 +1,64 @@
<template>
<TopBackBar
:title="commandName"
type="chief"
/>
<div class="bg0">
<div>
나라의 이름을 바꿉니다. 황제가 1 가능합니다.<br>
</div>
<div class="row">
<div class="col-6 col-md-3">
국명 :
<b-form-input
v-model="destNationName"
maxlength="18"
/>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar
:title="commandName"
type="chief"
/>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
declare const commandName: string;
export default defineComponent({
components: {
TopBackBar,
BottomBar,
},
setup() {
const destNationName = ref("");
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
nationName: destNationName.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
destNationName,
commandName,
submit,
};
},
});
</script>
@@ -0,0 +1,186 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" type="chief" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div>
타국에게 원조합니다.<br />
작위별로 금액 제한이 있습니다.<br /><br />
<ul>
<template v-for="({ text, amount }, level) in levelInfo" :key="level">
<li>
<span
:style="{
width: '4em',
display: 'inline-block',
...(level != currentNationLevel
? {}
: {
textDecoration: 'underline',
fontWeight: 'bold',
}),
}"
>{{ text }}</span
>: {{ amount.toLocaleString() }}
</li>
</template>
</ul>
<br />
원조할 국가를 목록에서 선택하세요.<br /><br />
</div>
<div class="row">
<div class="col-6 col-md-3">
국가 :
<SelectNation v-model="selectedNationID" :nations="nationList" :searchable="searchable" />
</div>
<div class="col-6 col-md-0" />
<div class="col-8 col-md-4">
:
<SelectAmount
v-model="goldAmount"
:amountGuide="amountGuide"
:step="10"
:maxAmount="maxAmount"
:minAmount="minAmount"
/>
</div>
<div class="col-8 col-md-4">
:
<SelectAmount
v-model="riceAmount"
:amountGuide="amountGuide"
:step="10"
:maxAmount="maxAmount"
:minAmount="minAmount"
/>
</div>
<div class="col-4 col-md-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" type="chief" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
serverID: string;
mapName: string;
commandName: string;
};
declare const procRes: {
nationList: procNationList;
currentNationLevel: number;
levelInfo: Record<
number,
{
text: string;
amount: number;
}
>;
minAmount: number;
maxAmount: number;
amountGuide: number[];
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "@/components/MapViewer.vue";
import SelectNation from "@/processing/SelectNation.vue";
import SelectAmount from "@/processing/SelectAmount.vue";
import { ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { getProcSearchable, type procNationItem, type procNationList } from "../processingRes";
import type { MapResult } from "@/defs";
import { SammoAPI } from "@/SammoAPI";
import { getGameConstStore, type GameConstStore } from "@/GameConstStore";
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
const goldAmount = ref(procRes.minAmount);
const riceAmount = ref(procRes.minAmount);
const currentNationLevel = procRes.currentNationLevel;
const levelInfo = procRes.levelInfo;
const minAmount = ref(procRes.minAmount);
const maxAmount = ref(procRes.maxAmount);
const amountGuide = procRes.amountGuide;
const selectedNationID = ref(procRes.nationList[0]?.id);
const map = ref<MapResult>();
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amountList: [goldAmount.value, riceAmount.value],
destNationID: selectedNationID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
if (city.nationID === undefined) {
return;
}
selectedNationID.value = city.nationID;
});
onMounted(async () => {
try {
map.value = await SammoAPI.Global.GetMap({ neutralView: 0, showMe: 1 });
} catch (e) {
console.error(e);
}
});
</script>
+184
View File
@@ -0,0 +1,184 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" type="chief" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div>
선택된 도시로 아국 장수를 발령합니다.<br />
아국 도시로만 발령이 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div class="row">
<div class="col-12 col-md-6">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:cities="citiesMap"
:generals="generalList"
:troops="troops"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-6 col-md-4">
도시 :
<SelectCity v-model="selectedCityID" :cities="citiesMap" :searchable="searchable" />
</div>
<div class="col-4 col-md-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" type="chief" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
serverID: string;
currentCity: number;
commandName: string;
};
declare const procRes: {
troops: procTroopList;
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "@/components/MapViewer.vue";
import SelectCity from "@/processing/SelectCity.vue";
import SelectGeneral from "@/processing/SelectGeneral.vue";
import { ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
type procTroopList,
} from "../processingRes";
import { getNpcColor } from "@/common_legacy";
import type { MapResult } from '@/defs';
import { SammoAPI } from '@/SammoAPI';
import { getGameConstStore, type GameConstStore } from '@/GameConstStore';
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const selectedCityID = ref(staticValues.currentCity);
const map = ref<MapResult>();
const citiesMap = ref(
new Map<
number,
{
name: string;
info?: string;
}
>()
);
watch(gameConstStore, (store)=>{
if(!store){
return;
}
const tmpCitiesMap = new Map<
number,
{
name: string;
info?: string;
}
>();
for(const city of Object.values(store.cityConst)){
tmpCitiesMap.set(city.id, {
name: city.name,
});
}
citiesMap.value = tmpCitiesMap;
})
//TODO: onMount로 이전하고 장수 목록은 실시간으로 받아와야함
const generalList = convertGeneralList(procRes.generalsKey, procRes.generals);
const troops = procRes.troops;
const selectedGeneralID = ref(generalList[0].no);
function textHelpGeneral(gen: procGeneralItem): string {
const troops = !gen.troopID ? "" : `,${procRes.troops[gen.troopID].name}`;
const nameColor = getNpcColor(gen.npc);
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
return `${name} [${citiesMap.value.get(unwrap(gen.cityID))?.name}${troops}] (${gen.leadership}/${gen.strength}/${
gen.intel
}) <병${unwrap(gen.crew).toLocaleString()}/훈${gen.train}/사${gen.atmos}>`;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destCityID: selectedCityID.value,
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
selectedCityID.value = city.id;
});
onMounted(async () => {
try{
map.value = await SammoAPI.Global.GetMap({neutralView:0, showMe: 1});
}
catch(e){
console.error(e);
}
});
</script>
@@ -0,0 +1,161 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" type="chief" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div>
타국에게 불가침을 제의합니다.<br />
제의할 국가를 목록에서 선택하세요.<br />
불가침 기한 다음 달부터 선포 가능합니다.<br />
현재 제의가 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div class="row">
<div class="col-4 col-md-3">
국가 :
<SelectNation v-model="selectedNationID" :nations="nationList" :searchable="searchable" />
</div>
<div class="col-5 col-md-3">
기간 :
<div class="input-group">
<b-form-select v-model="selectedYear" class="text-end selectedYear">
<b-form-select-option v-for="yearP in maxYear - minYear + 1" :key="yearP" :value="yearP + minYear - 1">
{{ yearP + minYear - 1 }}
</b-form-select-option>
</b-form-select>
<span class="input-group-text px-2"></span>
<b-form-select v-model="selectedMonth" class="text-center">
<b-form-select-option v-for="month in 12" :key="month" :value="month">
{{ month }}
</b-form-select-option>
</b-form-select>
<span class="input-group-text px-2"></span>
</div>
</div>
<div class="col-3 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" type="chief" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
serverID: string;
mapName: string;
commandName: string;
};
declare const procRes: {
nationList: procNationList;
startYear: number;
minYear: number;
maxYear: number;
month: number;
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "@/components/MapViewer.vue";
import SelectNation from "@/processing/SelectNation.vue";
import { ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { getProcSearchable, type procNationItem, type procNationList } from "../processingRes";
import type { MapResult } from "@/defs";
import { SammoAPI } from "@/SammoAPI";
import { getGameConstStore, type GameConstStore } from "@/GameConstStore";
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
const selectedNationID = ref(procRes.nationList[0].id);
const map = ref<MapResult>();
const minYear = procRes.minYear;
const maxYear = procRes.maxYear;
const selectedYear = ref(procRes.minYear);
const selectedMonth = ref(procRes.month);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destNationID: selectedNationID.value,
year: selectedYear.value,
month: selectedMonth.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
if (city.nationID === undefined) {
return;
}
selectedNationID.value = city.nationID;
});
onMounted(async () => {
try {
map.value = await SammoAPI.Global.GetMap({ neutralView: 0, showMe: 1 });
} catch (e) {
console.error(e);
}
});
</script>
<style lang="scss" scoped>
.selectedYear {
width: 32%;
}
</style>
@@ -0,0 +1,165 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" type="chief" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div>
선택된 국가에 피장파장을 발동합니다.<br />
지정한 전략을 상대국이
{{ delayCnt }} 동안 사용할 없게됩니다.<br />
대신 아국은 지정한 전략을 {{ postReqTurn }} 동안 사용할 없습니다.<br />
선포, 전쟁중인 상대국에만 가능합니다.<br />
상대 국가를 목록에서 선택하세요.<br />
현재 피장파장이 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div class="row gx-3">
<div class="col-5 col-md-3">
국가 :
<SelectNation v-model="selectedNationID" :nations="nationList" :searchable="searchable" />
</div>
<div class="col-4 col-md-2">
<label>전략 :</label>
<select
v-model="selectedCommandID"
class="form-control"
:style="{
color: availableCommandTypeList[selectedCommandID].remainTurn > 0 ? 'red' : undefined,
}"
>
<option
v-for="(command, commandRawName) in availableCommandTypeList"
:key="commandRawName"
:value="commandRawName"
:style="{
color: command.remainTurn > 0 ? 'red' : 'black',
}"
>
{{ command.name }} {{ command.remainTurn > 0 ? `(불가, ${command.remainTurn}턴)` : "" }}
</option>
</select>
</div>
<div class="col-3 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" type="chief" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
serverID: string;
mapName: string;
commandName: string;
};
declare const procRes: {
nationList: procNationList;
startYear: number;
delayCnt: number;
postReqTurn: number;
availableCommandTypeList: Record<
string,
{
name: string;
remainTurn: number;
}
>;
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "@/components/MapViewer.vue";
import SelectNation from "@/processing/SelectNation.vue";
import { ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { getProcSearchable, type procNationItem, type procNationList } from "../processingRes";
import type { MapResult } from "@/defs";
import { SammoAPI } from "@/SammoAPI";
import { getGameConstStore, type GameConstStore } from "@/GameConstStore";
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const nationList = new Map<number, procNationItem>();
for (const nationItem of procRes.nationList) {
nationList.set(nationItem.id, nationItem);
}
const selectedNationID = ref(procRes.nationList[0].id);
const map = ref<MapResult>();
const delayCnt = procRes.delayCnt;
const postReqTurn = procRes.postReqTurn;
const availableCommandTypeList = procRes.availableCommandTypeList;
const selectedCommandID = ref(Object.keys(availableCommandTypeList)[0]);
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destNationID: selectedNationID.value,
commandType: selectedCommandID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
if (city.nationID === undefined) {
return;
}
selectedNationID.value = city.nationID;
});
onMounted(async () => {
try {
map.value = await SammoAPI.Global.GetMap({ neutralView: 0, showMe: 1 });
} catch (e) {
console.error(e);
}
});
</script>
+36
View File
@@ -0,0 +1,36 @@
import { default as che_국기변경 } from "./che_국기변경.vue";
import { default as che_국호변경 } from "./che_국호변경.vue";
import { default as che_물자원조 } from "./che_물자원조.vue";
import { default as che_불가침제의 } from "./che_불가침제의.vue";
import { default as che_피장파장 } from "./che_피장파장.vue";
import { default as ProcessNation } from "../ProcessNation.vue";
import { default as ProcessGeneralAmount } from "../ProcessGeneralAmount.vue";
import { default as ProcessGeneralCity } from "./che_발령.vue";
import { default as ProcessCity } from "../ProcessCity.vue";
export const commandMap: Record<string, typeof ProcessNation | typeof ProcessCity> = {
che_국기변경,
che_국호변경,
che_급습: ProcessNation,
che_몰수: ProcessGeneralAmount,
che_물자원조,
che_발령: ProcessGeneralCity,
che_백성동원: ProcessCity,
che_불가침제의,
che_불가침파기제의: ProcessNation,
che_선전포고: ProcessNation,
che_수몰: ProcessCity,
che_이호경식: ProcessNation,
che_종전제의: ProcessNation,
che_천도: ProcessCity,
che_초토화: ProcessCity,
che_포상: ProcessGeneralAmount,
che_피장파장,
che_허보: ProcessCity,
}
/*
- 항목들
고유 양식 - 불가침제의
*/
+201
View File
@@ -0,0 +1,201 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" :type="procEntryMode" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div v-if="commandName == '강행'">
선택된 도시로 강행합니다.<br />
최대 3칸내 도시로만 강행이 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '이동'">
선택된 도시로 이동합니다.<br />
인접 도시로만 이동이 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '출병'">
선택된 도시를 향해 침공을 합니다.<br />
침공 경로에 적군의 도시가 있다면 전투를 벌입니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '첩보'">
선택된 도시에 첩보를 실행합니다.<br />
인접도시일 경우 많은 정보를 얻을 있습니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName in { 화계: 1, 탈취: 1, 파괴: 1, 선동: 1 }">
선택된 도시에 {{ commandName }}{{ JosaPick(commandName, "") }} 실행합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '수몰'">
선택된 도시에 수몰을 발동합니다.<br />
전쟁중인 상대국 도시만 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '백성동원'">
선택된 도시에 백성을 동원해 성벽을 쌓습니다.<br />
아국 도시만 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '천도'">
선택된 도시로 천도합니다.<br />
현재 수도에서 연결된 도시만 가능하며, 1+2×거리만큼의 턴이 필요합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '허보'">
선택된 도시에 허보를 발동합니다.<br />
전쟁중인 상대국 도시만 가능합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div v-else-if="commandName == '초토화'">
선택된 도시를 초토화 시킵니다.<br />
도시가 공백지가 되며, 도시의 인구, 내정 상태에 따라 상당량의 국고가 확보됩니다.<br />
국가의 수뇌들은 명성을 잃고, 모든 장수들은 배신 수치가 1 증가합니다.<br />
목록을 선택하거나 도시를 클릭하세요.<br />
</div>
<div class="row">
<div class="col-4 col-md-2">
도시:
<SelectCity v-model="selectedCityID" :cities="citiesMap" :searchable="searchable" />
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
<CityBasedOnDistance :citiesMap="citiesMap" :distanceList="distanceList" @selected="selected" />
</div>
<BottomBar :title="commandName" :type="procEntryMode" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string,
serverID: string,
currentCity: number;
commandName: string;
entryInfo: ["General" | "Nation", unknown];
};
declare const procRes: {
distanceList: Record<number, number[]>;
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw} from '@/components/MapViewer.vue';
import SelectCity from "@/processing/SelectCity.vue";
import CityBasedOnDistance from "@/processing/CitiesBasedOnDistance.vue";
import { ref, type Ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { pick as JosaPick } from "@util/JosaUtil";
import { getProcSearchable } from "./processingRes";
import type { MapResult } from '@/defs';
import { SammoAPI } from '@/SammoAPI';
import { getGameConstStore, type GameConstStore } from '@/GameConstStore';
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const { distanceList } = procRes;
const selectedCityID = ref(staticValues.currentCity);
const map = ref<MapResult>();
const citiesMap = ref(
new Map<
number,
{
name: string;
info?: string;
}
>()
);
watch(gameConstStore, (store)=>{
if(!store){
return;
}
const tmpCitiesMap = new Map<
number,
{
name: string;
info?: string;
}
>();
for(const city of Object.values(store.cityConst)){
tmpCitiesMap.set(city.id, {
name: city.name,
});
}
citiesMap.value = tmpCitiesMap;
})
function selected(cityID: number) {
selectedCityID.value = cityID;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destCityID: selectedCityID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const procEntryMode: Ref<"chief" | "normal"> = ref(staticValues.entryInfo[0] == "Nation" ? "chief" : "normal");
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
selectedCityID.value = city.id;
});
onMounted(async () => {
try{
map.value = await SammoAPI.Global.GetMap({neutralView:0, showMe: 1});
}
catch(e){
console.error(e);
}
});
</script>
+167
View File
@@ -0,0 +1,167 @@
<template>
<TopBackBar
v-model:searchable="searchable"
:title="commandName"
:type="procEntryMode"
/>
<div class="bg0">
<div v-if="commandName == '몰수'">
장수의 자금이나 군량을 몰수합니다.<br>
몰수한것은 국가재산으로 귀속됩니다.<br>
</div>
<div v-else-if="commandName == '포상'">
국고로 장수에게 자금이나 군량을 지급합니다.<br>
</div>
<div v-else-if="commandName == '증여'">
자신의 자금이나 군량을 다른 장수에게 증여합니다.<br>
</div>
<div class="row">
<div class="col-12 col-md-5">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:cities="citiesMap"
:generals="generalList"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-2 col-md-1">
자원 :
<b-button-group>
<b-button
:pressed="isGold"
@click="isGold=true"
>
</b-button>
<b-button
:pressed="!isGold"
@click="isGold=false"
>
</b-button>
</b-button-group>
</div>
<div class="col-7 col-md-4">
금액 :
<SelectAmount
v-model="amount"
:amountGuide="amountGuide"
:maxAmount="maxAmount"
:minAmount="minAmount"
/>
</div>
<div class="col-3 col-md-2 d-grid">
<b-button
variant="primary"
@click="submit"
>
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar
:title="commandName"
:type="procEntryMode"
/>
</template>
<script lang="ts">
import SelectGeneral from "@/processing/SelectGeneral.vue";
import SelectAmount from "@/processing/SelectAmount.vue";
import { defineComponent, ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
} from "./processingRes";
import { getNpcColor } from "@/common_legacy";
declare const commandName: string;
declare const entryInfo: ['General'|'Nation', unknown];
declare const procRes: {
distanceList: Record<number, number[]>;
cities: [number, string][];
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
minAmount: number;
maxAmount: number;
amountGuide: number[];
};
export default defineComponent({
components: {
SelectGeneral,
SelectAmount,
TopBackBar,
BottomBar,
},
setup() {
const citiesMap = new Map<
number,
{
name: string;
info?: string;
}
>();
for (const [id, name] of procRes.cities) {
citiesMap.set(id, { name });
}
const generalList = convertGeneralList(
procRes.generalsKey,
procRes.generals
);
const amount = ref(1000);
const isGold = ref(true);
const selectedGeneralID = ref(generalList[0].no);
function textHelpGeneral(gen: procGeneralItem): string {
const nameColor = getNpcColor(gen.npc);
const name = nameColor
? `<span style="color:${nameColor}">${gen.name}</span>`
: gen.name;
return `${name} (금${unwrap(gen.gold).toLocaleString()}/쌀${unwrap(gen.rice).toLocaleString()}) (${
gen.leadership
}/${gen.strength}/${gen.intel})`;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amount: amount.value,
isGold: isGold.value,
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
return {
procEntryMode: <'chief'|'normal'>(entryInfo[0] == 'Nation'?'chief':'normal'),
searchable: getProcSearchable(),
amount,
isGold,
selectedGeneralID,
citiesMap: ref(citiesMap),
distanceList: procRes.distanceList,
minAmount: ref(procRes.minAmount),
maxAmount: ref(procRes.maxAmount),
amountGuide: procRes.amountGuide,
generalList,
commandName,
textHelpGeneral,
submit,
};
},
});
</script>
+171
View File
@@ -0,0 +1,171 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" :type="procEntryMode" />
<div v-if="asyncReady" class="bg0">
<MapViewer
v-if="map"
v-model="selectedCityObj"
:server-nick="serverNick"
:serverID="serverID"
:map-name="unwrap(gameConstStore?.gameConst.mapName)"
:mapData="map"
:isDetailMap="false"
:cityPosition="cityPosition"
:formatCityInfo="formatCityInfoText"
:image-path="imagePath"
/>
<div v-if="commandName == '선전포고'">
타국에게 선전 포고합니다.<br />
선전 포고할 국가를 목록에서 선택하세요.<br />
고립되지 않은 아국 도시에서 인접한 국가에 선포 가능합니다.<br />
초반제한 해제 2년전부터 선포가 가능합니다. ({{ startYear + 1 }} 1월부터 가능)<br />
현재 선포가 불가능한 국가는 배경색이
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div v-else-if="commandName == '급습'">
선택된 국가에 급습을 발동합니다.<br />
선포, 전쟁중인 상대국에만 가능합니다.<br />
상대 국가를 목록에서 선택하세요.<br />
현재 급습이 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div v-else-if="commandName == '불가침 파기 제의'">
불가침중인 국가에 조약 파기를 제의합니다.<br />
제의할 국가를 목록에서 선택하세요.<br />
현재 제의가 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div v-else-if="commandName == '이호경식'">
선택된 국가에 이호경식을 발동합니다.<br />
선포, 전쟁중인 상대국에만 가능합니다.<br />
상대 국가를 목록에서 선택하세요.<br />
현재 이호경식이 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div v-else-if="commandName == '종전 제의'">
전쟁중인 국가에 종전을 제의합니다.<br />
제의할 국가를 목록에서 선택하세요.<br />
현재 제의가 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div v-else-if="commandName == '허보'">
전쟁중인 국가에 종전을 제의합니다.<br />
제의할 국가를 목록에서 선택하세요.<br />
현재 제의가 불가능한 국가는
<span style="color: red">붉은색</span>으로 표시됩니다.<br />
</div>
<div class="row">
<div class="col-6 col-md-3">
국가 :
<SelectNation v-model="selectedNationID" :nations="nationList" :searchable="searchable" />
</div>
<div class="col-4 col-md-2 d-grid">
<b-button @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" :type="procEntryMode" />
</template>
<script lang="ts">
declare const staticValues: {
serverNick: string;
serverID: string;
commandName: string;
entryInfo: ["General" | "Nation", unknown];
};
declare const procRes: {
nationList: procNationList;
startYear: number;
};
declare const getCityPosition: () => CityPositionMap;
declare const formatCityInfo: (city: MapCityParsedRaw) => MapCityParsed;
</script>
<script lang="ts" setup>
import MapViewer, { type CityPositionMap, type MapCityParsed, type MapCityParsedRaw } from "@/components/MapViewer.vue";
import SelectNation from "@/processing/SelectNation.vue";
import { ref, type Ref, watch, onMounted, provide } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import { getProcSearchable, type procNationItem, type procNationList } from "./processingRes";
import { getGameConstStore, type GameConstStore } from "@/GameConstStore";
import { SammoAPI } from "@/SammoAPI";
import type { MapResult } from "@/defs";
const serverNick = staticValues.serverNick;
const serverID = staticValues.serverID;
const startYear = procRes.startYear;
const cityPosition = getCityPosition();
const formatCityInfoText = formatCityInfo;
const imagePath = window.pathConfig.gameImage;
const asyncReady = ref<boolean>(false);
const gameConstStore = ref<GameConstStore>();
provide("gameConstStore", gameConstStore);
const storeP = getGameConstStore().then((store) => {
gameConstStore.value = store;
});
void Promise.all([storeP]).then(() => {
asyncReady.value = true;
});
const nationList = ref(new Map<number, procNationItem>());
watch(
() => procRes.nationList,
(newNationList) => {
const tmpNationList = new Map<number, procNationItem>();
for (const nationItem of newNationList) {
tmpNationList.set(nationItem.id, nationItem);
}
nationList.value = tmpNationList;
},
{ immediate: true }
);
const selectedNationID = ref(procRes.nationList[0].id);
const map = ref<MapResult>();
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
destNationID: selectedNationID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const searchable = getProcSearchable();
const procEntryMode: Ref<"chief" | "normal"> = ref(staticValues.entryInfo[0] == "Nation" ? "chief" : "normal");
const selectedCityObj = ref<MapCityParsed>();
const commandName = ref(staticValues.commandName);
watch(selectedCityObj, (city?: MapCityParsed) => {
if (city === undefined) {
return;
}
if(city.nationID === undefined){
return;
}
selectedNationID.value = city.nationID;
});
onMounted(async () => {
try{
map.value = await SammoAPI.Global.GetMap({neutralView:0, showMe: 1});
}
catch(e){
console.error(e);
}
});
</script>
+69
View File
@@ -0,0 +1,69 @@
<template>
<div class="input-group">
<b-button v-if="maxAmount > 20000" class="btn-sm" @click="amount = Math.max(amount - 10000, minAmount)">
-만
</b-button>
<b-button v-if="maxAmount > 2000" class="btn-sm" @click="amount = Math.max(amount - 1000, minAmount)">
-천
</b-button>
<b-button v-if="maxAmount > 200" class="btn-sm" @click="amount = Math.max(amount - 100, minAmount)"> -백 </b-button>
<input
v-model.number="amount"
type="number"
class="form-control text-end"
:max="maxAmount"
:min="minAmount"
:step="step"
placeholder="금액"
/>
<b-dropdown v-if="amountGuide" right text="" class="amount-dropdown">
<b-dropdown-item v-for="guide in amountGuide" :key="guide" @click="amount = guide">
<div class="text-end">
{{ guide.toLocaleString() }}
</div>
</b-dropdown-item>
</b-dropdown>
<b-button v-if="maxAmount > 200" class="btn-sm" @click="amount = Math.min(amount + 100, maxAmount)"> + </b-button>
<b-button v-if="maxAmount > 2000" class="btn-sm" @click="amount = Math.min(amount + 1000, maxAmount)">
+
</b-button>
<b-button v-if="maxAmount >= 10000" class="btn-sm" @click="amount = Math.min(amount + 10000, maxAmount)">
+
</b-button>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import VueTypes from "vue-types";
export default defineComponent({
props: {
modelValue: VueTypes.number.isRequired,
minAmount: VueTypes.number.isRequired,
maxAmount: VueTypes.number.isRequired,
amountGuide: VueTypes.arrayOf(Number).def([1000, 2000, 5000, 10000]),
step: VueTypes.number.def(1),
},
emits: ["update:modelValue"],
data() {
return {
amount: this.modelValue,
};
},
watch: {
amount(val: number) {
this.$emit("update:modelValue", val);
},
},
});
</script>
<style lang="scss">
.btn-group.amount-dropdown > .btn {
border-radius: 0;
}
.btn-group.amount-dropdown .dropdown-menu.show {
min-width: 6rem;
}
</style>
+104
View File
@@ -0,0 +1,104 @@
<template>
<v-multiselect
v-model="selectedCity"
:allow-empty="false"
:options="citiesForFind"
:group-select="false"
label="searchText"
track-by="value"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="도시 선택"
:maxHeight="400"
:searchable="searchable"
>
<template #option="props"
><span
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
>
{{ props.option.title }}
<span v-if="props.option.info">({{ props.option.info }})</span>
{{ props.option.notAvailable ? "(불가)" : undefined }}</span
>
</template>
<template #singleLabel="props">
<span
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
>{{ props.option.simpleName }} {{ props.option.notAvailable ? "(불가)" : undefined }}</span
>
</template>
</v-multiselect>
</template>
<script lang="ts">
import { convertSearch초성 } from "@/util/convertSearch초성";
import { defineComponent, type PropType } from "vue";
type SelectedCity = {
value: number;
searchText: string;
title: string;
simpleName: string;
info?: string;
notAvailable?: boolean;
};
export default defineComponent({
props: {
modelValue: {
type: Number,
required: true,
},
cities: {
type: Map as PropType<Map<number, { name: string; info?: string }>>,
required: true,
},
searchable: {
type: Boolean,
required: false,
default: true,
},
},
emits: ["update:modelValue"],
data() {
const citiesForFind = [];
const targets = new Map<number, SelectedCity>();
let selectedCity;
for (const [value, { name, info }] of this.cities.entries()) {
const obj: SelectedCity = {
value,
title: name,
info: info,
simpleName: name,
searchText: convertSearch초성(name).join("|"),
};
if (value == this.modelValue) {
selectedCity = obj;
}
citiesForFind.push(obj);
targets.set(value, obj);
}
return {
selectedCity,
citiesForFind,
targets,
};
},
watch: {
modelValue(val: number) {
const target = this.targets.get(val);
this.selectedCity = target;
},
selectedCity(val: SelectedCity) {
this.$emit("update:modelValue", val.value);
},
},
});
</script>
+107
View File
@@ -0,0 +1,107 @@
<template>
<v-multiselect
v-model="selectedColor"
:allow-empty="false"
:options="forFind"
:group-select="false"
label="searchText"
track-by="value"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="색상 선택"
:maxHeight="400"
:searchable="false"
>
<template #option="props">
<div
:class="`sam-color-${props.option.title.slice(1)}`"
:style="{
margin: '-0.375rem -0.75rem',
}"
>
<div
class="sam-nation-own-bgcolor"
:style="{
padding: '0.545rem 0.75rem',
}"
>
{{ props.option.title }}
</div>
</div>
</template>
<template #singleLabel="props">
<div
:class="`sam-color-${props.option.title.slice(1)}`"
:style="{
margin: '-0.25rem -0.75rem',
}"
>
<div
class="sam-nation-own-bgcolor"
:style="{
padding: '0.30rem 0.75rem',
borderRadius: '0.25rem',
}"
>
{{ props.option.title }}
</div>
</div>
</template>
</v-multiselect>
</template>
<script lang="ts">
import { unwrap } from "@/util/unwrap";
import { defineComponent, type PropType } from "vue";
type SelectedColor = {
value: number;
title: string;
};
export default defineComponent({
props: {
modelValue: {
type: Number,
required: true,
},
colors: {
type: Array as PropType<string[]>,
required: true,
},
},
emits: ["update:modelValue"],
data() {
const forFind = [];
const targets = new Map<number, SelectedColor>();
for (const [value, title] of this.colors.entries()) {
const obj: SelectedColor = {
value,
title,
};
forFind.push(obj);
targets.set(value, obj);
}
let selectedColor = forFind[0];
return {
selectedColor,
searchMode: false,
forFind,
targets,
};
},
watch: {
modelValue(val: number) {
const target = unwrap(this.targets.get(val));
this.selectedColor = target;
},
selectedColor(val: SelectedColor) {
this.$emit("update:modelValue", val.value);
},
},
});
</script>
+177
View File
@@ -0,0 +1,177 @@
<template>
<v-multiselect
v-model="selectedGeneral"
class="selectedGeneral"
:allow-empty="false"
:options="forFind"
:group-select="false"
:group-values="groupByNation ? 'values' : undefined"
group-label="nationID"
label="searchText"
track-by="value"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="장수 선택"
:maxHeight="400"
:searchable="searchable"
>
<template #option="props">
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="props.option.title" v-html="props.option.title" />
<div
v-if="props.option.$groupLabel !== undefined"
class="margin-filler"
:style="{
backgroundColor: groupByNation?.get(props.option.$groupLabel)?.color,
color: isBrightColor(groupByNation?.get(props.option.$groupLabel)?.color ?? '#ffffff') ? 'black' : 'white',
}"
>
{{ groupByNation?.get(props.option.$groupLabel)?.name }}
</div>
</template>
<template #singleLabel="props">
{{ props.option.simpleName }}
{{ groupByNation ? `[${groupByNation.get(props.option.obj.nationID)?.name}]` : undefined }}
</template>
</v-multiselect>
</template>
<script lang="ts">
import { getNpcColor } from "@/common_legacy";
import { convertSearch초성 } from "@/util/convertSearch초성";
import { isBrightColor } from "@/util/isBrightColor";
import { unwrap } from "@/util/unwrap";
import { defineComponent, type PropType } from "vue";
import VueTypes from "vue-types";
import type { procGeneralItem, procGeneralList, procNationItem } from "./processingRes";
type SelectedGeneral = {
value: number;
searchText: string;
title: string;
simpleName: string;
obj: procGeneralItem;
};
export default defineComponent({
props: {
modelValue: VueTypes.number.isRequired,
generals: {
type: Array as PropType<procGeneralList>,
required: true,
},
textHelper: {
type: Function as PropType<(item: procGeneralItem) => string>,
required: false,
default: undefined,
},
groupByNation: {
type: Map as PropType<Map<number, procNationItem>>,
required: false,
default: undefined,
},
searchable: {
type: Boolean,
required: false,
default: true,
},
},
emits: ["update:modelValue"],
data() {
const forFind: (
| SelectedGeneral
| {
values: SelectedGeneral[];
nationID: number;
}
)[] = [];
const forFindGroup = new Map<number, SelectedGeneral[]>();
const targets = new Map<number, SelectedGeneral>();
let selectedGeneral;
for (const gen of this.generals) {
let groupArray = forFind;
if (this.groupByNation) {
const nationID = gen.nationID ?? 0;
if (!forFindGroup.has(nationID)) {
const nationItem = unwrap(this.groupByNation.get(nationID));
let tmpArr: SelectedGeneral[] = [];
forFindGroup.set(nationID, tmpArr);
groupArray = tmpArr;
forFind.push({
nationID: nationItem.id,
values: tmpArr,
});
} else {
groupArray = unwrap(forFindGroup.get(nationID));
}
}
const nameColor = getNpcColor(gen.npc);
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
const obj: SelectedGeneral = {
value: gen.no,
title: this.textHelper ? this.textHelper(gen) : `${name} (${gen.leadership}/${gen.strength}/${gen.intel})`,
simpleName: gen.name,
searchText: convertSearch초성(gen.name).join("|"),
obj: gen,
};
if (gen.no == this.modelValue) {
selectedGeneral = obj;
}
groupArray.push(obj);
targets.set(gen.no, obj);
}
return {
selectedGeneral,
forFind,
targets,
isBrightColor,
};
},
watch: {
modelValue(val: number) {
const target = this.targets.get(val);
this.selectedGeneral = target;
},
selectedGeneral(val: SelectedGeneral) {
this.$emit("update:modelValue", val.value);
},
},
});
</script>
<style lang="scss">
@import "@scss/common/break_500px.scss";
@import "@scss/common/variables.scss";
@import "@scss/common/bootswatch_custom_variables.scss";
@import "bootstrap/scss/bootstrap-utilities.scss";
.selectedGeneral {
$vue-multiselect-bg: $gray-700;
$vue-multiselect-color: $gray-100;
$form-select-color: $gray-100;
$text-muted: $gray-400;
$dark: $gray-100;
$light: $gray-700;
$vue-multiselect-option-selected-bg: $gray-600;
@import "@scss/common/vue-multiselect.scss";
color: $vue-multiselect-color;
input {
color: $vue-multiselect-color;
}
.multiselect__option--group {
padding: 0;
}
.margin-filler {
padding: calc($vue-multiselect-padding-y + 0.17em) $vue-multiselect-padding-x;
}
}
</style>
+108
View File
@@ -0,0 +1,108 @@
<template>
<v-multiselect
v-model="selectedNation"
:allow-empty="false"
:options="forFind"
:group-select="false"
label="searchText"
track-by="value"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="국가 선택"
:maxHeight="400"
:searchable="searchable"
>
<template #option="props">
<span
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
>
{{ props.option.title }}
<span v-if="props.option.info">({{ props.option.info }})</span>
{{ props.option.notAvailable ? "(불가)" : undefined }}
</span>
</template>
<template #singleLabel="props">
<span
:style="{
color: props.option.notAvailable ? 'red' : undefined,
}"
>
{{ props.option.simpleName }}
{{ props.option.notAvailable ? "(불가)" : undefined }}</span
>
</template>
</v-multiselect>
</template>
<script lang="ts">
import { convertSearch초성 } from "@/util/convertSearch초성";
import { defineComponent, type PropType } from "vue";
import type { procNationItem } from "./processingRes";
type SelectedNation = {
value: number;
searchText: string;
title: string;
simpleName: string;
info?: string;
notAvailable?: boolean;
};
export default defineComponent({
props: {
modelValue: {
type: Number,
required: true,
},
nations: {
type: Map as PropType<Map<number, procNationItem>>,
required: true,
},
searchable: {
type: Boolean,
required: false,
default: true,
},
},
emits: ["update:modelValue"],
data() {
const forFind = [];
const targets = new Map<number, SelectedNation>();
let selectedNation;
for (const nationItem of this.nations.values()) {
const obj: SelectedNation = {
value: nationItem.id,
title: nationItem.name,
info: nationItem.info,
simpleName: nationItem.name,
notAvailable: nationItem.notAvailable,
searchText: convertSearch초성(nationItem.name).join("|"),
};
if (nationItem.id == this.modelValue) {
selectedNation = obj;
}
forFind.push(obj);
targets.set(nationItem.id, obj);
}
return {
selectedNation,
forFind,
targets,
};
},
watch: {
modelValue(val: number) {
const target = this.targets.get(val);
this.selectedNation = target;
},
selectedNation(val: SelectedNation) {
this.$emit("update:modelValue", val.value);
},
},
});
</script>
+87
View File
@@ -0,0 +1,87 @@
import { mb_strwidth } from "@/util/mb_strwidth";
import { isArray, isBoolean, isInteger, isString } from "lodash";
const stringArgs = [
'nationName', 'optionText', 'itemType', 'nationType', 'itemCode', 'commandType',
] as const;
const intArgs = [
'crewType', 'destGeneralID', 'destCityID', 'destNationID',
'amount', 'colorType',
'year', 'month',
'srcArmType', 'destArmType', //숙련전환 전용
] as const;
const booleanArgs = [
'isGold', 'buyRice',
] as const;
const integerArrayArgs = [
'destNationIDList', 'destGeneralIDList', 'amountList'
] as const;
type StringKeys = typeof stringArgs[number];
type IntKeys = typeof intArgs[number];
type BooleanKeys = typeof booleanArgs[number];
type IntegerArrayKeys = typeof integerArrayArgs[number];
export type Args = {
[key in StringKeys]?: string;
} & {
[key in IntKeys]?: number;
} & {
[key in BooleanKeys]?: boolean;
} & {
[key in IntegerArrayKeys]?: number[]
};
export function testSubmitArgs(args: Args): true | ['int' | 'string' | 'boolean' | 'int[]', keyof Args, number | string | boolean | number[]] {
for (const intKey of intArgs) {
const testVal = args[intKey];
if (testVal === undefined) {
continue;
}
if (!isInteger(testVal)) {
return ['int', intKey, testVal];
}
}
for (const stringKey of stringArgs) {
const testVal = args[stringKey];
if (testVal === undefined) {
continue;
}
if (!isString(testVal)) {
return ['string', stringKey, testVal];
}
if(stringKey == 'nationName' && mb_strwidth(testVal) > 18){
throw `길이가 반각 18자 분량을 넘었습니다.`;
}
}
for (const booleanKey of booleanArgs) {
const testVal = args[booleanKey];
if (testVal === undefined) {
continue;
}
if (!isBoolean(testVal)) {
return ['boolean', booleanKey, testVal];
}
}
for (const integerArrayKey of integerArrayArgs) {
const testVal = args[integerArrayKey];
if (testVal === undefined) {
continue;
}
if (!isArray(args[integerArrayKey])) {
return ['int[]', integerArrayKey, testVal];
}
for (const value of testVal) {
if (!isInteger(value)) {
return ['int[]', integerArrayKey, testVal];
}
}
}
return true;
}
+111
View File
@@ -0,0 +1,111 @@
import type { ItemTypeKey } from "@/defs";
import { combineArray } from "@/util/combineArray";
import { type Ref, ref, watch } from "vue";
export type procGeneralItem = {
no: number,
name: string,
nationID?: number,
officerLevel: number,
npc: number,
gold?: number,
rice?: number,
leadership: number,
strength: number,
intel: number,
cityID?: number,
crew?: number,
train?: number,
atmos?: number,
troopID?: number,
}
export type procGeneralList = procGeneralItem[];
export type procGeneralKey = 'no' | 'name' | 'nationID' | 'officerLevel' | 'npc' | 'gold' | 'rice' | 'leadership' | 'strength' | 'intel' | 'cityID' | 'crew' | 'train' | 'atmos' | 'troopID';
export type procGeneralRawItem = procGeneralItem[procGeneralKey][];
export type procTroopItem = {
troop_leader: number,
nation: number,
name: string
};
export type procTroopList = Record<number, procTroopItem>;
export type procGeneralRawItemList = procGeneralRawItem[];
export function convertGeneralList(keys: procGeneralKey[], rawList: procGeneralRawItemList): procGeneralList {
return combineArray(rawList, keys) as procGeneralList;
}
export type procNationItem = {
id: number,
name: string,
color: string,
power: number,
scoutMsg?: string,
info?: string,
notAvailable?: boolean,
};
export type procNationList = procNationItem[];
export type procNationTypeItem = {
type: string,
name: string,
pros: string,
cons: string,
}
export type procNationTypeList = Record<string, procNationTypeItem>;
export type procArmTypeItem = {
armType: number,
armName: string,
values: procCrewTypeItem[],
}
export type procCrewTypeItem = {
id: number,
reqTech: number,
reqYear: number,
notAvailable?: boolean,
baseRice: number,
baseCost: number,
name: string,
attack: number,
defence: number,
speed: number,
avoid: number,
img: string,
info: string[],
}
export type procItemType = {
id: string,
name: string,
reqSecu: number,
cost: number,
info: string,//<br>
isBuyable: boolean,
}
export type procItemList = Record<ItemTypeKey, {
typeName: string,
values: procItemType[],
}>
//XXX: vuex 쓰기 전까지...
export const searchableProcessingMode = 'sam.processing.searchable';
const searchable = ref((localStorage.getItem(searchableProcessingMode) ?? "0") != "0");
watch(searchable, (val) => {
localStorage.setItem(searchableProcessingMode, val ? "1" : "0");
});
export function getProcSearchable():Ref<boolean>{
return searchable;
}