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 {
ChatColor,
......@@ -15,7 +14,7 @@ import {
} from 'ygopro-msg-encode';
import { Context } from '../../app';
import { Client } from '../../client';
import { DuelRecord, OnRoomCreate, OnRoomWin, Room } from '../../room';
import { DuelRecord, OnRoomWin, Room } from '../../room';
import { ClientKeyProvider } from '../client-key-provider';
import { MenuEntry, MenuManager } from '../menu-manager';
import { DuelRecordEntity } from './duel-record.entity';
......@@ -33,7 +32,6 @@ import {
encodeSeedBase64,
resolveCurrentDeckMainc,
resolveIngameDeckMainc,
resolveIngamePosBySeat,
resolveIsFirstPlayer,
resolvePlayerScore,
resolveStartDeckMainc,
......@@ -52,12 +50,6 @@ type ReplayDetailMenuOptions = {
type DirectReplayPassAction = 'detail' | 'watch' | 'downloadYrp';
declare module '../../room' {
interface Room {
identifier?: string;
}
}
declare module '../../client' {
interface Client {
cloudReplayPageCursors?: Array<number | null>;
......@@ -72,11 +64,6 @@ export class CloudReplayService {
private menuManager = this.ctx.get(() => MenuManager);
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) => {
await this.saveDuelRecord(event);
return next();
......@@ -129,13 +116,6 @@ export class CloudReplayService {
return true;
}
private createRoomIdentifier() {
return cryptoRandomString({
length: 64,
type: 'alphanumeric',
});
}
private async saveDuelRecord(event: OnRoomWin) {
const database = this.ctx.database;
if (!database) {
......@@ -151,10 +131,9 @@ export class CloudReplayService {
const duelRecordRepo = database.getRepository(DuelRecordEntity);
try {
const now = new Date();
const record = duelRecordRepo.create({
startTime: duelRecord.date,
endTime: now,
startTime: duelRecord.startTime,
endTime: duelRecord.endTime || new Date(),
name: room.name,
roomIdentifier: this.getRoomIdentifier(room),
hostInfo: room.hostinfo,
......@@ -201,22 +180,15 @@ export class CloudReplayService {
player.score = resolvePlayerScore(room, client);
player.startDeckBuffer = encodeDeckBase64(client.startDeck);
player.startDeckMainc = resolveStartDeckMainc(client);
player.currentDeckBuffer = encodeCurrentDeckBase64(
room,
client,
wasSwapped,
);
player.currentDeckMainc = resolveCurrentDeckMainc(room, client, wasSwapped);
player.ingameDeckBuffer = encodeIngameDeckBase64(room, client, wasSwapped);
player.ingameDeckMainc = resolveIngameDeckMainc(room, client, wasSwapped);
player.currentDeckBuffer = encodeCurrentDeckBase64(room, client);
player.currentDeckMainc = resolveCurrentDeckMainc(room, client);
player.ingameDeckBuffer = encodeIngameDeckBase64(room, client);
player.ingameDeckMainc = resolveIngameDeckMainc(room, client);
player.winner = room.getDuelPos(client) === winPlayer;
return player;
}
private getRoomIdentifier(room: Room) {
if (!room.identifier) {
room.identifier = this.createRoomIdentifier();
}
return room.identifier;
}
......@@ -553,7 +525,6 @@ export class CloudReplayService {
}
private restoreDuelRecord(replay: DuelRecordEntity) {
const isTag = this.isTagMode(replay.hostInfo);
const wasSwapped = this.resolveReplaySwappedByIsFirst(replay);
const seatCount = this.resolveSeatCount(replay.hostInfo);
const players = Array.from({ length: seatCount }, () => ({
......@@ -565,14 +536,22 @@ export class CloudReplayService {
for (const player of sortedPlayers) {
const deckBuffer = player.ingameDeckBuffer || player.currentDeckBuffer;
const mainc = player.ingameDeckMainc ?? player.currentDeckMainc ?? 0;
const ingamePos = resolveIngamePosBySeat(player.pos, isTag, wasSwapped);
players[ingamePos] = {
if (player.pos < 0 || player.pos >= seatCount) {
continue;
}
players[player.pos] = {
name: player.name,
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);
return duelRecord;
}
......
......@@ -14,29 +14,6 @@ export function resolvePlayerScore(room: Room, client: Client) {
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(
room: Room,
client: Client,
......@@ -94,49 +71,32 @@ export function resolveStartDeckMainc(client: Client) {
return client.startDeck?.main?.length || 0;
}
function resolveRecordDeck(room: Room, client: Client, wasSwapped = false) {
const ingamePos = resolveRecordIngamePos(room, client, wasSwapped);
const duelRecordPlayer = room.lastDuelRecord?.players[ingamePos];
function resolveRecordDeck(room: Room, client: Client) {
const duelRecordPlayer = room.lastDuelRecord?.players[client.pos];
return duelRecordPlayer?.deck;
}
function resolveCurrentDeck(room: Room, client: Client, wasSwapped = false) {
function resolveCurrentDeck(room: Room, client: Client) {
if (client.deck) {
return client.deck;
}
return resolveRecordDeck(room, client, wasSwapped);
return resolveRecordDeck(room, client);
}
export function resolveCurrentDeckMainc(
room: Room,
client: Client,
wasSwapped = false,
) {
return resolveCurrentDeck(room, client, wasSwapped)?.main?.length || 0;
export function resolveCurrentDeckMainc(room: Room, client: Client) {
return resolveCurrentDeck(room, client)?.main?.length || 0;
}
export function encodeCurrentDeckBase64(
room: Room,
client: Client,
wasSwapped = false,
) {
return encodeDeckBase64(resolveCurrentDeck(room, client, wasSwapped));
export function encodeCurrentDeckBase64(room: Room, client: Client) {
return encodeDeckBase64(resolveCurrentDeck(room, client));
}
export function encodeIngameDeckBase64(
room: Room,
client: Client,
wasSwapped: boolean,
) {
return encodeDeckBase64(resolveRecordDeck(room, client, wasSwapped));
export function encodeIngameDeckBase64(room: Room, client: Client) {
return encodeDeckBase64(resolveRecordDeck(room, client));
}
export function resolveIngameDeckMainc(
room: Room,
client: Client,
wasSwapped: boolean,
) {
return resolveRecordDeck(room, client, wasSwapped)?.main?.length || 0;
export function resolveIngameDeckMainc(room: Room, client: Client) {
return resolveRecordDeck(room, client)?.main?.length || 0;
}
export function decodeMessagesBase64(messagesBase64: string) {
......
......@@ -15,8 +15,10 @@ export class DuelRecord {
constructor(
public seed: number[],
public players: { name: string; deck: YGOProDeck }[],
public isSwapped: boolean,
) {}
date = new Date();
startTime = new Date();
endTime?: Date;
winPosition?: number;
responses: Buffer[] = [];
messages: YGOProMsgBase[] = [];
......@@ -46,7 +48,22 @@ export class DuelRecord {
}
header.seedSequence = this.seed;
// 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
// Note: players array is already swapped
......@@ -62,22 +79,20 @@ export class DuelRecord {
// (note the deck order: 0,1,3,2 - this matches ygopro's load order)
const yrp = new YGOProYrp({
header,
hostName: this.players[0]?.name || '',
clientName: isTag
? this.players[3]?.name || ''
: this.players[1]?.name || '',
hostName: players[0]?.name || '',
clientName: isTag ? players[3]?.name || '' : players[1]?.name || '',
startLp: room.hostinfo.start_lp,
startHand: room.hostinfo.start_hand,
drawCount: room.hostinfo.draw_count,
opt: calculateDuelOptions(room.hostinfo),
hostDeck: this.toReplayDeck(this.players[0]?.deck),
hostDeck: this.toReplayDeck(players[0]?.deck),
clientDeck: isTag
? this.toReplayDeck(this.players[2]?.deck)
: this.toReplayDeck(this.players[1]?.deck),
tagHostName: isTag ? this.players[1]?.name || '' : null,
tagClientName: isTag ? this.players[2]?.name || '' : null,
tagHostDeck: isTag ? this.toReplayDeck(this.players[1]?.deck) : null,
tagClientDeck: isTag ? this.toReplayDeck(this.players[3]?.deck) : null,
? this.toReplayDeck(players[2]?.deck)
: this.toReplayDeck(players[1]?.deck),
tagHostName: isTag ? players[1]?.name || '' : null,
tagClientName: isTag ? players[2]?.name || '' : null,
tagHostDeck: isTag ? this.toReplayDeck(players[1]?.deck) : null,
tagClientDeck: isTag ? this.toReplayDeck(players[3]?.deck) : null,
singleScript: null,
responses: this.responses.map((buf) => new Uint8Array(buf)),
});
......
......@@ -106,6 +106,9 @@ import { OnRoomSidingReady } from './room-event/on-room-siding-ready';
import { OnRoomFinger } from './room-event/on-room-finger';
import { OnRoomSelectTp } from './room-event/on-room-select-tp';
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;
......@@ -118,6 +121,11 @@ export class Room {
private partialHostinfo: Partial<HostInfo> = {},
) {}
identifier = cryptoRandomString({
length: 64,
type: 'alphanumeric',
});
createTime = new Date();
private logger = this.ctx.createLogger(`Room:${this.name}`);
hostinfo = this.ctx
......@@ -512,6 +520,7 @@ export class Room {
const lastDuelRecord = this.lastDuelRecord;
if (lastDuelRecord) {
lastDuelRecord.winPosition = duelPos;
lastDuelRecord.endTime = new Date();
}
if (typeof forceWinMatch === 'number') {
const loseDuelPos = (1 - duelPos) as 0 | 1;
......@@ -1106,6 +1115,9 @@ export class Room {
private registry: Record<string, string> = {};
turnCount = 0;
turnIngamePos = 0;
get turnPos() {
return this.getIngameDuelPosByDuelPos(this.turnIngamePos);
}
phase = undefined;
timerState = new TimerState();
lastResponseRequestMsg?: YGOProMsgResponseBase;
......@@ -1250,17 +1262,8 @@ export class Room {
const duelRecord = new DuelRecord(
generateSeed(),
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) {
const shuffledDecks = shuffleDecksBySeed(
duelRecord.players.map((p) => p.deck),
......@@ -1842,4 +1845,44 @@ export class Room {
// TODO: teammate surrender in tag duel
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