fix: Ref 장수 기본 카드 표시를 보완
This commit is contained in:
@@ -42,6 +42,12 @@ import {
|
||||
resolveMainNationTech,
|
||||
splitNationTraitInfo,
|
||||
} from '../../services/mainNationProjection.js';
|
||||
import {
|
||||
resolveGeneralTypeCall,
|
||||
resolveLeadershipBonus,
|
||||
resolveRefreshScoreText,
|
||||
resolveRemainingMinutes,
|
||||
} from '../../services/generalBasicCardProjection.js';
|
||||
|
||||
const zGeneralSettings = z.object({
|
||||
tnmt: z.number().int().optional(),
|
||||
@@ -268,63 +274,91 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [city, queriedNation, worldState] = await Promise.all([
|
||||
general.cityId > 0
|
||||
? ctx.db.city.findUnique({
|
||||
where: { id: general.cityId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
level: true,
|
||||
nationId: true,
|
||||
population: true,
|
||||
populationMax: true,
|
||||
agriculture: true,
|
||||
agricultureMax: true,
|
||||
commerce: true,
|
||||
commerceMax: true,
|
||||
security: true,
|
||||
securityMax: true,
|
||||
trust: true,
|
||||
trade: true,
|
||||
defence: true,
|
||||
defenceMax: true,
|
||||
wall: true,
|
||||
wallMax: true,
|
||||
region: true,
|
||||
supplyState: true,
|
||||
frontState: true,
|
||||
},
|
||||
})
|
||||
: null,
|
||||
general.nationId > 0
|
||||
? ctx.db.nation.findUnique({
|
||||
where: { id: general.nationId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
color: true,
|
||||
level: true,
|
||||
gold: true,
|
||||
rice: true,
|
||||
tech: true,
|
||||
typeCode: true,
|
||||
capitalCityId: true,
|
||||
meta: true,
|
||||
},
|
||||
})
|
||||
: Promise.resolve(NEUTRAL_NATION_CONTEXT),
|
||||
ctx.db.worldState.findFirst({ select: { currentYear: true, currentMonth: true, config: true, meta: true } }),
|
||||
]);
|
||||
const metaRecord = asRecord(general.meta);
|
||||
const officerCityId = readNumber(metaRecord.officerCity ?? metaRecord.officer_city ?? metaRecord.officerCityId, 0);
|
||||
const [city, queriedNation, worldState, officerCity, troop, troopLeader, troopLeaderFirstTurn, accessLog] =
|
||||
await Promise.all([
|
||||
general.cityId > 0
|
||||
? ctx.db.city.findUnique({
|
||||
where: { id: general.cityId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
level: true,
|
||||
nationId: true,
|
||||
population: true,
|
||||
populationMax: true,
|
||||
agriculture: true,
|
||||
agricultureMax: true,
|
||||
commerce: true,
|
||||
commerceMax: true,
|
||||
security: true,
|
||||
securityMax: true,
|
||||
trust: true,
|
||||
trade: true,
|
||||
defence: true,
|
||||
defenceMax: true,
|
||||
wall: true,
|
||||
wallMax: true,
|
||||
region: true,
|
||||
supplyState: true,
|
||||
frontState: true,
|
||||
},
|
||||
})
|
||||
: null,
|
||||
general.nationId > 0
|
||||
? ctx.db.nation.findUnique({
|
||||
where: { id: general.nationId },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
color: true,
|
||||
level: true,
|
||||
gold: true,
|
||||
rice: true,
|
||||
tech: true,
|
||||
typeCode: true,
|
||||
capitalCityId: true,
|
||||
meta: true,
|
||||
},
|
||||
})
|
||||
: Promise.resolve(NEUTRAL_NATION_CONTEXT),
|
||||
ctx.db.worldState.findFirst({
|
||||
select: { currentYear: true, currentMonth: true, tickSeconds: true, config: true, meta: true },
|
||||
}),
|
||||
officerCityId > 0
|
||||
? ctx.db.city.findUnique({ where: { id: officerCityId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
general.troopId > 0
|
||||
? ctx.db.troop.findUnique({ where: { troopLeaderId: general.troopId }, select: { name: true } })
|
||||
: Promise.resolve(null),
|
||||
general.troopId > 0
|
||||
? ctx.db.general.findUnique({ where: { id: general.troopId }, select: { cityId: true } })
|
||||
: Promise.resolve(null),
|
||||
general.troopId > 0
|
||||
? ctx.db.generalTurn.findFirst({
|
||||
where: { generalId: general.troopId },
|
||||
orderBy: { turnIdx: 'asc' },
|
||||
select: { actionCode: true },
|
||||
})
|
||||
: Promise.resolve(null),
|
||||
ctx.db.generalAccessLog.findUnique({
|
||||
where: { generalId: general.id },
|
||||
select: { refreshScore: true, refreshScoreTotal: true },
|
||||
}),
|
||||
]);
|
||||
const nation = queriedNation ?? NEUTRAL_NATION_CONTEXT;
|
||||
|
||||
const [capitalCity, cityNation, nationPopulation, nationCrew, topChiefRows] = await Promise.all([
|
||||
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 },
|
||||
@@ -356,9 +390,12 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
loadItemDisplayNames([general.horseCode, general.weaponCode, general.bookCode, general.itemCode]),
|
||||
]);
|
||||
|
||||
const metaRecord = asRecord(general.meta);
|
||||
const worldConfig = asRecord(worldState?.config);
|
||||
const constValues = asRecord(worldConfig.const ?? worldConfig.consts);
|
||||
const scenarioStat = asRecord(worldConfig.stat);
|
||||
const chiefStatMin = readNumber(scenarioStat.chiefMin, 70);
|
||||
const statGradeLevel = readNumber(constValues.statGradeLevel, 5);
|
||||
const retirementYear = readNumber(constValues.retirementYear, 70);
|
||||
const maxDedicationLevel = readNumber(constValues.maxDedLevel, 30);
|
||||
const settings = resolveUserSettings(metaRecord);
|
||||
const penalties = resolvePenalty(general.penalty);
|
||||
@@ -379,6 +416,27 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
const normalized = normalizeItemCode(code);
|
||||
return normalized ? (itemNames.get(normalized) ?? sanitizeInternalDisplayCode(normalized)) : null;
|
||||
};
|
||||
const worldMeta = asRecord(worldState?.meta);
|
||||
const rawLastExecuted = worldMeta.lastTurnTime ?? worldMeta.turntime;
|
||||
const parsedLastExecuted =
|
||||
rawLastExecuted instanceof Date
|
||||
? rawLastExecuted
|
||||
: typeof rawLastExecuted === 'string'
|
||||
? new Date(rawLastExecuted)
|
||||
: null;
|
||||
const stats = {
|
||||
leadership: general.leadership,
|
||||
strength: general.strength,
|
||||
intelligence: general.intel,
|
||||
};
|
||||
const refreshScore = accessLog?.refreshScore ?? 0;
|
||||
const refreshScoreTotal = accessLog?.refreshScoreTotal ?? 0;
|
||||
const troopStatus: 'inactive' | 'present' | 'away' =
|
||||
troopLeaderFirstTurn?.actionCode !== undefined && troopLeaderFirstTurn.actionCode !== 'che_집합'
|
||||
? 'inactive'
|
||||
: troopLeader?.cityId === general.cityId
|
||||
? 'present'
|
||||
: 'away';
|
||||
|
||||
return {
|
||||
general: {
|
||||
@@ -392,11 +450,11 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
imageServer: general.imageServer,
|
||||
officerLevel: general.officerLevel,
|
||||
officerLevelText: resolveOfficerLevelName(general.officerLevel, nation.level),
|
||||
stats: {
|
||||
leadership: general.leadership,
|
||||
strength: general.strength,
|
||||
intelligence: general.intel,
|
||||
},
|
||||
officerCityName:
|
||||
general.officerLevel >= 2 && general.officerLevel <= 4 ? (officerCity?.name ?? null) : null,
|
||||
generalType: resolveGeneralTypeCall(stats, chiefStatMin, statGradeLevel),
|
||||
leadershipBonus: resolveLeadershipBonus(general.officerLevel, nation.level),
|
||||
stats,
|
||||
gold: general.gold,
|
||||
rice: general.rice,
|
||||
crew: general.crew,
|
||||
@@ -406,7 +464,15 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
experience: general.experience,
|
||||
dedication: general.dedication,
|
||||
age: general.age,
|
||||
retirementYear,
|
||||
turnTime: general.turnTime.toISOString(),
|
||||
defenceTrain: settings.defence_train,
|
||||
killTurn: readNumber(metaRecord.killturn ?? metaRecord.killTurn, 0),
|
||||
remainingMinutes: resolveRemainingMinutes(
|
||||
general.turnTime,
|
||||
parsedLastExecuted,
|
||||
worldState?.tickSeconds ?? 0
|
||||
),
|
||||
crewTypeId: general.crewTypeId,
|
||||
crewTypeName: crewTypeNames.get(general.crewTypeId) ?? '-',
|
||||
traits: {
|
||||
@@ -438,6 +504,18 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
||||
book: itemName(general.bookCode),
|
||||
item: itemName(general.itemCode),
|
||||
},
|
||||
troop: troop
|
||||
? {
|
||||
name: troop.name,
|
||||
status: troopStatus,
|
||||
leaderCityName: troopLeaderCity?.name ?? null,
|
||||
}
|
||||
: null,
|
||||
refreshScore: {
|
||||
current: refreshScore,
|
||||
total: refreshScoreTotal,
|
||||
text: resolveRefreshScoreText(refreshScoreTotal),
|
||||
},
|
||||
},
|
||||
iconChoices: ctx.auth?.user.canUseGeneralPicture === false ? [] : (ctx.auth?.user.icons ?? []),
|
||||
canChangeIcon: general.npcState === 0 && ctx.auth?.user.canUseGeneralPicture !== false,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export interface GeneralBasicStats {
|
||||
leadership: number;
|
||||
strength: number;
|
||||
intelligence: number;
|
||||
}
|
||||
|
||||
export const resolveGeneralTypeCall = (stats: GeneralBasicStats, chiefStatMin: number, statGradeLevel = 5): string => {
|
||||
const { leadership, strength, intelligence } = stats;
|
||||
if (leadership < 40) {
|
||||
if (strength + intelligence < 40) return '아둔';
|
||||
if (intelligence >= chiefStatMin && strength < intelligence * 0.8) return '학자';
|
||||
if (strength >= chiefStatMin && intelligence < strength * 0.8) return '장사';
|
||||
return '명사';
|
||||
}
|
||||
|
||||
const maxStat = Math.max(leadership, strength, intelligence);
|
||||
const sumTwoStats = Math.min(leadership + strength, strength + intelligence, intelligence + leadership);
|
||||
if (maxStat >= chiefStatMin + statGradeLevel && sumTwoStats >= maxStat * 1.7) return '만능';
|
||||
if (strength >= chiefStatMin - statGradeLevel && intelligence < strength * 0.8) return '용장';
|
||||
if (intelligence >= chiefStatMin - statGradeLevel && strength < intelligence * 0.8) return '명장';
|
||||
if (leadership >= chiefStatMin - statGradeLevel && strength + intelligence < leadership) return '차장';
|
||||
return '평범';
|
||||
};
|
||||
|
||||
export const resolveLeadershipBonus = (officerLevel: number, nationLevel: number): number => {
|
||||
if (officerLevel === 12) return nationLevel * 2;
|
||||
if (officerLevel >= 5) return nationLevel;
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const resolveRefreshScoreText = (score: number): string => {
|
||||
if (score < 50) return '안함';
|
||||
if (score < 100) return '무관심';
|
||||
if (score < 200) return '보통';
|
||||
if (score < 400) return '가끔';
|
||||
if (score < 800) return '자주';
|
||||
if (score < 1_600) return '열심';
|
||||
if (score < 3_200) return '중독';
|
||||
if (score < 6_400) return '폐인';
|
||||
if (score < 12_800) return '경고';
|
||||
return '헐...';
|
||||
};
|
||||
|
||||
export const resolveRemainingMinutes = (
|
||||
turnTime: Date,
|
||||
lastExecuted: Date | null,
|
||||
turnTermSeconds: number
|
||||
): number | null => {
|
||||
if (!lastExecuted || !Number.isFinite(lastExecuted.getTime()) || turnTermSeconds <= 0) return null;
|
||||
let nextTurnMillis = turnTime.getTime();
|
||||
if (nextTurnMillis < lastExecuted.getTime()) {
|
||||
nextTurnMillis += turnTermSeconds * 1_000;
|
||||
}
|
||||
return Math.floor(Math.min(999, Math.max(0, (nextTurnMillis - lastExecuted.getTime()) / 60_000)));
|
||||
};
|
||||
Reference in New Issue
Block a user