merge: preserve seizure NPC public messages
This commit is contained in:
@@ -205,6 +205,7 @@ const buildCommandEnv = (worldState: WorldStateRow): CommandEnv => {
|
|||||||
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], 0),
|
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], 0),
|
||||||
generalMinimumGold: resolveNumber(constValues, ['generalMinimumGold'], 0),
|
generalMinimumGold: resolveNumber(constValues, ['generalMinimumGold'], 0),
|
||||||
generalMinimumRice: resolveNumber(constValues, ['generalMinimumRice'], 500),
|
generalMinimumRice: resolveNumber(constValues, ['generalMinimumRice'], 500),
|
||||||
|
npcSeizureMessageProb: resolveNumber(constValues, ['npcSeizureMessageProb'], 0.01),
|
||||||
maxResourceActionAmount: resolveNumber(constValues, ['maxResourceActionAmount'], 0),
|
maxResourceActionAmount: resolveNumber(constValues, ['maxResourceActionAmount'], 0),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const DEFAULT_BASE_GOLD = 0;
|
|||||||
const DEFAULT_BASE_RICE = 2000;
|
const DEFAULT_BASE_RICE = 2000;
|
||||||
const DEFAULT_GENERAL_MINIMUM_GOLD = 0;
|
const DEFAULT_GENERAL_MINIMUM_GOLD = 0;
|
||||||
const DEFAULT_GENERAL_MINIMUM_RICE = 500;
|
const DEFAULT_GENERAL_MINIMUM_RICE = 500;
|
||||||
|
const DEFAULT_NPC_SEIZURE_MESSAGE_PROB = 0.01;
|
||||||
const DEFAULT_MAX_RESOURCE_ACTION_AMOUNT = 10000;
|
const DEFAULT_MAX_RESOURCE_ACTION_AMOUNT = 10000;
|
||||||
|
|
||||||
const normalizeCode = (value: string | null | undefined): string | null => {
|
const normalizeCode = (value: string | null | undefined): string | null => {
|
||||||
@@ -136,6 +137,7 @@ export const buildCommandEnv = (config: ScenarioConfig, unitSet?: UnitSetDefinit
|
|||||||
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], DEFAULT_BASE_RICE),
|
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], DEFAULT_BASE_RICE),
|
||||||
generalMinimumGold: resolveNumber(constValues, ['generalMinimumGold'], DEFAULT_GENERAL_MINIMUM_GOLD),
|
generalMinimumGold: resolveNumber(constValues, ['generalMinimumGold'], DEFAULT_GENERAL_MINIMUM_GOLD),
|
||||||
generalMinimumRice: resolveNumber(constValues, ['generalMinimumRice'], DEFAULT_GENERAL_MINIMUM_RICE),
|
generalMinimumRice: resolveNumber(constValues, ['generalMinimumRice'], DEFAULT_GENERAL_MINIMUM_RICE),
|
||||||
|
npcSeizureMessageProb: resolveNumber(constValues, ['npcSeizureMessageProb'], DEFAULT_NPC_SEIZURE_MESSAGE_PROB),
|
||||||
maxResourceActionAmount: resolveNumber(
|
maxResourceActionAmount: resolveNumber(
|
||||||
constValues,
|
constValues,
|
||||||
['maxResourceActionAmount'],
|
['maxResourceActionAmount'],
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export interface TurnCommandEnv {
|
|||||||
baseRice: number;
|
baseRice: number;
|
||||||
generalMinimumGold?: number;
|
generalMinimumGold?: number;
|
||||||
generalMinimumRice?: number;
|
generalMinimumRice?: number;
|
||||||
|
npcSeizureMessageProb?: number;
|
||||||
maxResourceActionAmount: number;
|
maxResourceActionAmount: number;
|
||||||
itemCatalog?: Record<string, TurnCommandItemCatalogEntry>;
|
itemCatalog?: Record<string, TurnCommandItemCatalogEntry>;
|
||||||
generalActionModules?: Array<GeneralActionModule>;
|
generalActionModules?: Array<GeneralActionModule>;
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ import type {
|
|||||||
GeneralActionOutcome,
|
GeneralActionOutcome,
|
||||||
GeneralActionResolveContext,
|
GeneralActionResolveContext,
|
||||||
} from '@sammo-ts/logic/actions/engine.js';
|
} from '@sammo-ts/logic/actions/engine.js';
|
||||||
import { createLogEffect, createNationPatchEffect, createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js';
|
import {
|
||||||
|
createGeneralPatchEffect,
|
||||||
|
createLogEffect,
|
||||||
|
createMessageEffect,
|
||||||
|
createNationPatchEffect,
|
||||||
|
} from '@sammo-ts/logic/actions/engine.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
@@ -37,9 +42,40 @@ export interface SeizureResolveContext<
|
|||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
> extends GeneralActionResolveContext<TriggerState> {
|
> extends GeneralActionResolveContext<TriggerState> {
|
||||||
destGeneral: General<TriggerState>;
|
destGeneral: General<TriggerState>;
|
||||||
|
messageTime: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACTION_NAME = '몰수';
|
const ACTION_NAME = '몰수';
|
||||||
|
const NPC_SEIZURE_MESSAGE_PROB = 0.01;
|
||||||
|
const NPC_SEIZURE_MESSAGES = [
|
||||||
|
'몰수를 하다니... 이것이 윗사람이 할 짓이란 말입니까...',
|
||||||
|
'사유재산까지 몰수해가면서 이 나라가 잘 될거라 믿습니까? 정말 이해할 수가 없군요...',
|
||||||
|
'내 돈 내놔라! 내 돈! 몰수가 웬 말이냐!',
|
||||||
|
'몰수해간 내 자금... 언젠가 몰래 다시 빼내올 것이다...',
|
||||||
|
'몰수로 인한 사기 저하는 몰수로 얻은 물자보다 더 손해란걸 모른단 말인가!',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type InclusiveRandomGenerator = GeneralActionResolveContext['rng'] & {
|
||||||
|
nextIntInclusive?: (maxInclusive: number) => number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pickLegacyNpcMessage = (rng: GeneralActionResolveContext['rng']): string => {
|
||||||
|
const inclusive = rng as InclusiveRandomGenerator;
|
||||||
|
const index = inclusive.nextIntInclusive
|
||||||
|
? inclusive.nextIntInclusive(NPC_SEIZURE_MESSAGES.length - 1)
|
||||||
|
: rng.nextInt(0, NPC_SEIZURE_MESSAGES.length);
|
||||||
|
return NPC_SEIZURE_MESSAGES[index]!;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveGeneralIcon = (general: General): string => {
|
||||||
|
const runtimePicture = (general as General & { picture?: unknown }).picture;
|
||||||
|
const rawPicture = runtimePicture ?? general.meta.picture;
|
||||||
|
const picture =
|
||||||
|
(typeof rawPicture === 'string' && rawPicture !== '') || typeof rawPicture === 'number'
|
||||||
|
? String(rawPicture)
|
||||||
|
: 'default.jpg';
|
||||||
|
return `/image/icons/${picture}`;
|
||||||
|
};
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -149,6 +185,30 @@ export class ActionDefinition<
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
destGeneral.npcState >= 2 &&
|
||||||
|
context.rng.nextBool(this.env.npcSeizureMessageProb ?? NPC_SEIZURE_MESSAGE_PROB)
|
||||||
|
) {
|
||||||
|
const target = {
|
||||||
|
generalId: destGeneral.id,
|
||||||
|
generalName: destGeneral.name,
|
||||||
|
nationId: nation.id,
|
||||||
|
nationName: nation.name,
|
||||||
|
color: nation.color,
|
||||||
|
icon: resolveGeneralIcon(destGeneral),
|
||||||
|
};
|
||||||
|
effects.push(
|
||||||
|
createMessageEffect({
|
||||||
|
msgType: 'public',
|
||||||
|
src: target,
|
||||||
|
dest: target,
|
||||||
|
text: pickLegacyNpcMessage(context.rng),
|
||||||
|
time: context.messageTime,
|
||||||
|
validUntil: new Date('9999-12-31T00:00:00.000Z'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return { effects };
|
return { effects };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +226,7 @@ export const actionContextBuilder: ActionContextBuilder<SeizureArgs> = (base, op
|
|||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
destGeneral,
|
destGeneral,
|
||||||
|
messageTime: base.general.turnTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ const buildWorldInput = (
|
|||||||
baseRice: 2_000,
|
baseRice: 2_000,
|
||||||
generalMinimumGold: 0,
|
generalMinimumGold: 0,
|
||||||
generalMinimumRice: 500,
|
generalMinimumRice: 500,
|
||||||
|
npcSeizureMessageProb: 0.01,
|
||||||
maxResourceActionAmount: 10_000,
|
maxResourceActionAmount: 10_000,
|
||||||
maxTechLevel: 12,
|
maxTechLevel: 12,
|
||||||
maxLevel: 255,
|
maxLevel: 255,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const workspaceRoot = configuredWorkspaceRoot ?? findTurnDifferentialWorkspaceRo
|
|||||||
const integration = describe.skipIf(!workspaceRoot || process.env.TURN_DIFFERENTIAL_REFERENCE !== '1');
|
const integration = describe.skipIf(!workspaceRoot || process.env.TURN_DIFFERENTIAL_REFERENCE !== '1');
|
||||||
|
|
||||||
const readGold = (row: { gold?: unknown } | undefined): number => (typeof row?.gold === 'number' ? row.gold : 0);
|
const readGold = (row: { gold?: unknown } | undefined): number => (typeof row?.gold === 'number' ? row.gold : 0);
|
||||||
|
const NPC_SEIZURE_MESSAGE_TEXT = '몰수를 하다니... 이것이 윗사람이 할 짓이란 말입니까...';
|
||||||
|
|
||||||
const ignoredLifecyclePaths = [
|
const ignoredLifecyclePaths = [
|
||||||
/^generalTurns/,
|
/^generalTurns/,
|
||||||
@@ -627,3 +628,48 @@ integration('nation command resource balance and target boundaries', () => {
|
|||||||
120_000
|
120_000
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
integration('nation seizure NPC public message parity', () => {
|
||||||
|
it('matches the legacy fixed-seed RNG and public message side effect', async () => {
|
||||||
|
const request = buildRequest(
|
||||||
|
'che_몰수',
|
||||||
|
{ isGold: true, amount: 100, destGeneralID: 3 },
|
||||||
|
{
|
||||||
|
world: { hiddenSeed: 'seizure-message-37' },
|
||||||
|
generals: { 3: { name: '몰수NPC', npcState: 2 } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const reference = runReferenceTurnCommandTraceRequest(
|
||||||
|
workspaceRoot!,
|
||||||
|
request as unknown as Record<string, unknown>
|
||||||
|
);
|
||||||
|
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||||
|
|
||||||
|
expect(reference.execution.outcome).toMatchObject({ completed: true });
|
||||||
|
expect(reference.rng).toHaveLength(2);
|
||||||
|
expect(reference.rng.map((call) => call.operation)).toEqual(['nextFloat1', 'nextInt']);
|
||||||
|
expect(core.rng).toEqual(reference.rng);
|
||||||
|
const referenceMessages = reference.after.messages.slice(reference.before.messages.length);
|
||||||
|
expect(referenceMessages).toHaveLength(1);
|
||||||
|
expect(core.after.messages).toHaveLength(1);
|
||||||
|
expect(referenceMessages[0]).toMatchObject({
|
||||||
|
mailbox: 9999,
|
||||||
|
type: 'public',
|
||||||
|
sourceId: 3,
|
||||||
|
destinationId: 9999,
|
||||||
|
payload: {
|
||||||
|
src: { id: 3, name: '몰수NPC', nation_id: 1, nation: '아국' },
|
||||||
|
dest: { id: 3, name: '몰수NPC', nation_id: 1, nation: '아국' },
|
||||||
|
text: NPC_SEIZURE_MESSAGE_TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(core.after.messages[0]).toMatchObject({
|
||||||
|
payload: {
|
||||||
|
msgType: 'public',
|
||||||
|
src: { generalId: 3, generalName: '몰수NPC', nationId: 1, nationName: '아국' },
|
||||||
|
dest: { generalId: 3, generalName: '몰수NPC', nationId: 1, nationName: '아국' },
|
||||||
|
text: NPC_SEIZURE_MESSAGE_TEXT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 120_000);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user