@genex-ai/multiplayer 0.16.1 → 0.17.0
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/PROTOCOL.md +624 -0
- package/README.md +191 -27
- package/dist/bridge.d.ts +89 -0
- package/dist/bridge.global.js +14 -0
- package/dist/bridge.js +558 -0
- package/dist/chunk-C52XFIB6.js +312 -0
- package/dist/chunk-CTDU4IJ5.js +2191 -0
- package/dist/index.d.ts +110 -18
- package/dist/index.js +7 -1778
- package/dist/protocol.d.ts +419 -0
- package/dist/protocol.js +58 -0
- package/dist/schema.d.ts +122 -68
- package/dist/schema.js +95 -16
- package/package.json +27 -10
package/dist/schema.d.ts
CHANGED
|
@@ -1,75 +1,129 @@
|
|
|
1
1
|
import * as _colyseus_schema from '@colyseus/schema';
|
|
2
|
-
import { SchemaType
|
|
2
|
+
import { SchemaType } from '@colyseus/schema';
|
|
3
3
|
|
|
4
4
|
declare const PlayerState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
5
|
-
name:
|
|
6
|
-
data:
|
|
7
|
-
epoch:
|
|
8
|
-
connected:
|
|
9
|
-
avatarUrl:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
name: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
6
|
+
data: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
7
|
+
epoch: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
8
|
+
connected: _colyseus_schema.FieldBuilder<boolean, true, false>;
|
|
9
|
+
avatarUrl: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
10
|
+
uid: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
11
|
+
team: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
12
|
+
x: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
13
|
+
y: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
14
|
+
z: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
15
|
+
yaw: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
16
|
+
vx: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
17
|
+
vy: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
18
|
+
vz: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
19
|
+
anim: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
20
|
+
poseT: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
21
|
+
posed: _colyseus_schema.FieldBuilder<boolean, true, false>;
|
|
22
|
+
}, _colyseus_schema.BuilderInitProps<{
|
|
23
|
+
name: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
24
|
+
data: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
25
|
+
epoch: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
26
|
+
connected: _colyseus_schema.FieldBuilder<boolean, true, false>;
|
|
27
|
+
avatarUrl: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
28
|
+
uid: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
29
|
+
team: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
30
|
+
x: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
31
|
+
y: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
32
|
+
z: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
33
|
+
yaw: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
34
|
+
vx: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
35
|
+
vy: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
36
|
+
vz: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
37
|
+
anim: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
38
|
+
poseT: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
39
|
+
posed: _colyseus_schema.FieldBuilder<boolean, true, false>;
|
|
40
|
+
}>, typeof _colyseus_schema.Schema>;
|
|
17
41
|
type PlayerState = SchemaType<typeof PlayerState>;
|
|
42
|
+
declare const ObjectState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
43
|
+
owner: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
44
|
+
seq: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
45
|
+
epoch: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
46
|
+
t: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
47
|
+
d: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
48
|
+
}, _colyseus_schema.BuilderInitProps<{
|
|
49
|
+
owner: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
50
|
+
seq: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
51
|
+
epoch: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
52
|
+
t: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
53
|
+
d: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
54
|
+
}>, typeof _colyseus_schema.Schema>;
|
|
55
|
+
type ObjectState = SchemaType<typeof ObjectState>;
|
|
18
56
|
declare const RoomState: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
19
|
-
players: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
players: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<{
|
|
58
|
+
name: string;
|
|
59
|
+
data: string;
|
|
60
|
+
epoch: number;
|
|
61
|
+
connected: boolean;
|
|
62
|
+
avatarUrl: string;
|
|
63
|
+
uid: string;
|
|
64
|
+
team: number;
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
z: number;
|
|
68
|
+
yaw: number;
|
|
69
|
+
vx: number;
|
|
70
|
+
vy: number;
|
|
71
|
+
vz: number;
|
|
72
|
+
anim: string;
|
|
73
|
+
poseT: number;
|
|
74
|
+
posed: boolean;
|
|
75
|
+
} & {} & _colyseus_schema.Schema<any> & _colyseus_schema.Schema<unknown>, string>, true, false>;
|
|
76
|
+
shared: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<string, string>, true, false>;
|
|
77
|
+
objects: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<{
|
|
78
|
+
owner: string;
|
|
79
|
+
seq: number;
|
|
80
|
+
epoch: number;
|
|
81
|
+
t: number;
|
|
82
|
+
d: string;
|
|
83
|
+
} & {} & _colyseus_schema.Schema<any> & _colyseus_schema.Schema<unknown>, string>, true, false>;
|
|
84
|
+
host: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
85
|
+
match: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
86
|
+
voice: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<boolean, string>, true, false>;
|
|
87
|
+
clockOrigin: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
88
|
+
}, _colyseus_schema.BuilderInitProps<{
|
|
89
|
+
players: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<{
|
|
90
|
+
name: string;
|
|
91
|
+
data: string;
|
|
92
|
+
epoch: number;
|
|
93
|
+
connected: boolean;
|
|
94
|
+
avatarUrl: string;
|
|
95
|
+
uid: string;
|
|
96
|
+
team: number;
|
|
97
|
+
x: number;
|
|
98
|
+
y: number;
|
|
99
|
+
z: number;
|
|
100
|
+
yaw: number;
|
|
101
|
+
vx: number;
|
|
102
|
+
vy: number;
|
|
103
|
+
vz: number;
|
|
104
|
+
anim: string;
|
|
105
|
+
poseT: number;
|
|
106
|
+
posed: boolean;
|
|
107
|
+
} & {} & _colyseus_schema.Schema<any> & _colyseus_schema.Schema<unknown>, string>, true, false>;
|
|
108
|
+
shared: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<string, string>, true, false>;
|
|
109
|
+
objects: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<{
|
|
110
|
+
owner: string;
|
|
111
|
+
seq: number;
|
|
112
|
+
epoch: number;
|
|
113
|
+
t: number;
|
|
114
|
+
d: string;
|
|
115
|
+
} & {} & _colyseus_schema.Schema<any> & _colyseus_schema.Schema<unknown>, string>, true, false>;
|
|
116
|
+
host: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
117
|
+
match: _colyseus_schema.FieldBuilder<string, true, false>;
|
|
118
|
+
voice: _colyseus_schema.FieldBuilder<_colyseus_schema.MapSchema<boolean, string>, true, false>;
|
|
119
|
+
clockOrigin: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
120
|
+
}>, typeof _colyseus_schema.Schema>;
|
|
73
121
|
type RoomState = SchemaType<typeof RoomState>;
|
|
122
|
+
declare const HeartbeatInput: _colyseus_schema.SchemaWithExtendsConstructor<{
|
|
123
|
+
n: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
124
|
+
}, _colyseus_schema.BuilderInitProps<{
|
|
125
|
+
n: _colyseus_schema.FieldBuilder<number, true, false>;
|
|
126
|
+
}>, typeof _colyseus_schema.Schema>;
|
|
127
|
+
type HeartbeatInput = SchemaType<typeof HeartbeatInput>;
|
|
74
128
|
|
|
75
|
-
export { PlayerState, RoomState };
|
|
129
|
+
export { HeartbeatInput, ObjectState, PlayerState, RoomState };
|
package/dist/schema.js
CHANGED
|
@@ -1,31 +1,110 @@
|
|
|
1
1
|
// src/schema.ts
|
|
2
|
-
import { schema,
|
|
2
|
+
import { schema, t } from "@colyseus/schema";
|
|
3
3
|
var PlayerState = schema(
|
|
4
4
|
{
|
|
5
|
-
name:
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
avatarUrl:
|
|
5
|
+
// Display name: the verified identity's name, else the `name` join option, ≤64 chars.
|
|
6
|
+
name: t.string().default(""),
|
|
7
|
+
// The player's own JSON object, replaced wholesale by `state` / `__state:snap` (≤16 KiB). Free
|
|
8
|
+
// form — the game decides what to sync. Carries `__t`, the sender's clock stamp in ms that
|
|
9
|
+
// remote interpolation runs on (PROTOCOL.md §10); SDKs strip it before a game sees the state.
|
|
10
|
+
data: t.string().default("{}"),
|
|
11
|
+
// Deliberate-discontinuity counter: `__state:snap` increments it in the same patch as the new
|
|
12
|
+
// data, so receivers drop interpolation history instead of easing across a teleport/respawn.
|
|
13
|
+
epoch: t.number().default(0),
|
|
14
|
+
// Transport liveness. False while the seat is held only by reconnection grace.
|
|
15
|
+
connected: t.boolean().default(true),
|
|
16
|
+
// Verified VRM avatar URL from the embed token (never a join option), ≤512 chars. '' = none.
|
|
17
|
+
avatarUrl: t.string().default(""),
|
|
18
|
+
// Stable per-game player id, stamped by the relay from the verified identity (PROTOCOL.md §3.3).
|
|
19
|
+
// The same person gets the same uid in this game across reloads, devices and rooms, and a
|
|
20
|
+
// different one in every other game. Opaque, ≤64 chars — key scores, colors and saves by it,
|
|
21
|
+
// never by the session id (which changes on every reload).
|
|
22
|
+
uid: t.string().default(""),
|
|
23
|
+
// Team index assigned by the relay: the open preset's `teams: N` (assigned at join, never
|
|
24
|
+
// reshuffled, remembered by uid) or the team preset's current round. -1 = no team.
|
|
25
|
+
team: t.number().default(-1),
|
|
26
|
+
// ---- Optional typed pose (PROTOCOL.md §6.6), appended in protocol 3. Written only from the
|
|
27
|
+
// seat's own `__pose` / `__pose:snap` messages. A game that never sends a pose leaves all of
|
|
28
|
+
// these at their defaults and keeps its whole state in `data`, exactly as before. Positions are
|
|
29
|
+
// plain float32 (never quantized: world sizes vary by orders of magnitude); the relay rounds
|
|
30
|
+
// with Math.fround before writing, so an unchanged float32 value never re-sends.
|
|
31
|
+
// Receivers interpolate x..vz (yaw along the shortest arc) and SNAP anim (§10.1).
|
|
32
|
+
x: t.float32().default(0),
|
|
33
|
+
y: t.float32().default(0),
|
|
34
|
+
z: t.float32().default(0),
|
|
35
|
+
// Heading in radians, any finite value; receivers wrap it (never lerp across ±π the long way).
|
|
36
|
+
yaw: t.float32().default(0),
|
|
37
|
+
// Velocity in world units per second: the sender's own, used for extrapolation past the newest
|
|
38
|
+
// sample. Not derived by the relay.
|
|
39
|
+
vx: t.float32().default(0),
|
|
40
|
+
vy: t.float32().default(0),
|
|
41
|
+
vz: t.float32().default(0),
|
|
42
|
+
// Current animation/state name (≤64 chars). Discrete: never interpolated. '' = none.
|
|
43
|
+
anim: t.string().default(""),
|
|
44
|
+
// The sender's clock stamp (ms, same clock as `__t`) of the pose above, written in the same
|
|
45
|
+
// patch as the pose. 0 = no pose published yet.
|
|
46
|
+
poseT: t.float64().default(0),
|
|
47
|
+
// True from this seat's first accepted pose until the seat is gone (a reconnect keeps it).
|
|
48
|
+
// While true, x..anim are this player's pose and `data` is snapped, not blended (§10.1).
|
|
49
|
+
posed: t.boolean().default(false)
|
|
18
50
|
},
|
|
19
51
|
"PlayerState"
|
|
20
52
|
);
|
|
53
|
+
var ObjectState = schema(
|
|
54
|
+
{
|
|
55
|
+
// Owner session id, or '' when unowned (released, or its owner left an empty room).
|
|
56
|
+
owner: t.string().default(""),
|
|
57
|
+
// Ownership sequence: +1 on EVERY owner change (claim, release, orphan reassignment, host
|
|
58
|
+
// failover). Never changes on a state publish. Control results report it as `sequence`.
|
|
59
|
+
seq: t.number().default(0),
|
|
60
|
+
// Deliberate-discontinuity counter, incremented by `__object:snap` (a reset/teleport).
|
|
61
|
+
epoch: t.number().default(0),
|
|
62
|
+
// The CURRENT owner's send-clock stamp (ms) for `d`, preserved from the owner's message. The
|
|
63
|
+
// relay resets it to 0 on every owner change, so a nonzero `t` is always on the current owner's
|
|
64
|
+
// clock and 0 means "this `d` was not stamped by the current owner" (a handoff bridge pose).
|
|
65
|
+
t: t.float64().default(0),
|
|
66
|
+
// The object's JSON state object (≤64 KiB), as last published. '' until the first publish.
|
|
67
|
+
// Survives owner changes untouched. A plain field on purpose — never .patchOnly(), or a late
|
|
68
|
+
// joiner's full state would not carry it.
|
|
69
|
+
d: t.string().default("")
|
|
70
|
+
},
|
|
71
|
+
"ObjectState"
|
|
72
|
+
);
|
|
21
73
|
var RoomState = schema(
|
|
22
74
|
{
|
|
23
|
-
|
|
24
|
-
|
|
75
|
+
// Seated players by session id.
|
|
76
|
+
players: t.map(PlayerState),
|
|
77
|
+
// Game-writable room key -> JSON text (≤256 keys of ≤256 chars, values ≤64 KiB). Only game keys
|
|
78
|
+
// live here; the `__` key prefix is refused, and nothing the relay owns is mirrored into it.
|
|
79
|
+
shared: t.map("string"),
|
|
80
|
+
// Shared objects by id (≤128 per room).
|
|
81
|
+
objects: t.map(ObjectState),
|
|
82
|
+
// Session id of the elected host, or '' while the room has none.
|
|
83
|
+
host: t.string().default(""),
|
|
84
|
+
// Match rooms only: the match state as one JSON text (phase, players, teams, scores, cfg, …;
|
|
85
|
+
// PROTOCOL.md §9). '' in a plain play room. A single string until native decoders are shown to
|
|
86
|
+
// handle the nested team rosters a typed MatchState would need.
|
|
87
|
+
match: t.string().default(""),
|
|
88
|
+
// Voice roster: session id -> true for each participant (≤6). Membership is key presence;
|
|
89
|
+
// the value carries nothing. A map rather than a set — see the header.
|
|
90
|
+
voice: t.map("boolean"),
|
|
91
|
+
// The relay's wall clock (epoch ms) at room-clock zero: `clock.currentTime − clock.elapsedTime`,
|
|
92
|
+
// written once in onCreate and never again. Maps relay epoch stamps (match `startsAt`/`endsBy`,
|
|
93
|
+
// chat `at`) onto the room clock that TIMED frames carry: `serverClockMs = epochMs − clockOrigin`
|
|
94
|
+
// (PROTOCOL.md §16). 0 = the relay does not publish its clock.
|
|
95
|
+
clockOrigin: t.float64().default(0)
|
|
25
96
|
},
|
|
26
97
|
"RoomState"
|
|
27
98
|
);
|
|
99
|
+
var HeartbeatInput = schema(
|
|
100
|
+
{
|
|
101
|
+
n: t.number().default(0)
|
|
102
|
+
},
|
|
103
|
+
"HeartbeatInput"
|
|
104
|
+
);
|
|
28
105
|
export {
|
|
106
|
+
HeartbeatInput,
|
|
107
|
+
ObjectState,
|
|
29
108
|
PlayerState,
|
|
30
109
|
RoomState
|
|
31
110
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/multiplayer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "The multiplayer client wrapper + shared room schema for genex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,42 +15,59 @@
|
|
|
15
15
|
"@genex-ai/source": "./src/schema.ts",
|
|
16
16
|
"types": "./dist/schema.d.ts",
|
|
17
17
|
"import": "./dist/schema.js"
|
|
18
|
-
}
|
|
18
|
+
},
|
|
19
|
+
"./protocol": {
|
|
20
|
+
"@genex-ai/source": "./src/protocol.ts",
|
|
21
|
+
"types": "./dist/protocol.d.ts",
|
|
22
|
+
"import": "./dist/protocol.js"
|
|
23
|
+
},
|
|
24
|
+
"./bridge": {
|
|
25
|
+
"@genex-ai/source": "./src/bridge.ts",
|
|
26
|
+
"types": "./dist/bridge.d.ts",
|
|
27
|
+
"import": "./dist/bridge.js"
|
|
28
|
+
},
|
|
29
|
+
"./bridge.global.js": "./dist/bridge.global.js"
|
|
19
30
|
},
|
|
20
31
|
"files": [
|
|
21
|
-
"dist"
|
|
32
|
+
"dist",
|
|
33
|
+
"PROTOCOL.md"
|
|
22
34
|
],
|
|
23
35
|
"scripts": {
|
|
24
36
|
"build": "tsup",
|
|
25
37
|
"typecheck": "tsc --noEmit",
|
|
26
|
-
"test": "node --test test/*.test.ts",
|
|
27
|
-
"check": "pnpm build && publint && attw --pack . --profile esm-only",
|
|
38
|
+
"test": "node --conditions=@genex-ai/source --test test/*.test.ts",
|
|
39
|
+
"check": "pnpm build && publint && attw --pack . --profile esm-only --exclude-entrypoints ./bridge.global.js",
|
|
28
40
|
"prepack": "pnpm build && publint"
|
|
29
41
|
},
|
|
30
42
|
"publishConfig": {
|
|
31
43
|
"access": "public"
|
|
32
44
|
},
|
|
33
45
|
"dependencies": {
|
|
34
|
-
"@colyseus/schema": "^
|
|
35
|
-
"@colyseus/sdk": "^0.
|
|
46
|
+
"@colyseus/schema": "^5.0.40",
|
|
47
|
+
"@colyseus/sdk": "^0.18.5"
|
|
36
48
|
},
|
|
37
49
|
"peerDependencies": {
|
|
38
|
-
"three": ">=0.160.0"
|
|
50
|
+
"three": ">=0.160.0",
|
|
51
|
+
"@genex-ai/embed-sdk": ">=0.33.0"
|
|
39
52
|
},
|
|
40
53
|
"peerDependenciesMeta": {
|
|
41
54
|
"three": {
|
|
42
55
|
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@genex-ai/embed-sdk": {
|
|
58
|
+
"optional": true
|
|
43
59
|
}
|
|
44
60
|
},
|
|
45
61
|
"devDependencies": {
|
|
46
62
|
"@arethetypeswrong/cli": "^0.18.0",
|
|
63
|
+
"@genex-ai/embed-sdk": "workspace:*",
|
|
47
64
|
"publint": "^0.3.0",
|
|
48
65
|
"tsup": "^8.0.0",
|
|
49
66
|
"typescript": "^5.8.0"
|
|
50
67
|
},
|
|
51
68
|
"repository": {
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/genex-games/genex.git"
|
|
54
71
|
},
|
|
55
72
|
"license": "MIT"
|
|
56
73
|
}
|