Commit 76b99077 authored by nanahira's avatar nanahira

wip

parent 9a20f3ef
......@@ -42,3 +42,4 @@ Dockerfile
.dockerignore
/tests
/ssl
/ygopro
......@@ -37,3 +37,4 @@ lerna-debug.log*
/output
/config.yaml
/ssl
/ygopro
......@@ -16,6 +16,7 @@ import {
firstValueFrom,
merge,
map,
take,
} from 'rxjs';
import { YGOProCtosDisconnect } from '../utility/ygopro-ctos-disconnect';
......@@ -52,8 +53,8 @@ export class ClientHandler {
YGOProCtosExternalAddress,
YGOProCtosPlayerInfo,
YGOProCtosJoinGame,
].some((allowed) => msg instanceof allowed);
if (client.established !== isPreHandshakeMsg) {
].some((allowed) => (msg instanceof allowed) as boolean);
if (client.established === isPreHandshakeMsg) {
// disallow any messages before handshake is complete, except for the ones needed for handshake
return undefined;
}
......@@ -85,7 +86,7 @@ export class ClientHandler {
await this.ctx.dispatch(msg, client);
} catch (e) {
this.logger.warn(
`Error dispatching message ${msg.constructor.name} from ${client.loggingIp()}: ${(e as Error).message}`,
`Error dispatching message ${msg.constructor.name} from ${client.loggingIp()}: ${(e as Error).stack}`,
);
}
});
......@@ -93,16 +94,19 @@ export class ClientHandler {
const handshake$ = forkJoin([
client.receive$.pipe(
filter((msg) => msg instanceof YGOProCtosPlayerInfo),
take(1),
takeUntil(client.disconnect$),
),
client.receive$.pipe(
filter((msg) => msg instanceof YGOProCtosJoinGame),
take(1),
takeUntil(client.disconnect$),
),
]).pipe(timeout(5000), takeUntil(client.disconnect$));
firstValueFrom(handshake$)
.then(() => {
this.logger.debug({ client: client.name }, 'Handshake completed');
client.established = true;
})
.catch(() => {
......
......@@ -3,11 +3,13 @@ import { Context } from '../app';
import { RoomManager } from '../room/room-manager';
export class JoinRoom {
private logger = this.ctx.createLogger(this.constructor.name);
constructor(private ctx: Context) {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!msg.pass) {
return next();
}
this.logger.debug({ name: client.name, pass: msg.pass }, 'Joining room');
const roomManager = this.ctx.get(() => RoomManager);
const room = await roomManager.findOrCreateByName(msg.pass);
return room.join(client);
......
......@@ -10,10 +10,12 @@ const REPLAY_ID_YRP2 = 0x32707279;
const PRO_VERSION = 0x1362;
export class DuelRecord {
seed: number[];
players: { name: string; deck: YGOProDeck }[];
constructor(
public seed: number[],
public players: { name: string; deck: YGOProDeck }[],
) {}
winPosition?: number;
responses: Buffer[];
responses: Buffer[] = [];
toYrp(room: Room) {
const isTag = room.isTag;
......
......@@ -27,6 +27,8 @@ import {
YGOProStocDuelEnd,
YGOProStocChangeSide,
YGOProStocWaitingSide,
YGOProCtosTpResult,
TurnPlayerResult,
} from 'ygopro-msg-encode';
import { DefaultHostInfoProvider } from './default-hostinfo-provder';
import { CardReaderFinalized } from 'koishipro-core.js';
......@@ -45,11 +47,12 @@ import { OnRoomLeavePlayer } from './room-event/on-room-leave-player';
import { OnRoomLeaveObserver } from './room-event/on-room-leave-observer';
import { OnRoomMatchStart } from './room-event/on-room-match-start';
import { OnRoomGameStart } from './room-event/on-room-game-start';
// import { OnRoomDuelStart } from './room-event/on-room-duel-start'; // 备用事件,暂未使用
import YGOProDeck from 'ygopro-deck-encode';
import { checkDeck, checkChangeSide } from '../utility/check-deck';
import { DuelRecord } from './duel-record';
import { last } from 'rxjs';
import { RoomEvent } from './room-event/room-event';
import { generateSeed } from '../utility/generate-seed';
import { OnRoomDuelStart } from './room-event/on-room-duel-start';
export type RoomFinalizor = (self: Room) => Awaitable<any>;
......@@ -336,6 +339,7 @@ export class Room {
.forEach((p) => p.send(new YGOProStocDuelStart()));
}
const duelPos = this.getSwappedDuelPosByDuelPos(winMsg.player!);
this.isPosSwapped = false;
await Promise.all(
this.allPlayers.map((p) =>
p.send(
......@@ -513,15 +517,21 @@ export class Room {
const oldPos = client.pos;
// 从当前位置的下一个位置开始循环查找空位
let nextPos = (oldPos + 1) % 4;
while (this.players[nextPos]) {
nextPos = (nextPos + 1) % 4;
}
// 移动到新位置
this.players[oldPos] = undefined;
this.players[firstEmptyPlayerSlot] = client;
client.pos = firstEmptyPlayerSlot;
this.players[nextPos] = client;
client.pos = nextPos;
// 发送 PlayerChange 给所有人
const changeMsg = new YGOProStocHsPlayerChange().fromPartial({
playerPosition: oldPos,
playerState: firstEmptyPlayerSlot,
playerState: nextPos,
});
this.allPlayers.forEach((p) => p.send(changeMsg));
......@@ -713,7 +723,7 @@ export class Room {
if (![DuelStage.Finger, DuelStage.Siding].includes(this.duelStage)) {
return false;
}
if (this.winnerPositions.length === 0) {
if (this.duelRecords.length === 0) {
this.allPlayers.forEach((p) => p.send(new YGOProStocDuelStart()));
const displayCountDecks = [0, 1].map(
(p) => this.getDuelPosPlayers(p)[0].deck!,
......@@ -754,7 +764,7 @@ export class Room {
}
// 触发事件
if (this.winnerPositions.length === 0) {
if (this.duelRecords.length === 0) {
// 触发比赛开始事件(第一局)
await this.ctx.dispatch(
new OnRoomMatchStart(this),
......@@ -767,4 +777,37 @@ export class Room {
return true;
}
@RoomMethod()
private async onDuelStart(client: Client, msg: YGOProCtosTpResult) {
if (this.duelStage !== DuelStage.FirstGo) {
return;
}
if (client !== this.firstgoPlayer) {
return;
}
this.isPosSwapped =
(msg.res === TurnPlayerResult.FIRST) !== (this.getDuelPos(client) === 0);
const duelRecord = new DuelRecord(
generateSeed(),
this.playingPlayers.map((p) => ({ name: p.name, deck: p.deck! })),
);
if (this.isPosSwapped) {
this.playingPlayers.forEach((p) => {
duelRecord.players[this.getSwappedDuelPos(p)] = {
name: p.name,
deck: p.deck!,
};
});
}
this.duelRecords.push(duelRecord);
await this.ctx.dispatch(
new OnRoomDuelStart(this),
this.getDuelPosPlayers(this.getSwappedDuelPos(0))[0],
);
this.allPlayers.forEach((p) => p.sendChat('TODO: duel start'));
return this.finalize();
}
}
......@@ -13,7 +13,13 @@ export class YGOProResourceLoader {
]);
extraScriptPaths = loadPaths(this.ctx.getConfig('EXTRA_SCRIPT_PATH'));
private logger = this.ctx.createLogger(this.constructor.name);
async getCardReader() {
this.logger.debug(
{ ygoproPaths: this.ygoproPaths, sql: typeof this.ctx.SQL.Database },
'Getting card reader',
);
return DirCardReader(this.ctx.SQL, ...this.ygoproPaths);
}
......
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