feat(wip): 세력장수/암행부 컬럼 모두 등록 완료
This commit is contained in:
@@ -147,7 +147,7 @@ class GeneralList extends \sammo\BaseAPI
|
||||
|
||||
[$queryColumns, $rankColumns] = General::mergeQueryColumn(array_keys(static::$viewColumns), 1);
|
||||
|
||||
$rawGeneralList = Util::convertArrayToDict($db->query('SELECT %l from general WHERE nation = %i', Util::formatListOfBackticks($queryColumns), $nationID), 'no');
|
||||
$rawGeneralList = Util::convertArrayToDict($db->query('SELECT %l from general WHERE nation = %i ORDER BY turntime ASC', Util::formatListOfBackticks($queryColumns), $nationID), 'no');
|
||||
|
||||
$reservedCommand = [];
|
||||
if ($this->permission >= 2) {
|
||||
@@ -200,7 +200,6 @@ class GeneralList extends \sammo\BaseAPI
|
||||
|
||||
$getRankVar = fn ($key) => (fn ($rawGeneral) => (($rankList[$rawGeneral['no']] ?? [])[$key] ?? 0));
|
||||
|
||||
|
||||
$specialViewFilter = [
|
||||
'officerLevel' => fn ($rawGeneral) => $this->getOfficerLevel($rawGeneral),
|
||||
'officerLevelText' => fn ($rawGeneral) => getOfficerLevelText($this->getOfficerLevel($rawGeneral), $nationArr['level']),
|
||||
@@ -262,11 +261,20 @@ class GeneralList extends \sammo\BaseAPI
|
||||
$generalList[] = $item;
|
||||
}
|
||||
|
||||
if($this->permission >= 2){
|
||||
$troops = $db->queryAllLists('SELECT troop_leader,name FROM troop WHERE nation = %i', $nationID);
|
||||
}
|
||||
else{
|
||||
$troops = null;
|
||||
}
|
||||
|
||||
|
||||
$result = [
|
||||
'result' => true,
|
||||
'permission' => $this->permission,
|
||||
'column' => array_keys($resultColumns),
|
||||
'list' => $generalList,
|
||||
'troops' => $troops,
|
||||
'env' => $env,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<BContainer id="container" :toast="{ root: true }" class="pageNationGeneral bg0">
|
||||
<TopBackBar :title="title" />
|
||||
<GeneralList v-if="asyncReady" :list="generalList" :env="envVal" :height="'fill'" />
|
||||
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
|
||||
<GeneralList v-if="asyncReady" :list="generalList" :troops="troopList" :env="envVal" :height="'fill'" />
|
||||
<BottomBar />
|
||||
</BContainer>
|
||||
</template>
|
||||
@@ -25,46 +26,57 @@ import GeneralList from "./components/GeneralList.vue";
|
||||
import { type GameConstStore, getGameConstStore } from "./GameConstStore";
|
||||
|
||||
const generalList = ref<GeneralListItem[]>([]);
|
||||
const envVal = ref<GeneralListResponse['env']>({
|
||||
const troopList = ref<Record<number, string>>({});
|
||||
const envVal = ref<GeneralListResponse["env"]>({
|
||||
year: 1,
|
||||
month: 1,
|
||||
turnterm: 1,
|
||||
turntime: '2022-02-22 22:22:22.22222'
|
||||
turntime: "2022-02-22 22:22:22.22222",
|
||||
});
|
||||
|
||||
const gameConstStore = ref<GameConstStore>();
|
||||
provide('gameConstStore', gameConstStore);
|
||||
provide("gameConstStore", gameConstStore);
|
||||
|
||||
const asyncReady = ref(false);
|
||||
|
||||
const title = "세력 장수";
|
||||
|
||||
const storeP = getGameConstStore().then((store)=>{
|
||||
const storeP = getGameConstStore().then((store) => {
|
||||
gameConstStore.value = store;
|
||||
});
|
||||
|
||||
void Promise.all([storeP]).then(()=>{
|
||||
void Promise.all([storeP]).then(() => {
|
||||
asyncReady.value = true;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { column, list, permission, env } = await SammoAPI.Nation.GeneralList();
|
||||
const { column, list, permission, troops, env } = await SammoAPI.Nation.GeneralList();
|
||||
troopList.value = {};
|
||||
//XXX: 로직상 똑같은데....
|
||||
if (permission == 0) {
|
||||
const rawGeneralList = merge2DArrToObjectArr(column, list);
|
||||
generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
|
||||
generalList.value = rawGeneralList.map((v) => {
|
||||
return { permission, ...v };
|
||||
});
|
||||
} else if (permission == 1) {
|
||||
const rawGeneralList = merge2DArrToObjectArr(column, list);
|
||||
generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
|
||||
generalList.value = rawGeneralList.map((v) => {
|
||||
return { permission, ...v };
|
||||
});
|
||||
} else if ([2, 3, 4].includes(permission)) {
|
||||
const rawGeneralList = merge2DArrToObjectArr(column, list);
|
||||
generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
|
||||
generalList.value = rawGeneralList.map((v) => {
|
||||
return { permission, ...v };
|
||||
});
|
||||
|
||||
for(const [troopLeader, troopName] of troops){
|
||||
troopList.value[troopLeader] = troopName;
|
||||
}
|
||||
} else {
|
||||
throw `?? ${permission}`;
|
||||
}
|
||||
envVal.value = env;
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
@@ -73,13 +85,15 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
html, body, #app {
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pageNationGeneral{
|
||||
.pageNationGeneral {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
GetRowIdParams,
|
||||
GridApi,
|
||||
GridReadyEvent,
|
||||
RowNode,
|
||||
} from "ag-grid-community";
|
||||
import { getNpcColor } from "@/common_legacy";
|
||||
import type { BaseWithValueColDefParams, ValueGetterParams } from "ag-grid-community/dist/lib/entities/colDef";
|
||||
@@ -40,6 +41,9 @@ import { unwrap } from "@/util/unwrap";
|
||||
import SimpleTooltipCell from "@/gridCellRenderer/SimpleTooltipCell.vue";
|
||||
import GridTooltipCell, { type GridCellInfo } from "@/gridCellRenderer/GridTooltipCell.vue";
|
||||
import { formatConnectScore } from "@/utilGame/formatConnectScore";
|
||||
import { convertSearch초성 } from "@/util/convertSearch초성";
|
||||
import { isString } from "lodash";
|
||||
import { formatDefenceTrain } from "@/utilGame/formatDefenceTrain";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
@@ -47,6 +51,11 @@ const props = defineProps({
|
||||
required: true,
|
||||
},
|
||||
|
||||
troops: {
|
||||
type: Object as PropType<Record<number, string>>,
|
||||
required: true,
|
||||
},
|
||||
|
||||
height: {
|
||||
type: String as PropType<"static" | "fill" | number | `${number}px` | `${number}%`>,
|
||||
required: false,
|
||||
@@ -82,6 +91,18 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
const generalByID = ref(new Map<number, GeneralListItem>());
|
||||
|
||||
function refineGeneralList(list: GeneralListItem[]) {
|
||||
const map = new Map<number, GeneralListItem>();
|
||||
for (const general of list) {
|
||||
map.set(general.no, general);
|
||||
}
|
||||
generalByID.value = map;
|
||||
}
|
||||
refineGeneralList(props.list);
|
||||
watch(() => props.list, refineGeneralList);
|
||||
|
||||
const gridApi = ref<GridApi>();
|
||||
const columnApi = ref<ColumnApi>();
|
||||
|
||||
@@ -133,10 +154,15 @@ interface GenValueGetterParams extends ValueGetterParams {
|
||||
data: GeneralListItem;
|
||||
}
|
||||
|
||||
interface GenRowNode extends RowNode {
|
||||
data: GeneralListItem;
|
||||
}
|
||||
|
||||
interface GenColDef extends ColDef {
|
||||
field?: headerType;
|
||||
valueFormatter?: string | ((params: GenValueParams) => string);
|
||||
filterValueGetter?: string | ((params: GenValueGetterParams) => string);
|
||||
filterValueGetter?: string | ((params: GenValueGetterParams) => unknown);
|
||||
valueGetter?: string | ((params: GenValueGetterParams) => unknown);
|
||||
}
|
||||
|
||||
interface GenColGroupDef extends ColGroupDef {
|
||||
@@ -156,8 +182,32 @@ function naiveCheClassNameFilter(value: string): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
function numberFormatter(value: GenValueParams): string {
|
||||
return (value.value as number).toLocaleString();
|
||||
function numberFormatter(unit?: string) {
|
||||
if (unit) {
|
||||
return (value: GenValueParams): string => {
|
||||
const valueText = (value.value as number).toLocaleString();
|
||||
return `${valueText} ${unit}`;
|
||||
};
|
||||
}
|
||||
return (value: GenValueParams): string => {
|
||||
return (value.value as number).toLocaleString();
|
||||
};
|
||||
}
|
||||
|
||||
function extractTroopInfo(value: GeneralListItem): [string, GeneralListItemP2] | undefined {
|
||||
if (value.permission == 0 || value.permission == 1) {
|
||||
return undefined;
|
||||
}
|
||||
const troopID = value.troop;
|
||||
if (!(troopID in props.troops)) {
|
||||
return undefined;
|
||||
}
|
||||
const troopName = props.troops[troopID];
|
||||
const troopLeader = generalByID.value.get(troopID) as GeneralListItemP2 | undefined;
|
||||
if (troopLeader === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return [troopName, troopLeader];
|
||||
}
|
||||
|
||||
const defaultCellClass = ["cell-middle"];
|
||||
@@ -206,6 +256,7 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
};
|
||||
return style as CellStyle;
|
||||
},
|
||||
filterValueGetter: ({ data }) => convertSearch초성(data.name),
|
||||
cellClass: defaultCellClass,
|
||||
filter: true,
|
||||
},
|
||||
@@ -269,10 +320,10 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
const dataP2 = data as GeneralListItemP2;
|
||||
if (2 <= dataP2.officerLevel && dataP2.officerLevel <= 4) {
|
||||
const cityName = gameConstStore.value.cityConst[dataP2.officer_city].name;
|
||||
return `${cityName} ${dataP2.officerLevelText}`;
|
||||
return convertSearch초성(`${cityName} ${dataP2.officerLevelText}`);
|
||||
}
|
||||
}
|
||||
return data.officerLevelText;
|
||||
return convertSearch초성(data.officerLevelText);
|
||||
},
|
||||
filter: true,
|
||||
cellClass: centerCellClass,
|
||||
@@ -315,8 +366,116 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
},
|
||||
],
|
||||
},
|
||||
gold: { headerName: "금", field: "gold", ...sortableNumber, valueFormatter: numberFormatter, width: 70 },
|
||||
rice: { headerName: "쌀", field: "rice", ...sortableNumber, valueFormatter: numberFormatter, width: 70 },
|
||||
goldRice: {
|
||||
headerName: "자금",
|
||||
|
||||
children: [
|
||||
{
|
||||
headerName: "금/쌀",
|
||||
cellRenderer: ({ data }: GenValueParams) => {
|
||||
return `${data.gold.toLocaleString()} 금<br>${data.rice.toLocaleString()} 쌀`;
|
||||
},
|
||||
width: 80,
|
||||
cellClass: rightAlignClass,
|
||||
columnGroupShow: "closed",
|
||||
sortable: true,
|
||||
sortingOrder: ["desc", "asc", null],
|
||||
comparator(_a, _b, { data: lhs }: GenRowNode, { data: rhs }: GenRowNode) {
|
||||
const lhsAmount = lhs.gold + lhs.rice;
|
||||
const rhsAmount = rhs.gold + rhs.rice;
|
||||
return lhsAmount - rhsAmount;
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "금",
|
||||
field: "gold",
|
||||
...sortableNumber,
|
||||
valueFormatter: numberFormatter("금"),
|
||||
width: 70,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "쌀",
|
||||
field: "rice",
|
||||
...sortableNumber,
|
||||
valueFormatter: numberFormatter("쌀"),
|
||||
width: 70,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
city: {
|
||||
headerName: "도시",
|
||||
field: "city",
|
||||
valueGetter: ({ data }) => {
|
||||
if (data.permission == 0 || data.permission == 1) {
|
||||
return "-";
|
||||
}
|
||||
return gameConstStore.value.cityConst[data.city].name;
|
||||
},
|
||||
filter: true,
|
||||
sortable: true,
|
||||
width: 60,
|
||||
filterValueGetter: ({ data }) => {
|
||||
if (data.permission == 0 || data.permission == 1) {
|
||||
return "-";
|
||||
}
|
||||
return convertSearch초성(gameConstStore.value.cityConst[data.city].name);
|
||||
},
|
||||
},
|
||||
|
||||
troop: {
|
||||
headerName: "부대",
|
||||
field: "troop",
|
||||
valueGetter: ({ data }: GenValueGetterParams) => {
|
||||
const troopInfo = extractTroopInfo(data);
|
||||
if (troopInfo === undefined) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
const [troopName, troopLeader] = troopInfo;
|
||||
const cityName = gameConstStore.value.cityConst[troopLeader.city].name;
|
||||
return [troopName, cityName];
|
||||
},
|
||||
cellRenderer: ({ value }: { value: [string, string] | string }) => {
|
||||
if (isString(value)) {
|
||||
return value;
|
||||
}
|
||||
const [troopName, cityName] = value;
|
||||
return `${troopName}<br>[${cityName}]`;
|
||||
},
|
||||
width: 90,
|
||||
sortable: true,
|
||||
comparator: (valX, valB, { data: lhs }: GenRowNode, { data: rhs }: GenRowNode) => {
|
||||
const troopInfoLhs = extractTroopInfo(lhs);
|
||||
const troopInfoRhs = extractTroopInfo(rhs);
|
||||
console.log(troopInfoLhs, troopInfoRhs);
|
||||
if (troopInfoLhs === troopInfoRhs) {
|
||||
return 0;
|
||||
}
|
||||
if (troopInfoLhs === undefined) {
|
||||
return 1;
|
||||
}
|
||||
if (troopInfoRhs === undefined) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return troopInfoLhs[0].localeCompare(troopInfoRhs[0]);
|
||||
},
|
||||
filter: true,
|
||||
filterValueGetter: ({ data }) => {
|
||||
const troopInfo = extractTroopInfo(data);
|
||||
if (troopInfo === undefined) {
|
||||
return "-";
|
||||
}
|
||||
const [troopName, troopLeader] = troopInfo;
|
||||
const cityName = gameConstStore.value.cityConst[troopLeader.city].name;
|
||||
|
||||
return convertSearch초성(`${troopName}$${cityName}`);
|
||||
},
|
||||
},
|
||||
|
||||
crew: {
|
||||
headerName: "보유 병력",
|
||||
children: [
|
||||
@@ -331,6 +490,7 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
];
|
||||
})(),
|
||||
},
|
||||
columnGroupShow: "closed",
|
||||
},
|
||||
{
|
||||
headerName: "훈/사",
|
||||
@@ -341,6 +501,57 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
}
|
||||
return `${data.train}<br>${data.atmos}`;
|
||||
},
|
||||
columnGroupShow: "closed",
|
||||
},
|
||||
{
|
||||
headerName: "병종",
|
||||
field: "crewtype",
|
||||
cellRenderer: SimpleTooltipCell,
|
||||
cellRendererParams: {
|
||||
iActionMap: gameConstStore.value.iActionInfo.crewtype,
|
||||
},
|
||||
sortable: true,
|
||||
columnGroupShow: "open",
|
||||
filter: true,
|
||||
filterValueGetter: ({ data }) => {
|
||||
if (data.permission == 0 || data.permission == 1) {
|
||||
return "-";
|
||||
}
|
||||
const name = gameConstStore.value.iActionInfo.crewtype[data.crewtype].name;
|
||||
return convertSearch초성(name);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "병력",
|
||||
field: "crew",
|
||||
...sortableNumber,
|
||||
valueFormatter: numberFormatter("명"),
|
||||
width: 70,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "훈련",
|
||||
field: "train",
|
||||
...sortableNumber,
|
||||
valueFormatter: numberFormatter(),
|
||||
width: 70,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "병력",
|
||||
field: "atmos",
|
||||
...sortableNumber,
|
||||
valueFormatter: numberFormatter(),
|
||||
width: 70,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "수비",
|
||||
field: "defence_train",
|
||||
sortable: true,
|
||||
sortingOrder: ["desc", "asc", null],
|
||||
valueFormatter: ({ value }) => formatDefenceTrain(value as number),
|
||||
width: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -375,6 +586,10 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
sortable: true,
|
||||
filter: true,
|
||||
columnGroupShow: "open",
|
||||
filterValueGetter: ({ data }) => {
|
||||
const name = gameConstStore.value.iActionInfo.personality[data.personal].name;
|
||||
return convertSearch초성(name);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "내특",
|
||||
@@ -387,6 +602,10 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
sortable: true,
|
||||
filter: true,
|
||||
columnGroupShow: "open",
|
||||
filterValueGetter: ({ data }) => {
|
||||
const name = gameConstStore.value.iActionInfo.specialDomestic[data.specialDomestic].name;
|
||||
return convertSearch초성(name);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: "전특",
|
||||
@@ -399,38 +618,10 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
sortable: true,
|
||||
filter: true,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
],
|
||||
},
|
||||
years: {
|
||||
headerName: "연도",
|
||||
children: [
|
||||
{
|
||||
headerName: "요약",
|
||||
width: 60,
|
||||
cellRenderer: ({ data }: GenValueParams) => {
|
||||
return `${data.age}세<br>${data.belong}년`;
|
||||
filterValueGetter: ({ data }) => {
|
||||
const name = gameConstStore.value.iActionInfo.specialWar[data.specialWar].name;
|
||||
return convertSearch초성(name);
|
||||
},
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "closed",
|
||||
},
|
||||
{
|
||||
headerName: "연령",
|
||||
field: "age",
|
||||
...sortableNumber,
|
||||
valueFormatter: (v: GenValueParams) => `${v.value}세`,
|
||||
width: 60,
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "사관",
|
||||
field: "belong",
|
||||
...sortableNumber,
|
||||
valueFormatter: (v: GenValueParams) => `${v.value}년`,
|
||||
width: 60,
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -477,6 +668,52 @@ const columnRawDefs = ref<Partial<Record<headerType, GenColDef | GenColGroupDef>
|
||||
},
|
||||
],
|
||||
},
|
||||
turntime: {
|
||||
headerName: "턴",
|
||||
field: 'turntime',
|
||||
width: 60,
|
||||
valueFormatter: ({value, data}) => {
|
||||
if(data.permission == 0 || data.permission == 1){
|
||||
return '-';
|
||||
}
|
||||
const turntime = value as string;
|
||||
return turntime.substring(14, 19);
|
||||
},
|
||||
sortable: true,
|
||||
cellClass: centerCellClass,
|
||||
},
|
||||
years: {
|
||||
headerName: "연도",
|
||||
children: [
|
||||
{
|
||||
headerName: "요약",
|
||||
width: 60,
|
||||
cellRenderer: ({ data }: GenValueParams) => {
|
||||
return `${data.age}세<br>${data.belong}년`;
|
||||
},
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "closed",
|
||||
},
|
||||
{
|
||||
headerName: "연령",
|
||||
field: "age",
|
||||
...sortableNumber,
|
||||
valueFormatter: (v: GenValueParams) => `${v.value}세`,
|
||||
width: 60,
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
{
|
||||
headerName: "사관",
|
||||
field: "belong",
|
||||
...sortableNumber,
|
||||
valueFormatter: (v: GenValueParams) => `${v.value}년`,
|
||||
width: 60,
|
||||
cellClass: centerCellClass,
|
||||
columnGroupShow: "open",
|
||||
},
|
||||
],
|
||||
},
|
||||
killturnAndConnect: {
|
||||
headerName: "기타",
|
||||
children: [
|
||||
@@ -521,7 +758,6 @@ watch(columnRawDefs, (val) => {
|
||||
columnDefs.value = [...Object.values(val)];
|
||||
gridApi.value?.redrawRows();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -88,6 +88,7 @@ export type RawGeneralListP0 = ValidResponse & {
|
||||
permission: 0,
|
||||
column: (keyof GeneralListItemP0)[],
|
||||
list: ValuesOf<GeneralListItemP0>[][],
|
||||
troops?: null,
|
||||
env: ResponseEnv,
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ export type RawGeneralListP1 = ValidResponse & {
|
||||
permission: 1,
|
||||
column: (keyof GeneralListItemP1)[],
|
||||
list: ValuesOf<GeneralListItemP1>[][],
|
||||
troops?: null,
|
||||
env: ResponseEnv,
|
||||
}
|
||||
|
||||
@@ -102,6 +104,7 @@ export type RawGeneralListP2 = ValidResponse & {
|
||||
permission: 2 | 3 | 4,
|
||||
column: (keyof GeneralListItemP2)[],
|
||||
list: ValuesOf<GeneralListItemP2>[][],
|
||||
troops: [number, string][],
|
||||
env: ResponseEnv,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user