feat: 메인 도시 정보를 Ref와 일치
도시 소유 국가색과 태수·군사·종사를 general.me에 추가한다. 주민 영역, 반응형 grid와 국가색 대비를 Ref 카드와 같은 geometry로 맞추고 API 및 production Chromium 회귀 검증을 추가한다.
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
resolveNationBlockWar,
|
||||
resolveNationNotice,
|
||||
resolveNationRate,
|
||||
resolveOfficerCity,
|
||||
type TraitNameMap,
|
||||
} from '../nation/shared.js';
|
||||
import {
|
||||
@@ -348,38 +349,73 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
]);
|
||||
const nation = queriedNation ?? NEUTRAL_NATION_CONTEXT;
|
||||
|
||||
const [capitalCity, cityNation, troopLeaderCity, nationPopulation, nationCrew, topChiefRows] = await Promise.all([
|
||||
nation.capitalCityId
|
||||
? ctx.db.city.findUnique({ where: { id: nation.capitalCityId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
city && city.nationId > 0
|
||||
? ctx.db.nation.findUnique({ where: { id: city.nationId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
troopLeader && troopLeader.cityId > 0
|
||||
? ctx.db.city.findUnique({ where: { id: troopLeader.cityId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
nation.id > 0
|
||||
? ctx.db.city.aggregate({
|
||||
where: { nationId: nation.id },
|
||||
_count: true,
|
||||
_sum: { population: true, populationMax: true },
|
||||
})
|
||||
: Promise.resolve({ _count: 0, _sum: { population: 0, populationMax: 0 } }),
|
||||
nation.id > 0
|
||||
? ctx.db.general.aggregate({
|
||||
where: { nationId: nation.id, npcState: { not: 5 } },
|
||||
_count: true,
|
||||
_sum: { crew: true, leadership: true },
|
||||
})
|
||||
: Promise.resolve({ _count: 0, _sum: { crew: 0, leadership: 0 } }),
|
||||
nation.id > 0
|
||||
? ctx.db.general.findMany({
|
||||
where: { nationId: nation.id, officerLevel: { gte: 11 } },
|
||||
select: { id: true, name: true, npcState: true, officerLevel: true },
|
||||
orderBy: { id: 'asc' },
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
const [capitalCity, cityNation, troopLeaderCity, nationPopulation, nationCrew, chiefAndCityOfficerRows] =
|
||||
await Promise.all([
|
||||
nation.capitalCityId
|
||||
? ctx.db.city.findUnique({ where: { id: nation.capitalCityId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
city && city.nationId > 0
|
||||
? ctx.db.nation.findUnique({ where: { id: city.nationId }, select: { name: true, color: true } })
|
||||
: Promise.resolve(null),
|
||||
troopLeader && troopLeader.cityId > 0
|
||||
? ctx.db.city.findUnique({ where: { id: troopLeader.cityId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
nation.id > 0
|
||||
? ctx.db.city.aggregate({
|
||||
where: { nationId: nation.id },
|
||||
_count: true,
|
||||
_sum: { population: true, populationMax: true },
|
||||
})
|
||||
: Promise.resolve({ _count: 0, _sum: { population: 0, populationMax: 0 } }),
|
||||
nation.id > 0
|
||||
? ctx.db.general.aggregate({
|
||||
where: { nationId: nation.id, npcState: { not: 5 } },
|
||||
_count: true,
|
||||
_sum: { crew: true, leadership: true },
|
||||
})
|
||||
: Promise.resolve({ _count: 0, _sum: { crew: 0, leadership: 0 } }),
|
||||
nation.id > 0 || city
|
||||
? ctx.db.general.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ nationId: nation.id > 0 ? nation.id : -1, officerLevel: { gte: 11 } },
|
||||
{
|
||||
officerLevel: { in: [2, 3, 4] },
|
||||
meta: { path: ['officerCity'], equals: city?.id ?? -1 },
|
||||
},
|
||||
{
|
||||
officerLevel: { in: [2, 3, 4] },
|
||||
meta: { path: ['officer_city'], equals: city?.id ?? -1 },
|
||||
},
|
||||
],
|
||||
},
|
||||
select: { id: true, name: true, npcState: true, nationId: true, officerLevel: true, meta: true },
|
||||
orderBy: { id: 'asc' },
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
const topChiefRows = chiefAndCityOfficerRows.filter(
|
||||
(chief) => nation.id > 0 && chief.nationId === nation.id && chief.officerLevel >= 11
|
||||
);
|
||||
const cityOfficers: Record<2 | 3 | 4, { id: number; name: string; npcState: number } | null> = {
|
||||
2: null,
|
||||
3: null,
|
||||
4: null,
|
||||
};
|
||||
if (city) {
|
||||
for (const officer of chiefAndCityOfficerRows) {
|
||||
if (
|
||||
(officer.officerLevel === 2 || officer.officerLevel === 3 || officer.officerLevel === 4) &&
|
||||
resolveOfficerCity(asRecord(officer.meta)) === city.id
|
||||
) {
|
||||
cityOfficers[officer.officerLevel] = {
|
||||
id: officer.id,
|
||||
name: officer.name,
|
||||
npcState: officer.npcState,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
const [personalityNames, domesticNames, warNames, nationTypeNames, crewTypeNames, itemNames] = await Promise.all([
|
||||
loadTraitNames([general.personalCode], 'personality'),
|
||||
loadTraitNames([general.specialCode], 'domestic'),
|
||||
@@ -528,6 +564,8 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
levelName: resolveCityLevelName(city.level),
|
||||
regionName: resolveRegionName(city.region),
|
||||
nationName: city.nationId > 0 ? (cityNation?.name ?? '-') : '공백지',
|
||||
nationColor: city.nationId > 0 ? (cityNation?.color ?? '#000000') : '#000000',
|
||||
officers: cityOfficers,
|
||||
}
|
||||
: null,
|
||||
nation: {
|
||||
|
||||
@@ -290,6 +290,64 @@ describe('in-game my information ownership', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the current city nation color and its Ref city officers', async () => {
|
||||
const me = buildGeneral();
|
||||
const fixture = createContext({
|
||||
me,
|
||||
targets: [
|
||||
me,
|
||||
buildGeneral({ id: 8, name: '태수장', officerLevel: 4, npcState: 0, meta: { officerCity: 1 } }),
|
||||
buildGeneral({ id: 9, name: '군사장', officerLevel: 3, npcState: 2, meta: { officer_city: 1 } }),
|
||||
buildGeneral({ id: 10, name: '타도시종사', officerLevel: 2, npcState: 6, meta: { officerCity: 2 } }),
|
||||
],
|
||||
city: {
|
||||
id: 1,
|
||||
name: '업',
|
||||
level: 8,
|
||||
nationId: 1,
|
||||
population: 1_000,
|
||||
populationMax: 2_000,
|
||||
agriculture: 100,
|
||||
agricultureMax: 200,
|
||||
commerce: 100,
|
||||
commerceMax: 200,
|
||||
security: 100,
|
||||
securityMax: 200,
|
||||
trust: 70,
|
||||
trade: 100,
|
||||
defence: 100,
|
||||
defenceMax: 200,
|
||||
wall: 100,
|
||||
wallMax: 200,
|
||||
region: 2,
|
||||
supplyState: 1,
|
||||
frontState: 0,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(appRouter.createCaller(fixture.context).general.me()).resolves.toMatchObject({
|
||||
city: {
|
||||
nationName: '위',
|
||||
nationColor: '#777777',
|
||||
officers: {
|
||||
4: { id: 8, name: '태수장', npcState: 0 },
|
||||
3: { id: 9, name: '군사장', npcState: 2 },
|
||||
2: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(fixture.db.general.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: {
|
||||
OR: expect.arrayContaining([
|
||||
expect.objectContaining({ meta: { path: ['officerCity'], equals: 1 } }),
|
||||
expect.objectContaining({ meta: { path: ['officer_city'], equals: 1 } }),
|
||||
]),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns Ref display names instead of numeric levels and internal codes for the main GUI', async () => {
|
||||
const fixture = createContext({
|
||||
me: buildGeneral({
|
||||
|
||||
Reference in New Issue
Block a user