Merge branch 'main' into audit/lint-test-baseline-20260726
# Conflicts: # app/game-api/src/battleSim/worker.ts # app/game-api/src/router/general/index.ts
This commit is contained in:
@@ -51,6 +51,8 @@ export interface TurnCommandEnv {
|
||||
initialAllowedTechLevel?: number;
|
||||
baseGold: number;
|
||||
baseRice: number;
|
||||
generalMinimumGold?: number;
|
||||
generalMinimumRice?: number;
|
||||
maxResourceActionAmount: number;
|
||||
itemCatalog?: Record<string, TurnCommandItemCatalogEntry>;
|
||||
generalActionModules?: Array<GeneralActionModule>;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/action
|
||||
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { normalizeResourceActionAmount } from './resourceAmount.js';
|
||||
|
||||
export interface TradeEnvironment {
|
||||
exchangeFee?: number;
|
||||
@@ -46,8 +47,10 @@ export class ActionDefinition<
|
||||
if (!parsed || !Number.isFinite(parsed.amount)) {
|
||||
return null;
|
||||
}
|
||||
const maxAmount = this.env.maxResourceActionAmount ?? 10_000;
|
||||
const amount = Math.max(100, Math.min(Math.round(parsed.amount / 100) * 100, maxAmount));
|
||||
const amount = normalizeResourceActionAmount(parsed.amount, this.env.maxResourceActionAmount ?? 10_000);
|
||||
if (amount === null) {
|
||||
return null;
|
||||
}
|
||||
return { buyRice: parsed.buyRice, amount };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { General, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
||||
import {
|
||||
denyWithReason,
|
||||
existsDestGeneral,
|
||||
friendlyDestGeneral,
|
||||
notBeNeutral,
|
||||
@@ -19,15 +20,13 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
||||
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { normalizeResourceActionAmount } from './resourceAmount.js';
|
||||
|
||||
const ACTION_NAME = '증여';
|
||||
const ACTION_KEY = 'che_증여';
|
||||
const ARGS_SCHEMA = z.object({
|
||||
isGold: z.boolean(),
|
||||
amount: z.preprocess(
|
||||
(value) => (typeof value === 'number' ? Math.floor(value / 100) * 100 : value),
|
||||
z.number().int().positive()
|
||||
),
|
||||
amount: z.number(),
|
||||
destGeneralID: z.number().int().positive(),
|
||||
});
|
||||
export type GiftArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||
@@ -51,10 +50,13 @@ export class ActionDefinition<
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
const maxAmount = this.env.maxResourceActionAmount > 0 ? this.env.maxResourceActionAmount : 10000;
|
||||
const amount = normalizeResourceActionAmount(parsed.amount, this.env.maxResourceActionAmount);
|
||||
if (amount === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...parsed,
|
||||
amount: Math.max(100, Math.min(parsed.amount, maxAmount)),
|
||||
amount,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,9 +64,12 @@ export class ActionDefinition<
|
||||
return [notBeNeutral(), occupiedCity(), suppliedCity()];
|
||||
}
|
||||
|
||||
buildConstraints(_ctx: ConstraintContext, args: GiftArgs): Constraint[] {
|
||||
const minGold = this.env.baseGold > 0 ? this.env.baseGold : 1000;
|
||||
const minRice = this.env.baseRice > 0 ? this.env.baseRice : 1000;
|
||||
buildConstraints(ctx: ConstraintContext, args: GiftArgs): Constraint[] {
|
||||
if (ctx.actorId === args.destGeneralID) {
|
||||
return [denyWithReason('본인입니다')];
|
||||
}
|
||||
const minGold = this.env.generalMinimumGold ?? 0;
|
||||
const minRice = this.env.generalMinimumRice ?? 500;
|
||||
|
||||
return [
|
||||
notBeNeutral(),
|
||||
@@ -83,8 +88,8 @@ export class ActionDefinition<
|
||||
throw new Error('증여 대상 장수가 없습니다.');
|
||||
}
|
||||
|
||||
const minGold = this.env.baseGold > 0 ? this.env.baseGold : 1000;
|
||||
const minRice = this.env.baseRice > 0 ? this.env.baseRice : 1000;
|
||||
const minGold = this.env.generalMinimumGold ?? 0;
|
||||
const minRice = this.env.generalMinimumRice ?? 500;
|
||||
|
||||
const resKey = args.isGold ? 'gold' : 'rice';
|
||||
const resName = args.isGold ? '금' : '쌀';
|
||||
|
||||
@@ -21,6 +21,7 @@ import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/action
|
||||
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||
import { normalizeResourceActionAmount } from './resourceAmount.js';
|
||||
|
||||
const ACTION_NAME = '헌납';
|
||||
const ACTION_KEY = 'che_헌납';
|
||||
@@ -96,12 +97,23 @@ export class ActionDefinition<
|
||||
public readonly name = ACTION_NAME;
|
||||
private readonly resolver: ActionResolver<TriggerState>;
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly env: TurnCommandEnv) {
|
||||
this.resolver = new ActionResolver();
|
||||
}
|
||||
|
||||
parseArgs(raw: unknown): DonateArgs | null {
|
||||
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||
const parsed = parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
const amount = normalizeResourceActionAmount(parsed.amount, this.env.maxResourceActionAmount);
|
||||
if (amount === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...parsed,
|
||||
amount,
|
||||
};
|
||||
}
|
||||
|
||||
buildMinConstraints(_ctx: ConstraintContext, _args: DonateArgs): Constraint[] {
|
||||
@@ -109,10 +121,12 @@ export class ActionDefinition<
|
||||
}
|
||||
|
||||
buildConstraints(_ctx: ConstraintContext, args: DonateArgs): Constraint[] {
|
||||
const minGold = this.env.generalMinimumGold ?? 0;
|
||||
const minRice = this.env.generalMinimumRice ?? 500;
|
||||
if (args.isGold) {
|
||||
return [notBeNeutral(), occupiedCity(), suppliedCity(), reqGeneralGold(() => args.amount)];
|
||||
return [notBeNeutral(), occupiedCity(), suppliedCity(), reqGeneralGold(() => minGold)];
|
||||
}
|
||||
return [notBeNeutral(), occupiedCity(), suppliedCity(), reqGeneralRice(() => args.amount)];
|
||||
return [notBeNeutral(), occupiedCity(), suppliedCity(), reqGeneralRice(() => minRice)];
|
||||
}
|
||||
|
||||
resolve(context: GeneralActionResolveContext<TriggerState>, args: DonateArgs): GeneralActionOutcome<TriggerState> {
|
||||
@@ -128,5 +142,5 @@ export const commandSpec: GeneralTurnCommandSpec = {
|
||||
reqArg: true,
|
||||
availabilityArgs: { isGold: true, amount: 0 },
|
||||
argsSchema: ARGS_SCHEMA,
|
||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const DEFAULT_MIN_RESOURCE_ACTION_AMOUNT = 100;
|
||||
const DEFAULT_MAX_RESOURCE_ACTION_AMOUNT = 10_000;
|
||||
const RESOURCE_ACTION_AMOUNT_UNIT = 100;
|
||||
|
||||
export const normalizeResourceActionAmount = (amount: number, configuredMaxAmount: number): number | null => {
|
||||
if (!Number.isFinite(amount)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maxAmount = configuredMaxAmount > 0 ? configuredMaxAmount : DEFAULT_MAX_RESOURCE_ACTION_AMOUNT;
|
||||
const roundedAmount = Math.round(amount / RESOURCE_ACTION_AMOUNT_UNIT) * RESOURCE_ACTION_AMOUNT_UNIT;
|
||||
|
||||
return Math.max(DEFAULT_MIN_RESOURCE_ACTION_AMOUNT, Math.min(roundedAmount, maxAmount));
|
||||
};
|
||||
Reference in New Issue
Block a user