인게임 예약 명령과 장수 상태 표시를 바로잡음

This commit is contained in:
2026-09-04 19:16:56 +00:00
parent 5d220de739
commit 0157ff6a6b
31 changed files with 511 additions and 81 deletions
+52 -9
View File
@@ -1149,6 +1149,8 @@ export class GeneralAI {
const effectiveOfficerLevel = new Map(generals.map((candidate) => [candidate.id, candidate.officerLevel]));
let userChiefCount = 0;
let ambassadorCount = 0;
const assignedAmbassadorIds = new Set<number>();
const worldKillturn = readMetaNumber(asRecord(this.world.meta), 'killturn', 0);
const minUserKillturn = worldKillturn - Math.trunc(240 / this.turnTermMinutes);
const minNpcKillturn = 36;
@@ -1162,13 +1164,25 @@ export class GeneralAI {
const killturn = readRequiredMetaNumber(asRecord(chief.meta), 'killturn', `generalId=${chief.id}`);
if (chief.npcState < 2 && killturn >= minUserKillturn && penalty.noAmbassador !== true) {
userChiefCount += 1;
chief.meta = { ...chief.meta, permission: 'ambassador' };
this.promotionPatches.push({
generalId: chief.id,
officerLevel: chief.officerLevel,
officerCity: readMetaNumber(asRecord(chief.meta), 'officer_city', 0),
permission: 'ambassador',
});
if (ambassadorCount < 2) {
ambassadorCount += 1;
assignedAmbassadorIds.add(chief.id);
chief.meta = { ...chief.meta, permission: 'ambassador' };
this.promotionPatches.push({
generalId: chief.id,
officerLevel: chief.officerLevel,
officerCity: readMetaNumber(asRecord(chief.meta), 'officer_city', 0),
permission: 'ambassador',
});
} else if (asRecord(chief.meta).permission === 'ambassador') {
chief.meta = { ...chief.meta, permission: 'normal' };
this.promotionPatches.push({
generalId: chief.id,
officerLevel: chief.officerLevel,
officerCity: readMetaNumber(asRecord(chief.meta), 'officer_city', 0),
permission: 'normal',
});
}
}
}
@@ -1222,7 +1236,7 @@ export class GeneralAI {
this.promotionPatches.push({ generalId: oldChief.id, officerLevel: 1, officerCity: 0 });
effectiveOfficerLevel.set(oldChief.id, 1);
}
const permission = penalty.noAmbassador === true ? undefined : 'ambassador';
const permission = penalty.noAmbassador !== true && ambassadorCount < 2 ? 'ambassador' : undefined;
candidate.officerLevel = 11;
candidate.meta = {
...candidate.meta,
@@ -1238,6 +1252,10 @@ export class GeneralAI {
effectiveOfficerLevel.set(candidate.id, 11);
chiefSet |= 1 << 11;
userChiefCount += 1;
if (permission) {
ambassadorCount += 1;
assignedAmbassadorIds.add(candidate.id);
}
break;
}
}
@@ -1307,10 +1325,16 @@ export class GeneralAI {
this.promotionPatches.push({ generalId: oldChief.id, officerLevel: 1, officerCity: 0 });
}
const permission =
nextChief.npcState < 2 && asRecord(nextChief.penalty).noAmbassador !== true ? 'ambassador' : undefined;
nextChief.npcState < 2 && asRecord(nextChief.penalty).noAmbassador !== true && ambassadorCount < 2
? 'ambassador'
: undefined;
if (nextChief.npcState < 2) {
userChiefCount += 1;
}
if (permission) {
ambassadorCount += 1;
assignedAmbassadorIds.add(nextChief.id);
}
this.promotionPatches.push({
generalId: nextChief.id,
officerLevel: chiefLevel,
@@ -1331,6 +1355,25 @@ export class GeneralAI {
chiefSet |= 1 << chiefLevel;
}
for (const candidate of generals) {
if (
asRecord(candidate.meta).permission !== 'ambassador' ||
assignedAmbassadorIds.has(candidate.id) ||
this.promotionPatches.some(
(patch) => patch.generalId === candidate.id && patch.permission === 'normal'
)
) {
continue;
}
candidate.meta = { ...candidate.meta, permission: 'normal' };
this.promotionPatches.push({
generalId: candidate.id,
officerLevel: effectiveOfficerLevel.get(candidate.id) ?? candidate.officerLevel,
officerCity: readMetaNumber(asRecord(candidate.meta), 'officer_city', 0),
permission: 'normal',
});
}
if (chiefSet !== initialChiefSet) {
this.promotionNationMeta = {
...this.nation.meta,
@@ -711,7 +711,7 @@ describe('legacy NPC user-chief promotion parity', () => {
expect(ai.consumePromotionPatches()).toEqual({ generals: [], nationMeta: null });
});
it('does not appoint a fourth user chief in the ordinary NPC-ruler fill pass', () => {
it('keeps NPC-assigned diplomatic authority at two even when three user chiefs exist', () => {
const ruler = makePromotionGeneral({
id: 1,
officerLevel: 12,
@@ -745,11 +745,11 @@ describe('legacy NPC user-chief promotion parity', () => {
const promotion = ai.consumePromotionPatches();
const result = promotion.generals;
expect(result.filter((entry) => entry.generalId === candidate.id)).toEqual([]);
expect(result).toHaveLength(3);
expect(result).toHaveLength(2);
expect(promotion.nationMeta).toBeNull();
expect(result).toEqual(
expect.arrayContaining(
existingChiefs.map((chief) => ({
existingChiefs.slice(1).map((chief) => ({
generalId: chief.id,
officerLevel: chief.officerLevel,
officerCity: 0,
@@ -759,6 +759,52 @@ describe('legacy NPC user-chief promotion parity', () => {
);
});
it('repairs a third diplomatic authority left by an earlier NPC promotion pass', () => {
const ruler = makePromotionGeneral({
id: 1,
officerLevel: 12,
npcState: 2,
meta: { killturn: 100, belong: 4 },
});
const existingChiefs = [11, 10, 9].map((officerLevel, index) =>
makePromotionGeneral({
id: index + 2,
npcState: 0,
officerLevel,
meta: { killturn: 100, belong: 4, officer_city: 0, permission: 'ambassador' },
})
);
const formerChief = makePromotionGeneral({
id: 5,
npcState: 0,
officerLevel: 1,
meta: { killturn: 100, belong: 4, officer_city: 0, permission: 'ambassador' },
});
const ai = makePromotionAi({
ruler,
generals: [ruler, ...existingChiefs, formerChief],
nation: { level: 6 },
userGenerals: [...existingChiefs, formerChief],
chiefGenerals: [ruler, ...existingChiefs],
});
chooseNpcPromotion(ai);
const promotion = ai.consumePromotionPatches();
expect(promotion.generals).toContainEqual({
generalId: existingChiefs[0]!.id,
officerLevel: 11,
officerCity: 0,
permission: 'normal',
});
expect(promotion.generals).toContainEqual({
generalId: formerChief.id,
officerLevel: 1,
officerCity: 0,
permission: 'normal',
});
});
it('lets an NPC non-ruler fill an open seat with a user immediately when no NPC pool exists', () => {
const actor = makePromotionGeneral({
id: 1,
@@ -1561,6 +1607,7 @@ describe('legacy NPC AI final-decision parity', () => {
});
expect(do집합(ai)?.action).toBe('che_집합');
expect(ai.general.meta.killturn).toBe(72);
expect(ai.general.meta.killturn).toBeGreaterThanOrEqual(70);
});
it('does not warp to the rear when recruitment is disabled', () => {