Commit 7f1d5d6d authored by nanahira's avatar nanahira

refa DuelRecord to pos

parent 4bac7039
import cryptoRandomString from 'crypto-random-string';
import YGOProDeck from 'ygopro-deck-encode'; import YGOProDeck from 'ygopro-deck-encode';
import { import {
ChatColor, ChatColor,
...@@ -15,7 +14,7 @@ import { ...@@ -15,7 +14,7 @@ import {
} from 'ygopro-msg-encode'; } from 'ygopro-msg-encode';
import { Context } from '../../app'; import { Context } from '../../app';
import { Client } from '../../client'; import { Client } from '../../client';
import { DuelRecord, OnRoomCreate, OnRoomWin, Room } from '../../room'; import { DuelRecord, OnRoomWin, Room } from '../../room';
import { ClientKeyProvider } from '../client-key-provider'; import { ClientKeyProvider } from '../client-key-provider';
import { MenuEntry, MenuManager } from '../menu-manager'; import { MenuEntry, MenuManager } from '../menu-manager';
import { DuelRecordEntity } from './duel-record.entity'; import { DuelRecordEntity } from './duel-record.entity';
...@@ -33,7 +32,6 @@ import { ...@@ -33,7 +32,6 @@ import {
encodeSeedBase64, encodeSeedBase64,
resolveCurrentDeckMainc, resolveCurrentDeckMainc,
resolveIngameDeckMainc, resolveIngameDeckMainc,
resolveIngamePosBySeat,
resolveIsFirstPlayer, resolveIsFirstPlayer,
resolvePlayerScore, resolvePlayerScore,
resolveStartDeckMainc, resolveStartDeckMainc,
...@@ -52,12 +50,6 @@ type ReplayDetailMenuOptions = { ...@@ -52,12 +50,6 @@ type ReplayDetailMenuOptions = {
type DirectReplayPassAction = 'detail' | 'watch' | 'downloadYrp'; type DirectReplayPassAction = 'detail' | 'watch' | 'downloadYrp';
declare module '../../room' {
interface Room {
identifier?: string;
}
}
declare module '../../client' { declare module '../../client' {
interface Client { interface Client {
cloudReplayPageCursors?: Array<number | null>; cloudReplayPageCursors?: Array<number | null>;
...@@ -72,11 +64,6 @@ export class CloudReplayService { ...@@ -72,11 +64,6 @@ export class CloudReplayService {
private menuManager = this.ctx.get(() => MenuManager); private menuManager = this.ctx.get(() => MenuManager);
constructor(private ctx: Context) { constructor(private ctx: Context) {
this.ctx.middleware(OnRoomCreate, async (event, _client, next) => {
event.room.identifier = this.createRoomIdentifier();
return next();
});
this.ctx.middleware(OnRoomWin, async (event, _client, next) => { this.ctx.middleware(OnRoomWin, async (event, _client, next) => {
await this.saveDuelRecord(event); await this.saveDuelRecord(event);
return next(); return next();
...@@ -129,13 +116,6 @@ export class CloudReplayService { ...@@ -129,13 +116,6 @@ export class CloudReplayService {
return true; return true;
} }
private createRoomIdentifier() {
return cryptoRandomString({
length: 64,
type: 'alphanumeric',
});
}
private async saveDuelRecord(event: OnRoomWin) { private async saveDuelRecord(event: OnRoomWin) {
const database = this.ctx.database; const database = this.ctx.database;
if (!database) { if (!database) {
...@@ -151,10 +131,9 @@ export class CloudReplayService { ...@@ -151,10 +131,9 @@ export class CloudReplayService {
const duelRecordRepo = database.getRepository(DuelRecordEntity); const duelRecordRepo = database.getRepository(DuelRecordEntity);
try { try {
const now = new Date();
const record = duelRecordRepo.create({ const record = duelRecordRepo.create({
startTime: duelRecord.date, startTime: duelRecord.startTime,
endTime: now, endTime: duelRecord.endTime || new Date(),
name: room.name, name: room.name,
roomIdentifier: this.getRoomIdentifier(room), roomIdentifier: this.getRoomIdentifier(room),
hostInfo: room.hostinfo, hostInfo: room.hostinfo,
...@@ -201,22 +180,15 @@ export class CloudReplayService { ...@@ -201,22 +180,15 @@ export class CloudReplayService {
player.score = resolvePlayerScore(room, client); player.score = resolvePlayerScore(room, client);
player.startDeckBuffer = encodeDeckBase64(client.startDeck); player.startDeckBuffer = encodeDeckBase64(client.startDeck);
player.startDeckMainc = resolveStartDeckMainc(client); player.startDeckMainc = resolveStartDeckMainc(client);
player.currentDeckBuffer = encodeCurrentDeckBase64( player.currentDeckBuffer = encodeCurrentDeckBase64(room, client);
room, player.currentDeckMainc = resolveCurrentDeckMainc(room, client);
client, player.ingameDeckBuffer = encodeIngameDeckBase64(room, client);
wasSwapped, player.ingameDeckMainc = resolveIngameDeckMainc(room, client);
);
player.currentDeckMainc = resolveCurrentDeckMainc(room, client, wasSwapped);
player.ingameDeckBuffer = encodeIngameDeckBase64(room, client, wasSwapped);
player.ingameDeckMainc = resolveIngameDeckMainc(room, client, wasSwapped);
player.winner = room.getDuelPos(client) === winPlayer; player.winner = room.getDuelPos(client) === winPlayer;
return player; return player;
} }
private getRoomIdentifier(room: Room) { private getRoomIdentifier(room: Room) {
if (!room.identifier) {
room.identifier = this.createRoomIdentifier();
}
return room.identifier; return room.identifier;
} }
...@@ -553,7 +525,6 @@ export class CloudReplayService { ...@@ -553,7 +525,6 @@ export class CloudReplayService {
} }
private restoreDuelRecord(replay: DuelRecordEntity) { private restoreDuelRecord(replay: DuelRecordEntity) {
const isTag = this.isTagMode(replay.hostInfo);
const wasSwapped = this.resolveReplaySwappedByIsFirst(replay); const wasSwapped = this.resolveReplaySwappedByIsFirst(replay);
const seatCount = this.resolveSeatCount(replay.hostInfo); const seatCount = this.resolveSeatCount(replay.hostInfo);
const players = Array.from({ length: seatCount }, () => ({ const players = Array.from({ length: seatCount }, () => ({
...@@ -565,14 +536,22 @@ export class CloudReplayService { ...@@ -565,14 +536,22 @@ export class CloudReplayService {
for (const player of sortedPlayers) { for (const player of sortedPlayers) {
const deckBuffer = player.ingameDeckBuffer || player.currentDeckBuffer; const deckBuffer = player.ingameDeckBuffer || player.currentDeckBuffer;
const mainc = player.ingameDeckMainc ?? player.currentDeckMainc ?? 0; const mainc = player.ingameDeckMainc ?? player.currentDeckMainc ?? 0;
const ingamePos = resolveIngamePosBySeat(player.pos, isTag, wasSwapped); if (player.pos < 0 || player.pos >= seatCount) {
players[ingamePos] = { continue;
}
players[player.pos] = {
name: player.name, name: player.name,
deck: decodeDeckBase64(deckBuffer, mainc), deck: decodeDeckBase64(deckBuffer, mainc),
}; };
} }
const duelRecord = new DuelRecord(decodeSeedBase64(replay.seed), players); const duelRecord = new DuelRecord(
decodeSeedBase64(replay.seed),
players,
wasSwapped,
);
duelRecord.startTime = replay.startTime;
duelRecord.endTime = replay.endTime;
duelRecord.responses = decodeResponsesBase64(replay.responses); duelRecord.responses = decodeResponsesBase64(replay.responses);
return duelRecord; return duelRecord;
} }
......
...@@ -14,29 +14,6 @@ export function resolvePlayerScore(room: Room, client: Client) { ...@@ -14,29 +14,6 @@ export function resolvePlayerScore(room: Room, client: Client) {
return room.score[duelPos] || 0; return room.score[duelPos] || 0;
} }
function resolveTeamOffsetBit(isTag: boolean) {
return isTag ? 1 : 0;
}
export function resolveIngamePosBySeat(
pos: number,
isTag: boolean,
wasSwapped: boolean,
) {
if (!wasSwapped) {
return pos;
}
return pos ^ (0x1 << resolveTeamOffsetBit(isTag));
}
export function resolveRecordIngamePos(
room: Room,
client: Client,
wasSwapped: boolean,
) {
return resolveIngamePosBySeat(client.pos, room.isTag, wasSwapped);
}
export function resolveIsFirstPlayer( export function resolveIsFirstPlayer(
room: Room, room: Room,
client: Client, client: Client,
...@@ -94,49 +71,32 @@ export function resolveStartDeckMainc(client: Client) { ...@@ -94,49 +71,32 @@ export function resolveStartDeckMainc(client: Client) {
return client.startDeck?.main?.length || 0; return client.startDeck?.main?.length || 0;
} }
function resolveRecordDeck(room: Room, client: Client, wasSwapped = false) { function resolveRecordDeck(room: Room, client: Client) {
const ingamePos = resolveRecordIngamePos(room, client, wasSwapped); const duelRecordPlayer = room.lastDuelRecord?.players[client.pos];
const duelRecordPlayer = room.lastDuelRecord?.players[ingamePos];
return duelRecordPlayer?.deck; return duelRecordPlayer?.deck;
} }
function resolveCurrentDeck(room: Room, client: Client, wasSwapped = false) { function resolveCurrentDeck(room: Room, client: Client) {
if (client.deck) { if (client.deck) {
return client.deck; return client.deck;
} }
return resolveRecordDeck(room, client, wasSwapped); return resolveRecordDeck(room, client);
} }
export function resolveCurrentDeckMainc( export function resolveCurrentDeckMainc(room: Room, client: Client) {
room: Room, return resolveCurrentDeck(room, client)?.main?.length || 0;
client: Client,
wasSwapped = false,
) {
return resolveCurrentDeck(room, client, wasSwapped)?.main?.length || 0;
} }
export function encodeCurrentDeckBase64( export function encodeCurrentDeckBase64(room: Room, client: Client) {
room: Room, return encodeDeckBase64(resolveCurrentDeck(room, client));
client: Client,
wasSwapped = false,
) {
return encodeDeckBase64(resolveCurrentDeck(room, client, wasSwapped));
} }
export function encodeIngameDeckBase64( export function encodeIngameDeckBase64(room: Room, client: Client) {
room: Room, return encodeDeckBase64(resolveRecordDeck(room, client));
client: Client,
wasSwapped: boolean,
) {
return encodeDeckBase64(resolveRecordDeck(room, client, wasSwapped));
} }
export function resolveIngameDeckMainc( export function resolveIngameDeckMainc(room: Room, client: Client) {
room: Room, return resolveRecordDeck(room, client)?.main?.length || 0;
client: Client,
wasSwapped: boolean,
) {
return resolveRecordDeck(room, client, wasSwapped)?.main?.length || 0;
} }
export function decodeMessagesBase64(messagesBase64: string) { export function decodeMessagesBase64(messagesBase64: string) {
......
...@@ -15,8 +15,10 @@ export class DuelRecord { ...@@ -15,8 +15,10 @@ export class DuelRecord {
constructor( constructor(
public seed: number[], public seed: number[],
public players: { name: string; deck: YGOProDeck }[], public players: { name: string; deck: YGOProDeck }[],
public isSwapped: boolean,
) {} ) {}
date = new Date(); startTime = new Date();
endTime?: Date;
winPosition?: number; winPosition?: number;
responses: Buffer[] = []; responses: Buffer[] = [];
messages: YGOProMsgBase[] = []; messages: YGOProMsgBase[] = [];
...@@ -46,7 +48,22 @@ export class DuelRecord { ...@@ -46,7 +48,22 @@ export class DuelRecord {
} }
header.seedSequence = this.seed; header.seedSequence = this.seed;
// Set start_time (stored in hash field) as Unix timestamp in seconds // Set start_time (stored in hash field) as Unix timestamp in seconds
header.hash = Math.floor(this.date.getTime() / 1000); header.hash = Math.floor(this.startTime.getTime() / 1000);
const players = [...this.players];
if (this.isSwapped) {
const swapElements = (a: number, b: number) => {
const temp = players[a];
players[a] = players[b];
players[b] = temp;
};
if (isTag) {
swapElements(0, 2);
swapElements(1, 3);
} else {
swapElements(0, 1);
}
}
// Build YGOProYrp object // Build YGOProYrp object
// Note: players array is already swapped // Note: players array is already swapped
...@@ -62,22 +79,20 @@ export class DuelRecord { ...@@ -62,22 +79,20 @@ export class DuelRecord {
// (note the deck order: 0,1,3,2 - this matches ygopro's load order) // (note the deck order: 0,1,3,2 - this matches ygopro's load order)
const yrp = new YGOProYrp({ const yrp = new YGOProYrp({
header, header,
hostName: this.players[0]?.name || '', hostName: players[0]?.name || '',
clientName: isTag clientName: isTag ? players[3]?.name || '' : players[1]?.name || '',
? this.players[3]?.name || ''
: this.players[1]?.name || '',
startLp: room.hostinfo.start_lp, startLp: room.hostinfo.start_lp,
startHand: room.hostinfo.start_hand, startHand: room.hostinfo.start_hand,
drawCount: room.hostinfo.draw_count, drawCount: room.hostinfo.draw_count,
opt: calculateDuelOptions(room.hostinfo), opt: calculateDuelOptions(room.hostinfo),
hostDeck: this.toReplayDeck(this.players[0]?.deck), hostDeck: this.toReplayDeck(players[0]?.deck),
clientDeck: isTag clientDeck: isTag
? this.toReplayDeck(this.players[2]?.deck) ? this.toReplayDeck(players[2]?.deck)
: this.toReplayDeck(this.players[1]?.deck), : this.toReplayDeck(players[1]?.deck),
tagHostName: isTag ? this.players[1]?.name || '' : null, tagHostName: isTag ? players[1]?.name || '' : null,
tagClientName: isTag ? this.players[2]?.name || '' : null, tagClientName: isTag ? players[2]?.name || '' : null,
tagHostDeck: isTag ? this.toReplayDeck(this.players[1]?.deck) : null, tagHostDeck: isTag ? this.toReplayDeck(players[1]?.deck) : null,
tagClientDeck: isTag ? this.toReplayDeck(this.players[3]?.deck) : null, tagClientDeck: isTag ? this.toReplayDeck(players[3]?.deck) : null,
singleScript: null, singleScript: null,
responses: this.responses.map((buf) => new Uint8Array(buf)), responses: this.responses.map((buf) => new Uint8Array(buf)),
}); });
......
...@@ -106,6 +106,9 @@ import { OnRoomSidingReady } from './room-event/on-room-siding-ready'; ...@@ -106,6 +106,9 @@ import { OnRoomSidingReady } from './room-event/on-room-siding-ready';
import { OnRoomFinger } from './room-event/on-room-finger'; import { OnRoomFinger } from './room-event/on-room-finger';
import { OnRoomSelectTp } from './room-event/on-room-select-tp'; import { OnRoomSelectTp } from './room-event/on-room-select-tp';
import { RoomCheckDeck } from './room-event/room-check-deck'; import { RoomCheckDeck } from './room-event/room-check-deck';
import cryptoRandomString from 'crypto-random-string';
import { pick } from 'koishi';
import { get } from 'http';
const { OcgcoreScriptConstants } = _OcgcoreConstants; const { OcgcoreScriptConstants } = _OcgcoreConstants;
...@@ -118,6 +121,11 @@ export class Room { ...@@ -118,6 +121,11 @@ export class Room {
private partialHostinfo: Partial<HostInfo> = {}, private partialHostinfo: Partial<HostInfo> = {},
) {} ) {}
identifier = cryptoRandomString({
length: 64,
type: 'alphanumeric',
});
createTime = new Date();
private logger = this.ctx.createLogger(`Room:${this.name}`); private logger = this.ctx.createLogger(`Room:${this.name}`);
hostinfo = this.ctx hostinfo = this.ctx
...@@ -512,6 +520,7 @@ export class Room { ...@@ -512,6 +520,7 @@ export class Room {
const lastDuelRecord = this.lastDuelRecord; const lastDuelRecord = this.lastDuelRecord;
if (lastDuelRecord) { if (lastDuelRecord) {
lastDuelRecord.winPosition = duelPos; lastDuelRecord.winPosition = duelPos;
lastDuelRecord.endTime = new Date();
} }
if (typeof forceWinMatch === 'number') { if (typeof forceWinMatch === 'number') {
const loseDuelPos = (1 - duelPos) as 0 | 1; const loseDuelPos = (1 - duelPos) as 0 | 1;
...@@ -1106,6 +1115,9 @@ export class Room { ...@@ -1106,6 +1115,9 @@ export class Room {
private registry: Record<string, string> = {}; private registry: Record<string, string> = {};
turnCount = 0; turnCount = 0;
turnIngamePos = 0; turnIngamePos = 0;
get turnPos() {
return this.getIngameDuelPosByDuelPos(this.turnIngamePos);
}
phase = undefined; phase = undefined;
timerState = new TimerState(); timerState = new TimerState();
lastResponseRequestMsg?: YGOProMsgResponseBase; lastResponseRequestMsg?: YGOProMsgResponseBase;
...@@ -1250,17 +1262,8 @@ export class Room { ...@@ -1250,17 +1262,8 @@ export class Room {
const duelRecord = new DuelRecord( const duelRecord = new DuelRecord(
generateSeed(), generateSeed(),
this.playingPlayers.map((p) => ({ name: p.name, deck: p.deck! })), this.playingPlayers.map((p) => ({ name: p.name, deck: p.deck! })),
this.isPosSwapped,
); );
if (this.isPosSwapped) {
this.playingPlayers.forEach((p) => {
// Keep full seat order (0/1/2/3 in tag), matching tag_duel.cpp swap:
// swap(0,2) and swap(1,3)
duelRecord.players[this.getIngamePos(p)] = {
name: p.name,
deck: p.deck!,
};
});
}
if (!this.hostinfo.no_shuffle_deck) { if (!this.hostinfo.no_shuffle_deck) {
const shuffledDecks = shuffleDecksBySeed( const shuffledDecks = shuffleDecksBySeed(
duelRecord.players.map((p) => p.deck), duelRecord.players.map((p) => p.deck),
...@@ -1842,4 +1845,44 @@ export class Room { ...@@ -1842,4 +1845,44 @@ export class Room {
// TODO: teammate surrender in tag duel // TODO: teammate surrender in tag duel
return this.win({ player: 1 - this.getIngameDuelPos(client), type: 0x0 }); return this.win({ player: 1 - this.getIngameDuelPos(client), type: 0x0 });
} }
async getCurrentFieldInfo() {
if (!this.ocgcore) {
return undefined;
}
const fieldInfo = await this.ocgcore.queryFieldInfo();
const players = fieldInfo?.field?.players;
if (!players?.length) {
return undefined;
}
return players.map((p) => ({
lp: p.lp,
cardCount:
p.handCount + [...p.mzone, ...p.szone].filter((p) => p.occupied).length,
}));
}
async getInfo() {
const fieldInfo = await this.getCurrentFieldInfo();
return {
...pick(this as Room, [
'identifier',
'name',
'hostinfo',
'duelStage',
'createTime',
]),
watcherCount: this.watchers.size,
players: this.playingPlayers.map((p) => ({
...pick(p, ['name', 'pos', 'ip']),
deck: p.deck?.toYGOMobileDeckURL(),
startDeck: p.startDeck?.toYGOMobileDeckURL(),
lp: fieldInfo?.[this.getIngameDuelPos(p)]?.lp,
cardCount: fieldInfo?.[this.getIngameDuelPos(p)]?.cardCount,
})),
duels: this.duelRecords.map((d) => ({
...pick(d, ['startTime', 'endTime'])
})),
};
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment