portals-mcp 1.3.3 → 1.3.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/marketplace-client.d.ts +9 -0
- package/dist/marketplace-client.d.ts.map +1 -1
- package/dist/marketplace-client.js +120 -20
- package/dist/resources/index/effects.json +88 -1
- package/dist/resources/index/items.json +59 -8
- package/dist/resources/index/knowledge-map.json +6 -0
- package/dist/resources/index/triggers.json +28 -0
- package/dist/resources/logic/js-function-reference.md +16 -6
- package/dist/resources/logic/js-use-effector.md +60 -7
- package/dist/resources/python/README.md +2 -0
- package/dist/resources/python/lib/portals_core.py +159 -8
- package/dist/resources/python/lib/portals_effects.py +180 -7
- package/dist/resources/python/lib/portals_ops.py +148 -16
- package/dist/resources/python/lib/portals_utils.py +1171 -1170
- package/dist/resources/python/tools/build_reactgui_feature_test_room.py +386 -0
- package/dist/resources/python/tools/validate_room.py +430 -40
- package/dist/resources/python/tools/verify_reactgui_feature_room.py +199 -0
- package/dist/resources/ref/items/camera.json +52 -47
- package/dist/resources/ref/items/cube.json +51 -5
- package/dist/resources/ref/items/effects-vfx.json +171 -115
- package/dist/resources/ref/items/glb.json +97 -64
- package/dist/resources/ref/items/text.json +71 -69
- package/dist/resources/ref/items/trigger-zone.json +4 -0
- package/dist/resources/ref/settings.json +198 -191
- package/dist/resources/ref/systems/function-effector-js.json +9 -6
- package/dist/resources/ref/systems/use-effector.json +74 -73
- package/dist/resources/reference/api-cheatsheet.md +16 -3
- package/dist/resources/reference/cameras.md +569 -565
- package/dist/resources/reference/gotchas.md +13 -3
- package/dist/resources/reference/interactions.md +6 -1
- package/dist/resources/reference/items/building.md +52 -0
- package/dist/resources/reference/items/effects.md +110 -170
- package/dist/resources/reference/items/gameplay.md +7 -5
- package/dist/resources/reference/items/models.md +184 -168
- package/dist/resources/reference/reactgui-feature-test-room.md +97 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +149 -19
- package/package.json +1 -1
|
@@ -24,6 +24,12 @@ export interface MarketplaceItem {
|
|
|
24
24
|
claimCount: number;
|
|
25
25
|
acquireCount: number;
|
|
26
26
|
}
|
|
27
|
+
export interface MarketplaceGlbResolution {
|
|
28
|
+
glb: string | null;
|
|
29
|
+
source: "catalog_direct" | "user_inventory" | "room_build_items" | "unresolved";
|
|
30
|
+
attachedToRoomInventory: boolean;
|
|
31
|
+
reason?: string | undefined;
|
|
32
|
+
}
|
|
27
33
|
export interface MarketplaceFilter {
|
|
28
34
|
query?: string | undefined;
|
|
29
35
|
type?: string | undefined;
|
|
@@ -44,6 +50,8 @@ export interface ClaimResult {
|
|
|
44
50
|
itemId: string;
|
|
45
51
|
message?: string | undefined;
|
|
46
52
|
}
|
|
53
|
+
export declare function isMarketplaceAssetRoute(value: string | null | undefined): boolean;
|
|
54
|
+
export declare function isRuntimeGlbUrl(value: string | null | undefined): boolean;
|
|
47
55
|
export declare function clearMarketplaceCache(): void;
|
|
48
56
|
export declare function searchMarketplace(filter?: MarketplaceFilter): Promise<MarketplaceItem[]>;
|
|
49
57
|
export declare function getPackItems(packId: string): Promise<MarketplaceItem[]>;
|
|
@@ -116,4 +124,5 @@ export interface OwnershipCheck {
|
|
|
116
124
|
export declare function verifyOwnership(cids: string[]): Promise<OwnershipCheck[]>;
|
|
117
125
|
export declare function attachItemsToRoom(roomId: string, itemIds: string[]): Promise<AttachToRoomResult>;
|
|
118
126
|
export declare function claimMarketplaceItem(itemId: string): Promise<ClaimResult>;
|
|
127
|
+
export declare function resolveMarketplaceGlbUrl(item: MarketplaceItem, roomId?: string): Promise<MarketplaceGlbResolution>;
|
|
119
128
|
//# sourceMappingURL=marketplace-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketplace-client.d.ts","sourceRoot":"","sources":["../src/marketplace-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"marketplace-client.d.ts","sourceRoot":"","sources":["../src/marketplace-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
|
+
import { getAccessKey, isAuthenticated } from "./api.js";
|
|
2
3
|
const API_BASE = process.env.API_BASE || "https://portals.to";
|
|
3
4
|
const CACHE_TTL_MS = 60_000;
|
|
4
5
|
let catalogueCache = null;
|
|
6
|
+
function getRuntimeAccessKey() {
|
|
7
|
+
if (process.env.PORTALS_ACCESS_KEY)
|
|
8
|
+
return process.env.PORTALS_ACCESS_KEY;
|
|
9
|
+
if (!isAuthenticated())
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return getAccessKey();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function authHeaders() {
|
|
19
|
+
const headers = {};
|
|
20
|
+
const token = process.env.PORTALS_USER_TOKEN;
|
|
21
|
+
const accessKey = getRuntimeAccessKey();
|
|
22
|
+
if (token)
|
|
23
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
24
|
+
if (accessKey)
|
|
25
|
+
headers["x-access-key"] = accessKey;
|
|
26
|
+
return headers;
|
|
27
|
+
}
|
|
28
|
+
function jsonAuthHeaders() {
|
|
29
|
+
return { "Content-Type": "application/json", ...authHeaders() };
|
|
30
|
+
}
|
|
31
|
+
export function isMarketplaceAssetRoute(value) {
|
|
32
|
+
if (!value)
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
return new URL(value, API_BASE).pathname.startsWith("/api/v2/marketplace/asset/");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return value.startsWith("/api/v2/marketplace/asset/");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function isRuntimeGlbUrl(value) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return false;
|
|
44
|
+
if (isMarketplaceAssetRoute(value))
|
|
45
|
+
return false;
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("~") || trimmed.startsWith("#")) {
|
|
48
|
+
return /\.glb(?:[?#]|$)/i.test(trimmed);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
5
52
|
async function fetchCatalogue() {
|
|
6
53
|
if (catalogueCache && Date.now() - catalogueCache.ts < CACHE_TTL_MS) {
|
|
7
54
|
return catalogueCache.items;
|
|
@@ -156,7 +203,7 @@ export async function getUserInventory() {
|
|
|
156
203
|
if (!token)
|
|
157
204
|
return [];
|
|
158
205
|
const res = await fetch(`${API_BASE}/api/v2/marketplace/my-items`, {
|
|
159
|
-
headers:
|
|
206
|
+
headers: authHeaders(),
|
|
160
207
|
});
|
|
161
208
|
if (!res.ok)
|
|
162
209
|
return [];
|
|
@@ -171,17 +218,10 @@ export async function getUserInventory() {
|
|
|
171
218
|
* scene with AI so we know what assets the user actually has.
|
|
172
219
|
*/
|
|
173
220
|
export async function getRoomBuildItems(roomId) {
|
|
174
|
-
const token = process.env.PORTALS_USER_TOKEN;
|
|
175
|
-
const accessKey = process.env.PORTALS_ACCESS_KEY;
|
|
176
221
|
if (!roomId)
|
|
177
222
|
return [];
|
|
178
|
-
const headers = {};
|
|
179
|
-
if (token)
|
|
180
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
181
|
-
if (accessKey)
|
|
182
|
-
headers["x-access-key"] = accessKey;
|
|
183
223
|
try {
|
|
184
|
-
const res = await fetch(`${API_BASE}/api/v2/room/get-build-items?roomId=${encodeURIComponent(roomId)}`, { headers });
|
|
224
|
+
const res = await fetch(`${API_BASE}/api/v2/room/get-build-items?roomId=${encodeURIComponent(roomId)}`, { headers: authHeaders() });
|
|
185
225
|
if (!res.ok)
|
|
186
226
|
return [];
|
|
187
227
|
const json = (await res.json());
|
|
@@ -199,7 +239,7 @@ export async function getRoomBuildItems(roomId) {
|
|
|
199
239
|
*/
|
|
200
240
|
export async function verifyOwnership(cids) {
|
|
201
241
|
const token = process.env.PORTALS_USER_TOKEN;
|
|
202
|
-
const accessKey =
|
|
242
|
+
const accessKey = getRuntimeAccessKey();
|
|
203
243
|
if (!token && !accessKey) {
|
|
204
244
|
// No auth — return unknowns (the caller decides how to proceed).
|
|
205
245
|
return cids.map((cid) => ({
|
|
@@ -210,9 +250,7 @@ export async function verifyOwnership(cids) {
|
|
|
210
250
|
}));
|
|
211
251
|
}
|
|
212
252
|
// Pull catalogue + user inventory in parallel.
|
|
213
|
-
const headers =
|
|
214
|
-
if (token)
|
|
215
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
253
|
+
const headers = authHeaders();
|
|
216
254
|
const [inventoryRes, catalogue] = await Promise.all([
|
|
217
255
|
token ? fetch(`${API_BASE}/api/v2/marketplace/my-items`, { headers }) : Promise.resolve(null),
|
|
218
256
|
fetchCatalogue(),
|
|
@@ -248,18 +286,14 @@ export async function verifyOwnership(cids) {
|
|
|
248
286
|
}
|
|
249
287
|
export async function attachItemsToRoom(roomId, itemIds) {
|
|
250
288
|
const token = process.env.PORTALS_USER_TOKEN;
|
|
251
|
-
const accessKey =
|
|
289
|
+
const accessKey = getRuntimeAccessKey();
|
|
252
290
|
if (!token && !accessKey) {
|
|
253
|
-
return { ok: false, code: "AUTH_REQUIRED", message: "No Firebase token or access key
|
|
291
|
+
return { ok: false, code: "AUTH_REQUIRED", message: "No Firebase token, access key env, or authenticated MCP access key available.", attachedItemIds: [] };
|
|
254
292
|
}
|
|
255
293
|
if (!roomId) {
|
|
256
294
|
return { ok: false, code: "ROOM_ID_REQUIRED", message: "roomId is required.", attachedItemIds: [] };
|
|
257
295
|
}
|
|
258
|
-
const headers =
|
|
259
|
-
if (token)
|
|
260
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
261
|
-
if (accessKey)
|
|
262
|
-
headers["x-access-key"] = accessKey;
|
|
296
|
+
const headers = jsonAuthHeaders();
|
|
263
297
|
let existing = [];
|
|
264
298
|
try {
|
|
265
299
|
const settingsRes = await fetch(`${API_BASE}/api/v2/room/get-settings?roomID=${encodeURIComponent(roomId)}`, { headers });
|
|
@@ -324,3 +358,69 @@ export async function claimMarketplaceItem(itemId) {
|
|
|
324
358
|
message: body.message,
|
|
325
359
|
};
|
|
326
360
|
}
|
|
361
|
+
function matchBuildItem(items, itemId) {
|
|
362
|
+
return items.find((item) => item.id === itemId) ?? null;
|
|
363
|
+
}
|
|
364
|
+
function matchInventoryItem(items, itemId) {
|
|
365
|
+
return items.find((item) => item.itemId === itemId || item.id === itemId) ?? null;
|
|
366
|
+
}
|
|
367
|
+
export async function resolveMarketplaceGlbUrl(item, roomId) {
|
|
368
|
+
if (isRuntimeGlbUrl(item.glb)) {
|
|
369
|
+
return {
|
|
370
|
+
glb: item.glb,
|
|
371
|
+
source: "catalog_direct",
|
|
372
|
+
attachedToRoomInventory: false,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const inventoryItem = matchInventoryItem(await getUserInventory(), item.id);
|
|
376
|
+
if (inventoryItem?.glb && isRuntimeGlbUrl(inventoryItem.glb)) {
|
|
377
|
+
return {
|
|
378
|
+
glb: inventoryItem.glb,
|
|
379
|
+
source: "user_inventory",
|
|
380
|
+
attachedToRoomInventory: false,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (!roomId) {
|
|
384
|
+
return {
|
|
385
|
+
glb: null,
|
|
386
|
+
source: "unresolved",
|
|
387
|
+
attachedToRoomInventory: false,
|
|
388
|
+
reason: isMarketplaceAssetRoute(item.glb)
|
|
389
|
+
? "Catalog glb is a protected marketplace asset route; pass roomId so MCP can resolve the room build-palette CDN URL."
|
|
390
|
+
: "Catalog item did not include a runtime GLB URL.",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
let buildItems = await getRoomBuildItems(roomId);
|
|
394
|
+
let buildItem = matchBuildItem(buildItems, item.id);
|
|
395
|
+
if (buildItem?.glb && isRuntimeGlbUrl(buildItem.glb)) {
|
|
396
|
+
return {
|
|
397
|
+
glb: buildItem.glb,
|
|
398
|
+
source: "room_build_items",
|
|
399
|
+
attachedToRoomInventory: false,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const attachResult = await attachItemsToRoom(roomId, [item.id]);
|
|
403
|
+
if (!attachResult.ok) {
|
|
404
|
+
return {
|
|
405
|
+
glb: null,
|
|
406
|
+
source: "unresolved",
|
|
407
|
+
attachedToRoomInventory: false,
|
|
408
|
+
reason: `Could not attach marketplace item to room inventory for URL resolution (${attachResult.code}${attachResult.message ? `: ${attachResult.message}` : ""}).`,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
buildItems = await getRoomBuildItems(roomId);
|
|
412
|
+
buildItem = matchBuildItem(buildItems, item.id);
|
|
413
|
+
if (buildItem?.glb && isRuntimeGlbUrl(buildItem.glb)) {
|
|
414
|
+
return {
|
|
415
|
+
glb: buildItem.glb,
|
|
416
|
+
source: "room_build_items",
|
|
417
|
+
attachedToRoomInventory: true,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
glb: null,
|
|
422
|
+
source: "unresolved",
|
|
423
|
+
attachedToRoomInventory: true,
|
|
424
|
+
reason: "Item was attached to the room inventory, but get_room_build_items did not return a runtime .glb URL for it.",
|
|
425
|
+
};
|
|
426
|
+
}
|
|
@@ -39,6 +39,16 @@
|
|
|
39
39
|
"Scale in relative mode is ADDITIVE (scale [1,1,1] adds 1 to each axis per trigger)"
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"$type": "SetTransform",
|
|
44
|
+
"category": "movement",
|
|
45
|
+
"parameters": {
|
|
46
|
+
"position": "array - optional [x,y,z] world position",
|
|
47
|
+
"rotation": "array - optional [x,y,z] Euler degrees",
|
|
48
|
+
"scale": "array - optional [x,y,z] scale"
|
|
49
|
+
},
|
|
50
|
+
"gotchas": ["Immediate local-player transform write. Include at least one of position, rotation, or scale."]
|
|
51
|
+
},
|
|
42
52
|
{
|
|
43
53
|
"$type": "PortalsAnimation",
|
|
44
54
|
"category": "movement",
|
|
@@ -76,6 +86,82 @@
|
|
|
76
86
|
"parameters": {},
|
|
77
87
|
"gotchas": []
|
|
78
88
|
},
|
|
89
|
+
{
|
|
90
|
+
"$type": "AddForceToCubeEffector",
|
|
91
|
+
"category": "physics",
|
|
92
|
+
"parameters": {
|
|
93
|
+
"fx": "float - impulse X",
|
|
94
|
+
"fy": "float - impulse Y",
|
|
95
|
+
"fz": "float - impulse Z",
|
|
96
|
+
"rel": "bool - true=force vector is local to item rotation"
|
|
97
|
+
},
|
|
98
|
+
"gotchas": ["Direct task effector only; not in ReactGUI UseEffector/PrepareEffector map. Works on ResizableCube and GLB-family physics items."]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"$type": "AddTorqueToCubeEffector",
|
|
102
|
+
"category": "physics",
|
|
103
|
+
"parameters": {
|
|
104
|
+
"tx": "float - rotational impulse X",
|
|
105
|
+
"ty": "float - rotational impulse Y",
|
|
106
|
+
"tz": "float - rotational impulse Z",
|
|
107
|
+
"rel": "bool - true=torque vector is local to item rotation"
|
|
108
|
+
},
|
|
109
|
+
"gotchas": ["Direct task effector only; not in ReactGUI UseEffector/PrepareEffector map. Works on ResizableCube and GLB-family physics items."]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"$type": "ExplosionForceCubeEffector",
|
|
113
|
+
"category": "physics",
|
|
114
|
+
"parameters": {
|
|
115
|
+
"f": "float - explosion impulse force",
|
|
116
|
+
"r": "float - explosion radius",
|
|
117
|
+
"u": "float - upward modifier",
|
|
118
|
+
"ox/oy/oz": "floats - origin offset from item position",
|
|
119
|
+
"aff": "bool - true=also affect nearby physics cubes"
|
|
120
|
+
},
|
|
121
|
+
"gotchas": ["Direct task effector only; not in ReactGUI UseEffector/PrepareEffector map."]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"$type": "SetCubeVelocityEffector",
|
|
125
|
+
"category": "physics",
|
|
126
|
+
"parameters": {
|
|
127
|
+
"vx/vy/vz": "floats - linear velocity",
|
|
128
|
+
"ax/ay/az": "floats - angular velocity",
|
|
129
|
+
"sl": "bool - set linear velocity",
|
|
130
|
+
"sa": "bool - set angular velocity",
|
|
131
|
+
"rel": "bool - true=vectors are local to item rotation"
|
|
132
|
+
},
|
|
133
|
+
"gotchas": ["Direct task effector only. Velocity assignment is most reliable when this client already owns the physics item."]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"$type": "ResetCubeVelocityEffector",
|
|
137
|
+
"category": "physics",
|
|
138
|
+
"parameters": {},
|
|
139
|
+
"gotchas": ["Direct task effector only. Stops linear and angular velocity on the owned physics item."]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"$type": "ToggleCubeGravityEffector",
|
|
143
|
+
"category": "physics",
|
|
144
|
+
"parameters": {
|
|
145
|
+
"g": "bool - true=use gravity, false=float"
|
|
146
|
+
},
|
|
147
|
+
"gotchas": ["Direct task effector only. Ensures physics is attached before changing gravity."]
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"$type": "AttachToMouseCubeEffector",
|
|
151
|
+
"category": "physics",
|
|
152
|
+
"parameters": {
|
|
153
|
+
"snap": "bool - true=snap item bottom to placeable surfaces"
|
|
154
|
+
},
|
|
155
|
+
"gotchas": ["JS UseEffector name is AttachToMouse, while direct room-data $type is AttachToMouseCubeEffector."]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"$type": "DetachFromMouseCubeEffector",
|
|
159
|
+
"category": "physics",
|
|
160
|
+
"parameters": {
|
|
161
|
+
"save": "bool - true=persist final drop position when server permissions and item opt-in allow it"
|
|
162
|
+
},
|
|
163
|
+
"gotchas": ["JS UseEffector name is DetachFromMouse, while direct room-data $type is DetachFromMouseCubeEffector."]
|
|
164
|
+
},
|
|
79
165
|
{
|
|
80
166
|
"$type": "AddVelocityToPlayer",
|
|
81
167
|
"category": "player",
|
|
@@ -235,7 +321,8 @@
|
|
|
235
321
|
"category": "camera",
|
|
236
322
|
"parameters": {
|
|
237
323
|
"camState": "string — camera state name (matches CameraObject sn)",
|
|
238
|
-
"
|
|
324
|
+
"smoothTransition": "bool — blend to the new state, default true",
|
|
325
|
+
"transitionSpeed": "float — transition speed, default 6.0"
|
|
239
326
|
},
|
|
240
327
|
"gotchas": []
|
|
241
328
|
},
|
|
@@ -15,9 +15,52 @@
|
|
|
15
15
|
"s": "bool — shadows (false=off)",
|
|
16
16
|
"nav": "bool — AI nav mesh surface",
|
|
17
17
|
"t": "float — texture tiling",
|
|
18
|
+
"sm": "float — material smoothness 0-1",
|
|
19
|
+
"m": "float — material metallic 0-1",
|
|
20
|
+
"p": "bool — has physics",
|
|
21
|
+
"sp": "bool — save settled physics position",
|
|
22
|
+
"pm": "bool — pushable by player",
|
|
23
|
+
"mass": "float — physics mass 0.1-50",
|
|
24
|
+
"lbl": "string — physics label for trigger-zone filtering",
|
|
18
25
|
"title": "string — internal label"
|
|
19
26
|
},
|
|
20
|
-
"valid_triggers": ["OnClickEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerDied", "OnPlayerRevived", "OnPlayerMove", "OnPlayerStoppedMoving", "OnMicrophoneUnmuted", "OnTimerStopped", "OnCountdownTimerFinished", "ScoreTrigger", "OnAnimationStoppedEvent", "OnItemClickEvent", "PlayerLeave", "SwapVolume", "OnDestroyedEvent"],
|
|
27
|
+
"valid_triggers": ["OnClickEvent", "OnDragStartEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerDied", "OnPlayerRevived", "OnPlayerMove", "OnPlayerStoppedMoving", "OnMicrophoneUnmuted", "OnTimerStopped", "OnCountdownTimerFinished", "ScoreTrigger", "OnAnimationStoppedEvent", "OnItemClickEvent", "PlayerLeave", "SwapVolume", "OnDestroyedEvent"],
|
|
28
|
+
"spec_uri": "docs://ref/items/cube"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "resizable-shapes",
|
|
32
|
+
"name": "Resizable Shape Variants",
|
|
33
|
+
"prefabNames": ["ResizableSphere", "ResizableCylinder", "ResizableCone", "ResizablePyramid", "ResizableWedge", "ResizableTriangularPrism", "ResizableCapsule", "ResizableTorus"],
|
|
34
|
+
"category": "building",
|
|
35
|
+
"aliases": {
|
|
36
|
+
"Sphere": "ResizableSphere",
|
|
37
|
+
"Cylinder": "ResizableCylinder",
|
|
38
|
+
"Cone": "ResizableCone",
|
|
39
|
+
"Pyramid": "ResizablePyramid",
|
|
40
|
+
"Ramp": "ResizableWedge",
|
|
41
|
+
"Wedge": "ResizableWedge",
|
|
42
|
+
"Prism": "ResizableTriangularPrism",
|
|
43
|
+
"TriangularPrism": "ResizableTriangularPrism",
|
|
44
|
+
"Capsule": "ResizableCapsule",
|
|
45
|
+
"Ring": "ResizableTorus",
|
|
46
|
+
"Torus": "ResizableTorus"
|
|
47
|
+
},
|
|
48
|
+
"key_fields": {
|
|
49
|
+
"col": "string (6-char hex, no #) — color",
|
|
50
|
+
"contentString": "string — optional texture URL",
|
|
51
|
+
"e": "float — emission/glow 0.0-1.0+",
|
|
52
|
+
"o": "float — opacity 0.0-1.0",
|
|
53
|
+
"sm": "float — material smoothness 0-1",
|
|
54
|
+
"m": "float — material metallic 0-1",
|
|
55
|
+
"c": "bool — collider (false=pass-through)",
|
|
56
|
+
"nav": "bool — AI nav mesh surface",
|
|
57
|
+
"p": "bool — has physics",
|
|
58
|
+
"pm": "bool — pushable by player",
|
|
59
|
+
"mass": "float — physics mass 0.1-50",
|
|
60
|
+
"lbl": "string — physics label for trigger-zone filtering"
|
|
61
|
+
},
|
|
62
|
+
"notes": "ReactGUI Shapes panel variants use the same extraData schema as ResizableCube. cf is GLB-only and is invalid on these shapes.",
|
|
63
|
+
"valid_triggers": ["OnClickEvent", "OnDragStartEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerDied", "OnPlayerRevived", "OnPlayerMove", "OnPlayerStoppedMoving", "OnMicrophoneUnmuted", "OnTimerStopped", "OnCountdownTimerFinished", "ScoreTrigger", "OnAnimationStoppedEvent", "OnItemClickEvent", "PlayerLeave", "SwapVolume", "OnDestroyedEvent"],
|
|
21
64
|
"spec_uri": "docs://ref/items/cube"
|
|
22
65
|
},
|
|
23
66
|
{
|
|
@@ -27,9 +70,10 @@
|
|
|
27
70
|
"category": "building",
|
|
28
71
|
"key_fields": {
|
|
29
72
|
"text": "string — content with Unity rich text tags",
|
|
30
|
-
"lookAtCamera": "bool — billboard mode (default true)"
|
|
73
|
+
"lookAtCamera": "bool — billboard mode (default true)",
|
|
74
|
+
"font": "string — optional ReactGUI font identifier/name"
|
|
31
75
|
},
|
|
32
|
-
"valid_triggers": ["OnClickEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "ScoreTrigger", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
76
|
+
"valid_triggers": ["OnClickEvent", "OnDragStartEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "ScoreTrigger", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
33
77
|
"spec_uri": "docs://ref/items/text"
|
|
34
78
|
},
|
|
35
79
|
{
|
|
@@ -68,7 +112,12 @@
|
|
|
68
112
|
"c": "bool — collider",
|
|
69
113
|
"l": "bool — local animation (true=per-client)",
|
|
70
114
|
"f": "bool — camera fade",
|
|
71
|
-
"r": "bool — remove first frame"
|
|
115
|
+
"r": "bool — remove first frame",
|
|
116
|
+
"p": "bool — has physics",
|
|
117
|
+
"pm": "bool — pushable by player",
|
|
118
|
+
"mass": "float — physics mass 0.1-50",
|
|
119
|
+
"lbl": "string — physics label for trigger-zone filtering",
|
|
120
|
+
"cf": "int — physics collider shape 0-3"
|
|
72
121
|
},
|
|
73
122
|
"valid_triggers": ["OnClickEvent", "OnCollideEvent", "OnCollisionStoppedEvent", "OnHoverStartEvent", "OnHoverEndEvent", "OnPlayerLoggedIn", "OnKeyPressedEvent", "OnKeyReleasedEvent", "ScoreTrigger", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
74
123
|
"spec_uri": "docs://ref/items/glb"
|
|
@@ -118,8 +167,8 @@
|
|
|
118
167
|
"cm": "string — custom press message",
|
|
119
168
|
"opacity": "float — editor opacity"
|
|
120
169
|
},
|
|
121
|
-
"valid_triggers": ["OnEnterEvent", "OnExitEvent", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerLoggedIn", "ScoreTrigger", "OnTimerStopped", "OnCountdownTimerFinished", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
122
|
-
"notes": "Invisible during play. NEVER use OnClickEvent/OnHoverStart/OnHoverEnd on triggers. pressBtn:true gates OnEnterEvent until keyCode is pressed while inside; it is not global OnKeyPressedEvent.",
|
|
170
|
+
"valid_triggers": ["OnEnterEvent", "OnExitEvent", "OnPhysicsItemEnterEvent", "OnPhysicsItemExitEvent", "OnKeyPressedEvent", "OnKeyReleasedEvent", "OnPlayerLoggedIn", "ScoreTrigger", "OnTimerStopped", "OnCountdownTimerFinished", "OnAnimationStoppedEvent", "OnPlayerDied", "OnPlayerRevived", "PlayerLeave"],
|
|
171
|
+
"notes": "Invisible during play. NEVER use OnClickEvent/OnHoverStart/OnHoverEnd on triggers. pressBtn:true gates OnEnterEvent until keyCode is pressed while inside; it is not global OnKeyPressedEvent. Physics item enter/exit only detects physics items with p:true/CubePhysicsSync, optionally filtered by lbl/label.",
|
|
123
172
|
"spec_uri": "docs://ref/items/trigger-zone"
|
|
124
173
|
},
|
|
125
174
|
{
|
|
@@ -173,7 +222,9 @@
|
|
|
173
222
|
"category": "gameplay",
|
|
174
223
|
"key_fields": {
|
|
175
224
|
"fov": "float — field of view degrees",
|
|
176
|
-
"sn": "string — state name
|
|
225
|
+
"sn": "string — unique CameraObject state name for ChangeCamState",
|
|
226
|
+
"aP": "bool — Look At Player",
|
|
227
|
+
"renderRateFollow": "bool — active camera copies this CameraObject every render frame"
|
|
177
228
|
},
|
|
178
229
|
"valid_triggers": [],
|
|
179
230
|
"notes": "Place at desired camera viewpoint. Use ChangeCamState effect to activate.",
|
|
@@ -385,7 +436,7 @@
|
|
|
385
436
|
"prefabName": "Addressable",
|
|
386
437
|
"category": "effects",
|
|
387
438
|
"key_fields": {},
|
|
388
|
-
"notes": "contentString=FurnitureAddressables/{EffectName}.
|
|
439
|
+
"notes": "contentString=FurnitureAddressables/{EffectName}. Set c:false to avoid blocking movement. ReactGUI catalog includes fire, explosions, lightning, sparks, smoke, weather, water, trails, and hit impacts. Toggle with Show/HideObjectEvent.",
|
|
389
440
|
"valid_triggers": ["OnPlayerLoggedIn", "OnClickEvent", "OnCollideEvent"],
|
|
390
441
|
"spec_uri": "docs://ref/items/effects-vfx"
|
|
391
442
|
}
|
|
@@ -224,6 +224,12 @@
|
|
|
224
224
|
"description": "Quick reference for MCP tool calls and room JSON structure",
|
|
225
225
|
"uri": "docs://reference/api-cheatsheet"
|
|
226
226
|
},
|
|
227
|
+
{
|
|
228
|
+
"id": "logic.guides.reactgui-feature-test-room",
|
|
229
|
+
"label": "ReactGUI Feature Test Room",
|
|
230
|
+
"description": "Checklist, generator, and validation notes for ReactGUI/Core feature coverage in room data",
|
|
231
|
+
"uri": "docs://reference/reactgui-feature-test-room"
|
|
232
|
+
},
|
|
227
233
|
{
|
|
228
234
|
"id": "logic.guides.sandbox-environment",
|
|
229
235
|
"label": "JS Sandbox Environment",
|
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
"applicable_items": "any",
|
|
9
9
|
"gotchas": ["Requires VISIBLE item — NEVER use on Trigger cubes (invisible)"]
|
|
10
10
|
},
|
|
11
|
+
{
|
|
12
|
+
"$type": "OnDragStartEvent",
|
|
13
|
+
"description": "Build/edit drag starts on this item",
|
|
14
|
+
"applicable_items": "any",
|
|
15
|
+
"params": {
|
|
16
|
+
"adminOnly": "optional bool; true restricts to admins/builders",
|
|
17
|
+
"dis": "optional distance in meters; 0 or omitted means unlimited"
|
|
18
|
+
},
|
|
19
|
+
"gotchas": ["This is a creator/build interaction trigger, not a runtime physics collision trigger."]
|
|
20
|
+
},
|
|
11
21
|
{
|
|
12
22
|
"$type": "OnEnterEvent",
|
|
13
23
|
"description": "Player enters trigger zone. If the Trigger item has pressBtn:true, this fires only after the player enters and presses the Trigger keyCode.",
|
|
@@ -20,6 +30,24 @@
|
|
|
20
30
|
"applicable_items": ["Trigger"],
|
|
21
31
|
"gotchas": ["ONLY works on Trigger cubes, not ResizableCubes or GLBs"]
|
|
22
32
|
},
|
|
33
|
+
{
|
|
34
|
+
"$type": "OnPhysicsItemEnterEvent",
|
|
35
|
+
"description": "Physics-enabled item enters a trigger zone, optionally filtered by physics label",
|
|
36
|
+
"applicable_items": ["Trigger"],
|
|
37
|
+
"params": {
|
|
38
|
+
"label": "optional string; empty or omitted matches any physics item"
|
|
39
|
+
},
|
|
40
|
+
"gotchas": ["ONLY works on Trigger cubes", "The entering item needs physics p:true and a CubePhysicsSync body", "Held/build-mode items are ignored by Core's physics trigger detector"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"$type": "OnPhysicsItemExitEvent",
|
|
44
|
+
"description": "Physics-enabled item exits a trigger zone, optionally filtered by physics label",
|
|
45
|
+
"applicable_items": ["Trigger"],
|
|
46
|
+
"params": {
|
|
47
|
+
"label": "optional string; empty or omitted matches any physics item"
|
|
48
|
+
},
|
|
49
|
+
"gotchas": ["ONLY works on Trigger cubes", "The exiting item needs physics p:true and a CubePhysicsSync body", "Held/build-mode items are ignored by Core's physics trigger detector"]
|
|
50
|
+
},
|
|
23
51
|
{
|
|
24
52
|
"$type": "OnCollideEvent",
|
|
25
53
|
"description": "Player collides with item",
|
|
@@ -38,18 +38,23 @@ FunctionEffectorJS runs JavaScript code inside the Portals engine. It is more po
|
|
|
38
38
|
| `S` | bool | Conditional | **Activate on Start.** REQUIRED when trigger is `OnPlayerLoggedIn`. Without it, the JS silently never fires on login. |
|
|
39
39
|
| `R` | bool | Optional | **Trigger on Tasks Change.** Auto-re-fires the JS whenever any referenced variable or task changes. Use for reactive watchers. |
|
|
40
40
|
|
|
41
|
-
## Reading
|
|
41
|
+
## Reading Values
|
|
42
42
|
|
|
43
|
-
Use `$N{}` to read
|
|
43
|
+
Use `$N{}` to read variables, player data, and timer-like values:
|
|
44
44
|
|
|
45
45
|
```javascript
|
|
46
46
|
var score = $N{score};
|
|
47
47
|
var team = $N{teamname};
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
`FunctionEffectorJS` in the current ReactGUI branch also rewrites task-state macros before evaluation:
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
```javascript
|
|
53
|
+
var stateName = $T{intro}; // "NotActive", "Active", "Completed", or "Undefined"
|
|
54
|
+
var stateCode = $TN{intro}; // 0, 1, 2, or -1
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`$N{}` is still the preferred read path for ordinary numeric/string variables. There is no `$S{}` syntax.
|
|
53
58
|
|
|
54
59
|
## Writing Variables
|
|
55
60
|
|
|
@@ -83,6 +88,10 @@ var alive = CountArray(SelectPlayers({{Players}}, 'alive', 'true'));
|
|
|
83
88
|
// Assign sequential numbers
|
|
84
89
|
AssignNumbersInOrder({{Players}}, 'playernum');
|
|
85
90
|
|
|
91
|
+
// Set/read a parameter for the local player
|
|
92
|
+
SetPlayerParameter('ready', 'true');
|
|
93
|
+
var localReady = SelectPlayerParameter('ready');
|
|
94
|
+
|
|
86
95
|
// Chain operations with +
|
|
87
96
|
SetPlayersParameters({{Players}}, 'team', 'blue') + SetPlayersParameters(SelectRandomPlayers({{Players}}, 3), 'team', 'red');
|
|
88
97
|
|
|
@@ -99,12 +108,13 @@ See `docs://logic/multiplayer` for full reference on opTypes, variable scopes, a
|
|
|
99
108
|
|
|
100
109
|
## Calling Effects from JS
|
|
101
110
|
|
|
102
|
-
Use `UseEffector()` to trigger any Portals effect. See `docs://logic/js-use-effector` for the full effect catalog.
|
|
111
|
+
Use `UseEffector()` to trigger any Portals effect exposed by Core's `EffectorsLists`. Use `PrepareEffector()` with the same arguments to create/cache the temporary effector without activating it yet. See `docs://logic/js-use-effector` for the full effect catalog.
|
|
103
112
|
|
|
104
113
|
```javascript
|
|
105
114
|
UseEffector({{Object}}, 'HideObject');
|
|
106
115
|
UseEffector('760', 'ShowObject');
|
|
107
116
|
UseEffector({{Object}}, 'MoveToSpot', '{"transformState":{"position":[0,5,0],"rotation":[0,0,0,1],"scale":[1,1,1],"duration":2.0},"relative":true}');
|
|
117
|
+
PrepareEffector({{Object}}, 'SetTransform', '{"scale":[2,2,2]}', 'grow');
|
|
108
118
|
```
|
|
109
119
|
|
|
110
120
|
**Targets:** `{{Object}}`, `{{Parent}}`, `{{Child}}` are unquoted template tokens. String IDs like `'760'` are quoted.
|
|
@@ -153,7 +163,7 @@ In FunctionEffectorJS, use `return;` to exit early. Do not use the NCalc pattern
|
|
|
153
163
|
"V": "if (Number($N{done}) == 1.0) { return; } SetVariable('counter', $N{counter} + 1.0, 0.0);"
|
|
154
164
|
```
|
|
155
165
|
|
|
156
|
-
|
|
166
|
+
For quest/task state checks, the current ReactGUI JS bridge rewrites `$T{task}` to a state name and `$TN{task}` to a numeric state code. Use mirror variables only when you need custom derived state.
|
|
157
167
|
|
|
158
168
|
### 6. Reserved-keyword caveats
|
|
159
169
|
Some older docs warn that `from` causes a silent parse error. Live testing (B002) showed `var from = 6.0;` worked on the current build, so the warning is unreliable as a strict rule. Still — prefer non-keyword names (`source`, `origin`, `start`) so the code is robust across engine versions.
|