Merge branch 'main' into feature/tournament-betting-parity

This commit is contained in:
2026-07-26 04:32:29 +00:00
42 changed files with 4791 additions and 1950 deletions
+2
View File
@@ -32,6 +32,8 @@ export interface DatabaseClient {
inheritanceLog: GamePrisma.InheritanceLogDelegate;
inheritanceResult: GamePrisma.InheritanceResultDelegate;
inheritanceUserState: GamePrisma.InheritanceUserStateDelegate;
boardPost: GamePrisma.BoardPostDelegate;
boardComment: GamePrisma.BoardCommentDelegate;
inputEvent: GamePrisma.InputEventDelegate;
turnDaemonLease: GamePrisma.TurnDaemonLeaseDelegate;
}
@@ -136,6 +136,10 @@ export class ActionResolver<
consumeSuccessfulStrategyItem(this.pipeline, context);
for (const injured of result.injuredGenerals) {
effects.push(createGeneralPatchEffect(injured.patch, injured.id));
ctx.addLog('<M>계략</>으로 인해 <R>부상</>을 당했습니다.', {
generalId: injured.id,
format: LogFormat.MONTH,
});
}
return { effects };
@@ -140,14 +140,12 @@ export class ActionResolver<
)
);
} else {
const commDmg = stolenGold / 12;
const agriDmg = stolenRice / 12;
// 레거시는 미보급 도시 자원을 먼저 감소시키지만 같은 명령 끝의
// 원본 destCity 전체 저장이 이를 덮어쓴다. 관찰 가능한 최종
// 상태는 자원 변화 없이 state 32만 남는다.
effects.push(
createCityPatchEffect(
{
commerce: Math.round(Math.max(0, destCity.commerce - commDmg)),
agriculture: Math.round(Math.max(0, destCity.agriculture - agriDmg)),
state: 32,
},
args.destCityId
@@ -106,7 +106,6 @@ export class ActionResolver<
effects.push(
createCityPatchEffect(
{
...destCity,
defence: newDef,
wall: newWall,
state: 32, // Legacy sabotage state
@@ -118,6 +117,10 @@ export class ActionResolver<
consumeSuccessfulStrategyItem(this.pipeline, context);
for (const injured of result.injuredGenerals) {
effects.push(createGeneralPatchEffect(injured.patch, injured.id));
ctx.addLog('<M>계략</>으로 인해 <R>부상</>을 당했습니다.', {
generalId: injured.id,
format: LogFormat.MONTH,
});
}
return { effects };
@@ -212,9 +212,9 @@ export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralT
id: defender.id,
patch: {
injury: clamp(defender.injury + injuryAmount, 0, INJURY_MAX),
crew: Math.floor(defender.crew * 0.98),
atmos: Math.floor(defender.atmos * 0.98),
train: Math.floor(defender.train * 0.98),
crew: Math.round(defender.crew * 0.98),
atmos: Math.round(defender.atmos * 0.98),
train: Math.round(defender.train * 0.98),
},
});
}
@@ -386,7 +386,7 @@ export class ActionResolver<
for (const injured of result.injuredGenerals) {
// 타겟 장수는 Draft가 아니므로 Effect 반환
effects.push(createGeneralPatchEffect(injured.patch, injured.id));
context.addLog(`<M>${ACTION_KEY}</>로 인해 <R>부상</>을 당했습니다.`, {
context.addLog('<M>계략</>로 인해 <R>부상</>을 당했습니다.', {
generalId: injured.id,
format: LogFormat.MONTH,
});
@@ -144,6 +144,24 @@ export const countOccupiedUniqueItems = (
return counts;
};
export const addOccupiedUniqueItemKeys = (
counts: Map<string, number>,
itemKeys: Iterable<string | null | undefined>,
itemRegistry: Map<string, ItemModule>
): Map<string, number> => {
for (const itemKey of itemKeys) {
if (!itemKey) {
continue;
}
const module = itemRegistry.get(itemKey);
if (!module || module.buyable) {
continue;
}
counts.set(itemKey, (counts.get(itemKey) ?? 0) + 1);
}
return counts;
};
const joinYearMonth = (year: number, month: number): number => year * 12 + month - 1;
const serializeSeed = (...values: Array<string | number>): string =>
+17
View File
@@ -5,6 +5,7 @@ import type { GeneralItemSlots } from '../src/domain/entities.js';
import type { ItemModule } from '../src/items/types.js';
import {
type UniqueAcquireType,
addOccupiedUniqueItemKeys,
buildVoteUniqueSeed,
countOccupiedUniqueItems,
resolveUniqueConfig,
@@ -131,4 +132,20 @@ describe('unique lottery', () => {
expect(counts.get('uniqueItem')).toBe(1);
expect(counts.get('buyableItem')).toBeUndefined();
});
it('adds active auction and storage reservations without counting buyable items', () => {
const itemRegistry = new Map<string, ItemModule>([
['uniqueItem', buildItem('uniqueItem', 'weapon', false)],
['buyableItem', buildItem('buyableItem', 'book', true)],
]);
const counts = addOccupiedUniqueItemKeys(
new Map([['uniqueItem', 1]]),
['uniqueItem', 'uniqueItem', 'buyableItem', null],
itemRegistry
);
expect(counts.get('uniqueItem')).toBe(3);
expect(counts.get('buyableItem')).toBeUndefined();
});
});