feat: add state property to city and update related processing logic in battle simulation and world map
This commit is contained in:
@@ -49,6 +49,7 @@ const mapCityPayload = (payload: BattleSimJobPayload['attackerCity']): City => (
|
|||||||
name: payload.name,
|
name: payload.name,
|
||||||
nationId: payload.nation,
|
nationId: payload.nation,
|
||||||
level: payload.level,
|
level: payload.level,
|
||||||
|
state: payload.state,
|
||||||
population: payload.pop,
|
population: payload.pop,
|
||||||
populationMax: payload.pop_max,
|
populationMax: payload.pop_max,
|
||||||
agriculture: payload.agri,
|
agriculture: payload.agri,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { GameSessionTokenPayload } from '@sammo-ts/common';
|
import type { GameSessionTokenPayload } from '@sammo-ts/common';
|
||||||
import type { DatabaseClient as InfraDatabaseClient } from '@sammo-ts/infra';
|
import type { DatabaseClient as InfraDatabaseClient, RedisConnector } from '@sammo-ts/infra';
|
||||||
|
|
||||||
import type { TurnDaemonTransport } from './daemon/transport.js';
|
import type { TurnDaemonTransport } from './daemon/transport.js';
|
||||||
import type { BattleSimTransport } from './battleSim/transport.js';
|
import type { BattleSimTransport } from './battleSim/transport.js';
|
||||||
@@ -117,6 +117,7 @@ export type DatabaseClient = InfraDatabaseClient<
|
|||||||
|
|
||||||
export interface GameApiContext {
|
export interface GameApiContext {
|
||||||
db: DatabaseClient;
|
db: DatabaseClient;
|
||||||
|
redis: RedisConnector['client'];
|
||||||
turnDaemon: TurnDaemonTransport;
|
turnDaemon: TurnDaemonTransport;
|
||||||
battleSim: BattleSimTransport;
|
battleSim: BattleSimTransport;
|
||||||
profile: GameProfile;
|
profile: GameProfile;
|
||||||
@@ -125,6 +126,7 @@ export interface GameApiContext {
|
|||||||
|
|
||||||
export const createGameApiContext = (options: {
|
export const createGameApiContext = (options: {
|
||||||
db: DatabaseClient;
|
db: DatabaseClient;
|
||||||
|
redis: RedisConnector['client'];
|
||||||
turnDaemon: TurnDaemonTransport;
|
turnDaemon: TurnDaemonTransport;
|
||||||
battleSim: BattleSimTransport;
|
battleSim: BattleSimTransport;
|
||||||
profile: GameProfile;
|
profile: GameProfile;
|
||||||
@@ -132,6 +134,7 @@ export const createGameApiContext = (options: {
|
|||||||
}): GameApiContext => {
|
}): GameApiContext => {
|
||||||
return {
|
return {
|
||||||
db: options.db,
|
db: options.db,
|
||||||
|
redis: options.redis,
|
||||||
turnDaemon: options.turnDaemon,
|
turnDaemon: options.turnDaemon,
|
||||||
battleSim: options.battleSim,
|
battleSim: options.battleSim,
|
||||||
profile: options.profile,
|
profile: options.profile,
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import type { GameApiContext, WorldStateRow } from '../context.js';
|
||||||
|
|
||||||
|
export type MapCityCompact = [number, number, number, number, number, number];
|
||||||
|
export type MapNationCompact = [number, string, string, number];
|
||||||
|
|
||||||
|
export type BaseMapResult = {
|
||||||
|
result: true;
|
||||||
|
version: 0;
|
||||||
|
startYear: number;
|
||||||
|
year: number;
|
||||||
|
month: number;
|
||||||
|
cityList: MapCityCompact[];
|
||||||
|
nationList: MapNationCompact[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WorldMapResult = BaseMapResult & {
|
||||||
|
spyList: Record<number, number>;
|
||||||
|
shownByGeneralList: number[];
|
||||||
|
myCity: number | null;
|
||||||
|
myNation: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapCityRow = {
|
||||||
|
id: number;
|
||||||
|
level: number;
|
||||||
|
nationId: number;
|
||||||
|
region: number;
|
||||||
|
supplyState: number;
|
||||||
|
meta: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MapNationRow = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
capitalCityId: number | null;
|
||||||
|
meta: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GeneralCityRow = {
|
||||||
|
cityId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAP_VERSION = 0 as const;
|
||||||
|
const BASE_MAP_TTL_SECONDS = 30;
|
||||||
|
|
||||||
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||||
|
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
|
||||||
|
const asRecord = (value: unknown): Record<string, unknown> =>
|
||||||
|
isRecord(value) ? value : {};
|
||||||
|
|
||||||
|
const resolveStartYear = (worldState: WorldStateRow): number => {
|
||||||
|
const meta = asRecord(worldState.meta);
|
||||||
|
const scenarioMeta = asRecord(meta.scenarioMeta);
|
||||||
|
const startYear = scenarioMeta.startYear;
|
||||||
|
if (typeof startYear === 'number' && Number.isFinite(startYear)) {
|
||||||
|
return startYear;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readState = (meta: Record<string, unknown>): number => {
|
||||||
|
const raw = meta.state;
|
||||||
|
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
||||||
|
return Math.floor(raw);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeNumberRecord = (value: unknown): Record<number, number> => {
|
||||||
|
if (!isRecord(value)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const output: Record<number, number> = {};
|
||||||
|
for (const [key, rawValue] of Object.entries(value)) {
|
||||||
|
const keyNumber = Number(key);
|
||||||
|
if (!Number.isFinite(keyNumber)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof rawValue === 'number' && Number.isFinite(rawValue)) {
|
||||||
|
output[keyNumber] = Math.floor(rawValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveSpyList = (meta: Record<string, unknown>): Record<number, number> => {
|
||||||
|
if (meta.spyList !== undefined) {
|
||||||
|
return normalizeNumberRecord(meta.spyList);
|
||||||
|
}
|
||||||
|
if (meta.spy !== undefined) {
|
||||||
|
return normalizeNumberRecord(meta.spy);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildBaseMapCacheKey = (ctx: GameApiContext): string =>
|
||||||
|
`sammo:map:base:${ctx.profile.id}:${ctx.profile.scenario}`;
|
||||||
|
|
||||||
|
const loadBaseMap = async (
|
||||||
|
ctx: GameApiContext,
|
||||||
|
useCache: boolean
|
||||||
|
): Promise<BaseMapResult | null> => {
|
||||||
|
const cacheKey = buildBaseMapCacheKey(ctx);
|
||||||
|
if (useCache) {
|
||||||
|
const cached = await ctx.redis.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(cached) as BaseMapResult;
|
||||||
|
} catch {
|
||||||
|
// Ignore cache parse errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const worldState = await ctx.db.worldState.findFirst();
|
||||||
|
if (!worldState) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [cityRows, nationRows] = await Promise.all([
|
||||||
|
ctx.db.$queryRaw<MapCityRow[]>`
|
||||||
|
SELECT id,
|
||||||
|
level,
|
||||||
|
nation_id as "nationId",
|
||||||
|
region,
|
||||||
|
supply_state as "supplyState",
|
||||||
|
meta
|
||||||
|
FROM city
|
||||||
|
`,
|
||||||
|
ctx.db.$queryRaw<MapNationRow[]>`
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
capital_city_id as "capitalCityId",
|
||||||
|
meta
|
||||||
|
FROM nation
|
||||||
|
`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cityList: MapCityCompact[] = cityRows.map((row) => {
|
||||||
|
const meta = asRecord(row.meta);
|
||||||
|
const state = readState(meta);
|
||||||
|
const supplyFlag = row.supplyState > 0 ? 1 : 0;
|
||||||
|
return [
|
||||||
|
row.id,
|
||||||
|
row.level,
|
||||||
|
state,
|
||||||
|
row.nationId,
|
||||||
|
row.region,
|
||||||
|
supplyFlag,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const nationList: MapNationCompact[] = nationRows.map((row) => [
|
||||||
|
row.id,
|
||||||
|
row.name,
|
||||||
|
row.color,
|
||||||
|
row.capitalCityId ?? 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const baseMap: BaseMapResult = {
|
||||||
|
result: true,
|
||||||
|
version: MAP_VERSION,
|
||||||
|
startYear: resolveStartYear(worldState),
|
||||||
|
year: worldState.currentYear,
|
||||||
|
month: worldState.currentMonth,
|
||||||
|
cityList,
|
||||||
|
nationList,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
await ctx.redis.set(cacheKey, JSON.stringify(baseMap), {
|
||||||
|
EX: BASE_MAP_TTL_SECONDS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadWorldMap = async (
|
||||||
|
ctx: GameApiContext,
|
||||||
|
options: {
|
||||||
|
generalId?: number;
|
||||||
|
neutralView?: boolean;
|
||||||
|
showMe?: boolean;
|
||||||
|
useCache?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<WorldMapResult | null> => {
|
||||||
|
const baseMap = await loadBaseMap(ctx, options.useCache ?? true);
|
||||||
|
if (!baseMap) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let myCity: number | null = null;
|
||||||
|
let myNation: number | null = null;
|
||||||
|
let spyList: Record<number, number> = {};
|
||||||
|
let shownByGeneralList: number[] = [];
|
||||||
|
|
||||||
|
if (options.generalId) {
|
||||||
|
const general = await ctx.db.general.findUnique({
|
||||||
|
where: { id: options.generalId },
|
||||||
|
});
|
||||||
|
if (general) {
|
||||||
|
if (options.showMe !== false && general.cityId > 0) {
|
||||||
|
myCity = general.cityId;
|
||||||
|
}
|
||||||
|
if (options.neutralView !== true && general.nationId > 0) {
|
||||||
|
myNation = general.nationId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myNation !== null) {
|
||||||
|
const nation = await ctx.db.nation.findUnique({
|
||||||
|
where: { id: myNation },
|
||||||
|
});
|
||||||
|
if (nation) {
|
||||||
|
spyList = resolveSpyList(asRecord(nation.meta));
|
||||||
|
}
|
||||||
|
|
||||||
|
const generalCities = await ctx.db.$queryRaw<GeneralCityRow[]>`
|
||||||
|
SELECT DISTINCT city_id as "cityId"
|
||||||
|
FROM general
|
||||||
|
WHERE nation_id = ${myNation}
|
||||||
|
`;
|
||||||
|
shownByGeneralList = generalCities
|
||||||
|
.map((row) => row.cityId)
|
||||||
|
.filter((id) => Number.isFinite(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseMap,
|
||||||
|
spyList,
|
||||||
|
shownByGeneralList,
|
||||||
|
myCity,
|
||||||
|
myNation,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
} from './messages/store.js';
|
} from './messages/store.js';
|
||||||
import { buildBattleSimJobPayload } from './battleSim/environment.js';
|
import { buildBattleSimJobPayload } from './battleSim/environment.js';
|
||||||
import { zBattleSimJobId, zBattleSimRequest } from './battleSim/schema.js';
|
import { zBattleSimJobId, zBattleSimRequest } from './battleSim/schema.js';
|
||||||
|
import { loadWorldMap } from './maps/worldMap.js';
|
||||||
|
|
||||||
const zRunReason = z.enum(['schedule', 'manual', 'poke']);
|
const zRunReason = z.enum(['schedule', 'manual', 'poke']);
|
||||||
const zMessageType = z.enum(['private', 'public', 'national', 'diplomacy']);
|
const zMessageType = z.enum(['private', 'public', 'national', 'diplomacy']);
|
||||||
@@ -101,6 +102,25 @@ export const appRouter = router({
|
|||||||
const state = await ctx.db.worldState.findFirst();
|
const state = await ctx.db.worldState.findFirst();
|
||||||
return state ? toWorldStateSnapshot(state) : null;
|
return state ? toWorldStateSnapshot(state) : null;
|
||||||
}),
|
}),
|
||||||
|
getMap: procedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
generalId: z.number().int().positive().optional(),
|
||||||
|
neutralView: z.boolean().optional(),
|
||||||
|
showMe: z.boolean().optional(),
|
||||||
|
useCache: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const map = await loadWorldMap(ctx, input);
|
||||||
|
if (!map) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'PRECONDITION_FAILED',
|
||||||
|
message: 'World state is not initialized.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
turns: router({
|
turns: router({
|
||||||
getCommandTable: authedProcedure
|
getCommandTable: authedProcedure
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const createGameApiServer = async () => {
|
|||||||
const auth = token ? tokenVerifier.verify(token) : null;
|
const auth = token ? tokenVerifier.verify(token) : null;
|
||||||
return createGameApiContext({
|
return createGameApiContext({
|
||||||
db: postgres.prisma as unknown as DatabaseClient,
|
db: postgres.prisma as unknown as DatabaseClient,
|
||||||
|
redis: redis.client,
|
||||||
turnDaemon,
|
turnDaemon,
|
||||||
battleSim,
|
battleSim,
|
||||||
profile: {
|
profile: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const t = initTRPC.context<GameApiContext>().create();
|
|||||||
|
|
||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
export const procedure = t.procedure;
|
export const procedure = t.procedure;
|
||||||
export const authedProcedure = t.procedure.use(({ ctx, next }) => {
|
export const authedProcedure: typeof t.procedure = t.procedure.use(({ ctx, next }) => {
|
||||||
if (!ctx.auth) {
|
if (!ctx.auth) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'UNAUTHORIZED',
|
code: 'UNAUTHORIZED',
|
||||||
|
|||||||
@@ -325,32 +325,40 @@ const mapGeneralRow = (row: GeneralRow): General => ({
|
|||||||
meta: asTriggerRecord(row.meta),
|
meta: asTriggerRecord(row.meta),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapCityRow = (row: CityRow): City => ({
|
const mapCityRow = (row: CityRow): City => {
|
||||||
id: row.id,
|
const meta = asTriggerRecord(row.meta);
|
||||||
name: row.name,
|
const state =
|
||||||
nationId: row.nationId,
|
typeof meta.state === 'number' && Number.isFinite(meta.state)
|
||||||
level: row.level,
|
? Math.floor(meta.state)
|
||||||
population: row.population,
|
: 0;
|
||||||
populationMax: row.populationMax,
|
return {
|
||||||
agriculture: row.agriculture,
|
id: row.id,
|
||||||
agricultureMax: row.agricultureMax,
|
name: row.name,
|
||||||
commerce: row.commerce,
|
nationId: row.nationId,
|
||||||
commerceMax: row.commerceMax,
|
level: row.level,
|
||||||
security: row.security,
|
state,
|
||||||
securityMax: row.securityMax,
|
population: row.population,
|
||||||
supplyState: row.supplyState,
|
populationMax: row.populationMax,
|
||||||
frontState: row.frontState,
|
agriculture: row.agriculture,
|
||||||
defence: row.defence,
|
agricultureMax: row.agricultureMax,
|
||||||
defenceMax: row.defenceMax,
|
commerce: row.commerce,
|
||||||
wall: row.wall,
|
commerceMax: row.commerceMax,
|
||||||
wallMax: row.wallMax,
|
security: row.security,
|
||||||
meta: {
|
securityMax: row.securityMax,
|
||||||
...asTriggerRecord(row.meta),
|
supplyState: row.supplyState,
|
||||||
trust: row.trust,
|
frontState: row.frontState,
|
||||||
trade: row.trade,
|
defence: row.defence,
|
||||||
region: row.region,
|
defenceMax: row.defenceMax,
|
||||||
},
|
wall: row.wall,
|
||||||
});
|
wallMax: row.wallMax,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
trust: row.trust,
|
||||||
|
trade: row.trade,
|
||||||
|
region: row.region,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapNationRow = (row: NationRow): Nation => ({
|
const mapNationRow = (row: NationRow): Nation => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ export const seedScenarioToDatabase = async (
|
|||||||
meta: asJson({
|
meta: asJson({
|
||||||
position: city.position,
|
position: city.position,
|
||||||
connections: city.connections,
|
connections: city.connections,
|
||||||
|
state: city.state,
|
||||||
...city.meta,
|
...city.meta,
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -110,7 +110,10 @@ const buildGeneralCreate = (
|
|||||||
const buildCityUpdate = (
|
const buildCityUpdate = (
|
||||||
city: ReturnType<InMemoryTurnWorld['consumeDirtyState']>['cities'][number]
|
city: ReturnType<InMemoryTurnWorld['consumeDirtyState']>['cities'][number]
|
||||||
): TurnEngineCityUpdateInput => {
|
): TurnEngineCityUpdateInput => {
|
||||||
const meta = city.meta as Record<string, unknown>;
|
const meta = {
|
||||||
|
...(city.meta as Record<string, unknown>),
|
||||||
|
state: city.state,
|
||||||
|
};
|
||||||
const trust = readMetaNumber(meta, 'trust');
|
const trust = readMetaNumber(meta, 'trust');
|
||||||
const trade = readMetaNumber(meta, 'trade');
|
const trade = readMetaNumber(meta, 'trade');
|
||||||
const region = readMetaNumber(meta, 'region');
|
const region = readMetaNumber(meta, 'region');
|
||||||
@@ -133,7 +136,7 @@ const buildCityUpdate = (
|
|||||||
defenceMax: city.defenceMax,
|
defenceMax: city.defenceMax,
|
||||||
wall: city.wall,
|
wall: city.wall,
|
||||||
wallMax: city.wallMax,
|
wallMax: city.wallMax,
|
||||||
meta: asJson(city.meta),
|
meta: asJson(meta),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (trust !== null) {
|
if (trust !== null) {
|
||||||
|
|||||||
@@ -179,32 +179,40 @@ const mapGeneralRow = (row: TurnEngineGeneralRow): TurnGeneral => ({
|
|||||||
recentWarTime: row.recentWarTime ?? null,
|
recentWarTime: row.recentWarTime ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapCityRow = (row: TurnEngineCityRow): City => ({
|
const mapCityRow = (row: TurnEngineCityRow): City => {
|
||||||
id: row.id,
|
const meta = asTriggerRecord(row.meta);
|
||||||
name: row.name,
|
const state =
|
||||||
nationId: row.nationId,
|
typeof meta.state === 'number' && Number.isFinite(meta.state)
|
||||||
level: row.level,
|
? Math.floor(meta.state)
|
||||||
population: row.population,
|
: 0;
|
||||||
populationMax: row.populationMax,
|
return {
|
||||||
agriculture: row.agriculture,
|
id: row.id,
|
||||||
agricultureMax: row.agricultureMax,
|
name: row.name,
|
||||||
commerce: row.commerce,
|
nationId: row.nationId,
|
||||||
commerceMax: row.commerceMax,
|
level: row.level,
|
||||||
security: row.security,
|
state,
|
||||||
securityMax: row.securityMax,
|
population: row.population,
|
||||||
supplyState: row.supplyState,
|
populationMax: row.populationMax,
|
||||||
frontState: row.frontState,
|
agriculture: row.agriculture,
|
||||||
defence: row.defence,
|
agricultureMax: row.agricultureMax,
|
||||||
defenceMax: row.defenceMax,
|
commerce: row.commerce,
|
||||||
wall: row.wall,
|
commerceMax: row.commerceMax,
|
||||||
wallMax: row.wallMax,
|
security: row.security,
|
||||||
meta: {
|
securityMax: row.securityMax,
|
||||||
...asTriggerRecord(row.meta),
|
supplyState: row.supplyState,
|
||||||
trust: row.trust,
|
frontState: row.frontState,
|
||||||
trade: row.trade,
|
defence: row.defence,
|
||||||
region: row.region,
|
defenceMax: row.defenceMax,
|
||||||
},
|
wall: row.wall,
|
||||||
});
|
wallMax: row.wallMax,
|
||||||
|
meta: {
|
||||||
|
...meta,
|
||||||
|
trust: row.trust,
|
||||||
|
trade: row.trade,
|
||||||
|
region: row.region,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const mapNationRow = (row: TurnEngineNationRow): Nation => ({
|
const mapNationRow = (row: TurnEngineNationRow): Nation => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface City {
|
|||||||
name: string;
|
name: string;
|
||||||
nationId: NationId;
|
nationId: NationId;
|
||||||
level: number;
|
level: number;
|
||||||
|
state: number;
|
||||||
population: number;
|
population: number;
|
||||||
populationMax: number;
|
populationMax: number;
|
||||||
agriculture: number;
|
agriculture: number;
|
||||||
|
|||||||
@@ -559,11 +559,17 @@ export const buildScenarioBootstrap = (
|
|||||||
|
|
||||||
for (const city of map.cities) {
|
for (const city of map.cities) {
|
||||||
const nationId = cityOwnership.get(city.id) ?? 0;
|
const nationId = cityOwnership.get(city.id) ?? 0;
|
||||||
|
const rawCityMeta = city.meta ?? {};
|
||||||
|
const state =
|
||||||
|
typeof rawCityMeta.state === 'number' && Number.isFinite(rawCityMeta.state)
|
||||||
|
? Math.floor(rawCityMeta.state)
|
||||||
|
: 0;
|
||||||
const seed: CitySeed = {
|
const seed: CitySeed = {
|
||||||
id: city.id,
|
id: city.id,
|
||||||
name: city.name,
|
name: city.name,
|
||||||
nationId,
|
nationId,
|
||||||
level: city.level,
|
level: city.level,
|
||||||
|
state,
|
||||||
population: city.initial.population,
|
population: city.initial.population,
|
||||||
populationMax: city.max.population,
|
populationMax: city.max.population,
|
||||||
agriculture: city.initial.agriculture,
|
agriculture: city.initial.agriculture,
|
||||||
@@ -583,11 +589,14 @@ export const buildScenarioBootstrap = (
|
|||||||
region: city.region,
|
region: city.region,
|
||||||
position: city.position,
|
position: city.position,
|
||||||
connections: city.connections,
|
connections: city.connections,
|
||||||
meta: city.meta ?? {},
|
meta: {
|
||||||
|
...rawCityMeta,
|
||||||
|
state,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
seedCities.push(seed);
|
seedCities.push(seed);
|
||||||
|
|
||||||
const cityMeta: Record<string, TriggerValue> = {
|
const cityTriggerMeta: Record<string, TriggerValue> = {
|
||||||
region: city.region,
|
region: city.region,
|
||||||
trust: seed.trust,
|
trust: seed.trust,
|
||||||
trade: seed.trade,
|
trade: seed.trade,
|
||||||
@@ -600,6 +609,7 @@ export const buildScenarioBootstrap = (
|
|||||||
name: seed.name,
|
name: seed.name,
|
||||||
nationId: seed.nationId,
|
nationId: seed.nationId,
|
||||||
level: seed.level,
|
level: seed.level,
|
||||||
|
state,
|
||||||
population: seed.population,
|
population: seed.population,
|
||||||
populationMax: seed.populationMax,
|
populationMax: seed.populationMax,
|
||||||
agriculture: seed.agriculture,
|
agriculture: seed.agriculture,
|
||||||
@@ -614,7 +624,7 @@ export const buildScenarioBootstrap = (
|
|||||||
defenceMax: seed.defenceMax,
|
defenceMax: seed.defenceMax,
|
||||||
wall: seed.wall,
|
wall: seed.wall,
|
||||||
wallMax: seed.wallMax,
|
wallMax: seed.wallMax,
|
||||||
meta: cityMeta,
|
meta: cityTriggerMeta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export interface CitySeed {
|
|||||||
name: string;
|
name: string;
|
||||||
nationId: number;
|
nationId: number;
|
||||||
level: number;
|
level: number;
|
||||||
|
state: number;
|
||||||
population: number;
|
population: number;
|
||||||
populationMax: number;
|
populationMax: number;
|
||||||
agriculture: number;
|
agriculture: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user