feat: 토너먼트 메타 조정 및 결과 처리 기능 추가
This commit is contained in:
@@ -421,6 +421,17 @@ export const tournamentRouter = router({
|
|||||||
throw new TRPCError({ code: 'BAD_REQUEST', message: adjustResult.reason });
|
throw new TRPCError({ code: 'BAD_REQUEST', message: adjustResult.reason });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ctx.turnDaemon.sendCommand({
|
||||||
|
type: 'adjustGeneralMeta',
|
||||||
|
reason: 'tournamentBet',
|
||||||
|
adjustments: [
|
||||||
|
{
|
||||||
|
generalId: general.id,
|
||||||
|
metaDelta: { rank_betgold: input.amount },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
await store.appendBettingEntry({
|
await store.appendBettingEntry({
|
||||||
generalId: general.id,
|
generalId: general.id,
|
||||||
targetId: input.targetId,
|
targetId: input.targetId,
|
||||||
|
|||||||
@@ -33,13 +33,15 @@ import {
|
|||||||
seedNpcBets,
|
seedNpcBets,
|
||||||
sleepMs,
|
sleepMs,
|
||||||
sortByRanking,
|
sortByRanking,
|
||||||
|
type TournamentMatchOutcome,
|
||||||
type TournamentPrismaClient,
|
type TournamentPrismaClient,
|
||||||
} from './workerHelpers.js';
|
} from './workerHelpers.js';
|
||||||
|
|
||||||
export const applyBattle = async (
|
export const applyBattle = async (
|
||||||
store: TournamentStore,
|
store: TournamentStore,
|
||||||
state: TournamentState,
|
state: TournamentState,
|
||||||
baseSeed: string
|
baseSeed: string,
|
||||||
|
daemonTransport?: TurnDaemonTransport
|
||||||
): Promise<TournamentState> => {
|
): Promise<TournamentState> => {
|
||||||
const matches = await store.getMatches();
|
const matches = await store.getMatches();
|
||||||
const participants = await store.getParticipants();
|
const participants = await store.getParticipants();
|
||||||
@@ -133,6 +135,16 @@ export const applyBattle = async (
|
|||||||
const nextMatches = matches.map((entry) => (entry.id === target.id ? updatedMatch : entry));
|
const nextMatches = matches.map((entry) => (entry.id === target.id ? updatedMatch : entry));
|
||||||
await store.setMatches(nextMatches);
|
await store.setMatches(nextMatches);
|
||||||
|
|
||||||
|
if (daemonTransport && attacker.id > 0 && defender.id > 0) {
|
||||||
|
await daemonTransport.sendCommand({
|
||||||
|
type: 'tournamentMatchResult',
|
||||||
|
tournamentType: state.type,
|
||||||
|
attackerId: attacker.id,
|
||||||
|
defenderId: defender.id,
|
||||||
|
result: result.draw ? 'draw' : result.winnerId === attacker.id ? 'attacker' : 'defender',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const nextPhase = state.phase + 1;
|
const nextPhase = state.phase + 1;
|
||||||
const nextState: TournamentState = {
|
const nextState: TournamentState = {
|
||||||
...state,
|
...state,
|
||||||
@@ -192,6 +204,7 @@ export const applyPreBattleStage = async (
|
|||||||
let updated = participants.some((entry) => entry.groupId === undefined)
|
let updated = participants.some((entry) => entry.groupId === undefined)
|
||||||
? assignGroupSlots(participants, 8, 8, 0)
|
? assignGroupSlots(participants, 8, 8, 0)
|
||||||
: participants;
|
: participants;
|
||||||
|
const outcomes: TournamentMatchOutcome[] = [];
|
||||||
for (let groupId = 0; groupId < 8; groupId += 1) {
|
for (let groupId = 0; groupId < 8; groupId += 1) {
|
||||||
const groupEntries = updated.filter((entry) => entry.groupId === groupId);
|
const groupEntries = updated.filter((entry) => entry.groupId === groupId);
|
||||||
const attacker = groupEntries.find((entry) => entry.groupNo === pair[0]);
|
const attacker = groupEntries.find((entry) => entry.groupNo === pair[0]);
|
||||||
@@ -199,10 +212,28 @@ export const applyPreBattleStage = async (
|
|||||||
if (!attacker || !defender) {
|
if (!attacker || !defender) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
updated = applyGroupMatch(updated, attacker, defender, state, baseSeed, groupId);
|
const result = applyGroupMatch(updated, attacker, defender, state, baseSeed, groupId);
|
||||||
|
updated = result.participants;
|
||||||
|
outcomes.push(result.outcome);
|
||||||
}
|
}
|
||||||
await store.setParticipants(updated);
|
await store.setParticipants(updated);
|
||||||
|
|
||||||
|
if (outcomes.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
outcomes
|
||||||
|
.filter((outcome) => outcome.attackerId > 0 && outcome.defenderId > 0)
|
||||||
|
.map((outcome) =>
|
||||||
|
daemonTransport.sendCommand({
|
||||||
|
type: 'tournamentMatchResult',
|
||||||
|
tournamentType: state.type,
|
||||||
|
attackerId: outcome.attackerId,
|
||||||
|
defenderId: outcome.defenderId,
|
||||||
|
result: outcome.result,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const maxPhase = 55;
|
const maxPhase = 55;
|
||||||
const isComplete = state.phase >= maxPhase;
|
const isComplete = state.phase >= maxPhase;
|
||||||
if (isComplete) {
|
if (isComplete) {
|
||||||
@@ -310,6 +341,7 @@ export const applyPreBattleStage = async (
|
|||||||
throw new Error('본선 매치 구성을 찾을 수 없습니다.');
|
throw new Error('본선 매치 구성을 찾을 수 없습니다.');
|
||||||
}
|
}
|
||||||
let updated = participants;
|
let updated = participants;
|
||||||
|
const outcomes: TournamentMatchOutcome[] = [];
|
||||||
for (let groupId = 10; groupId < 18; groupId += 1) {
|
for (let groupId = 10; groupId < 18; groupId += 1) {
|
||||||
const groupEntries = updated.filter((entry) => entry.groupId === groupId);
|
const groupEntries = updated.filter((entry) => entry.groupId === groupId);
|
||||||
const attacker = groupEntries.find((entry) => entry.groupNo === pair[0]);
|
const attacker = groupEntries.find((entry) => entry.groupNo === pair[0]);
|
||||||
@@ -317,10 +349,28 @@ export const applyPreBattleStage = async (
|
|||||||
if (!attacker || !defender) {
|
if (!attacker || !defender) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
updated = applyGroupMatch(updated, attacker, defender, state, baseSeed, groupId);
|
const result = applyGroupMatch(updated, attacker, defender, state, baseSeed, groupId);
|
||||||
|
updated = result.participants;
|
||||||
|
outcomes.push(result.outcome);
|
||||||
}
|
}
|
||||||
await store.setParticipants(updated);
|
await store.setParticipants(updated);
|
||||||
|
|
||||||
|
if (outcomes.length > 0) {
|
||||||
|
await Promise.all(
|
||||||
|
outcomes
|
||||||
|
.filter((outcome) => outcome.attackerId > 0 && outcome.defenderId > 0)
|
||||||
|
.map((outcome) =>
|
||||||
|
daemonTransport.sendCommand({
|
||||||
|
type: 'tournamentMatchResult',
|
||||||
|
tournamentType: state.type,
|
||||||
|
attackerId: outcome.attackerId,
|
||||||
|
defenderId: outcome.defenderId,
|
||||||
|
result: outcome.result,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const maxPhase = 5;
|
const maxPhase = 5;
|
||||||
if (state.phase >= maxPhase) {
|
if (state.phase >= maxPhase) {
|
||||||
for (let groupId = 10; groupId < 18; groupId += 1) {
|
for (let groupId = 10; groupId < 18; groupId += 1) {
|
||||||
@@ -509,7 +559,7 @@ export const runTournamentWorker = async (): Promise<void> => {
|
|||||||
const baseSeed = (worldState?.meta as Record<string, unknown> | null)?.hiddenSeed ?? 'tournament';
|
const baseSeed = (worldState?.meta as Record<string, unknown> | null)?.hiddenSeed ?? 'tournament';
|
||||||
let nextState = state;
|
let nextState = state;
|
||||||
if (isBattleStage(state.stage)) {
|
if (isBattleStage(state.stage)) {
|
||||||
nextState = await applyBattle(store, state, String(baseSeed));
|
nextState = await applyBattle(store, state, String(baseSeed), daemonTransport);
|
||||||
} else if (isPreBattleStage(state.stage)) {
|
} else if (isPreBattleStage(state.stage)) {
|
||||||
nextState = await applyPreBattleStage(
|
nextState = await applyPreBattleStage(
|
||||||
store,
|
store,
|
||||||
|
|||||||
@@ -282,6 +282,12 @@ export const fillParticipants = async (options: {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TournamentMatchOutcome = {
|
||||||
|
attackerId: number;
|
||||||
|
defenderId: number;
|
||||||
|
result: 'attacker' | 'defender' | 'draw';
|
||||||
|
};
|
||||||
|
|
||||||
export const applyGroupMatch = (
|
export const applyGroupMatch = (
|
||||||
participants: TournamentParticipantEntry[],
|
participants: TournamentParticipantEntry[],
|
||||||
attacker: TournamentParticipantEntry,
|
attacker: TournamentParticipantEntry,
|
||||||
@@ -289,7 +295,7 @@ export const applyGroupMatch = (
|
|||||||
state: TournamentState,
|
state: TournamentState,
|
||||||
baseSeed: string,
|
baseSeed: string,
|
||||||
matchIndex: number
|
matchIndex: number
|
||||||
): TournamentParticipantEntry[] => {
|
): { participants: TournamentParticipantEntry[]; outcome: TournamentMatchOutcome } => {
|
||||||
const result = resolveTournamentBattle({
|
const result = resolveTournamentBattle({
|
||||||
type: state.type,
|
type: state.type,
|
||||||
battleType: 0,
|
battleType: 0,
|
||||||
@@ -325,7 +331,14 @@ export const applyGroupMatch = (
|
|||||||
|
|
||||||
const glDelta = Math.round((result.totalDamage.defender - result.totalDamage.attacker) / 50);
|
const glDelta = Math.round((result.totalDamage.defender - result.totalDamage.attacker) / 50);
|
||||||
|
|
||||||
return participants.map((entry) => {
|
const outcome: TournamentMatchOutcome = {
|
||||||
|
attackerId: attacker.id,
|
||||||
|
defenderId: defender.id,
|
||||||
|
result: result.draw ? 'draw' : result.winnerId === attacker.id ? 'attacker' : 'defender',
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
participants: participants.map((entry) => {
|
||||||
if (entry.id !== attacker.id && entry.id !== defender.id) {
|
if (entry.id !== attacker.id && entry.id !== defender.id) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -348,7 +361,9 @@ export const applyGroupMatch = (
|
|||||||
next.lose += 1;
|
next.lose += 1;
|
||||||
next.gl -= glDelta;
|
next.gl -= glDelta;
|
||||||
return next;
|
return next;
|
||||||
});
|
}),
|
||||||
|
outcome,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortByRanking = (entries: TournamentParticipantEntry[]): TournamentParticipantEntry[] =>
|
export const sortByRanking = (entries: TournamentParticipantEntry[]): TournamentParticipantEntry[] =>
|
||||||
@@ -632,5 +647,13 @@ export const seedNpcBets = async (options: {
|
|||||||
goldDelta: -betGold,
|
goldDelta: -betGold,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
await daemonTransport.sendCommand({
|
||||||
|
type: 'adjustGeneralMeta',
|
||||||
|
reason: 'tournamentNpcBet',
|
||||||
|
adjustments: npcBetList.map((npc) => ({
|
||||||
|
generalId: npc.id as number,
|
||||||
|
metaDelta: { rank_betgold: betGold },
|
||||||
|
})),
|
||||||
|
});
|
||||||
await store.setBettingEntries(entries);
|
await store.setBettingEntries(entries);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -165,6 +165,27 @@ const zAdjustGeneralResources = z
|
|||||||
.min(1),
|
.min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const zAdjustGeneralMeta = z.object({
|
||||||
|
type: z.literal('adjustGeneralMeta'),
|
||||||
|
reason: z.string().optional(),
|
||||||
|
adjustments: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
generalId: zFiniteNumber,
|
||||||
|
metaDelta: z.record(z.string(), zFiniteNumber),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const zTournamentMatchResult = z.object({
|
||||||
|
type: z.literal('tournamentMatchResult'),
|
||||||
|
tournamentType: zFiniteNumber,
|
||||||
|
attackerId: zFiniteNumber,
|
||||||
|
defenderId: zFiniteNumber,
|
||||||
|
result: z.enum(['attacker', 'defender', 'draw']),
|
||||||
|
});
|
||||||
|
|
||||||
const zPatchGeneral = z.object({
|
const zPatchGeneral = z.object({
|
||||||
type: z.literal('patchGeneral'),
|
type: z.literal('patchGeneral'),
|
||||||
generalId: zFiniteNumber,
|
generalId: zFiniteNumber,
|
||||||
@@ -353,6 +374,22 @@ const normalizeAdjustGeneralResources: CommandNormalizer<'adjustGeneralResources
|
|||||||
return { ...command, requestId: envelope.requestId };
|
return { ...command, requestId: envelope.requestId };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeAdjustGeneralMeta: CommandNormalizer<'adjustGeneralMeta'> = (envelope) => {
|
||||||
|
const command = parseWith(zAdjustGeneralMeta, envelope.command);
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { ...command, requestId: envelope.requestId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeTournamentMatchResult: CommandNormalizer<'tournamentMatchResult'> = (envelope) => {
|
||||||
|
const command = parseWith(zTournamentMatchResult, envelope.command);
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { ...command, requestId: envelope.requestId };
|
||||||
|
};
|
||||||
|
|
||||||
const normalizePatchGeneral: CommandNormalizer<'patchGeneral'> = (envelope) => {
|
const normalizePatchGeneral: CommandNormalizer<'patchGeneral'> = (envelope) => {
|
||||||
const command = parseWith(zPatchGeneral, envelope.command);
|
const command = parseWith(zPatchGeneral, envelope.command);
|
||||||
if (!command) {
|
if (!command) {
|
||||||
@@ -396,6 +433,8 @@ const normalizers: CommandNormalizerMap = {
|
|||||||
tournamentReward: normalizeTournamentReward,
|
tournamentReward: normalizeTournamentReward,
|
||||||
setNationMeta: normalizeSetNationMeta,
|
setNationMeta: normalizeSetNationMeta,
|
||||||
adjustGeneralResources: normalizeAdjustGeneralResources,
|
adjustGeneralResources: normalizeAdjustGeneralResources,
|
||||||
|
adjustGeneralMeta: normalizeAdjustGeneralMeta,
|
||||||
|
tournamentMatchResult: normalizeTournamentMatchResult,
|
||||||
patchGeneral: normalizePatchGeneral,
|
patchGeneral: normalizePatchGeneral,
|
||||||
getStatus: normalizeGetStatus,
|
getStatus: normalizeGetStatus,
|
||||||
run: normalizeRun,
|
run: normalizeRun,
|
||||||
|
|||||||
@@ -150,6 +150,172 @@ async function handleAdjustGeneralResources(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAdjustGeneralMeta(
|
||||||
|
ctx: CommandHandlerContext,
|
||||||
|
command: Extract<TurnDaemonCommand, { type: 'adjustGeneralMeta' }>
|
||||||
|
): Promise<TurnDaemonCommandResult> {
|
||||||
|
const { world, hooks } = ctx;
|
||||||
|
if (!command.adjustments || command.adjustments.length === 0) {
|
||||||
|
return {
|
||||||
|
type: 'adjustGeneralMeta',
|
||||||
|
ok: false,
|
||||||
|
reason: '메타 조정 대상이 없습니다.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let processed = 0;
|
||||||
|
let missing = 0;
|
||||||
|
|
||||||
|
for (const adjustment of command.adjustments) {
|
||||||
|
if (!adjustment || typeof adjustment.generalId !== 'number') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const general = world.getGeneralById(adjustment.generalId);
|
||||||
|
if (!general) {
|
||||||
|
missing += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const nextMeta = { ...general.meta } as TurnGeneral['meta'];
|
||||||
|
for (const [key, delta] of Object.entries(adjustment.metaDelta ?? {})) {
|
||||||
|
if (typeof delta !== 'number' || !Number.isFinite(delta)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const current = typeof nextMeta[key] === 'number' ? Number(nextMeta[key]) : 0;
|
||||||
|
nextMeta[key] = current + delta;
|
||||||
|
}
|
||||||
|
world.updateGeneral(adjustment.generalId, { meta: nextMeta });
|
||||||
|
processed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await flushWorld(world, hooks);
|
||||||
|
return {
|
||||||
|
type: 'adjustGeneralMeta',
|
||||||
|
ok: true,
|
||||||
|
processed,
|
||||||
|
missing,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTournamentMatchResult(
|
||||||
|
ctx: CommandHandlerContext,
|
||||||
|
command: Extract<TurnDaemonCommand, { type: 'tournamentMatchResult' }>
|
||||||
|
): Promise<TurnDaemonCommandResult> {
|
||||||
|
const { world, hooks } = ctx;
|
||||||
|
const resolvePrefix = (type: number): string => {
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
return 'tl';
|
||||||
|
case 2:
|
||||||
|
return 'ts';
|
||||||
|
case 3:
|
||||||
|
return 'ti';
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
return 'tt';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prefix = resolvePrefix(command.tournamentType);
|
||||||
|
const attacker = command.attackerId > 0 ? world.getGeneralById(command.attackerId) : null;
|
||||||
|
const defender = command.defenderId > 0 ? world.getGeneralById(command.defenderId) : null;
|
||||||
|
if (!attacker && !defender) {
|
||||||
|
return {
|
||||||
|
type: 'tournamentMatchResult',
|
||||||
|
ok: false,
|
||||||
|
tournamentType: command.tournamentType,
|
||||||
|
attackerId: command.attackerId,
|
||||||
|
defenderId: command.defenderId,
|
||||||
|
result: command.result,
|
||||||
|
reason: '장수 정보를 찾을 수 없습니다.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const rankKey = (suffix: string): string => `rank_${prefix}${suffix}`;
|
||||||
|
const getRankNumber = (general: TurnGeneral | null, key: string): number =>
|
||||||
|
general && typeof general.meta[key] === 'number' ? Number(general.meta[key]) : 0;
|
||||||
|
|
||||||
|
const attackerG = getRankNumber(attacker, rankKey('g'));
|
||||||
|
const defenderG = getRankNumber(defender, rankKey('g'));
|
||||||
|
|
||||||
|
let attackerGDelta = 0;
|
||||||
|
let defenderGDelta = 0;
|
||||||
|
let attackerW = 0;
|
||||||
|
let attackerD = 0;
|
||||||
|
let attackerL = 0;
|
||||||
|
let defenderW = 0;
|
||||||
|
let defenderD = 0;
|
||||||
|
let defenderL = 0;
|
||||||
|
|
||||||
|
if (command.result === 'attacker') {
|
||||||
|
attackerW = 1;
|
||||||
|
defenderL = 1;
|
||||||
|
if (attackerG > defenderG) {
|
||||||
|
attackerGDelta = 1;
|
||||||
|
defenderGDelta = 0;
|
||||||
|
} else if (attackerG === defenderG) {
|
||||||
|
attackerGDelta = 2;
|
||||||
|
defenderGDelta = -1;
|
||||||
|
} else {
|
||||||
|
attackerGDelta = 3;
|
||||||
|
defenderGDelta = -2;
|
||||||
|
}
|
||||||
|
} else if (command.result === 'defender') {
|
||||||
|
attackerL = 1;
|
||||||
|
defenderW = 1;
|
||||||
|
if (defenderG > attackerG) {
|
||||||
|
defenderGDelta = 1;
|
||||||
|
attackerGDelta = 0;
|
||||||
|
} else if (attackerG === defenderG) {
|
||||||
|
defenderGDelta = 2;
|
||||||
|
attackerGDelta = -1;
|
||||||
|
} else {
|
||||||
|
defenderGDelta = 3;
|
||||||
|
attackerGDelta = -2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attackerD = 1;
|
||||||
|
defenderD = 1;
|
||||||
|
if (attackerG > defenderG) {
|
||||||
|
attackerGDelta = 1;
|
||||||
|
defenderGDelta = -1;
|
||||||
|
} else if (attackerG === defenderG) {
|
||||||
|
attackerGDelta = 0;
|
||||||
|
defenderGDelta = 0;
|
||||||
|
} else {
|
||||||
|
attackerGDelta = -1;
|
||||||
|
defenderGDelta = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attacker) {
|
||||||
|
const nextMeta = { ...attacker.meta } as TurnGeneral['meta'];
|
||||||
|
nextMeta[rankKey('w')] = getRankNumber(attacker, rankKey('w')) + attackerW;
|
||||||
|
nextMeta[rankKey('d')] = getRankNumber(attacker, rankKey('d')) + attackerD;
|
||||||
|
nextMeta[rankKey('l')] = getRankNumber(attacker, rankKey('l')) + attackerL;
|
||||||
|
nextMeta[rankKey('g')] = attackerG + attackerGDelta;
|
||||||
|
world.updateGeneral(attacker.id, { meta: nextMeta });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defender) {
|
||||||
|
const nextMeta = { ...defender.meta } as TurnGeneral['meta'];
|
||||||
|
nextMeta[rankKey('w')] = getRankNumber(defender, rankKey('w')) + defenderW;
|
||||||
|
nextMeta[rankKey('d')] = getRankNumber(defender, rankKey('d')) + defenderD;
|
||||||
|
nextMeta[rankKey('l')] = getRankNumber(defender, rankKey('l')) + defenderL;
|
||||||
|
nextMeta[rankKey('g')] = defenderG + defenderGDelta;
|
||||||
|
world.updateGeneral(defender.id, { meta: nextMeta });
|
||||||
|
}
|
||||||
|
|
||||||
|
await flushWorld(world, hooks);
|
||||||
|
return {
|
||||||
|
type: 'tournamentMatchResult',
|
||||||
|
ok: true,
|
||||||
|
tournamentType: command.tournamentType,
|
||||||
|
attackerId: command.attackerId,
|
||||||
|
defenderId: command.defenderId,
|
||||||
|
result: command.result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function handlePatchGeneral(
|
async function handlePatchGeneral(
|
||||||
ctx: CommandHandlerContext,
|
ctx: CommandHandlerContext,
|
||||||
command: Extract<TurnDaemonCommand, { type: 'patchGeneral' }>
|
command: Extract<TurnDaemonCommand, { type: 'patchGeneral' }>
|
||||||
@@ -691,6 +857,7 @@ async function handleTournamentBettingPayout(
|
|||||||
let processed = 0;
|
let processed = 0;
|
||||||
let missing = 0;
|
let missing = 0;
|
||||||
let totalPayout = 0;
|
let totalPayout = 0;
|
||||||
|
const metaDeltas = new Map<number, { betwin: number; betwingold: number }>();
|
||||||
|
|
||||||
for (const payout of command.payouts) {
|
for (const payout of command.payouts) {
|
||||||
if (!payout || typeof payout.generalId !== 'number' || typeof payout.amount !== 'number') {
|
if (!payout || typeof payout.generalId !== 'number' || typeof payout.amount !== 'number') {
|
||||||
@@ -709,6 +876,26 @@ async function handleTournamentBettingPayout(
|
|||||||
});
|
});
|
||||||
processed += 1;
|
processed += 1;
|
||||||
totalPayout += payout.amount;
|
totalPayout += payout.amount;
|
||||||
|
const currentDelta = metaDeltas.get(payout.generalId) ?? { betwin: 0, betwingold: 0 };
|
||||||
|
metaDeltas.set(payout.generalId, {
|
||||||
|
betwin: currentDelta.betwin + 1,
|
||||||
|
betwingold: currentDelta.betwingold + payout.amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [generalId, delta] of metaDeltas) {
|
||||||
|
const general = world.getGeneralById(generalId);
|
||||||
|
if (!general) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const nextMeta = { ...general.meta } as TurnGeneral['meta'];
|
||||||
|
const betwinKey = 'rank_betwin';
|
||||||
|
const betwingoldKey = 'rank_betwingold';
|
||||||
|
const currentBetwin = typeof nextMeta[betwinKey] === 'number' ? Number(nextMeta[betwinKey]) : 0;
|
||||||
|
const currentBetwingold = typeof nextMeta[betwingoldKey] === 'number' ? Number(nextMeta[betwingoldKey]) : 0;
|
||||||
|
nextMeta[betwinKey] = currentBetwin + delta.betwin;
|
||||||
|
nextMeta[betwingoldKey] = currentBetwingold + delta.betwingold;
|
||||||
|
world.updateGeneral(generalId, { meta: nextMeta });
|
||||||
}
|
}
|
||||||
|
|
||||||
await flushWorld(world, hooks);
|
await flushWorld(world, hooks);
|
||||||
@@ -774,6 +961,9 @@ export const createTurnDaemonCommandHandler = (options: {
|
|||||||
tournamentReward: (command) => handleTournamentReward(ctx, command as Extract<TurnDaemonCommand, { type: 'tournamentReward' }>),
|
tournamentReward: (command) => handleTournamentReward(ctx, command as Extract<TurnDaemonCommand, { type: 'tournamentReward' }>),
|
||||||
setNationMeta: (command) => handleSetNationMeta(ctx, command as Extract<TurnDaemonCommand, { type: 'setNationMeta' }>),
|
setNationMeta: (command) => handleSetNationMeta(ctx, command as Extract<TurnDaemonCommand, { type: 'setNationMeta' }>),
|
||||||
adjustGeneralResources: (command) => handleAdjustGeneralResources(ctx, command as Extract<TurnDaemonCommand, { type: 'adjustGeneralResources' }>),
|
adjustGeneralResources: (command) => handleAdjustGeneralResources(ctx, command as Extract<TurnDaemonCommand, { type: 'adjustGeneralResources' }>),
|
||||||
|
adjustGeneralMeta: (command) => handleAdjustGeneralMeta(ctx, command as Extract<TurnDaemonCommand, { type: 'adjustGeneralMeta' }>),
|
||||||
|
tournamentMatchResult: (command) =>
|
||||||
|
handleTournamentMatchResult(ctx, command as Extract<TurnDaemonCommand, { type: 'tournamentMatchResult' }>),
|
||||||
patchGeneral: (command) => handlePatchGeneral(ctx, command as Extract<TurnDaemonCommand, { type: 'patchGeneral' }>),
|
patchGeneral: (command) => handlePatchGeneral(ctx, command as Extract<TurnDaemonCommand, { type: 'patchGeneral' }>),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,23 @@ export type TurnDaemonCommand =
|
|||||||
riceDelta?: number;
|
riceDelta?: number;
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'adjustGeneralMeta';
|
||||||
|
requestId?: string;
|
||||||
|
reason?: string;
|
||||||
|
adjustments: Array<{
|
||||||
|
generalId: number;
|
||||||
|
metaDelta: Record<string, number>;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'tournamentMatchResult';
|
||||||
|
requestId?: string;
|
||||||
|
tournamentType: number;
|
||||||
|
attackerId: number;
|
||||||
|
defenderId: number;
|
||||||
|
result: 'attacker' | 'defender' | 'draw';
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'patchGeneral';
|
type: 'patchGeneral';
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
@@ -274,6 +291,34 @@ export type TurnDaemonCommandResult =
|
|||||||
ok: false;
|
ok: false;
|
||||||
reason: string;
|
reason: string;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'adjustGeneralMeta';
|
||||||
|
ok: true;
|
||||||
|
processed: number;
|
||||||
|
missing: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'adjustGeneralMeta';
|
||||||
|
ok: false;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'tournamentMatchResult';
|
||||||
|
ok: true;
|
||||||
|
tournamentType: number;
|
||||||
|
attackerId: number;
|
||||||
|
defenderId: number;
|
||||||
|
result: 'attacker' | 'defender' | 'draw';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'tournamentMatchResult';
|
||||||
|
ok: false;
|
||||||
|
tournamentType: number;
|
||||||
|
attackerId: number;
|
||||||
|
defenderId: number;
|
||||||
|
result: 'attacker' | 'defender' | 'draw';
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'patchGeneral';
|
type: 'patchGeneral';
|
||||||
ok: true;
|
ok: true;
|
||||||
|
|||||||
Reference in New Issue
Block a user