@colyseus/core 0.17.32 → 0.17.33
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/LICENSE +1 -3
- package/README.md +7 -7
- package/build/router/index.cjs +12 -3
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.mjs +13 -4
- package/build/router/index.mjs.map +2 -2
- package/package.json +6 -6
- package/src/router/index.ts +19 -7
- package/build/rooms/RankedQueueRoom.cjs +0 -240
- package/build/rooms/RankedQueueRoom.cjs.map +0 -7
- package/build/rooms/RankedQueueRoom.d.ts +0 -127
- package/build/rooms/RankedQueueRoom.mjs +0 -205
- package/build/rooms/RankedQueueRoom.mjs.map +0 -7
- package/build/serializer/SchemaSerializerDebug.cjs +0 -9
- package/build/serializer/SchemaSerializerDebug.cjs.map +0 -7
- package/build/serializer/SchemaSerializerDebug.d.ts +0 -7
- package/build/serializer/SchemaSerializerDebug.mjs +0 -7
- package/build/serializer/SchemaSerializerDebug.mjs.map +0 -7
- package/build/utils/Cloud.cjs +0 -124
- package/build/utils/Cloud.cjs.map +0 -7
- package/build/utils/Cloud.d.ts +0 -50
- package/build/utils/Cloud.mjs +0 -84
- package/build/utils/Cloud.mjs.map +0 -7
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
<a href="https://npmjs.com/package/colyseus">
|
|
8
8
|
<img src="https://img.shields.io/npm/dm/colyseus.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETESWYxR33AAAAtElEQVQoz4WQMQrCQBRE38Z0QoTcwF4Qg1h4BO0sxGOk80iCtViksrIQRRBTewWxMI1mbELYjYu+4rPMDPtn12ChMT3gavb4US5Jym0tcBIta3oDHv4Gwmr7nC4QAxBrCdzM2q6XqUnm9m9r59h7Rc0n2pFv24k4ttGMUXW+sGELTJjSr7QDKuqLS6UKFChVWWuFkZw9Z2AAvAirKT+JTlppIRnd6XgaP4goefI2Shj++OnjB3tBmHYK8z9zAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTAyLTAxVDE4OjE3OjM3KzAxOjAwGQQixQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wMi0wMVQxODoxNzozNyswMTowMGhZmnkAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC">
|
|
9
9
|
</a>
|
|
10
|
-
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
|
|
11
|
-
<img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
12
|
-
</a>
|
|
13
10
|
<a href="http://chat.colyseus.io">
|
|
14
11
|
<img src="https://img.shields.io/discord/525739117951320081.svg?style=for-the-badge&colorB=7581dc&logo=discord&logoColor=white">
|
|
15
12
|
</a>
|
|
13
|
+
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss Forum">
|
|
14
|
+
<img src="https://img.shields.io/badge/discuss-forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
15
|
+
</a>
|
|
16
16
|
<h3>
|
|
17
17
|
Multiplayer Framework for Node.js. <br /><a href="https://docs.colyseus.io/">View documentation</a>
|
|
18
18
|
</h3>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
Colyseus is an Authoritative Multiplayer Framework for Node.js, with
|
|
22
|
-
available for the Web,
|
|
21
|
+
Colyseus is an Authoritative Multiplayer Framework for Node.js, with SDKs
|
|
22
|
+
available for the Web, Unity, Defold, Haxe, Cocos and Construct3. ([See official SDKs](https://docs.colyseus.io/client/))
|
|
23
23
|
|
|
24
24
|
The project focuses on providing synchronizable data structures for realtime and
|
|
25
25
|
turn-based games, matchmaking, and ease of usage both on the server-side and
|
|
@@ -36,7 +36,7 @@ for any kind of project you can think of!
|
|
|
36
36
|
- Matchmaking clients into game rooms/sessions
|
|
37
37
|
- Scale vertically or horizontally
|
|
38
38
|
|
|
39
|
-
See [public roadmap](https://
|
|
39
|
+
See [public roadmap](https://docs.colyseus.io/roadmap) for version 1.0.
|
|
40
40
|
|
|
41
41
|
# 🚀 Quickstart
|
|
42
42
|
|
|
@@ -87,7 +87,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|
|
87
87
|
<td align="center"><a href="https://github.com/TinyDobbins"><img src="https://avatars2.githubusercontent.com/u/20824844?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikita Borisov</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3ATinyDobbins" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=TinyDobbins" title="Code">💻</a> <a href="#business-TinyDobbins" title="Business development">💼</a> <a href="#ideas-TinyDobbins" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
88
88
|
<td align="center"><a href="https://acemobe.com/"><img src="https://avatars2.githubusercontent.com/u/232101?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Phil Harvey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=filharvey" title="Documentation">📖</a></td>
|
|
89
89
|
<td align="center"><a href="https://github.com/serjek"><img src="https://avatars2.githubusercontent.com/u/18265157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3Aserjek" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=serjek" title="Code">💻</a></td>
|
|
90
|
-
<td align="center"><a href="https://
|
|
90
|
+
<td align="center"><a href="https://devlsh.com"><img src="https://avatars0.githubusercontent.com/u/853683?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sophie</b></sub></a><br /><a href="#question-devlsh" title="Answering Questions">💬</a> <a href="https://github.com/colyseus/colyseus/issues?q=author%3Adevlsh" title="Bug reports">🐛</a> <a href="#ideas-devlsh" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
91
91
|
<td align="center"><a href="https://github.com/supertommy"><img src="https://avatars0.githubusercontent.com/u/2236153?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tommy Leung</b></sub></a><br /><a href="#mentoring-supertommy" title="Mentoring">🧑🏫</a></td>
|
|
92
92
|
<td align="center"><a href="https://github.com/digimbyte"><img src="https://avatars2.githubusercontent.com/u/6645396?v=4?s=100" width="100px;" alt=""/><br /><sub><b>digimbyte</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=digimbyte" title="Documentation">📖</a></td>
|
|
93
93
|
</tr>
|
package/build/router/index.cjs
CHANGED
|
@@ -65,11 +65,20 @@ function bindRouterToTransport(transport, router) {
|
|
|
65
65
|
transport.bindRouter(router);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
let next
|
|
68
|
+
let next;
|
|
69
69
|
if (expressApp) {
|
|
70
70
|
server.removeListener("request", expressApp);
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
next = async (req, res) => {
|
|
72
|
+
const protocol = req.headers["x-forwarded-proto"] || (req.socket.encrypted ? "https" : "http");
|
|
73
|
+
const base = `${protocol}://${req.headers[":authority"] || req.headers.host}`;
|
|
74
|
+
const response = await router.handler((0, import_node.getRequest)({ base, request: req }));
|
|
75
|
+
if (response.status === 404) {
|
|
76
|
+
return expressApp["handle"](req, res);
|
|
77
|
+
}
|
|
78
|
+
return (0, import_node.setResponse)(res, response);
|
|
79
|
+
};
|
|
80
|
+
} else {
|
|
81
|
+
next = (0, import_node.toNodeHandler)(router.handler);
|
|
73
82
|
}
|
|
74
83
|
server.prependListener("request", (req, res) => {
|
|
75
84
|
const corsHeaders = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/router/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from \"@colyseus/better-call\";\nimport { toNodeHandler } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n // check if the server is bound to an express app\n const expressApp = transport.getExpressApp() as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n expressRootRoute(expressApp) !== undefined ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n const server = transport.server;\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n //
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAAsH;AACtH,
|
|
4
|
+
"sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n // check if the server is bound to an express app\n const expressApp = transport.getExpressApp() as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n expressRootRoute(expressApp) !== undefined ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n const server = transport.server;\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n // execute router first, fallback to express in case of 404\n next = async (req: IncomingMessage, res: ServerResponse) => {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n\n // fallback to express if 404\n if (response.status === 404) {\n return expressApp['handle'](req, res);\n }\n\n return setResponse(res, response);\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * Do not use this directly. This is used internally by `@colyseus/playground`.\n * TODO: refactor. Avoid using globals.\n * @internal\n */\nexport let __globalEndpoints: Record<string, Endpoint> = {};\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n // TODO: refactor. Avoid using globals.\n __globalEndpoints = endpoints;\n\n return createBetterCallRouter({ ...endpoints }, config);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,yBAAsH;AACtH,kBAAuD;AACvD,uBAA0B;AAC1B,wBAA2B;AAC3B,qBAAgB;AAEhB,IAAAA,sBAaO;AAIA,SAAS,sBAAsB,WAAsB,QAAgB;AAE1E,SAAO,gBAAY,mCAAe,kBAAkB,EAAE,QAAQ,MAAM,GAAG,OAAO,QAAQ;AACpF,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C,CAAC,CAAC;AAGF,QAAM,aAAa,UAAU,cAAc;AAG3C,QAAM;AAAA;AAAA,IAEJ,iBAAiB,UAAU,MAAM;AAAA,IAGjC,OAAO,OAAO,OAAO,SAAS,EAAE,KAAK,cAAY,SAAS,SAAS,GAAG;AAAA;AAGxE,MAAI,CAAC,cAAc;AACjB,WAAO,gBAAY,mCAAe,KAAK,EAAE,QAAQ,MAAM,GAAG,OAAO,QAAQ;AACvE,aAAO,IAAI,SAAS,YAAY,eAAAC,QAAI,OAAO,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChE,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,SAAS,UAAU;AAGzB,MAAI,CAAC,UAAU,UAAU,YAAY;AACnC,cAAU,WAAW,MAAM;AAC3B;AAAA,EACF;AAIA,MAAI;AAEJ,MAAI,YAAY;AACd,WAAO,eAAe,WAAW,UAAU;AAG3C,WAAO,OAAO,KAAsB,QAAwB;AAC1D,YAAM,WAAW,IAAI,QAAQ,mBAAmB,MAAO,IAAI,OAAe,YAAY,UAAU;AAChG,YAAM,OAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,YAAY,KAAK,IAAI,QAAQ,IAAI;AAC3E,YAAM,WAAW,MAAM,OAAO,YAAQ,wBAAW,EAAE,MAAM,SAAS,IAAI,CAAC,CAAC;AAGxE,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO,WAAW,QAAQ,EAAE,KAAK,GAAG;AAAA,MACtC;AAEA,iBAAO,yBAAY,KAAK,QAAQ;AAAA,IAClC;AAAA,EAEF,OAAO;AACL,eAAO,2BAAc,OAAO,OAAO;AAAA,EACrC;AAGA,SAAO,gBAAgB,WAAW,CAAC,KAAsB,QAAwB;AAC/E,UAAM,cAAc;AAAA,MAClB,GAAG,6BAAW;AAAA,MACd,GAAG,6BAAW,eAAe,IAAI,QAAQ,IAAI,OAAc,CAAC;AAAA,IAC9D;AAEA,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,KAAK,WAAW;AAC9B,UAAI,IAAI;AACR;AAAA,IACF;AAEA,WAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,UAAI,UAAU,KAAK,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,KAAK,GAAG;AAAA,EACf,CAAC;AACH;AAEA,SAAS,iBAAiB,YAAiC;AAOzD,QAAM,QAAS,YAAoB,SAAS,SAAU,YAAoB,QAAQ;AAElF,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,SAAO,MAAM,KAAK,CAAC,UAAe,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,SAAS,MAAM,IAAI,CAAC;AACtG;AAOO,IAAI,oBAA8C,CAAC;AAEnD,SAAS,aAGd,WAAc,SAAiB,CAAC,GAAa;AAE7C,sBAAoB;AAEpB,aAAO,mBAAAC,cAAuB,EAAE,GAAG,UAAU,GAAG,MAAM;AACxD;",
|
|
6
6
|
"names": ["import_better_call", "pkg", "createBetterCallRouter"]
|
|
7
7
|
}
|
package/build/router/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages/core/src/router/index.ts
|
|
2
2
|
import { createRouter as createBetterCallRouter, createEndpoint } from "@colyseus/better-call";
|
|
3
|
-
import { toNodeHandler } from "@colyseus/better-call/node";
|
|
3
|
+
import { toNodeHandler, getRequest, setResponse } from "@colyseus/better-call/node";
|
|
4
4
|
import "../Transport.mjs";
|
|
5
5
|
import { controller } from "../matchmaker/controller.mjs";
|
|
6
6
|
import pkg from "../../package.json" with { type: "json" };
|
|
@@ -29,11 +29,20 @@ function bindRouterToTransport(transport, router) {
|
|
|
29
29
|
transport.bindRouter(router);
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
|
-
let next
|
|
32
|
+
let next;
|
|
33
33
|
if (expressApp) {
|
|
34
34
|
server.removeListener("request", expressApp);
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
next = async (req, res) => {
|
|
36
|
+
const protocol = req.headers["x-forwarded-proto"] || (req.socket.encrypted ? "https" : "http");
|
|
37
|
+
const base = `${protocol}://${req.headers[":authority"] || req.headers.host}`;
|
|
38
|
+
const response = await router.handler(getRequest({ base, request: req }));
|
|
39
|
+
if (response.status === 404) {
|
|
40
|
+
return expressApp["handle"](req, res);
|
|
41
|
+
}
|
|
42
|
+
return setResponse(res, response);
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
next = toNodeHandler(router.handler);
|
|
37
46
|
}
|
|
38
47
|
server.prependListener("request", (req, res) => {
|
|
39
48
|
const corsHeaders = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/router/index.ts"],
|
|
4
|
-
"sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from \"@colyseus/better-call\";\nimport { toNodeHandler } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n // check if the server is bound to an express app\n const expressApp = transport.getExpressApp() as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n expressRootRoute(expressApp) !== undefined ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n const server = transport.server;\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n //
|
|
5
|
-
"mappings": ";AAEA,SAAwD,gBAAgB,wBAAwB,sBAAsB;AACtH,SAAS,
|
|
4
|
+
"sourcesContent": ["import type express from \"express\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from \"@colyseus/better-call\";\nimport { toNodeHandler, getRequest, setResponse } from \"@colyseus/better-call/node\";\nimport { Transport } from \"../Transport.ts\";\nimport { controller } from \"../matchmaker/controller.ts\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nexport {\n createEndpoint,\n createMiddleware,\n createInternalContext,\n\n // Re-export types needed for declaration emit\n type Router,\n type RouterConfig,\n type Endpoint,\n type EndpointHandler,\n type EndpointOptions,\n type EndpointContext,\n type StrictEndpoint,\n} from \"@colyseus/better-call\";\n\nexport { toNodeHandler };\n\nexport function bindRouterToTransport(transport: Transport, router: Router) {\n // add default \"/__healthcheck\" endpoint\n router.addEndpoint(createEndpoint(\"/__healthcheck\", { method: \"GET\" }, async (ctx) => {\n return new Response(\"OK\", { status: 200 });\n }));\n\n // check if the server is bound to an express app\n const expressApp = transport.getExpressApp() as express.Application;\n\n // add default \"/\" route, if not provided.\n const hasRootRoute = (\n // check if express app has a root route\n expressRootRoute(expressApp) !== undefined ||\n\n // check if router has a root route\n Object.values(router.endpoints).some(endpoint => endpoint.path === \"/\")\n );\n\n if (!hasRootRoute) {\n router.addEndpoint(createEndpoint(\"/\", { method: \"GET\" }, async (ctx) => {\n return new Response(`Colyseus ${pkg.version}`, { status: 200 });\n }));\n }\n\n const server = transport.server;\n\n // use custom bindRouter method if provided\n if (!server && transport.bindRouter) {\n transport.bindRouter(router);\n return;\n }\n\n // which route handler to use\n // (router + fallback to express, or just router)\n let next: any;\n\n if (expressApp) {\n server.removeListener('request', expressApp);\n\n // execute router first, fallback to express in case of 404\n next = async (req: IncomingMessage, res: ServerResponse) => {\n const protocol = req.headers[\"x-forwarded-proto\"] || ((req.socket as any).encrypted ? \"https\" : \"http\");\n const base = `${protocol}://${req.headers[\":authority\"] || req.headers.host}`;\n const response = await router.handler(getRequest({ base, request: req }));\n\n // fallback to express if 404\n if (response.status === 404) {\n return expressApp['handle'](req, res);\n }\n\n return setResponse(res, response);\n };\n\n } else {\n next = toNodeHandler(router.handler);\n }\n\n // handle cors headers for all requests by default\n server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {\n const corsHeaders = {\n ...controller.DEFAULT_CORS_HEADERS,\n ...controller.getCorsHeaders(new Headers(req.headers as any)),\n };\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n Object.entries(corsHeaders).forEach(([key, value]) => {\n res.setHeader(key, value);\n });\n\n next(req, res);\n });\n}\n\nfunction expressRootRoute(expressApp: express.Application) {\n //\n // express v5 uses `app.router`, express v4 uses `app._router`\n // check for `app._router` first, then `app.router`\n //\n // (express v4 will show a warning if `app.router` is used)\n //\n const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;\n\n if (!stack) {\n return false;\n }\n\n return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));\n}\n\n/**\n * Do not use this directly. This is used internally by `@colyseus/playground`.\n * TODO: refactor. Avoid using globals.\n * @internal\n */\nexport let __globalEndpoints: Record<string, Endpoint> = {};\n\nexport function createRouter<\n E extends Record<string, Endpoint>,\n Config extends RouterConfig\n>(endpoints: E, config: Config = {} as Config) {\n // TODO: refactor. Avoid using globals.\n __globalEndpoints = endpoints;\n\n return createBetterCallRouter({ ...endpoints }, config);\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,SAAwD,gBAAgB,wBAAwB,sBAAsB;AACtH,SAAS,eAAe,YAAY,mBAAmB;AACvD,OAA0B;AAC1B,SAAS,kBAAkB;AAC3B,OAAO,SAAS,qBAAqB,KAAK,EAAE,MAAM,OAAO;AAEzD;AAAA,EACE,kBAAAA;AAAA,EACA;AAAA,EACA;AAAA,OAUK;AAIA,SAAS,sBAAsB,WAAsB,QAAgB;AAE1E,SAAO,YAAY,eAAe,kBAAkB,EAAE,QAAQ,MAAM,GAAG,OAAO,QAAQ;AACpF,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3C,CAAC,CAAC;AAGF,QAAM,aAAa,UAAU,cAAc;AAG3C,QAAM;AAAA;AAAA,IAEJ,iBAAiB,UAAU,MAAM;AAAA,IAGjC,OAAO,OAAO,OAAO,SAAS,EAAE,KAAK,cAAY,SAAS,SAAS,GAAG;AAAA;AAGxE,MAAI,CAAC,cAAc;AACjB,WAAO,YAAY,eAAe,KAAK,EAAE,QAAQ,MAAM,GAAG,OAAO,QAAQ;AACvE,aAAO,IAAI,SAAS,YAAY,IAAI,OAAO,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IAChE,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,SAAS,UAAU;AAGzB,MAAI,CAAC,UAAU,UAAU,YAAY;AACnC,cAAU,WAAW,MAAM;AAC3B;AAAA,EACF;AAIA,MAAI;AAEJ,MAAI,YAAY;AACd,WAAO,eAAe,WAAW,UAAU;AAG3C,WAAO,OAAO,KAAsB,QAAwB;AAC1D,YAAM,WAAW,IAAI,QAAQ,mBAAmB,MAAO,IAAI,OAAe,YAAY,UAAU;AAChG,YAAM,OAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,YAAY,KAAK,IAAI,QAAQ,IAAI;AAC3E,YAAM,WAAW,MAAM,OAAO,QAAQ,WAAW,EAAE,MAAM,SAAS,IAAI,CAAC,CAAC;AAGxE,UAAI,SAAS,WAAW,KAAK;AAC3B,eAAO,WAAW,QAAQ,EAAE,KAAK,GAAG;AAAA,MACtC;AAEA,aAAO,YAAY,KAAK,QAAQ;AAAA,IAClC;AAAA,EAEF,OAAO;AACL,WAAO,cAAc,OAAO,OAAO;AAAA,EACrC;AAGA,SAAO,gBAAgB,WAAW,CAAC,KAAsB,QAAwB;AAC/E,UAAM,cAAc;AAAA,MAClB,GAAG,WAAW;AAAA,MACd,GAAG,WAAW,eAAe,IAAI,QAAQ,IAAI,OAAc,CAAC;AAAA,IAC9D;AAEA,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,KAAK,WAAW;AAC9B,UAAI,IAAI;AACR;AAAA,IACF;AAEA,WAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,UAAI,UAAU,KAAK,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,KAAK,GAAG;AAAA,EACf,CAAC;AACH;AAEA,SAAS,iBAAiB,YAAiC;AAOzD,QAAM,QAAS,YAAoB,SAAS,SAAU,YAAoB,QAAQ;AAElF,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,SAAO,MAAM,KAAK,CAAC,UAAe,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,aAAa,EAAE,SAAS,MAAM,IAAI,CAAC;AACtG;AAOO,IAAI,oBAA8C,CAAC;AAEnD,SAAS,aAGd,WAAc,SAAiB,CAAC,GAAa;AAE7C,sBAAoB;AAEpB,SAAO,uBAAuB,EAAE,GAAG,UAAU,GAAG,MAAM;AACxD;",
|
|
6
6
|
"names": ["createEndpoint"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/core",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.33",
|
|
4
4
|
"description": "Multiplayer Framework for Node.js.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"input": "./src/index.ts",
|
|
@@ -52,23 +52,23 @@
|
|
|
52
52
|
"debug": "^4.3.4",
|
|
53
53
|
"nanoid": "^3.3.11",
|
|
54
54
|
"@colyseus/shared-types": "^0.17.6",
|
|
55
|
-
"@colyseus/better-call": "^1.2.
|
|
55
|
+
"@colyseus/better-call": "^1.2.0",
|
|
56
56
|
"@colyseus/greeting-banner": "^3.0.8"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@colyseus/schema": "^4.0.7",
|
|
60
60
|
"express": "^5.0.0",
|
|
61
61
|
"@colyseus/redis-presence": "^0.17.6",
|
|
62
|
-
"@colyseus/
|
|
63
|
-
"@colyseus/
|
|
62
|
+
"@colyseus/tools": "^0.17.18",
|
|
63
|
+
"@colyseus/redis-driver": "^0.17.6"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@colyseus/schema": "^4.0.7",
|
|
67
67
|
"@pm2/io": "^6.1.0",
|
|
68
68
|
"express": "^4.16.0 || ^5.0.0",
|
|
69
69
|
"zod": "^4.1.12",
|
|
70
|
-
"@colyseus/
|
|
71
|
-
"@colyseus/
|
|
70
|
+
"@colyseus/ws-transport": "^0.17.9",
|
|
71
|
+
"@colyseus/better-call": "^1.2.0"
|
|
72
72
|
},
|
|
73
73
|
"optionalPeerDependencies": {
|
|
74
74
|
"@colyseus/tools": "0.17.x",
|
package/src/router/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type express from "express";
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
3
3
|
import { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from "@colyseus/better-call";
|
|
4
|
-
import { toNodeHandler } from "@colyseus/better-call/node";
|
|
4
|
+
import { toNodeHandler, getRequest, setResponse } from "@colyseus/better-call/node";
|
|
5
5
|
import { Transport } from "../Transport.ts";
|
|
6
6
|
import { controller } from "../matchmaker/controller.ts";
|
|
7
7
|
import pkg from "../../package.json" with { type: "json" };
|
|
@@ -55,17 +55,29 @@ export function bindRouterToTransport(transport: Transport, router: Router) {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
|
|
58
|
+
// which route handler to use
|
|
59
|
+
// (router + fallback to express, or just router)
|
|
60
|
+
let next: any;
|
|
60
61
|
|
|
61
62
|
if (expressApp) {
|
|
62
63
|
server.removeListener('request', expressApp);
|
|
63
64
|
|
|
64
|
-
//
|
|
65
|
-
|
|
65
|
+
// execute router first, fallback to express in case of 404
|
|
66
|
+
next = async (req: IncomingMessage, res: ServerResponse) => {
|
|
67
|
+
const protocol = req.headers["x-forwarded-proto"] || ((req.socket as any).encrypted ? "https" : "http");
|
|
68
|
+
const base = `${protocol}://${req.headers[":authority"] || req.headers.host}`;
|
|
69
|
+
const response = await router.handler(getRequest({ base, request: req }));
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
// fallback to express if 404
|
|
72
|
+
if (response.status === 404) {
|
|
73
|
+
return expressApp['handle'](req, res);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return setResponse(res, response);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
} else {
|
|
80
|
+
next = toNodeHandler(router.handler);
|
|
69
81
|
}
|
|
70
82
|
|
|
71
83
|
// handle cors headers for all requests by default
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// packages/core/src/rooms/RankedQueueRoom.ts
|
|
31
|
-
var RankedQueueRoom_exports = {};
|
|
32
|
-
__export(RankedQueueRoom_exports, {
|
|
33
|
-
RankedQueueRoom: () => RankedQueueRoom
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(RankedQueueRoom_exports);
|
|
36
|
-
var import_Room = require("../Room.cjs");
|
|
37
|
-
var matchMaker = __toESM(require("../MatchMaker.cjs"), 1);
|
|
38
|
-
var import_Debug = require("../Debug.cjs");
|
|
39
|
-
var import_ServerError = require("../errors/ServerError.cjs");
|
|
40
|
-
var import_shared_types = require("@colyseus/shared-types");
|
|
41
|
-
var DEFAULT_TEAM = /* @__PURE__ */ Symbol("$default_team");
|
|
42
|
-
var DEFAULT_COMPARE = (client, matchGroup) => {
|
|
43
|
-
const diff = Math.abs(client.rank - matchGroup.averageRank);
|
|
44
|
-
const diffRatio = diff / matchGroup.averageRank;
|
|
45
|
-
return diff < 10 || diffRatio <= 2;
|
|
46
|
-
};
|
|
47
|
-
var RankedQueueRoom = class extends import_Room.Room {
|
|
48
|
-
constructor() {
|
|
49
|
-
super(...arguments);
|
|
50
|
-
this.maxPlayers = 4;
|
|
51
|
-
this.allowIncompleteGroups = false;
|
|
52
|
-
this.maxWaitingCycles = 15;
|
|
53
|
-
this.maxWaitingCyclesForPriority = 10;
|
|
54
|
-
/**
|
|
55
|
-
* Evaluate groups for each client at interval
|
|
56
|
-
*/
|
|
57
|
-
this.cycleTickInterval = 1e3;
|
|
58
|
-
/**
|
|
59
|
-
* Groups of players per iteration
|
|
60
|
-
*/
|
|
61
|
-
this.groups = [];
|
|
62
|
-
this.highPriorityGroups = [];
|
|
63
|
-
this.compare = DEFAULT_COMPARE;
|
|
64
|
-
this.onGroupReady = (group) => matchMaker.createRoom(this.matchRoomName, {});
|
|
65
|
-
this.messages = {
|
|
66
|
-
confirm: (client, _) => {
|
|
67
|
-
const queueData = client.userData;
|
|
68
|
-
if (queueData && queueData.group && typeof queueData.group.confirmed === "number") {
|
|
69
|
-
queueData.confirmed = true;
|
|
70
|
-
queueData.group.confirmed++;
|
|
71
|
-
client.leave();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
onCreate(options) {
|
|
77
|
-
if (typeof options.maxWaitingCycles === "number") {
|
|
78
|
-
this.maxWaitingCycles = options.maxWaitingCycles;
|
|
79
|
-
}
|
|
80
|
-
if (typeof options.maxPlayers === "number") {
|
|
81
|
-
this.maxPlayers = options.maxPlayers;
|
|
82
|
-
}
|
|
83
|
-
if (typeof options.maxTeamSize === "number") {
|
|
84
|
-
this.maxTeamSize = options.maxTeamSize;
|
|
85
|
-
}
|
|
86
|
-
if (typeof options.allowIncompleteGroups !== "undefined") {
|
|
87
|
-
this.allowIncompleteGroups = options.allowIncompleteGroups;
|
|
88
|
-
}
|
|
89
|
-
if (typeof options.compare === "function") {
|
|
90
|
-
this.compare = options.compare;
|
|
91
|
-
}
|
|
92
|
-
if (typeof options.onGroupReady === "function") {
|
|
93
|
-
this.onGroupReady = options.onGroupReady;
|
|
94
|
-
}
|
|
95
|
-
if (options.matchRoomName) {
|
|
96
|
-
this.matchRoomName = options.matchRoomName;
|
|
97
|
-
} else {
|
|
98
|
-
throw new import_ServerError.ServerError(import_shared_types.ErrorCode.APPLICATION_ERROR, "RankedQueueRoom: 'matchRoomName' option is required.");
|
|
99
|
-
}
|
|
100
|
-
(0, import_Debug.debugMatchMaking)("RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);
|
|
101
|
-
this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);
|
|
102
|
-
}
|
|
103
|
-
onJoin(client, options, auth) {
|
|
104
|
-
this.addToQueue(client, {
|
|
105
|
-
rank: options.rank,
|
|
106
|
-
teamId: options.teamId,
|
|
107
|
-
options
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
addToQueue(client, queueData) {
|
|
111
|
-
if (queueData.currentCycle === void 0) {
|
|
112
|
-
queueData.currentCycle = 0;
|
|
113
|
-
}
|
|
114
|
-
client.userData = queueData;
|
|
115
|
-
client.send("clients", 1);
|
|
116
|
-
}
|
|
117
|
-
createMatchGroup() {
|
|
118
|
-
const group = { clients: [], averageRank: 0 };
|
|
119
|
-
this.groups.push(group);
|
|
120
|
-
return group;
|
|
121
|
-
}
|
|
122
|
-
reassignMatchGroups() {
|
|
123
|
-
this.groups.length = 0;
|
|
124
|
-
this.highPriorityGroups.length = 0;
|
|
125
|
-
const sortedClients = this.clients.filter((client) => {
|
|
126
|
-
return client.userData && client.userData.group?.ready !== true;
|
|
127
|
-
}).sort((a, b) => {
|
|
128
|
-
return a.userData.rank - b.userData.rank;
|
|
129
|
-
});
|
|
130
|
-
if (typeof this.maxTeamSize === "number") {
|
|
131
|
-
this.redistributeTeams(sortedClients);
|
|
132
|
-
} else {
|
|
133
|
-
this.redistributeClients(sortedClients);
|
|
134
|
-
}
|
|
135
|
-
this.evaluateHighPriorityGroups();
|
|
136
|
-
this.processGroupsReady();
|
|
137
|
-
}
|
|
138
|
-
redistributeTeams(sortedClients) {
|
|
139
|
-
const teamsByID = {};
|
|
140
|
-
sortedClients.forEach((client) => {
|
|
141
|
-
const teamId = client.userData.teamId || DEFAULT_TEAM;
|
|
142
|
-
if (!teamsByID[teamId]) {
|
|
143
|
-
teamsByID[teamId] = { teamId, clients: [], averageRank: 0 };
|
|
144
|
-
}
|
|
145
|
-
teamsByID[teamId].averageRank += client.userData.rank;
|
|
146
|
-
teamsByID[teamId].clients.push(client);
|
|
147
|
-
});
|
|
148
|
-
let teams = Object.values(teamsByID).map((team) => {
|
|
149
|
-
team.averageRank /= team.clients.length;
|
|
150
|
-
return team;
|
|
151
|
-
}).sort((a, b) => {
|
|
152
|
-
return a.averageRank - b.averageRank;
|
|
153
|
-
});
|
|
154
|
-
do {
|
|
155
|
-
let currentGroup = this.createMatchGroup();
|
|
156
|
-
teams = teams.filter((team) => {
|
|
157
|
-
const totalRank = team.averageRank * team.clients.length;
|
|
158
|
-
currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);
|
|
159
|
-
if (team.clients.length >= this.maxTeamSize) {
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
team.clients.forEach((client) => client.userData.currentCycle++);
|
|
163
|
-
return false;
|
|
164
|
-
});
|
|
165
|
-
} while (teams.length >= 2);
|
|
166
|
-
}
|
|
167
|
-
redistributeClients(sortedClients, currentGroup = this.createMatchGroup(), totalRank = 0) {
|
|
168
|
-
for (let i = 0, l = sortedClients.length; i < l; i++) {
|
|
169
|
-
const client = sortedClients[i];
|
|
170
|
-
const userData = client.userData;
|
|
171
|
-
const currentCycle = userData.currentCycle++;
|
|
172
|
-
if (currentGroup.averageRank > 0) {
|
|
173
|
-
if (!this.compare(userData, currentGroup) && !userData.highPriority) {
|
|
174
|
-
currentGroup = this.createMatchGroup();
|
|
175
|
-
totalRank = 0;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
userData.group = currentGroup;
|
|
179
|
-
currentGroup.clients.push(client);
|
|
180
|
-
totalRank += userData.rank;
|
|
181
|
-
currentGroup.averageRank = totalRank / currentGroup.clients.length;
|
|
182
|
-
if (currentGroup.clients.length === this.maxPlayers) {
|
|
183
|
-
currentGroup.ready = true;
|
|
184
|
-
currentGroup = this.createMatchGroup();
|
|
185
|
-
totalRank = 0;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {
|
|
189
|
-
if (this.highPriorityGroups.indexOf(currentGroup) === -1) {
|
|
190
|
-
this.highPriorityGroups.push(currentGroup);
|
|
191
|
-
}
|
|
192
|
-
} else if (this.maxWaitingCyclesForPriority !== void 0 && currentCycle >= this.maxWaitingCyclesForPriority) {
|
|
193
|
-
userData.highPriority = true;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return currentGroup;
|
|
197
|
-
}
|
|
198
|
-
evaluateHighPriorityGroups() {
|
|
199
|
-
this.highPriorityGroups.forEach((group) => {
|
|
200
|
-
group.ready = group.clients.every((c) => {
|
|
201
|
-
return c.userData?.currentCycle > 1;
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
processGroupsReady() {
|
|
206
|
-
this.groups.forEach(async (group) => {
|
|
207
|
-
if (group.ready) {
|
|
208
|
-
group.confirmed = 0;
|
|
209
|
-
try {
|
|
210
|
-
const room = await this.onGroupReady.call(this, group);
|
|
211
|
-
await matchMaker.reserveMultipleSeatsFor(
|
|
212
|
-
room,
|
|
213
|
-
group.clients.map((client) => ({
|
|
214
|
-
sessionId: client.sessionId,
|
|
215
|
-
options: client.userData.options,
|
|
216
|
-
auth: client.auth
|
|
217
|
-
}))
|
|
218
|
-
);
|
|
219
|
-
group.clients.forEach((client, i) => {
|
|
220
|
-
client.send("seat", matchMaker.buildSeatReservation(room, client.sessionId));
|
|
221
|
-
});
|
|
222
|
-
} catch (e) {
|
|
223
|
-
group.clients.forEach((client) => client.leave(1011, e.message));
|
|
224
|
-
}
|
|
225
|
-
} else {
|
|
226
|
-
group.clients.forEach((client) => {
|
|
227
|
-
const queueClientCount = group.clients.length;
|
|
228
|
-
if (client.userData.lastQueueClientCount !== queueClientCount) {
|
|
229
|
-
client.userData.lastQueueClientCount = queueClientCount;
|
|
230
|
-
client.send("clients", queueClientCount);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
238
|
-
0 && (module.exports = {
|
|
239
|
-
RankedQueueRoom
|
|
240
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/rooms/RankedQueueRoom.ts"],
|
|
4
|
-
"sourcesContent": ["import { Room } from '../Room.ts';\nimport type { Client } from '../Transport.ts';\nimport type { IRoomCache } from '../matchmaker/driver.ts';\nimport * as matchMaker from '../MatchMaker.ts';\nimport { debugMatchMaking } from '../Debug.ts';\nimport { ServerError } from '../errors/ServerError.ts';\nimport { ErrorCode } from '@colyseus/shared-types';\n\nexport interface RankedQueueOptions {\n /**\n * number of players on each match\n */\n maxPlayers?: number;\n\n /**\n * name of the room to create\n */\n matchRoomName: string;\n\n /**\n * after these cycles, create a match with a bot\n */\n maxWaitingCycles?: number;\n\n /**\n * after this time, try to fit this client with a not-so-compatible group\n */\n maxWaitingCyclesForPriority?: number;\n\n /**\n * If set, teams must have the same size to be matched together\n */\n maxTeamSize?: number;\n\n /**\n * If `allowIncompleteGroups` is true, players inside an unmatched group (that\n * did not reached `maxPlayers`, and `maxWaitingCycles` has been\n * reached) will be matched together. Your room should fill the remaining\n * spots with \"bots\" on this case.\n */\n allowIncompleteGroups?: boolean;\n\n /**\n * Comparison function for matching clients to groups\n * Returns true if the client is compatible with the group\n */\n compare?: (client: ClientQueueData, matchGroup: MatchGroup) => boolean;\n\n /**\n *\n * When onGroupReady is set, the \"roomNameToCreate\" option is ignored.\n */\n onGroupReady?: (this: RankedQueueRoom, group: MatchGroup) => Promise<IRoomCache>;\n}\n\nexport interface MatchGroup {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n ready?: boolean;\n confirmed?: number;\n}\n\nexport interface MatchTeam {\n averageRank: number;\n clients: Array<Client<{ userData: ClientQueueData }>>,\n teamId: string | symbol;\n}\n\nexport interface ClientQueueData {\n /**\n * Rank of the client\n */\n rank: number;\n\n /**\n * Timestamp of when the client entered the queue\n */\n currentCycle?: number;\n\n /**\n * Optional: if matching with a team, the team ID\n */\n teamId?: string;\n\n /**\n * Additional options passed by the client when joining the room\n */\n options?: any;\n\n /**\n * Match group the client is currently in\n */\n group?: MatchGroup;\n\n /**\n * Whether the client has confirmed the connection to the room\n */\n confirmed?: boolean;\n\n /**\n * Whether the client should be prioritized in the queue\n * (e.g. for players that are waiting for a long time)\n */\n highPriority?: boolean;\n\n /**\n * The last number of clients in the queue sent to the client\n */\n lastQueueClientCount?: number;\n}\n\nconst DEFAULT_TEAM = Symbol(\"$default_team\");\nconst DEFAULT_COMPARE = (client: ClientQueueData, matchGroup: MatchGroup) => {\n const diff = Math.abs(client.rank - matchGroup.averageRank);\n const diffRatio = (diff / matchGroup.averageRank);\n // If diff ratio is too high, create a new match group\n return (diff < 10 || diffRatio <= 2);\n}\n\nexport class RankedQueueRoom extends Room {\n maxPlayers = 4;\n maxTeamSize: number;\n allowIncompleteGroups: boolean = false;\n\n maxWaitingCycles = 15;\n maxWaitingCyclesForPriority?: number = 10;\n\n /**\n * Evaluate groups for each client at interval\n */\n cycleTickInterval = 1000;\n\n /**\n * Groups of players per iteration\n */\n groups: MatchGroup[] = [];\n highPriorityGroups: MatchGroup[] = [];\n\n matchRoomName: string;\n\n protected compare = DEFAULT_COMPARE;\n protected onGroupReady = (group: MatchGroup) => matchMaker.createRoom(this.matchRoomName, {});\n\n messages = {\n confirm: (client: Client, _: unknown) => {\n const queueData = client.userData;\n\n if (queueData && queueData.group && typeof (queueData.group.confirmed) === \"number\") {\n queueData.confirmed = true;\n queueData.group.confirmed++;\n client.leave();\n }\n },\n }\n\n onCreate(options: RankedQueueOptions) {\n if (typeof(options.maxWaitingCycles) === \"number\") {\n this.maxWaitingCycles = options.maxWaitingCycles;\n }\n\n if (typeof(options.maxPlayers) === \"number\") {\n this.maxPlayers = options.maxPlayers;\n }\n\n if (typeof(options.maxTeamSize) === \"number\") {\n this.maxTeamSize = options.maxTeamSize;\n }\n\n if (typeof(options.allowIncompleteGroups) !== \"undefined\") {\n this.allowIncompleteGroups = options.allowIncompleteGroups;\n }\n\n if (typeof(options.compare) === \"function\") {\n this.compare = options.compare;\n }\n\n if (typeof(options.onGroupReady) === \"function\") {\n this.onGroupReady = options.onGroupReady;\n }\n\n if (options.matchRoomName) {\n this.matchRoomName = options.matchRoomName;\n\n } else {\n throw new ServerError(ErrorCode.APPLICATION_ERROR, \"RankedQueueRoom: 'matchRoomName' option is required.\");\n }\n\n debugMatchMaking(\"RankedQueueRoom#onCreate() maxPlayers: %d, maxWaitingCycles: %d, maxTeamSize: %d, allowIncompleteGroups: %d, roomNameToCreate: %s\", this.maxPlayers, this.maxWaitingCycles, this.maxTeamSize, this.allowIncompleteGroups, this.matchRoomName);\n\n /**\n * Redistribute clients into groups at every interval\n */\n this.setSimulationInterval(() => this.reassignMatchGroups(), this.cycleTickInterval);\n }\n\n onJoin(client: Client, options: any, auth?: unknown) {\n this.addToQueue(client, {\n rank: options.rank,\n teamId: options.teamId,\n options,\n });\n }\n\n addToQueue(client: Client, queueData: ClientQueueData) {\n if (queueData.currentCycle === undefined) {\n queueData.currentCycle = 0;\n }\n client.userData = queueData;\n\n // FIXME: reassign groups upon joining [?] (without incrementing cycle count)\n client.send(\"clients\", 1);\n }\n\n createMatchGroup() {\n const group: MatchGroup = { clients: [], averageRank: 0 };\n this.groups.push(group);\n return group;\n }\n\n reassignMatchGroups() {\n // Re-set all groups\n this.groups.length = 0;\n this.highPriorityGroups.length = 0;\n\n const sortedClients = (this.clients)\n .filter((client) => {\n // Filter out:\n // - clients that are not in the queue\n // - clients that are already in a \"ready\" group\n return (\n client.userData &&\n client.userData.group?.ready !== true\n );\n })\n .sort((a, b) => {\n //\n // Sort by rank ascending\n //\n return a.userData.rank - b.userData.rank;\n });\n\n //\n // The room either distribute by teams or by clients\n //\n if (typeof(this.maxTeamSize) === \"number\") {\n this.redistributeTeams(sortedClients);\n\n } else {\n this.redistributeClients(sortedClients);\n }\n\n this.evaluateHighPriorityGroups();\n this.processGroupsReady();\n }\n\n redistributeTeams(sortedClients: Client<{ userData: ClientQueueData }>[]) {\n const teamsByID: { [teamId: string | symbol]: MatchTeam } = {};\n\n sortedClients.forEach((client) => {\n const teamId = client.userData.teamId || DEFAULT_TEAM;\n\n // Create a new team if it doesn't exist\n if (!teamsByID[teamId]) {\n teamsByID[teamId] = { teamId: teamId, clients: [], averageRank: 0, };\n }\n\n teamsByID[teamId].averageRank += client.userData.rank;\n teamsByID[teamId].clients.push(client);\n });\n\n // Calculate average rank for each team\n let teams = Object.values(teamsByID).map((team) => {\n team.averageRank /= team.clients.length;\n return team;\n }).sort((a, b) => {\n // Sort by average rank ascending\n return a.averageRank - b.averageRank;\n });\n\n // Iterate over teams multiple times until all clients are assigned to a group\n do {\n let currentGroup: MatchGroup = this.createMatchGroup();\n teams = teams.filter((team) => {\n // Remove clients from the team and add them to the current group\n const totalRank = team.averageRank * team.clients.length;\n\n // currentGroup.averageRank = (currentGroup.averageRank === undefined)\n // ? team.averageRank\n // : (currentGroup.averageRank + team.averageRank) / ;\n currentGroup = this.redistributeClients(team.clients.splice(0, this.maxTeamSize), currentGroup, totalRank);\n\n if (team.clients.length >= this.maxTeamSize) {\n // team still has enough clients to form a group\n return true;\n }\n\n // increment cycle count for all clients in the team\n team.clients.forEach((client) => client.userData.currentCycle++);\n\n return false;\n });\n } while (teams.length >= 2);\n }\n\n redistributeClients(\n sortedClients: Client<{ userData: ClientQueueData }>[],\n currentGroup: MatchGroup = this.createMatchGroup(),\n totalRank: number = 0,\n ) {\n for (let i = 0, l = sortedClients.length; i < l; i++) {\n const client = sortedClients[i];\n const userData = client.userData;\n const currentCycle = userData.currentCycle++;\n\n if (currentGroup.averageRank > 0) {\n if (\n !this.compare(userData, currentGroup) &&\n !userData.highPriority\n ) {\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n }\n }\n\n userData.group = currentGroup;\n currentGroup.clients.push(client);\n\n totalRank += userData.rank;\n currentGroup.averageRank = totalRank / currentGroup.clients.length;\n\n // Enough players in the group, mark it as ready!\n if (currentGroup.clients.length === this.maxPlayers) {\n currentGroup.ready = true;\n currentGroup = this.createMatchGroup();\n totalRank = 0;\n continue;\n }\n\n if (currentCycle >= this.maxWaitingCycles && this.allowIncompleteGroups) {\n /**\n * Match long-waiting clients with bots\n */\n if (this.highPriorityGroups.indexOf(currentGroup) === -1) {\n this.highPriorityGroups.push(currentGroup);\n }\n\n } else if (\n this.maxWaitingCyclesForPriority !== undefined &&\n currentCycle >= this.maxWaitingCyclesForPriority\n ) {\n /**\n * Force this client to join a group, even if rank is incompatible\n */\n userData.highPriority = true;\n }\n }\n\n return currentGroup;\n }\n\n evaluateHighPriorityGroups() {\n /**\n * Evaluate groups with high priority clients\n */\n this.highPriorityGroups.forEach((group) => {\n group.ready = group.clients.every((c) => {\n // Give new clients another chance to join a group that is not \"high priority\"\n return c.userData?.currentCycle > 1;\n // return c.userData?.currentCycle >= this.maxWaitingCycles;\n });\n });\n }\n\n processGroupsReady() {\n this.groups.forEach(async (group) => {\n if (group.ready) {\n group.confirmed = 0;\n\n try {\n /**\n * Create room instance in the server.\n */\n const room = await this.onGroupReady.call(this, group);\n\n /**\n * Reserve a seat for each client in the group.\n * (If one fails, force all clients to leave, re-queueing is up to the client-side logic)\n */\n await matchMaker.reserveMultipleSeatsFor(\n room,\n group.clients.map((client) => ({\n sessionId: client.sessionId,\n options: client.userData.options,\n auth: client.auth,\n })),\n );\n\n /**\n * Send room data for new WebSocket connection!\n */\n group.clients.forEach((client, i) => {\n client.send(\"seat\", matchMaker.buildSeatReservation(room, client.sessionId));\n });\n\n } catch (e: any) {\n //\n // If creating a room, or reserving a seat failed - fail all clients\n // Whether the clients retry or not is up to the client-side logic\n //\n group.clients.forEach(client => client.leave(1011, e.message));\n }\n\n } else {\n /**\n * Notify clients within the group on how many players are in the queue\n */\n group.clients.forEach((client) => {\n //\n // avoid sending the same number of clients to the client if it hasn't changed\n //\n const queueClientCount = group.clients.length;\n if (client.userData.lastQueueClientCount !== queueClientCount) {\n client.userData.lastQueueClientCount = queueClientCount;\n client.send(\"clients\", queueClientCount);\n }\n });\n }\n });\n }\n\n}"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|