Commit 12b95982 authored by nanahira's avatar nanahira

room thing

parent f51e04d7
......@@ -19,12 +19,13 @@
"pino": "^10.3.1",
"pino-pretty": "^13.1.3",
"rxjs": "^7.8.2",
"typed-reflector": "^1.0.14",
"ws": "^8.19.0",
"yaml": "^2.8.2",
"ygopro-cdb-encode": "^1.0.1",
"ygopro-deck-encode": "^1.0.15",
"ygopro-lflist-encode": "^1.0.3",
"ygopro-msg-encode": "^1.1.8",
"ygopro-msg-encode": "^1.1.9",
"ygopro-yrp-encode": "^1.0.1"
},
"devDependencies": {
......@@ -74,6 +75,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
......@@ -1600,6 +1602,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz",
"integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
......@@ -1683,6 +1686,7 @@
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
......@@ -2163,6 +2167,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -2553,6 +2558,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -3213,6 +3219,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
......@@ -3269,6 +3276,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
......@@ -4139,6 +4147,7 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz",
"integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.0",
"cluster-key-slot": "^1.1.0",
......@@ -4383,6 +4392,7 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
......@@ -5788,6 +5798,7 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
......@@ -6636,6 +6647,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -6788,6 +6800,7 @@
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
......@@ -6897,6 +6910,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......@@ -7346,9 +7360,9 @@
}
},
"node_modules/ygopro-msg-encode": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ygopro-msg-encode/-/ygopro-msg-encode-1.1.8.tgz",
"integrity": "sha512-KrGUuyRLftmiD51KNRs/IRInsw+Xz/ISlCTmg+2W+6VLD6MtO9qF9SgBI78tXJ4/zsxifYekSDYfMuzJf2PuUA==",
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/ygopro-msg-encode/-/ygopro-msg-encode-1.1.9.tgz",
"integrity": "sha512-sY/jveNdcN25pcTsf8MpC0hvZnNQ2vTpIjuVhP3zjAZw8Uw8+8kEqTZSFEnnpS4QAmu3QYSEaFH1Rb0CfG2jFA==",
"license": "MIT",
"dependencies": {
"typed-reflector": "^1.0.14",
......
......@@ -3,7 +3,7 @@ import { ConfigService } from './services/config';
import { Logger } from './services/logger';
import { Emitter } from './services/emitter';
import { SSLFinder } from './services/ssl-finder';
import { ClientHandler } from './services/client-handler';
import { ClientHandler } from './client/client-handler';
import { IpResolver } from './services/ip-resolver';
import { HttpClient } from './services/http-client';
import { Chnroute } from './services/chnroute';
......@@ -14,6 +14,7 @@ import { WsServer } from './transport/ws/server';
import { ClientVersionCheck } from './services/client-version-check';
import { AragamiService } from './services/aragami';
import { RoomManager } from './room/room-manager';
import { RoomEventRegister } from './room/room-event-register';
import { DefaultHostInfoProvider } from './room/default-hostinfo-provder';
import { YGOProResourceLoader } from './services/ygopro-resource-loader';
......@@ -41,6 +42,7 @@ export const app = core
.provide(DefaultHostInfoProvider)
.provide(YGOProResourceLoader)
.provide(RoomManager)
.provide(RoomEventRegister)
.define();
app.middleware(YGOProCtosJoinGame, async (msg, client, _next) => {
......
......@@ -6,8 +6,8 @@ import {
YGOProCtosPlayerInfo,
} from 'ygopro-msg-encode';
import { Context } from '../app';
import { Client } from '../client';
import { IpResolver } from './ip-resolver';
import { Client } from './client';
import { IpResolver } from '../services/ip-resolver';
import { WsClient } from '../transport/ws/client';
import { forkJoin, filter, takeUntil, timeout, firstValueFrom } from 'rxjs';
......
import { filter, merge, Observable, Subject } from 'rxjs';
import { map, share, take, takeUntil } from 'rxjs/operators';
import { Context } from './app';
import { Context } from '../app';
import {
YGOProCtos,
YGOProStocBase,
......@@ -13,9 +13,9 @@ import {
YGOProStocHsPlayerChange,
PlayerChangeState,
} from 'ygopro-msg-encode';
import { YGOProProtoPipe } from './utility/ygopro-proto-pipe';
import { I18nService } from './services/i18n';
import { Chnroute } from './services/chnroute';
import { YGOProProtoPipe } from '../utility/ygopro-proto-pipe';
import { I18nService } from '../services/i18n';
import { Chnroute } from '../services/chnroute';
import YGOProDeck from 'ygopro-deck-encode';
export abstract class Client {
......@@ -124,6 +124,7 @@ export abstract class Client {
established = false;
// in room
roomName?: string;
isHost = false;
pos = -1;
deck?: YGOProDeck;
......
import 'reflect-metadata';
import { Context } from '../app';
import { getSpecificFields } from '../utility/metadata';
import { Room } from './room';
import { Client } from '../client/client';
import { YGOProCtosBase } from 'ygopro-msg-encode';
import { RoomManager } from './room-manager';
export class RoomEventRegister {
private logger = this.ctx.createLogger('RoomEventRegister');
constructor(private ctx: Context) {
this.registerRoomEvents();
}
private registerRoomEvents() {
const roomMethods = getSpecificFields('roomMethod', Room);
for (const { key: method } of roomMethods) {
// 获取方法的参数类型
const paramTypes: any[] =
Reflect.getMetadata('design:paramtypes', Room.prototype, method) || [];
// 如果找不到参数类型,输出警告
if (!paramTypes || paramTypes.length === 0) {
this.logger.warn(
`Method ${method} has no parameter types metadata. Make sure tsconfig has "emitDecoratorMetadata": true`,
);
continue;
}
// 查找 Client 类型的参数和 YGOProCtosBase 派生类的参数
let clientParamIndex = -1;
let ctosParamIndex = -1;
let ctosParamType: any = null;
for (let i = 0; i < paramTypes.length; i++) {
const paramType = paramTypes[i];
if (paramType === Client) {
clientParamIndex = i;
} else if (paramType && paramType.prototype instanceof YGOProCtosBase) {
ctosParamIndex = i;
ctosParamType = paramType;
}
}
// 如果没有 YGOProCtosBase 派生类参数,跳过
if (ctosParamIndex === -1 || !ctosParamType) {
continue;
}
// 注册 middleware
this.ctx.middleware(ctosParamType, async (msg, client, next) => {
// 如果 client 没有关联的 room,直接跳过
if (!client.roomName) {
return next();
}
// 通过 roomName 查找 room
const roomManager = this.ctx.get(() => RoomManager);
const room = roomManager.findByName(client.roomName);
if (!room) {
return next();
}
// 构造参数数组
const args = new Array(paramTypes.length);
for (let i = 0; i < paramTypes.length; i++) {
if (i === clientParamIndex) {
args[i] = client;
} else if (i === ctosParamIndex) {
args[i] = msg;
}
}
// 调用 Room 实例的方法
await (room as any)[method](...args);
return next();
});
}
}
}
......@@ -10,7 +10,7 @@ import { DefaultHostInfoProvider } from './default-hostinfo-provder';
import { CardReaderFinalized } from 'koishipro-core.js';
import { YGOProResourceLoader } from '../services/ygopro-resource-loader';
import { blankLFList } from '../utility/blank-lflist';
import { Client } from '../client';
import { Client } from '../client/client';
export type RoomFinalizor = (self: Room) => Awaitable<any>;
......@@ -93,6 +93,7 @@ export class Room {
}
async join(client: Client) {
client.roomName = this.name;
client.disconnect$.subscribe(({ bySystem }) =>
this.onPlayerDisconnect(client, bySystem),
);
......@@ -122,7 +123,7 @@ export class Room {
}
}
async onPlayerDisconnect(client: Client, bySystem: boolean) {
async onPlayerDisconnect(client: Client) {
if (client.pos === NetPlayerType.OBSERVER) {
this.watchers.delete(client);
for (const p of this.allPlayers) {
......@@ -130,5 +131,6 @@ export class Room {
}
return;
}
client.roomName = undefined;
}
}
import { AppContext, ProtoMiddlewareDispatcher } from 'nfkit';
import { Client } from '../client';
import { Client } from '../client/client';
export class Emitter extends ProtoMiddlewareDispatcher<[Client]> {
constructor(private ctx: AppContext) {
......
import { Context } from '../app';
import { Client } from '../client';
import { Client } from '../client/client';
import * as ipaddr from 'ipaddr.js';
import { convertStringArray } from '../utility/convert-string-array';
......@@ -13,7 +13,7 @@ export class IpResolver {
const proxies = convertStringArray(
this.ctx.getConfig('TRUSTED_PROXIES', '127.0.0.0/8,::1/128'),
);
for (const trusted of proxies) {
try {
this.trustedProxies.push(ipaddr.parseCIDR(trusted));
......
......@@ -34,7 +34,7 @@ export class YGOProResourceLoader {
const lflist = new YGOProLFList().fromText(
Buffer.from(buf).toString('utf-8'),
);
for (const item of lflist.items) {
for (const item of lflist.items) {
yield item;
}
}
......
......@@ -2,7 +2,7 @@ import { Socket } from 'node:net';
import { Observable, fromEvent, merge } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { Context } from '../../app';
import { Client } from '../../client';
import { Client } from '../../client/client';
export class TcpClient extends Client {
constructor(
......
import { Server as NetServer, Socket, createServer } from 'node:net';
import { Context } from '../../app';
import { ClientHandler } from '../../services/client-handler';
import { ClientHandler } from '../../client/client-handler';
import { TcpClient } from './client';
export class TcpServer {
......
......@@ -4,7 +4,7 @@ import { Observable, fromEvent, merge } from 'rxjs';
import { map, take } from 'rxjs/operators';
import WebSocket, { RawData } from 'ws';
import { Context } from '../../app';
import { Client } from '../../client';
import { Client } from '../../client/client';
export class WsClient extends Client {
constructor(
......
......@@ -2,7 +2,7 @@ import { IncomingMessage, createServer as createHttpServer } from 'node:http';
import { createServer as createHttpsServer } from 'node:https';
import { Server as WebSocketServer } from 'ws';
import { Context } from '../../app';
import { ClientHandler } from '../../services/client-handler';
import { ClientHandler } from '../../client/client-handler';
import { SSLFinder } from '../../services/ssl-finder';
import { WsClient } from './client';
import { WebSocket } from 'ws';
......@@ -67,7 +67,8 @@ export class WsServer {
private handleConnection(ws: WebSocket, req: IncomingMessage): void {
const client = new WsClient(this.ctx, ws, req);
if (this.ctx.get(() => IpResolver).setClientIp(client, client.xffIp())) return;
if (this.ctx.get(() => IpResolver).setClientIp(client, client.xffIp()))
return;
client.hostname = req.headers.host?.split(':')[0] || '';
const handler = this.ctx.get(() => ClientHandler);
handler.handleClient(client).catch((err) => {
......
import { Metadata } from './metadata';
export const RoomMethod = () =>
Metadata.set('roomMethod', true, 'roomMethodKeys');
import { MetadataSetter, Reflector } from 'typed-reflector';
interface MetadataMap {
roomMethod: boolean;
}
type MetadataArrayMap = {
[K in keyof MetadataMap as `${K & string}Keys`]: string;
};
export const Metadata = new MetadataSetter<MetadataMap, MetadataArrayMap>();
export const reflector = new Reflector<MetadataMap, MetadataArrayMap>();
export function getSpecificFields<K extends keyof MetadataMap>(
key: K,
target: any,
): { key: string; metadata: MetadataMap[K] }[] {
const arrayKey = `${key}Keys` as keyof MetadataArrayMap;
const keys = reflector.getArray(arrayKey, target);
return keys
.map((k) => ({
key: k,
metadata: reflector.get(key, target, k),
}))
.filter((item) => item.metadata !== undefined) as {
key: string;
metadata: MetadataMap[K];
}[];
}
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