@rpgjs/server 5.0.0-beta.2 → 5.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Player/SkillManager.d.ts +13 -4
- package/dist/RpgServer.d.ts +8 -0
- package/dist/index.js +337 -885
- package/dist/index.js.map +1 -1
- package/dist/{module-CaCW1SDh.js → module-BmvXIvlE.js} +97 -69
- package/dist/{module-CaCW1SDh.js.map → module-BmvXIvlE.js.map} +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +6 -6
- package/src/Gui/MenuGui.ts +30 -20
- package/src/Player/ParameterManager.ts +4 -1
- package/src/Player/Player.ts +2 -0
- package/src/Player/SkillManager.ts +42 -5
- package/src/RpgServer.ts +9 -0
- package/src/rooms/lobby.ts +6 -1
- package/src/rooms/map.ts +17 -0
- package/tests/gui.spec.ts +76 -0
- package/tests/skill.spec.ts +48 -0
package/dist/node/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-
|
|
1
|
+
import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-BmvXIvlE.js";
|
|
2
2
|
//#region src/node/connection.ts
|
|
3
3
|
function readEnvVariable(name) {
|
|
4
4
|
const value = globalThis.process?.env?.[name];
|
package/dist/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/node/connection.ts","../../src/node/room.ts","../../src/node/transport.ts"],"sourcesContent":["import type { RpgWebSocketConnection } from \"./types\";\n\ntype RuntimeProcess = {\n env?: Record<string, string | undefined>;\n};\n\nfunction readEnvVariable(name: string): string | undefined {\n const value = (globalThis as { process?: RuntimeProcess }).process?.env?.[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport class PartyConnection {\n public id: string;\n public uri: string;\n private _state: any = {};\n private messageQueue: Array<{ message: string; timestamp: number; sequence: number }> = [];\n private isProcessingQueue = false;\n private sequenceCounter = 0;\n private incomingQueue: Array<{\n message: string;\n timestamp: number;\n processor: (messages: string[]) => Promise<void>;\n }> = [];\n private isProcessingIncomingQueue = false;\n\n public static packetLossRate = parseFloat(readEnvVariable(\"RPGJS_PACKET_LOSS_RATE\") || \"0.1\");\n public static packetLossEnabled = readEnvVariable(\"RPGJS_ENABLE_PACKET_LOSS\") === \"true\";\n public static packetLossFilter = readEnvVariable(\"RPGJS_PACKET_LOSS_FILTER\") || \"\";\n public static bandwidthEnabled = readEnvVariable(\"RPGJS_ENABLE_BANDWIDTH\") === \"true\";\n public static bandwidthKbps = parseInt(readEnvVariable(\"RPGJS_BANDWIDTH_KBPS\") || \"100\");\n public static bandwidthFilter = readEnvVariable(\"RPGJS_BANDWIDTH_FILTER\") || \"\";\n public static latencyEnabled = readEnvVariable(\"RPGJS_ENABLE_LATENCY\") === \"true\";\n public static latencyMs = parseInt(readEnvVariable(\"RPGJS_LATENCY_MS\") || \"50\");\n public static latencyFilter = readEnvVariable(\"RPGJS_LATENCY_FILTER\") || \"\";\n\n constructor(private ws: RpgWebSocketConnection, id?: string, uri?: string) {\n this.id = id || this.generateId();\n this.uri = uri || \"\";\n }\n\n private generateId(): string {\n return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n }\n\n async send(data: any): Promise<void> {\n if (this.ws.readyState !== 1) {\n return;\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const timestamp = Date.now();\n const sequence = ++this.sequenceCounter;\n\n this.messageQueue.push({ message, timestamp, sequence });\n\n if (!this.isProcessingQueue) {\n void this.processMessageQueue();\n }\n }\n\n private async processMessageQueue(): Promise<void> {\n if (this.isProcessingQueue) {\n return;\n }\n this.isProcessingQueue = true;\n\n while (this.messageQueue.length > 0) {\n const queueItem = this.messageQueue.shift()!;\n\n if (this.shouldApplyLatency(queueItem.message)) {\n await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);\n }\n\n if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {\n if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {\n const messageSizeBits = queueItem.message.length * 8;\n const transmissionTimeMs = (messageSizeBits / (PartyConnection.bandwidthKbps * 1000)) * 1000;\n const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);\n console.log(\n `\\x1b[34m[BANDWIDTH SIMULATION]\\x1b[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));\n }\n }\n\n this.ws.send(queueItem.message);\n }\n\n this.isProcessingQueue = false;\n }\n\n private shouldApplyLatency(message: string): boolean {\n if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) {\n return false;\n }\n if (!PartyConnection.latencyFilter) {\n return true;\n }\n return message.includes(PartyConnection.latencyFilter);\n }\n\n private async waitUntil(targetTimestamp: number): Promise<void> {\n const delayMs = targetTimestamp - Date.now();\n if (delayMs <= 0) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n close(): void {\n if (this.ws.readyState === 1) {\n this.ws.close();\n }\n }\n\n setState(value: any): void {\n this._state = value;\n }\n\n get state(): any {\n return this._state;\n }\n\n bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void {\n this.incomingQueue.push({\n message,\n timestamp: Date.now(),\n processor,\n });\n\n if (!this.isProcessingIncomingQueue) {\n void this.processIncomingQueue();\n }\n }\n\n private async processIncomingQueue(): Promise<void> {\n if (this.isProcessingIncomingQueue) {\n return;\n }\n this.isProcessingIncomingQueue = true;\n\n while (this.incomingQueue.length > 0) {\n const item = this.incomingQueue.shift()!;\n if (this.shouldApplyLatency(item.message)) {\n await this.waitUntil(item.timestamp + PartyConnection.latencyMs);\n }\n try {\n await item.processor([item.message]);\n } catch (err) {\n console.error(\"Error processing incoming message:\", err);\n }\n }\n\n this.isProcessingIncomingQueue = false;\n }\n\n static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void {\n PartyConnection.packetLossEnabled = enabled;\n PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));\n PartyConnection.packetLossFilter = filter || \"\";\n\n if (enabled && rate > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled but rate is 0% (no messages will be dropped)\");\n } else {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getPacketLossStatus(): { enabled: boolean; rate: number; filter: string } {\n return {\n enabled: PartyConnection.packetLossEnabled,\n rate: PartyConnection.packetLossRate,\n filter: PartyConnection.packetLossFilter,\n };\n }\n\n static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void {\n PartyConnection.bandwidthEnabled = enabled;\n PartyConnection.bandwidthKbps = Math.max(1, kbps);\n PartyConnection.bandwidthFilter = filter || \"\";\n\n if (enabled && kbps > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled but bandwidth is 0 kbps (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getBandwidthStatus(): { enabled: boolean; kbps: number; filter: string } {\n return {\n enabled: PartyConnection.bandwidthEnabled,\n kbps: PartyConnection.bandwidthKbps,\n filter: PartyConnection.bandwidthFilter,\n };\n }\n\n static configureLatency(enabled: boolean, ms: number, filter?: string): void {\n PartyConnection.latencyEnabled = enabled;\n PartyConnection.latencyMs = Math.max(0, ms);\n PartyConnection.latencyFilter = filter || \"\";\n\n if (enabled && ms > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled with ${ms}ms fixed latency${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled but latency is 0ms (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getLatencyStatus(): { enabled: boolean; ms: number; filter: string } {\n return {\n enabled: PartyConnection.latencyEnabled,\n ms: PartyConnection.latencyMs,\n filter: PartyConnection.latencyFilter,\n };\n }\n}\n\nexport function logNetworkSimulationStatus(): void {\n const packetLossStatus = PartyConnection.getPacketLossStatus();\n const bandwidthStatus = PartyConnection.getBandwidthStatus();\n const latencyStatus = PartyConnection.getLatencyStatus();\n\n if (packetLossStatus.enabled) {\n const filterInfo = packetLossStatus.filter ? ` (filter: \"${packetLossStatus.filter}\")` : \"\";\n console.log(\n `\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`,\n );\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: disabled\");\n }\n\n if (bandwidthStatus.enabled) {\n const filterInfo = bandwidthStatus.filter ? ` (filter: \"${bandwidthStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: disabled\");\n }\n\n if (latencyStatus.enabled) {\n const filterInfo = latencyStatus.filter ? ` (filter: \"${latencyStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: disabled\");\n }\n}\n","import { PartyConnection } from \"./connection\";\n\nexport class PartyRoom {\n public id: string;\n public internalID: string;\n public env: Record<string, any> = {};\n public context: any = {};\n\n private connections = new Map<string, PartyConnection>();\n private storageData = new Map<string, any>();\n\n constructor(id: string) {\n this.id = id;\n this.internalID = `internal_${id}_${Date.now()}`;\n }\n\n async broadcast(message: any, except: string[] = []): Promise<void> {\n const data = typeof message === \"string\" ? message : JSON.stringify(message);\n const sendPromises: Promise<void>[] = [];\n\n for (const [connectionId, connection] of this.connections) {\n if (!except.includes(connectionId)) {\n sendPromises.push(connection.send(data));\n }\n }\n\n await Promise.all(sendPromises);\n }\n\n getConnection(id: string): PartyConnection | undefined {\n return this.connections.get(id);\n }\n\n getConnections(tag?: string): IterableIterator<PartyConnection> {\n void tag;\n return this.connections.values();\n }\n\n addConnection(connection: PartyConnection): void {\n this.connections.set(connection.id, connection);\n }\n\n removeConnection(connectionId: string): void {\n this.connections.delete(connectionId);\n }\n\n get storage() {\n return {\n put: async (key: string, value: any) => {\n this.storageData.set(key, value);\n },\n get: async <T = any>(key: string): Promise<T | undefined> => {\n return this.storageData.get(key) as T;\n },\n delete: async (key: string) => {\n this.storageData.delete(key);\n },\n list: async () => {\n return Array.from(this.storageData.entries());\n },\n };\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { injector } from \"@signe/di\";\nimport { context as serverContext } from \"../core/context\";\nimport { setInject } from \"../core/inject\";\nimport { provideServerModules } from \"../module\";\nimport { PartyConnection } from \"./connection\";\nimport { createMapUpdateHeaders, resolveMapUpdateToken, updateMap } from \"./map\";\nimport { PartyRoom } from \"./room\";\nimport type {\n CreateRpgServerTransportOptions,\n HandleNodeRequestOptions,\n RpgTransportRequestLike,\n RpgTransportServer,\n RpgTransportServerConstructor,\n RpgWebSocketConnection,\n RpgWebSocketRequestLike,\n RpgWebSocketServer,\n SendMapUpdateOptions,\n} from \"./types\";\n\ntype PartiesFetchInit = {\n body?: any;\n headers?: HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>;\n method?: string;\n};\n\nfunction normalizePathPrefix(path: string, fallback: string): string {\n const trimmed = (path || fallback).trim();\n if (!trimmed) {\n return fallback;\n }\n const prefixed = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return prefixed !== \"/\" ? prefixed.replace(/\\/+$/, \"\") : prefixed;\n}\n\nfunction hasPathPrefix(pathname: string, prefix: string): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\nfunction prependMountedPath(pathname: string, mountedPath?: string): string {\n if (!mountedPath) {\n return pathname;\n }\n const normalizedMountedPath = normalizePathPrefix(mountedPath, \"/\");\n if (hasPathPrefix(pathname, normalizedMountedPath)) {\n return pathname;\n }\n if (pathname === \"/\") {\n return normalizedMountedPath;\n }\n return `${normalizedMountedPath}${pathname.startsWith(\"/\") ? pathname : `/${pathname}`}`.replace(/\\/{2,}/g, \"/\");\n}\n\nfunction parseHttpRoute(pathname: string, partiesPath: string): { roomId: string; requestPath: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 2) {\n return null;\n }\n\n return {\n roomId: segments[0],\n requestPath: `/${segments.slice(1).join(\"/\")}`,\n };\n}\n\nfunction parseSocketRoute(pathname: string, partiesPath: string): { roomId: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 1) {\n return null;\n }\n\n return { roomId: segments[0] };\n}\n\nfunction toHeaders(\n input?: Headers | HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>,\n): Headers {\n if (!input) {\n return new Headers();\n }\n if (input instanceof Headers) {\n return new Headers(input);\n }\n if (Array.isArray(input)) {\n return new Headers(input);\n }\n if (input instanceof Map) {\n const headers = new Headers();\n for (const [key, value] of input) {\n if (typeof value !== \"undefined\") {\n headers.set(key, value);\n }\n }\n return headers;\n }\n\n const headers = new Headers();\n Object.entries(input).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (typeof value[0] !== \"undefined\") {\n headers.set(key, value[0]);\n }\n return;\n }\n if (typeof value !== \"undefined\") {\n headers.set(key, String(value));\n }\n });\n return headers;\n}\n\nfunction createRequestLike(url: string, method: string, headers: Headers, bodyText: string): RpgTransportRequestLike {\n return {\n url,\n method,\n headers,\n json: async () => {\n if (!bodyText) {\n return undefined;\n }\n return JSON.parse(bodyText);\n },\n text: async () => bodyText,\n };\n}\n\nasync function normalizeEngineResponse(result: any): Promise<Response> {\n if (result instanceof Response) {\n return result;\n }\n if (typeof result === \"string\") {\n return new Response(result, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain\",\n },\n });\n }\n\n return new Response(JSON.stringify(result ?? {}), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n}\n\nasync function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n res.end(await response.text());\n}\n\nasync function readNodeBody(req: IncomingMessage): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => {\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n });\n req.on(\"error\", reject);\n });\n}\n\nfunction resolveUrlFromSocketRequest(request: RpgWebSocketRequestLike): { headers: Headers; method?: string; rawUrl: string; url: URL } {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const rawUrl = request.url || \"/\";\n const url = new URL(rawUrl, `http://${host}`);\n return {\n headers,\n method: request.method,\n rawUrl,\n url,\n };\n}\n\nfunction createConnectionContext(url: URL, headers: Headers, method?: string): any {\n const normalizedHeaders = new Map<string, string>();\n headers.forEach((value, key) => {\n normalizedHeaders.set(key.toLowerCase(), value);\n });\n\n return {\n request: {\n headers: {\n has: (name: string) => normalizedHeaders.has(name.toLowerCase()),\n get: (name: string) => normalizedHeaders.get(name.toLowerCase()),\n entries: () => normalizedHeaders.entries(),\n keys: () => normalizedHeaders.keys(),\n values: () => normalizedHeaders.values(),\n },\n method,\n url: url.toString(),\n },\n url,\n };\n}\n\nexport class RpgServerTransport {\n private serverContextInitialized = false;\n private partiesPath: string;\n private readonly initializeMaps: boolean;\n private readonly mapUpdateToken: string;\n private readonly tiledBasePaths?: string[];\n private readonly rooms = new Map<string, PartyRoom>();\n private readonly servers = new Map<string, RpgTransportServer>();\n private lastKnownHost = \"\";\n\n constructor(\n private readonly serverModule: RpgTransportServerConstructor,\n options: CreateRpgServerTransportOptions = {},\n ) {\n this.initializeMaps = options.initializeMaps ?? true;\n this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);\n this.partiesPath = normalizePathPrefix(options.partiesPath || \"/parties/main\", \"/parties/main\");\n this.tiledBasePaths = options.tiledBasePaths;\n }\n\n private async ensureServerContext(): Promise<void> {\n if (this.serverContextInitialized) {\n return;\n }\n\n setInject(serverContext);\n await injector(serverContext, [provideServerModules([])]);\n this.serverContextInitialized = true;\n }\n\n getRoom(roomId: string): PartyRoom | undefined {\n return this.rooms.get(roomId);\n }\n\n getServer(roomId: string): RpgTransportServer | undefined {\n return this.servers.get(roomId);\n }\n\n private async ensureRoomAndServer(roomId: string, host?: string): Promise<{ room: PartyRoom; rpgServer: RpgTransportServer }> {\n if (host) {\n this.lastKnownHost = host;\n }\n\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new PartyRoom(roomId);\n this.rooms.set(roomId, room);\n console.log(`Created new room: ${roomId}`);\n }\n\n let rpgServer = this.servers.get(roomId);\n if (!rpgServer) {\n await this.ensureServerContext();\n rpgServer = new this.serverModule(room);\n this.servers.set(roomId, rpgServer);\n console.log(`Created new server instance for room: ${roomId}`);\n\n if (typeof rpgServer.onStart === \"function\") {\n try {\n await rpgServer.onStart();\n console.log(`Server started for room: ${roomId}`);\n } catch (error) {\n console.error(`Error starting server for room ${roomId}:`, error);\n }\n }\n\n if (this.initializeMaps) {\n await updateMap(roomId, rpgServer, {\n host: host || this.lastKnownHost,\n mapUpdateToken: this.mapUpdateToken,\n tiledBasePaths: this.tiledBasePaths,\n });\n }\n }\n\n room.context.parties = this.buildPartiesContext();\n return { room, rpgServer };\n }\n\n private buildPartiesContext() {\n return {\n main: {\n get: async (targetRoomId: string) => {\n return {\n fetch: async (path: string, init?: PartiesFetchInit) => {\n const method = (init?.method || \"GET\").toUpperCase();\n const headers = toHeaders(init?.headers);\n const requestPath = path.startsWith(\"/\") ? path : `/${path}`;\n let bodyText = \"\";\n\n if (typeof init?.body === \"string\") {\n bodyText = init.body;\n } else if (typeof init?.body !== \"undefined\") {\n bodyText = JSON.stringify(init.body);\n }\n\n return this.dispatchRoomRequest(\n targetRoomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`,\n method,\n headers,\n bodyText,\n ),\n this.lastKnownHost,\n );\n },\n };\n },\n },\n } as any;\n }\n\n private async dispatchRoomRequest(roomId: string, requestLike: RpgTransportRequestLike, host?: string): Promise<Response> {\n const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);\n room.context.parties = this.buildPartiesContext();\n const result = await rpgServer.onRequest?.(requestLike);\n return normalizeEngineResponse(result);\n }\n\n async fetch(request: Request | string | URL, init?: RequestInit): Promise<Response> {\n const webRequest = request instanceof Request ? request : new Request(String(request), init);\n const url = new URL(webRequest.url);\n const route = parseHttpRoute(url.pathname, this.partiesPath);\n if (!route) {\n return new Response(JSON.stringify({ error: \"Not found\" }), {\n status: 404,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n const bodyText = await webRequest.text();\n return this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText),\n url.host,\n );\n }\n\n async updateMap(mapId: string, payload: any, options: SendMapUpdateOptions = {}): Promise<Response> {\n const roomId = mapId.startsWith(\"map-\") ? mapId : `map-${mapId}`;\n const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n return this.dispatchRoomRequest(\n roomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${roomId}/map/update`,\n \"POST\",\n headers,\n JSON.stringify(payload),\n ),\n options.host ?? this.lastKnownHost,\n );\n }\n\n async handleNodeRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void,\n options: HandleNodeRequestOptions = {},\n ): Promise<boolean> {\n try {\n const headers = toHeaders(req.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(req.url || \"/\", `http://${host}`);\n const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);\n const normalizedUrl = new URL(url.toString());\n normalizedUrl.pathname = normalizedPathname;\n\n const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);\n if (!route) {\n next?.();\n return false;\n }\n\n const bodyText = await readNodeBody(req);\n const response = await this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(normalizedUrl.toString(), (req.method || \"GET\").toUpperCase(), headers, bodyText),\n host,\n );\n\n await sendNodeResponse(res, response);\n return true;\n } catch (error) {\n console.error(\"Error handling RPG-JS request:\", error);\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n return true;\n }\n }\n\n async acceptWebSocket(ws: RpgWebSocketConnection, request: RpgWebSocketRequestLike): Promise<boolean> {\n const normalizedRequest = resolveUrlFromSocketRequest(request);\n const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);\n if (!route) {\n return false;\n }\n\n try {\n console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);\n\n const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());\n console.log(`Room: ${route.roomId}, Query params:`, queryParams);\n\n const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);\n room.context.parties = this.buildPartiesContext();\n\n const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);\n room.addConnection(connection);\n\n console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);\n\n let isClosed = false;\n const cleanup = async (logMessage?: string, error?: Error) => {\n if (isClosed) {\n return;\n }\n isClosed = true;\n if (logMessage) {\n console.log(logMessage);\n }\n if (error) {\n console.error(\"WebSocket error:\", error);\n }\n room.removeConnection(connection.id);\n await rpgServer.onClose?.(connection as any);\n };\n\n ws.on(\"message\", async (data: Buffer | string) => {\n try {\n const rawMessage = typeof data === \"string\" ? data : data.toString();\n\n if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {\n if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {\n const random = Math.random();\n if (random < PartyConnection.packetLossRate) {\n console.log(\n `\\x1b[31m[PACKET LOSS]\\x1b[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`,\n );\n console.log(`\\x1b[33m[PACKET DATA]\\x1b[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? \"...\" : \"\"}`);\n return;\n }\n }\n }\n\n connection.bufferIncoming(rawMessage, async (batch: string[]) => {\n for (const message of batch) {\n await rpgServer.onMessage?.(message, connection as any);\n }\n });\n } catch (error) {\n console.error(\"Error processing WebSocket message:\", error);\n }\n });\n\n ws.on(\"close\", () => {\n void cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);\n });\n\n ws.on(\"error\", (error: Error) => {\n void cleanup(undefined, error);\n });\n\n if (typeof rpgServer.onConnect === \"function\") {\n await rpgServer.onConnect(\n connection as any,\n createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method) as any,\n );\n }\n\n await connection.send({\n type: \"connected\",\n id: connection.id,\n message: \"Connected to RPG-JS server\",\n });\n\n return true;\n } catch (error) {\n console.error(\"Error establishing WebSocket connection:\", error);\n ws.close();\n return true;\n }\n }\n\n async handleUpgrade(\n wsServer: RpgWebSocketServer,\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): Promise<boolean> {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(request.url || \"/\", `http://${host}`);\n if (!parseSocketRoute(url.pathname, this.partiesPath)) {\n return false;\n }\n\n wsServer.handleUpgrade(request, socket, head, (ws) => {\n void this.acceptWebSocket(ws, request);\n });\n\n return true;\n }\n}\n\nexport function createRpgServerTransport(\n serverModule: RpgTransportServerConstructor,\n options?: CreateRpgServerTransportOptions,\n): RpgServerTransport {\n return new RpgServerTransport(serverModule, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/node/connection.ts","../../src/node/room.ts","../../src/node/transport.ts"],"sourcesContent":["import type { RpgWebSocketConnection } from \"./types\";\n\ntype RuntimeProcess = {\n env?: Record<string, string | undefined>;\n};\n\nfunction readEnvVariable(name: string): string | undefined {\n const value = (globalThis as { process?: RuntimeProcess }).process?.env?.[name];\n return typeof value === \"string\" ? value : undefined;\n}\n\nexport class PartyConnection {\n public id: string;\n public uri: string;\n private _state: any = {};\n private messageQueue: Array<{ message: string; timestamp: number; sequence: number }> = [];\n private isProcessingQueue = false;\n private sequenceCounter = 0;\n private incomingQueue: Array<{\n message: string;\n timestamp: number;\n processor: (messages: string[]) => Promise<void>;\n }> = [];\n private isProcessingIncomingQueue = false;\n\n public static packetLossRate = parseFloat(readEnvVariable(\"RPGJS_PACKET_LOSS_RATE\") || \"0.1\");\n public static packetLossEnabled = readEnvVariable(\"RPGJS_ENABLE_PACKET_LOSS\") === \"true\";\n public static packetLossFilter = readEnvVariable(\"RPGJS_PACKET_LOSS_FILTER\") || \"\";\n public static bandwidthEnabled = readEnvVariable(\"RPGJS_ENABLE_BANDWIDTH\") === \"true\";\n public static bandwidthKbps = parseInt(readEnvVariable(\"RPGJS_BANDWIDTH_KBPS\") || \"100\");\n public static bandwidthFilter = readEnvVariable(\"RPGJS_BANDWIDTH_FILTER\") || \"\";\n public static latencyEnabled = readEnvVariable(\"RPGJS_ENABLE_LATENCY\") === \"true\";\n public static latencyMs = parseInt(readEnvVariable(\"RPGJS_LATENCY_MS\") || \"50\");\n public static latencyFilter = readEnvVariable(\"RPGJS_LATENCY_FILTER\") || \"\";\n\n constructor(private ws: RpgWebSocketConnection, id?: string, uri?: string) {\n this.id = id || this.generateId();\n this.uri = uri || \"\";\n }\n\n private generateId(): string {\n return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;\n }\n\n async send(data: any): Promise<void> {\n if (this.ws.readyState !== 1) {\n return;\n }\n\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n const timestamp = Date.now();\n const sequence = ++this.sequenceCounter;\n\n this.messageQueue.push({ message, timestamp, sequence });\n\n if (!this.isProcessingQueue) {\n void this.processMessageQueue();\n }\n }\n\n private async processMessageQueue(): Promise<void> {\n if (this.isProcessingQueue) {\n return;\n }\n this.isProcessingQueue = true;\n\n while (this.messageQueue.length > 0) {\n const queueItem = this.messageQueue.shift()!;\n\n if (this.shouldApplyLatency(queueItem.message)) {\n await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);\n }\n\n if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {\n if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {\n const messageSizeBits = queueItem.message.length * 8;\n const transmissionTimeMs = (messageSizeBits / (PartyConnection.bandwidthKbps * 1000)) * 1000;\n const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);\n console.log(\n `\\x1b[34m[BANDWIDTH SIMULATION]\\x1b[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`,\n );\n await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));\n }\n }\n\n this.ws.send(queueItem.message);\n }\n\n this.isProcessingQueue = false;\n }\n\n private shouldApplyLatency(message: string): boolean {\n if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) {\n return false;\n }\n if (!PartyConnection.latencyFilter) {\n return true;\n }\n return message.includes(PartyConnection.latencyFilter);\n }\n\n private async waitUntil(targetTimestamp: number): Promise<void> {\n const delayMs = targetTimestamp - Date.now();\n if (delayMs <= 0) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n close(): void {\n if (this.ws.readyState === 1) {\n this.ws.close();\n }\n }\n\n setState(value: any): void {\n this._state = value;\n }\n\n get state(): any {\n return this._state;\n }\n\n bufferIncoming(message: string, processor: (messages: string[]) => Promise<void>): void {\n this.incomingQueue.push({\n message,\n timestamp: Date.now(),\n processor,\n });\n\n if (!this.isProcessingIncomingQueue) {\n void this.processIncomingQueue();\n }\n }\n\n private async processIncomingQueue(): Promise<void> {\n if (this.isProcessingIncomingQueue) {\n return;\n }\n this.isProcessingIncomingQueue = true;\n\n while (this.incomingQueue.length > 0) {\n const item = this.incomingQueue.shift()!;\n if (this.shouldApplyLatency(item.message)) {\n await this.waitUntil(item.timestamp + PartyConnection.latencyMs);\n }\n try {\n await item.processor([item.message]);\n } catch (err) {\n console.error(\"Error processing incoming message:\", err);\n }\n }\n\n this.isProcessingIncomingQueue = false;\n }\n\n static configurePacketLoss(enabled: boolean, rate: number, filter?: string): void {\n PartyConnection.packetLossEnabled = enabled;\n PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));\n PartyConnection.packetLossFilter = filter || \"\";\n\n if (enabled && rate > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Enabled but rate is 0% (no messages will be dropped)\");\n } else {\n console.log(\"\\x1b[35m[PACKET LOSS SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getPacketLossStatus(): { enabled: boolean; rate: number; filter: string } {\n return {\n enabled: PartyConnection.packetLossEnabled,\n rate: PartyConnection.packetLossRate,\n filter: PartyConnection.packetLossFilter,\n };\n }\n\n static configureBandwidth(enabled: boolean, kbps: number, filter?: string): void {\n PartyConnection.bandwidthEnabled = enabled;\n PartyConnection.bandwidthKbps = Math.max(1, kbps);\n PartyConnection.bandwidthFilter = filter || \"\";\n\n if (enabled && kbps > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Enabled but bandwidth is 0 kbps (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[BANDWIDTH SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getBandwidthStatus(): { enabled: boolean; kbps: number; filter: string } {\n return {\n enabled: PartyConnection.bandwidthEnabled,\n kbps: PartyConnection.bandwidthKbps,\n filter: PartyConnection.bandwidthFilter,\n };\n }\n\n static configureLatency(enabled: boolean, ms: number, filter?: string): void {\n PartyConnection.latencyEnabled = enabled;\n PartyConnection.latencyMs = Math.max(0, ms);\n PartyConnection.latencyFilter = filter || \"\";\n\n if (enabled && ms > 0) {\n const filterInfo = filter ? ` (filtered: \"${filter}\")` : \"\";\n console.log(`\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled with ${ms}ms fixed latency${filterInfo}`);\n } else if (enabled) {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Enabled but latency is 0ms (no delay will be applied)\");\n } else {\n console.log(\"\\x1b[35m[LATENCY SIMULATION]\\x1b[0m Disabled\");\n }\n }\n\n static getLatencyStatus(): { enabled: boolean; ms: number; filter: string } {\n return {\n enabled: PartyConnection.latencyEnabled,\n ms: PartyConnection.latencyMs,\n filter: PartyConnection.latencyFilter,\n };\n }\n}\n\nexport function logNetworkSimulationStatus(): void {\n const packetLossStatus = PartyConnection.getPacketLossStatus();\n const bandwidthStatus = PartyConnection.getBandwidthStatus();\n const latencyStatus = PartyConnection.getLatencyStatus();\n\n if (packetLossStatus.enabled) {\n const filterInfo = packetLossStatus.filter ? ` (filter: \"${packetLossStatus.filter}\")` : \"\";\n console.log(\n `\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`,\n );\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Packet loss simulation: disabled\");\n }\n\n if (bandwidthStatus.enabled) {\n const filterInfo = bandwidthStatus.filter ? ` (filter: \"${bandwidthStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Bandwidth simulation: disabled\");\n }\n\n if (latencyStatus.enabled) {\n const filterInfo = latencyStatus.filter ? ` (filter: \"${latencyStatus.filter}\")` : \"\";\n console.log(`\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);\n } else {\n console.log(\"\\x1b[36m[NETWORK SIMULATION]\\x1b[0m Latency simulation: disabled\");\n }\n}\n","import { PartyConnection } from \"./connection\";\n\nexport class PartyRoom {\n public id: string;\n public internalID: string;\n public env: Record<string, any> = {};\n public context: any = {};\n\n private connections = new Map<string, PartyConnection>();\n private storageData = new Map<string, any>();\n\n constructor(id: string) {\n this.id = id;\n this.internalID = `internal_${id}_${Date.now()}`;\n }\n\n async broadcast(message: any, except: string[] = []): Promise<void> {\n const data = typeof message === \"string\" ? message : JSON.stringify(message);\n const sendPromises: Promise<void>[] = [];\n\n for (const [connectionId, connection] of this.connections) {\n if (!except.includes(connectionId)) {\n sendPromises.push(connection.send(data));\n }\n }\n\n await Promise.all(sendPromises);\n }\n\n getConnection(id: string): PartyConnection | undefined {\n return this.connections.get(id);\n }\n\n getConnections(tag?: string): IterableIterator<PartyConnection> {\n void tag;\n return this.connections.values();\n }\n\n addConnection(connection: PartyConnection): void {\n this.connections.set(connection.id, connection);\n }\n\n removeConnection(connectionId: string): void {\n this.connections.delete(connectionId);\n }\n\n get storage() {\n return {\n put: async (key: string, value: any) => {\n this.storageData.set(key, value);\n },\n get: async <T = any>(key: string): Promise<T | undefined> => {\n return this.storageData.get(key) as T;\n },\n delete: async (key: string) => {\n this.storageData.delete(key);\n },\n list: async () => {\n return Array.from(this.storageData.entries());\n },\n };\n }\n}\n","import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { injector } from \"@signe/di\";\nimport { context as serverContext } from \"../core/context\";\nimport { setInject } from \"../core/inject\";\nimport { provideServerModules } from \"../module\";\nimport { PartyConnection } from \"./connection\";\nimport { createMapUpdateHeaders, resolveMapUpdateToken, updateMap } from \"./map\";\nimport { PartyRoom } from \"./room\";\nimport type {\n CreateRpgServerTransportOptions,\n HandleNodeRequestOptions,\n RpgTransportRequestLike,\n RpgTransportServer,\n RpgTransportServerConstructor,\n RpgWebSocketConnection,\n RpgWebSocketRequestLike,\n RpgWebSocketServer,\n SendMapUpdateOptions,\n} from \"./types\";\n\ntype PartiesFetchInit = {\n body?: any;\n headers?: HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>;\n method?: string;\n};\n\nfunction normalizePathPrefix(path: string, fallback: string): string {\n const trimmed = (path || fallback).trim();\n if (!trimmed) {\n return fallback;\n }\n const prefixed = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return prefixed !== \"/\" ? prefixed.replace(/\\/+$/, \"\") : prefixed;\n}\n\nfunction hasPathPrefix(pathname: string, prefix: string): boolean {\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\nfunction prependMountedPath(pathname: string, mountedPath?: string): string {\n if (!mountedPath) {\n return pathname;\n }\n const normalizedMountedPath = normalizePathPrefix(mountedPath, \"/\");\n if (hasPathPrefix(pathname, normalizedMountedPath)) {\n return pathname;\n }\n if (pathname === \"/\") {\n return normalizedMountedPath;\n }\n return `${normalizedMountedPath}${pathname.startsWith(\"/\") ? pathname : `/${pathname}`}`.replace(/\\/{2,}/g, \"/\");\n}\n\nfunction parseHttpRoute(pathname: string, partiesPath: string): { roomId: string; requestPath: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 2) {\n return null;\n }\n\n return {\n roomId: segments[0],\n requestPath: `/${segments.slice(1).join(\"/\")}`,\n };\n}\n\nfunction parseSocketRoute(pathname: string, partiesPath: string): { roomId: string } | null {\n if (!hasPathPrefix(pathname, partiesPath)) {\n return null;\n }\n\n const remainder = pathname.slice(partiesPath.length);\n const segments = remainder.split(\"/\").filter(Boolean);\n if (segments.length < 1) {\n return null;\n }\n\n return { roomId: segments[0] };\n}\n\nfunction toHeaders(\n input?: Headers | HeadersInit | IncomingHttpHeaders | Map<string, string | undefined>,\n): Headers {\n if (!input) {\n return new Headers();\n }\n if (input instanceof Headers) {\n return new Headers(input);\n }\n if (Array.isArray(input)) {\n return new Headers(input);\n }\n if (input instanceof Map) {\n const headers = new Headers();\n for (const [key, value] of input) {\n if (typeof value !== \"undefined\") {\n headers.set(key, value);\n }\n }\n return headers;\n }\n\n const headers = new Headers();\n Object.entries(input).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n if (typeof value[0] !== \"undefined\") {\n headers.set(key, value[0]);\n }\n return;\n }\n if (typeof value !== \"undefined\") {\n headers.set(key, String(value));\n }\n });\n return headers;\n}\n\nfunction createRequestLike(url: string, method: string, headers: Headers, bodyText: string): RpgTransportRequestLike {\n return {\n url,\n method,\n headers,\n json: async () => {\n if (!bodyText) {\n return undefined;\n }\n return JSON.parse(bodyText);\n },\n text: async () => bodyText,\n };\n}\n\nasync function normalizeEngineResponse(result: any): Promise<Response> {\n if (result instanceof Response) {\n return result;\n }\n if (typeof result === \"string\") {\n return new Response(result, {\n status: 200,\n headers: {\n \"Content-Type\": \"text/plain\",\n },\n });\n }\n\n return new Response(JSON.stringify(result ?? {}), {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n}\n\nasync function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n res.end(await response.text());\n}\n\nasync function readNodeBody(req: IncomingMessage): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer | string) => {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n });\n req.on(\"end\", () => {\n resolve(Buffer.concat(chunks).toString(\"utf8\"));\n });\n req.on(\"error\", reject);\n });\n}\n\nfunction resolveUrlFromSocketRequest(request: RpgWebSocketRequestLike): { headers: Headers; method?: string; rawUrl: string; url: URL } {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const rawUrl = request.url || \"/\";\n const url = new URL(rawUrl, `http://${host}`);\n return {\n headers,\n method: request.method,\n rawUrl,\n url,\n };\n}\n\nfunction createConnectionContext(url: URL, headers: Headers, method?: string): any {\n const normalizedHeaders = new Map<string, string>();\n headers.forEach((value, key) => {\n normalizedHeaders.set(key.toLowerCase(), value);\n });\n\n return {\n request: {\n headers: {\n has: (name: string) => normalizedHeaders.has(name.toLowerCase()),\n get: (name: string) => normalizedHeaders.get(name.toLowerCase()),\n entries: () => normalizedHeaders.entries(),\n keys: () => normalizedHeaders.keys(),\n values: () => normalizedHeaders.values(),\n },\n method,\n url: url.toString(),\n },\n url,\n };\n}\n\nexport class RpgServerTransport {\n private serverContextInitialized = false;\n private partiesPath: string;\n private readonly initializeMaps: boolean;\n private readonly mapUpdateToken: string;\n private readonly tiledBasePaths?: string[];\n private readonly rooms = new Map<string, PartyRoom>();\n private readonly servers = new Map<string, RpgTransportServer>();\n private lastKnownHost = \"\";\n\n constructor(\n private readonly serverModule: RpgTransportServerConstructor,\n options: CreateRpgServerTransportOptions = {},\n ) {\n this.initializeMaps = options.initializeMaps ?? true;\n this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);\n this.partiesPath = normalizePathPrefix(options.partiesPath || \"/parties/main\", \"/parties/main\");\n this.tiledBasePaths = options.tiledBasePaths;\n }\n\n private async ensureServerContext(): Promise<void> {\n if (this.serverContextInitialized) {\n return;\n }\n\n setInject(serverContext);\n await injector(serverContext, [provideServerModules([])]);\n this.serverContextInitialized = true;\n }\n\n getRoom(roomId: string): PartyRoom | undefined {\n return this.rooms.get(roomId);\n }\n\n getServer(roomId: string): RpgTransportServer | undefined {\n return this.servers.get(roomId);\n }\n\n private async ensureRoomAndServer(roomId: string, host?: string): Promise<{ room: PartyRoom; rpgServer: RpgTransportServer }> {\n if (host) {\n this.lastKnownHost = host;\n }\n\n let room = this.rooms.get(roomId);\n if (!room) {\n room = new PartyRoom(roomId);\n this.rooms.set(roomId, room);\n console.log(`Created new room: ${roomId}`);\n }\n\n let rpgServer = this.servers.get(roomId);\n if (!rpgServer) {\n await this.ensureServerContext();\n rpgServer = new this.serverModule(room);\n this.servers.set(roomId, rpgServer);\n console.log(`Created new server instance for room: ${roomId}`);\n\n if (typeof rpgServer.onStart === \"function\") {\n try {\n await rpgServer.onStart();\n console.log(`Server started for room: ${roomId}`);\n } catch (error) {\n console.error(`Error starting server for room ${roomId}:`, error);\n }\n }\n\n if (this.initializeMaps) {\n await updateMap(roomId, rpgServer, {\n host: host || this.lastKnownHost,\n mapUpdateToken: this.mapUpdateToken,\n tiledBasePaths: this.tiledBasePaths,\n });\n }\n }\n\n room.context.parties = this.buildPartiesContext();\n return { room, rpgServer };\n }\n\n private buildPartiesContext() {\n return {\n main: {\n get: async (targetRoomId: string) => {\n return {\n fetch: async (path: string, init?: PartiesFetchInit) => {\n const method = (init?.method || \"GET\").toUpperCase();\n const headers = toHeaders(init?.headers);\n const requestPath = path.startsWith(\"/\") ? path : `/${path}`;\n let bodyText = \"\";\n\n if (typeof init?.body === \"string\") {\n bodyText = init.body;\n } else if (typeof init?.body !== \"undefined\") {\n bodyText = JSON.stringify(init.body);\n }\n\n return this.dispatchRoomRequest(\n targetRoomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`,\n method,\n headers,\n bodyText,\n ),\n this.lastKnownHost,\n );\n },\n };\n },\n },\n } as any;\n }\n\n private async dispatchRoomRequest(roomId: string, requestLike: RpgTransportRequestLike, host?: string): Promise<Response> {\n const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);\n room.context.parties = this.buildPartiesContext();\n const result = await rpgServer.onRequest?.(requestLike);\n return normalizeEngineResponse(result);\n }\n\n async fetch(request: Request | string | URL, init?: RequestInit): Promise<Response> {\n const webRequest = request instanceof Request ? request : new Request(String(request), init);\n const url = new URL(webRequest.url);\n const route = parseHttpRoute(url.pathname, this.partiesPath);\n if (!route) {\n return new Response(JSON.stringify({ error: \"Not found\" }), {\n status: 404,\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n }\n\n const bodyText = await webRequest.text();\n return this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText),\n url.host,\n );\n }\n\n async updateMap(mapId: string, payload: any, options: SendMapUpdateOptions = {}): Promise<Response> {\n const roomId = mapId.startsWith(\"map-\") ? mapId : `map-${mapId}`;\n const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n return this.dispatchRoomRequest(\n roomId,\n createRequestLike(\n `http://localhost${this.partiesPath}/${roomId}/map/update`,\n \"POST\",\n headers,\n JSON.stringify(payload),\n ),\n options.host ?? this.lastKnownHost,\n );\n }\n\n async handleNodeRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void,\n options: HandleNodeRequestOptions = {},\n ): Promise<boolean> {\n try {\n const headers = toHeaders(req.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(req.url || \"/\", `http://${host}`);\n const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);\n const normalizedUrl = new URL(url.toString());\n normalizedUrl.pathname = normalizedPathname;\n\n const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);\n if (!route) {\n next?.();\n return false;\n }\n\n const bodyText = await readNodeBody(req);\n const response = await this.dispatchRoomRequest(\n route.roomId,\n createRequestLike(normalizedUrl.toString(), (req.method || \"GET\").toUpperCase(), headers, bodyText),\n host,\n );\n\n await sendNodeResponse(res, response);\n return true;\n } catch (error) {\n console.error(\"Error handling RPG-JS request:\", error);\n res.statusCode = 500;\n res.setHeader(\"Content-Type\", \"application/json\");\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n return true;\n }\n }\n\n async acceptWebSocket(ws: RpgWebSocketConnection, request: RpgWebSocketRequestLike): Promise<boolean> {\n const normalizedRequest = resolveUrlFromSocketRequest(request);\n const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);\n if (!route) {\n return false;\n }\n\n try {\n console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);\n\n const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());\n console.log(`Room: ${route.roomId}, Query params:`, queryParams);\n\n const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);\n room.context.parties = this.buildPartiesContext();\n\n const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);\n room.addConnection(connection);\n\n console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);\n\n let isClosed = false;\n const cleanup = async (logMessage?: string, error?: Error) => {\n if (isClosed) {\n return;\n }\n isClosed = true;\n if (logMessage) {\n console.log(logMessage);\n }\n if (error) {\n console.error(\"WebSocket error:\", error);\n }\n room.removeConnection(connection.id);\n await rpgServer.onClose?.(connection as any);\n };\n\n ws.on(\"message\", async (data: Buffer | string) => {\n try {\n const rawMessage = typeof data === \"string\" ? data : data.toString();\n\n if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {\n if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {\n const random = Math.random();\n if (random < PartyConnection.packetLossRate) {\n console.log(\n `\\x1b[31m[PACKET LOSS]\\x1b[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`,\n );\n console.log(`\\x1b[33m[PACKET DATA]\\x1b[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? \"...\" : \"\"}`);\n return;\n }\n }\n }\n\n connection.bufferIncoming(rawMessage, async (batch: string[]) => {\n for (const message of batch) {\n await rpgServer.onMessage?.(message, connection as any);\n }\n });\n } catch (error) {\n console.error(\"Error processing WebSocket message:\", error);\n }\n });\n\n ws.on(\"close\", () => {\n void cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);\n });\n\n ws.on(\"error\", (error: Error) => {\n void cleanup(undefined, error);\n });\n\n if (typeof rpgServer.onConnect === \"function\") {\n await rpgServer.onConnect(\n connection as any,\n createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method) as any,\n );\n }\n\n await connection.send({\n type: \"connected\",\n id: connection.id,\n message: \"Connected to RPG-JS server\",\n });\n\n return true;\n } catch (error) {\n console.error(\"Error establishing WebSocket connection:\", error);\n ws.close();\n return true;\n }\n }\n\n async handleUpgrade(\n wsServer: RpgWebSocketServer,\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): Promise<boolean> {\n const headers = toHeaders(request.headers);\n const host = headers.get(\"host\") || \"localhost\";\n const url = new URL(request.url || \"/\", `http://${host}`);\n if (!parseSocketRoute(url.pathname, this.partiesPath)) {\n return false;\n }\n\n wsServer.handleUpgrade(request, socket, head, (ws) => {\n void this.acceptWebSocket(ws, request);\n });\n\n return true;\n }\n}\n\nexport function createRpgServerTransport(\n serverModule: RpgTransportServerConstructor,\n options?: CreateRpgServerTransportOptions,\n): RpgServerTransport {\n return new RpgServerTransport(serverModule, options);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/server",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.4",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"description": "",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@canvasengine/tiled": "2.0.0-beta.
|
|
25
|
-
"@rpgjs/common": "5.0.0-beta.
|
|
26
|
-
"@rpgjs/physic": "5.0.0-beta.
|
|
27
|
-
"@rpgjs/testing": "5.0.0-beta.
|
|
24
|
+
"@canvasengine/tiled": "2.0.0-beta.58",
|
|
25
|
+
"@rpgjs/common": "5.0.0-beta.4",
|
|
26
|
+
"@rpgjs/physic": "5.0.0-beta.4",
|
|
27
|
+
"@rpgjs/testing": "5.0.0-beta.4",
|
|
28
28
|
"@rpgjs/database": "^4.3.0",
|
|
29
29
|
"@signe/di": "^2.9.0",
|
|
30
30
|
"@signe/reactive": "^2.9.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"zod": "^4.3.6"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"vite": "^8.0.
|
|
37
|
+
"vite": "^8.0.10",
|
|
38
38
|
"vite-plugin-dts": "^4.5.4"
|
|
39
39
|
},
|
|
40
40
|
"type": "module",
|
package/src/Gui/MenuGui.ts
CHANGED
|
@@ -22,6 +22,16 @@ export interface MenuGuiOptions {
|
|
|
22
22
|
saveAutoSlotLabel?: string
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function readReactiveValue(value: any, context?: any) {
|
|
26
|
+
return typeof value === 'function' ? value.call(context) : value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readField(source: any, key: string, fallback?: any) {
|
|
30
|
+
const value = source?.[key]
|
|
31
|
+
const resolved = readReactiveValue(value, source)
|
|
32
|
+
return resolved ?? fallback
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export class MenuGui extends Gui {
|
|
26
36
|
private menuOptions: MenuGuiOptions = {}
|
|
27
37
|
|
|
@@ -61,7 +71,7 @@ export class MenuGui extends Gui {
|
|
|
61
71
|
const player = this.player as any
|
|
62
72
|
const databaseById = player.databaseById?.bind(player)
|
|
63
73
|
const equippedIds = new Set(
|
|
64
|
-
(player.equipments?.() || []).map((it) => it
|
|
74
|
+
(player.equipments?.() || []).map((it) => readField(it, 'id', readField(it, 'name')))
|
|
65
75
|
)
|
|
66
76
|
|
|
67
77
|
const buildStats = () => {
|
|
@@ -76,19 +86,19 @@ export class MenuGui extends Gui {
|
|
|
76
86
|
]
|
|
77
87
|
const stats: Record<string, number> = {}
|
|
78
88
|
statKeys.forEach((key) => {
|
|
79
|
-
stats[key] = params[key] ?? 0
|
|
89
|
+
stats[key] = readReactiveValue(params[key]) ?? 0
|
|
80
90
|
})
|
|
81
|
-
stats.pdef = player.pdef ?? params.pdef ?? 0
|
|
82
|
-
stats.sdef = player.sdef ?? params.sdef ?? 0
|
|
83
|
-
stats.atk = player.atk ?? params.atk ?? 0
|
|
91
|
+
stats.pdef = readReactiveValue(player.pdef ?? params.pdef) ?? 0
|
|
92
|
+
stats.sdef = readReactiveValue(player.sdef ?? params.sdef) ?? 0
|
|
93
|
+
stats.atk = readReactiveValue(player.atk ?? params.atk) ?? 0
|
|
84
94
|
return stats
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const items = (player.items?.() || []).map((item) => {
|
|
88
|
-
const id = item
|
|
98
|
+
const id = readField(item, 'id')
|
|
89
99
|
const data = databaseById ? databaseById(id) : {}
|
|
90
|
-
const type = data
|
|
91
|
-
const consumable = data
|
|
100
|
+
const type = readField(data, '_type', 'item')
|
|
101
|
+
const consumable = readField(data, 'consumable')
|
|
92
102
|
const isConsumable = consumable !== undefined ? consumable : type === 'item'
|
|
93
103
|
const usable = isConsumable === false
|
|
94
104
|
? false
|
|
@@ -97,13 +107,13 @@ export class MenuGui extends Gui {
|
|
|
97
107
|
: true
|
|
98
108
|
return {
|
|
99
109
|
id,
|
|
100
|
-
name: item
|
|
101
|
-
description: item
|
|
102
|
-
quantity: item
|
|
103
|
-
icon: data
|
|
104
|
-
atk: item
|
|
105
|
-
pdef: item
|
|
106
|
-
sdef: item
|
|
110
|
+
name: readField(item, 'name'),
|
|
111
|
+
description: readField(item, 'description'),
|
|
112
|
+
quantity: readField(item, 'quantity'),
|
|
113
|
+
icon: readField(data, 'icon', readField(item, 'icon')),
|
|
114
|
+
atk: readField(item, 'atk'),
|
|
115
|
+
pdef: readField(item, 'pdef'),
|
|
116
|
+
sdef: readField(item, 'sdef'),
|
|
107
117
|
consumable: isConsumable,
|
|
108
118
|
type,
|
|
109
119
|
usable,
|
|
@@ -112,14 +122,14 @@ export class MenuGui extends Gui {
|
|
|
112
122
|
})
|
|
113
123
|
const menuEquips = items.filter((item) => item.type === 'weapon' || item.type === 'armor')
|
|
114
124
|
const skills = (player.skills?.() || []).map((skill) => ({
|
|
115
|
-
id: skill
|
|
116
|
-
name: skill
|
|
117
|
-
description: skill
|
|
118
|
-
spCost: skill
|
|
125
|
+
id: readField(skill, 'id', readField(skill, 'name')),
|
|
126
|
+
name: readField(skill, 'name', readField(skill, 'id', 'Skill')),
|
|
127
|
+
description: readField(skill, 'description', ''),
|
|
128
|
+
spCost: readField(skill, 'spCost', 0)
|
|
119
129
|
}))
|
|
120
130
|
const saveLoad = this.buildSaveLoad(options)
|
|
121
131
|
|
|
122
|
-
return { menus, items, equips: menuEquips, skills, saveLoad, playerStats: buildStats(), expForNextlevel: player.expForNextlevel }
|
|
132
|
+
return { menus, items, equips: menuEquips, skills, saveLoad, playerStats: buildStats(), expForNextlevel: readReactiveValue(player.expForNextlevel) }
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
private refreshMenu(clientActionId?: string) {
|
|
@@ -753,7 +753,10 @@ export function WithParameterManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
753
753
|
for (let i = this._level() ; i <= val; i++) {
|
|
754
754
|
for (let skill of currentClass.skillsToLearn as any[]) {
|
|
755
755
|
if (skill.level == i) {
|
|
756
|
-
this['learnSkill'](skill.skill
|
|
756
|
+
this['learnSkill'](skill.skill, {
|
|
757
|
+
source: skill.source ?? 'level',
|
|
758
|
+
level: i
|
|
759
|
+
})
|
|
757
760
|
}
|
|
758
761
|
}
|
|
759
762
|
}
|
package/src/Player/Player.ts
CHANGED
|
@@ -12,7 +12,20 @@ import { Effect } from "./EffectManager";
|
|
|
12
12
|
/**
|
|
13
13
|
* Type for skill class constructor
|
|
14
14
|
*/
|
|
15
|
-
type SkillClass = { new (...args: any[]): any };
|
|
15
|
+
export type SkillClass = { new (...args: any[]): any };
|
|
16
|
+
|
|
17
|
+
export type SkillChangeAction = "learn" | "forget";
|
|
18
|
+
|
|
19
|
+
export interface SkillChangeOptions {
|
|
20
|
+
source?: "manual" | "level" | "class" | "studio" | string;
|
|
21
|
+
level?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SkillChangePayload extends SkillChangeOptions {
|
|
25
|
+
action: SkillChangeAction;
|
|
26
|
+
skill: SkillClass | SkillObject | string;
|
|
27
|
+
skillId: string;
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
/**
|
|
18
31
|
* Interface defining the hooks that can be implemented on skill classes or objects
|
|
@@ -430,7 +443,10 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
|
|
|
430
443
|
* });
|
|
431
444
|
* ```
|
|
432
445
|
*/
|
|
433
|
-
learnSkill(
|
|
446
|
+
learnSkill(
|
|
447
|
+
skillInput: SkillClass | SkillObject | string,
|
|
448
|
+
options: SkillChangeOptions = {},
|
|
449
|
+
): any {
|
|
434
450
|
const map = this._getSkillMap();
|
|
435
451
|
const { skillId, skillData, skillInstance } = this._resolveSkillInput(skillInput, map);
|
|
436
452
|
|
|
@@ -446,6 +462,15 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
|
|
|
446
462
|
// Call onLearn hook
|
|
447
463
|
const hookTarget = (instance as any)._skillInstance || instance;
|
|
448
464
|
this["execMethod"]("onLearn", [this], hookTarget);
|
|
465
|
+
this["execMethod"]("onSkillChange", [
|
|
466
|
+
{
|
|
467
|
+
action: "learn",
|
|
468
|
+
skill: skillData,
|
|
469
|
+
skillId,
|
|
470
|
+
source: options.source ?? "manual",
|
|
471
|
+
level: options.level,
|
|
472
|
+
},
|
|
473
|
+
]);
|
|
449
474
|
|
|
450
475
|
return skillData;
|
|
451
476
|
}
|
|
@@ -466,7 +491,10 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
|
|
|
466
491
|
* player.forgetSkill(FireSkill);
|
|
467
492
|
* ```
|
|
468
493
|
*/
|
|
469
|
-
forgetSkill(
|
|
494
|
+
forgetSkill(
|
|
495
|
+
skillInput: SkillClass | SkillObject | string,
|
|
496
|
+
options: SkillChangeOptions = {},
|
|
497
|
+
): any {
|
|
470
498
|
const index = this._getSkillIndex(skillInput);
|
|
471
499
|
|
|
472
500
|
if (index === -1) {
|
|
@@ -494,6 +522,15 @@ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
|
|
|
494
522
|
(skillEntry as any)?._skillData ||
|
|
495
523
|
skillData;
|
|
496
524
|
this["execMethod"]("onForget", [this], hookTarget);
|
|
525
|
+
this["execMethod"]("onSkillChange", [
|
|
526
|
+
{
|
|
527
|
+
action: "forget",
|
|
528
|
+
skill: skillData,
|
|
529
|
+
skillId: skillData?.id ?? String(skillInput),
|
|
530
|
+
source: options.source ?? "manual",
|
|
531
|
+
level: options.level,
|
|
532
|
+
},
|
|
533
|
+
]);
|
|
497
534
|
|
|
498
535
|
return skillData;
|
|
499
536
|
}
|
|
@@ -607,7 +644,7 @@ export interface ISkillManager {
|
|
|
607
644
|
* @returns The learned skill data
|
|
608
645
|
* @throws SkillLog.alreadyLearned if the player already knows the skill
|
|
609
646
|
*/
|
|
610
|
-
learnSkill(skillInput: SkillClass | SkillObject | string): any;
|
|
647
|
+
learnSkill(skillInput: SkillClass | SkillObject | string, options?: SkillChangeOptions): any;
|
|
611
648
|
|
|
612
649
|
/**
|
|
613
650
|
* Forget a skill
|
|
@@ -616,7 +653,7 @@ export interface ISkillManager {
|
|
|
616
653
|
* @returns The forgotten skill data
|
|
617
654
|
* @throws SkillLog.notLearned if trying to forget a skill not learned
|
|
618
655
|
*/
|
|
619
|
-
forgetSkill(skillInput: SkillClass | SkillObject | string): any;
|
|
656
|
+
forgetSkill(skillInput: SkillClass | SkillObject | string, options?: SkillChangeOptions): any;
|
|
620
657
|
|
|
621
658
|
/**
|
|
622
659
|
* Use a skill
|
package/src/RpgServer.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type RpgMap } from "./rooms/map"
|
|
|
4
4
|
import { RpgServerEngine } from "./RpgServerEngine"
|
|
5
5
|
import { WorldMapConfig, RpgShape, type MapPhysicsInitContext, type MapPhysicsEntityContext } from "@rpgjs/common"
|
|
6
6
|
import { RpgEvent } from "./Player/Player"
|
|
7
|
+
import type { SkillChangePayload } from "./Player/SkillManager"
|
|
7
8
|
|
|
8
9
|
type RpgClassMap<T> = new () => T
|
|
9
10
|
type RpgClassEvent<T> = RpgEvent
|
|
@@ -210,6 +211,14 @@ export interface RpgPlayerHooks {
|
|
|
210
211
|
*/
|
|
211
212
|
onLevelUp?: (player: RpgPlayer, nbLevel: number) => any
|
|
212
213
|
|
|
214
|
+
/**
|
|
215
|
+
* When a player learns or forgets a skill
|
|
216
|
+
*
|
|
217
|
+
* @prop { (player: RpgPlayer, payload: SkillChangePayload) => any } [onSkillChange]
|
|
218
|
+
* @memberof RpgPlayerHooks
|
|
219
|
+
*/
|
|
220
|
+
onSkillChange?: (player: RpgPlayer, payload: SkillChangePayload) => any
|
|
221
|
+
|
|
213
222
|
/**
|
|
214
223
|
* When the player's HP drops to 0
|
|
215
224
|
*
|
package/src/rooms/lobby.ts
CHANGED
|
@@ -36,7 +36,12 @@ export class LobbyRoom extends BaseRoom {
|
|
|
36
36
|
const id = value.data.id
|
|
37
37
|
if (id === 'start') {
|
|
38
38
|
player.initializeDefaultStats();
|
|
39
|
-
|
|
39
|
+
try {
|
|
40
|
+
await lastValueFrom(this.hooks.callHooks("server-player-onStart", player));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("[RPGJS] Error during player onStart hooks:", error);
|
|
44
|
+
}
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
}
|
package/src/rooms/map.ts
CHANGED
|
@@ -1091,6 +1091,13 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1091
1091
|
*/
|
|
1092
1092
|
@Action('move')
|
|
1093
1093
|
async onInput(player: RpgPlayer, input: any) {
|
|
1094
|
+
if (typeof player.canMove === "function" && !player.canMove()) {
|
|
1095
|
+
player.pendingInputs = [];
|
|
1096
|
+
player.lastProcessedInputTs = 0;
|
|
1097
|
+
(this as any).stopMovement(player);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1094
1101
|
const lastAckedFrame = player._lastFramePositions?.frame ?? 0;
|
|
1095
1102
|
const now = Date.now();
|
|
1096
1103
|
const candidates: Array<{
|
|
@@ -1525,6 +1532,16 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1525
1532
|
}
|
|
1526
1533
|
}
|
|
1527
1534
|
|
|
1535
|
+
if (typeof player.canMove === "function" && !player.canMove()) {
|
|
1536
|
+
player.pendingInputs = [];
|
|
1537
|
+
player.lastProcessedInputTs = 0;
|
|
1538
|
+
(this as any).stopMovement(player);
|
|
1539
|
+
return {
|
|
1540
|
+
player,
|
|
1541
|
+
inputs: []
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1528
1545
|
const processedInputs: string[] = [];
|
|
1529
1546
|
const defaultControls: Required<Controls> = {
|
|
1530
1547
|
maxTimeDelta: 1000, // 1 second max between inputs
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { MenuGui, signal } from "../src";
|
|
3
|
+
|
|
4
|
+
describe("GUI", () => {
|
|
5
|
+
test("main menu sends cloneable data when inventory data contains signals", () => {
|
|
6
|
+
const inventoryItem = {
|
|
7
|
+
id: signal("sword"),
|
|
8
|
+
name: signal("Bronze Sword"),
|
|
9
|
+
description: signal("A starter weapon"),
|
|
10
|
+
quantity: signal(1),
|
|
11
|
+
atk: signal(5),
|
|
12
|
+
pdef: signal(0),
|
|
13
|
+
sdef: signal(0),
|
|
14
|
+
icon: signal("inventory-icon"),
|
|
15
|
+
};
|
|
16
|
+
const skill = {
|
|
17
|
+
id: signal("fire"),
|
|
18
|
+
name: signal("Fire"),
|
|
19
|
+
description: signal("Small fire spell"),
|
|
20
|
+
spCost: signal(3),
|
|
21
|
+
};
|
|
22
|
+
const sent: any[] = [];
|
|
23
|
+
const player: any = {
|
|
24
|
+
canMove: signal(true),
|
|
25
|
+
items: signal([inventoryItem]),
|
|
26
|
+
equipments: signal([inventoryItem]),
|
|
27
|
+
skills: signal([skill]),
|
|
28
|
+
param: { str: 4, dex: 3, int: 2, agi: 1, maxHp: 20, maxSp: 10 },
|
|
29
|
+
pdef: 1,
|
|
30
|
+
sdef: 2,
|
|
31
|
+
atk: 5,
|
|
32
|
+
expForNextlevel: signal(150),
|
|
33
|
+
databaseById() {
|
|
34
|
+
return {
|
|
35
|
+
_type: signal("weapon"),
|
|
36
|
+
icon: signal("db-icon"),
|
|
37
|
+
consumable: signal(false),
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
emit(type: string, value: any) {
|
|
41
|
+
sent.push(structuredClone({ type, value }));
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const gui = new MenuGui(player);
|
|
46
|
+
const pending = gui.open();
|
|
47
|
+
|
|
48
|
+
expect(sent[0]).toMatchObject({
|
|
49
|
+
type: "gui.open",
|
|
50
|
+
value: {
|
|
51
|
+
guiId: "rpg-main-menu",
|
|
52
|
+
data: {
|
|
53
|
+
expForNextlevel: 150,
|
|
54
|
+
items: [
|
|
55
|
+
{
|
|
56
|
+
id: "sword",
|
|
57
|
+
icon: "db-icon",
|
|
58
|
+
type: "weapon",
|
|
59
|
+
equipped: true,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
skills: [
|
|
63
|
+
{
|
|
64
|
+
id: "fire",
|
|
65
|
+
name: "Fire",
|
|
66
|
+
spCost: 3,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
gui.close();
|
|
74
|
+
return pending;
|
|
75
|
+
});
|
|
76
|
+
});
|