spoint 0.1.0 → 0.1.10
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/README.md +134 -209
- package/SKILL.md +95 -0
- package/apps/environment/index.js +200 -1
- package/apps/environment/models/decorative/.gitkeep +0 -0
- package/apps/environment/models/hazards/.gitkeep +0 -0
- package/apps/environment/models/interactive/.gitkeep +0 -0
- package/apps/environment/models/structures/.gitkeep +0 -0
- package/apps/environment/smartObjects.js +114 -0
- package/apps/interactable/index.js +155 -0
- package/apps/physics-crate/index.js +15 -9
- package/apps/power-crate/index.js +18 -12
- package/apps/tps-game/$GDUPI.vrm +0 -0
- package/apps/tps-game/Cleetus.vrm +0 -0
- package/apps/tps-game/index.js +185 -27
- package/apps/world/index.js +68 -22
- package/bin/create-app.js +337 -0
- package/client/ARControls.js +301 -0
- package/client/LoadingManager.js +117 -0
- package/client/MobileControls.js +1122 -0
- package/client/anim-lib.glb +0 -0
- package/client/animation.js +306 -0
- package/client/app.js +1341 -65
- package/client/camera.js +191 -33
- package/client/createLoadingScreen.js +69 -0
- package/client/editor/bridge.js +113 -0
- package/client/editor/css/main.css +794 -0
- package/client/editor/images/rotate.svg +4 -0
- package/client/editor/images/scale.svg +4 -0
- package/client/editor/images/translate.svg +4 -0
- package/client/editor/index.html +103 -0
- package/client/editor/js/Command.js +41 -0
- package/client/editor/js/Config.js +81 -0
- package/client/editor/js/Editor.js +785 -0
- package/client/editor/js/EditorControls.js +438 -0
- package/client/editor/js/History.js +321 -0
- package/client/editor/js/Loader.js +987 -0
- package/client/editor/js/LoaderUtils.js +90 -0
- package/client/editor/js/Menubar.Add.js +510 -0
- package/client/editor/js/Menubar.Edit.js +145 -0
- package/client/editor/js/Menubar.File.js +466 -0
- package/client/editor/js/Menubar.Help.js +73 -0
- package/client/editor/js/Menubar.Status.js +51 -0
- package/client/editor/js/Menubar.View.js +183 -0
- package/client/editor/js/Menubar.js +27 -0
- package/client/editor/js/Player.js +53 -0
- package/client/editor/js/Resizer.js +58 -0
- package/client/editor/js/Script.js +503 -0
- package/client/editor/js/Selector.js +102 -0
- package/client/editor/js/Sidebar.Geometry.BoxGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.BufferGeometry.js +115 -0
- package/client/editor/js/Sidebar.Geometry.CapsuleGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.CircleGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.CylinderGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.DodecahedronGeometry.js +73 -0
- package/client/editor/js/Sidebar.Geometry.ExtrudeGeometry.js +196 -0
- package/client/editor/js/Sidebar.Geometry.IcosahedronGeometry.js +73 -0
- package/client/editor/js/Sidebar.Geometry.LatheGeometry.js +98 -0
- package/client/editor/js/Sidebar.Geometry.Modifiers.js +73 -0
- package/client/editor/js/Sidebar.Geometry.OctahedronGeometry.js +74 -0
- package/client/editor/js/Sidebar.Geometry.PlaneGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.RingGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.ShapeGeometry.js +76 -0
- package/client/editor/js/Sidebar.Geometry.SphereGeometry.js +133 -0
- package/client/editor/js/Sidebar.Geometry.TetrahedronGeometry.js +74 -0
- package/client/editor/js/Sidebar.Geometry.TorusGeometry.js +109 -0
- package/client/editor/js/Sidebar.Geometry.TorusKnotGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.TubeGeometry.js +135 -0
- package/client/editor/js/Sidebar.Geometry.js +332 -0
- package/client/editor/js/Sidebar.Material.BooleanProperty.js +60 -0
- package/client/editor/js/Sidebar.Material.ColorProperty.js +87 -0
- package/client/editor/js/Sidebar.Material.ConstantProperty.js +62 -0
- package/client/editor/js/Sidebar.Material.MapProperty.js +249 -0
- package/client/editor/js/Sidebar.Material.NumberProperty.js +60 -0
- package/client/editor/js/Sidebar.Material.Program.js +73 -0
- package/client/editor/js/Sidebar.Material.RangeValueProperty.js +63 -0
- package/client/editor/js/Sidebar.Material.js +751 -0
- package/client/editor/js/Sidebar.Object.Animation.js +102 -0
- package/client/editor/js/Sidebar.Object.js +898 -0
- package/client/editor/js/Sidebar.Project.App.js +165 -0
- package/client/editor/js/Sidebar.Project.Image.js +225 -0
- package/client/editor/js/Sidebar.Project.Materials.js +82 -0
- package/client/editor/js/Sidebar.Project.Renderer.js +144 -0
- package/client/editor/js/Sidebar.Project.Video.js +242 -0
- package/client/editor/js/Sidebar.Project.js +31 -0
- package/client/editor/js/Sidebar.Properties.js +73 -0
- package/client/editor/js/Sidebar.Scene.js +585 -0
- package/client/editor/js/Sidebar.Script.js +129 -0
- package/client/editor/js/Sidebar.Settings.History.js +146 -0
- package/client/editor/js/Sidebar.Settings.Shortcuts.js +175 -0
- package/client/editor/js/Sidebar.Settings.js +60 -0
- package/client/editor/js/Sidebar.js +41 -0
- package/client/editor/js/Storage.js +98 -0
- package/client/editor/js/Strings.js +2028 -0
- package/client/editor/js/Toolbar.js +84 -0
- package/client/editor/js/Viewport.Controls.js +92 -0
- package/client/editor/js/Viewport.Info.js +136 -0
- package/client/editor/js/Viewport.Pathtracer.js +91 -0
- package/client/editor/js/Viewport.ViewHelper.js +39 -0
- package/client/editor/js/Viewport.XR.js +222 -0
- package/client/editor/js/Viewport.js +900 -0
- package/client/editor/js/commands/AddObjectCommand.js +68 -0
- package/client/editor/js/commands/AddScriptCommand.js +75 -0
- package/client/editor/js/commands/Commands.js +23 -0
- package/client/editor/js/commands/MoveObjectCommand.js +111 -0
- package/client/editor/js/commands/MultiCmdsCommand.js +85 -0
- package/client/editor/js/commands/RemoveObjectCommand.js +88 -0
- package/client/editor/js/commands/RemoveScriptCommand.js +81 -0
- package/client/editor/js/commands/SetColorCommand.js +73 -0
- package/client/editor/js/commands/SetGeometryCommand.js +87 -0
- package/client/editor/js/commands/SetGeometryValueCommand.js +70 -0
- package/client/editor/js/commands/SetMaterialColorCommand.js +86 -0
- package/client/editor/js/commands/SetMaterialCommand.js +79 -0
- package/client/editor/js/commands/SetMaterialMapCommand.js +143 -0
- package/client/editor/js/commands/SetMaterialRangeCommand.js +91 -0
- package/client/editor/js/commands/SetMaterialValueCommand.js +90 -0
- package/client/editor/js/commands/SetMaterialVectorCommand.js +79 -0
- package/client/editor/js/commands/SetPositionCommand.js +84 -0
- package/client/editor/js/commands/SetRotationCommand.js +84 -0
- package/client/editor/js/commands/SetScaleCommand.js +84 -0
- package/client/editor/js/commands/SetSceneCommand.js +103 -0
- package/client/editor/js/commands/SetScriptValueCommand.js +80 -0
- package/client/editor/js/commands/SetShadowValueCommand.js +73 -0
- package/client/editor/js/commands/SetUuidCommand.js +70 -0
- package/client/editor/js/commands/SetValueCommand.js +75 -0
- package/client/editor/js/libs/acorn/acorn.js +3236 -0
- package/client/editor/js/libs/acorn/acorn_loose.js +1299 -0
- package/client/editor/js/libs/acorn/walk.js +344 -0
- package/client/editor/js/libs/app/index.html +57 -0
- package/client/editor/js/libs/app.js +251 -0
- package/client/editor/js/libs/codemirror/addon/dialog.css +32 -0
- package/client/editor/js/libs/codemirror/addon/dialog.js +163 -0
- package/client/editor/js/libs/codemirror/addon/show-hint.css +36 -0
- package/client/editor/js/libs/codemirror/addon/show-hint.js +529 -0
- package/client/editor/js/libs/codemirror/addon/tern.css +87 -0
- package/client/editor/js/libs/codemirror/addon/tern.js +750 -0
- package/client/editor/js/libs/codemirror/codemirror.css +344 -0
- package/client/editor/js/libs/codemirror/codemirror.js +9849 -0
- package/client/editor/js/libs/codemirror/mode/glsl.js +233 -0
- package/client/editor/js/libs/codemirror/mode/javascript.js +959 -0
- package/client/editor/js/libs/codemirror/theme/monokai.css +41 -0
- package/client/editor/js/libs/esprima.js +6401 -0
- package/client/editor/js/libs/jsonlint.js +453 -0
- package/client/editor/js/libs/signals.min.js +14 -0
- package/client/editor/js/libs/tern-threejs/threejs.js +5031 -0
- package/client/editor/js/libs/ternjs/comment.js +87 -0
- package/client/editor/js/libs/ternjs/def.js +588 -0
- package/client/editor/js/libs/ternjs/doc_comment.js +401 -0
- package/client/editor/js/libs/ternjs/infer.js +1635 -0
- package/client/editor/js/libs/ternjs/polyfill.js +80 -0
- package/client/editor/js/libs/ternjs/signal.js +26 -0
- package/client/editor/js/libs/ternjs/tern.js +993 -0
- package/client/editor/js/libs/ui.js +1346 -0
- package/client/editor/js/libs/ui.three.js +855 -0
- package/client/facial-animation.js +455 -0
- package/client/index.html +7 -4
- package/client/loading.css +147 -0
- package/client/loading.html +25 -0
- package/client/style.css +251 -0
- package/package.json +7 -3
- package/server.js +9 -1
- package/src/apps/AppContext.js +1 -1
- package/src/apps/AppLoader.js +50 -37
- package/src/apps/AppRuntime.js +32 -8
- package/src/client/InputHandler.js +233 -0
- package/src/client/JitterBuffer.js +207 -0
- package/src/client/KalmanFilter.js +125 -0
- package/src/client/MessageHandler.js +101 -0
- package/src/client/PhysicsNetworkClient.js +141 -68
- package/src/client/ReconnectManager.js +62 -0
- package/src/client/SmoothInterpolation.js +127 -0
- package/src/client/SnapshotProcessor.js +144 -0
- package/src/connection/ConnectionManager.js +21 -3
- package/src/connection/SessionStore.js +13 -3
- package/src/index.client.js +4 -6
- package/src/netcode/EventLog.js +29 -15
- package/src/netcode/LagCompensator.js +25 -26
- package/src/netcode/NetworkState.js +4 -1
- package/src/netcode/PhysicsIntegration.js +20 -6
- package/src/netcode/PlayerManager.js +10 -2
- package/src/netcode/SnapshotEncoder.js +66 -19
- package/src/netcode/TickSystem.js +13 -4
- package/src/physics/World.js +66 -13
- package/src/protocol/msgpack.js +90 -63
- package/src/sdk/ReloadHandlers.js +12 -2
- package/src/sdk/ReloadManager.js +5 -0
- package/src/sdk/ServerHandlers.js +50 -11
- package/src/sdk/StaticHandler.js +22 -6
- package/src/sdk/TickHandler.js +101 -34
- package/src/sdk/scaffold.js +28 -0
- package/src/sdk/server.js +59 -33
- package/src/shared/movement.js +2 -1
- package/src/spatial/Octree.js +5 -0
- package/apps/interactive-door/index.js +0 -33
- package/apps/patrol-npc/index.js +0 -37
- package/src/connection/QualityMonitor.js +0 -46
- package/src/debug/StateInspector.js +0 -42
- package/src/index.js +0 -1
- package/src/index.server.js +0 -27
- package/src/protocol/Codec.js +0 -60
- package/src/protocol/SequenceTracker.js +0 -71
- package/src/sdk/ClientMessageHandler.js +0 -80
- package/src/sdk/client.js +0 -122
- package/world/kaira.glb +0 -0
- package/world/schwust.glb +0 -0
|
@@ -1,17 +1,216 @@
|
|
|
1
|
+
import { SMART_OBJECT_TEMPLATES, getTemplate, isValidTemplate, getPlaceholderColor, getPlaceholderDimensions } from './smartObjects.js'
|
|
2
|
+
import { readdirSync, statSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
|
|
6
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
7
|
+
const modelsDir = join(__dirname, 'models')
|
|
8
|
+
|
|
9
|
+
function discoverModels() {
|
|
10
|
+
const registry = {}
|
|
11
|
+
try {
|
|
12
|
+
const categories = readdirSync(modelsDir).filter(f => statSync(join(modelsDir, f)).isDirectory())
|
|
13
|
+
for (const cat of categories) {
|
|
14
|
+
const catPath = join(modelsDir, cat)
|
|
15
|
+
const files = readdirSync(catPath).filter(f => f.endsWith('.glb') || f.endsWith('.gltf'))
|
|
16
|
+
registry[cat] = files
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {
|
|
19
|
+
console.log('[Environment] Model discovery skipped (models dir not found)')
|
|
20
|
+
}
|
|
21
|
+
return registry
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
export default {
|
|
2
25
|
server: {
|
|
3
26
|
setup(ctx) {
|
|
4
27
|
ctx.physics.setStatic(true)
|
|
5
28
|
ctx.physics.addTrimeshCollider()
|
|
29
|
+
|
|
30
|
+
ctx.state.smartObjects = new Map()
|
|
31
|
+
ctx.state.editorMode = false
|
|
32
|
+
ctx.state.nextSmartObjectId = 0
|
|
33
|
+
ctx.state.modelRegistry = discoverModels()
|
|
34
|
+
|
|
35
|
+
ctx.debug.log(`[Environment] Initialized with ${Object.keys(SMART_OBJECT_TEMPLATES).length} smart object templates, ${Object.values(ctx.state.modelRegistry).flat().length} models discovered`)
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
update(ctx, dt) {
|
|
39
|
+
for (const [id, obj] of ctx.state.smartObjects) {
|
|
40
|
+
if (obj.template === 'platform') updatePlatform(ctx, id, obj, dt)
|
|
41
|
+
if (obj.template === 'hazard') updateHazard(ctx, id, obj, dt)
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
onEvent(payload, ctx) {
|
|
46
|
+
if (payload.type === 'dropModel' && payload.position && payload.modelPath) {
|
|
47
|
+
const id = `dropped_model_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
48
|
+
const entity = ctx.world.spawn(id, {
|
|
49
|
+
position: payload.position,
|
|
50
|
+
rotation: payload.rotation || [0, 0, 0, 1],
|
|
51
|
+
model: payload.modelPath
|
|
52
|
+
})
|
|
53
|
+
if (entity) {
|
|
54
|
+
entity.custom = {
|
|
55
|
+
droppedModel: true,
|
|
56
|
+
modelPath: payload.modelPath,
|
|
57
|
+
scale: payload.scale || [1, 1, 1]
|
|
58
|
+
}
|
|
59
|
+
ctx.debug.log(`[Environment] Dropped model spawned: ${id}`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
teardown(ctx) {
|
|
65
|
+
for (const id of ctx.state.smartObjects.keys()) {
|
|
66
|
+
ctx.world.destroy(id)
|
|
67
|
+
}
|
|
68
|
+
ctx.state.smartObjects.clear()
|
|
6
69
|
}
|
|
7
70
|
},
|
|
71
|
+
|
|
8
72
|
client: {
|
|
9
73
|
render(ctx) {
|
|
10
74
|
return {
|
|
11
75
|
model: ctx.entity.model,
|
|
12
76
|
position: ctx.entity.position,
|
|
13
|
-
rotation: ctx.entity.rotation
|
|
77
|
+
rotation: ctx.entity.rotation,
|
|
78
|
+
custom: ctx.entity.custom
|
|
14
79
|
}
|
|
15
80
|
}
|
|
16
81
|
}
|
|
17
82
|
}
|
|
83
|
+
|
|
84
|
+
function addCollider(ctx, collider) {
|
|
85
|
+
if (!collider) return
|
|
86
|
+
|
|
87
|
+
switch (collider.type) {
|
|
88
|
+
case 'box':
|
|
89
|
+
ctx.physics.addBoxCollider(collider.size)
|
|
90
|
+
break
|
|
91
|
+
case 'sphere':
|
|
92
|
+
ctx.physics.addSphereCollider(collider.radius)
|
|
93
|
+
break
|
|
94
|
+
case 'capsule':
|
|
95
|
+
ctx.physics.addCapsuleCollider(collider.radius, collider.halfHeight * 2)
|
|
96
|
+
break
|
|
97
|
+
case 'trimesh':
|
|
98
|
+
ctx.physics.addTrimeshCollider()
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function updatePlatform(ctx, id, obj, dt) {
|
|
104
|
+
if (!obj.config || !obj.config.waypoints || obj.config.waypoints.length < 2) return
|
|
105
|
+
|
|
106
|
+
const waypoints = obj.config.waypoints
|
|
107
|
+
const speed = obj.config.speed || 5
|
|
108
|
+
const waitTime = obj.config.waitTime || 1
|
|
109
|
+
|
|
110
|
+
if (obj.waitTimer === undefined) obj.waitTimer = 0
|
|
111
|
+
if (obj.currentWaypoint === undefined) obj.currentWaypoint = 0
|
|
112
|
+
|
|
113
|
+
obj.waitTimer -= dt
|
|
114
|
+
if (obj.waitTimer > 0) return
|
|
115
|
+
|
|
116
|
+
const entity = ctx.world.getEntity(id)
|
|
117
|
+
if (!entity) return
|
|
118
|
+
|
|
119
|
+
const current = waypoints[obj.currentWaypoint]
|
|
120
|
+
const next = waypoints[(obj.currentWaypoint + 1) % waypoints.length]
|
|
121
|
+
|
|
122
|
+
const dx = next[0] - entity.position[0]
|
|
123
|
+
const dy = next[1] - entity.position[1]
|
|
124
|
+
const dz = next[2] - entity.position[2]
|
|
125
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
126
|
+
|
|
127
|
+
if (dist < 0.1) {
|
|
128
|
+
obj.currentWaypoint = (obj.currentWaypoint + 1) % waypoints.length
|
|
129
|
+
obj.waitTimer = waitTime
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const moveDistance = Math.min(speed * dt, dist)
|
|
134
|
+
entity.position[0] += (dx / dist) * moveDistance
|
|
135
|
+
entity.position[1] += (dy / dist) * moveDistance
|
|
136
|
+
entity.position[2] += (dz / dist) * moveDistance
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function updateHazard(ctx, id, obj, dt) {
|
|
140
|
+
if (!obj.config) return
|
|
141
|
+
if (obj.damageTimer === undefined) obj.damageTimer = 0
|
|
142
|
+
|
|
143
|
+
obj.damageTimer -= dt
|
|
144
|
+
if (obj.damageTimer > 0) return
|
|
145
|
+
|
|
146
|
+
obj.damageTimer = obj.config.damageInterval || 0.5
|
|
147
|
+
|
|
148
|
+
const entity = ctx.world.getEntity(id)
|
|
149
|
+
if (!entity) return
|
|
150
|
+
|
|
151
|
+
const damage = obj.config.damage || 10
|
|
152
|
+
const players = ctx.players.getAll()
|
|
153
|
+
|
|
154
|
+
for (const player of players) {
|
|
155
|
+
if (!player.state) continue
|
|
156
|
+
const pp = player.state.position
|
|
157
|
+
const dx = pp[0] - entity.position[0]
|
|
158
|
+
const dy = pp[1] - entity.position[1]
|
|
159
|
+
const dz = pp[2] - entity.position[2]
|
|
160
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
161
|
+
const radius = obj.collider?.radius || 2
|
|
162
|
+
|
|
163
|
+
if (dist < radius) {
|
|
164
|
+
player.state.health = Math.max(0, (player.state.health || 100) - damage * dt)
|
|
165
|
+
ctx.network.broadcast({ type: 'hazard_damage', playerId: player.id, damage: Math.round(damage * dt) })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function spawnSmartObject(ctx, templateName, position, rotation = [0, 0, 0, 1], parent = null) {
|
|
171
|
+
if (!isValidTemplate(templateName)) {
|
|
172
|
+
ctx.debug.log(`[Environment] Invalid template: ${templateName}`)
|
|
173
|
+
return null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const template = getTemplate(templateName)
|
|
177
|
+
const id = `smart_${templateName}_${ctx.state.nextSmartObjectId++}`
|
|
178
|
+
|
|
179
|
+
const entity = ctx.world.spawn(id, {
|
|
180
|
+
position,
|
|
181
|
+
rotation,
|
|
182
|
+
parent,
|
|
183
|
+
model: template.model
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
if (!entity) return null
|
|
187
|
+
|
|
188
|
+
const isEditor = ctx.state.editorMode
|
|
189
|
+
|
|
190
|
+
entity.custom = {
|
|
191
|
+
smartObject: true,
|
|
192
|
+
template: templateName,
|
|
193
|
+
editorPlaceholder: isEditor,
|
|
194
|
+
color: getPlaceholderColor(templateName)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ctx.state.smartObjects.set(id, {
|
|
198
|
+
template: templateName,
|
|
199
|
+
config: { ...template.config },
|
|
200
|
+
collider: template.collider
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if (!isEditor && template.collider) {
|
|
204
|
+
addCollider(ctx, template.collider)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return entity
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const spawn = (ctx, template, position, config) => {
|
|
211
|
+
const obj = spawnSmartObject(ctx, template, position)
|
|
212
|
+
if (obj && config) Object.assign(ctx.state.smartObjects.get(obj.id).config, config)
|
|
213
|
+
return obj
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const setEditorMode = (ctx, enabled) => { ctx.state.editorMode = enabled }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Smart Object Templates
|
|
2
|
+
// Define behavior, physics, and visual properties for environment entities
|
|
3
|
+
|
|
4
|
+
export const SMART_OBJECT_TEMPLATES = {
|
|
5
|
+
door: {
|
|
6
|
+
displayName: 'Door',
|
|
7
|
+
model: null,
|
|
8
|
+
collider: { type: 'box', size: [1.5, 2.5, 0.1] },
|
|
9
|
+
physics: 'kinematic',
|
|
10
|
+
editorPlaceholder: { color: 0x0066ff, dims: [1.5, 2.5, 0.1] },
|
|
11
|
+
config: {
|
|
12
|
+
open: false,
|
|
13
|
+
openTime: 0.5,
|
|
14
|
+
closeTime: 0.5,
|
|
15
|
+
openAngle: Math.PI / 2
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
platform: {
|
|
20
|
+
displayName: 'Moving Platform',
|
|
21
|
+
model: null,
|
|
22
|
+
collider: { type: 'box', size: [4, 0.5, 4] },
|
|
23
|
+
physics: 'kinematic',
|
|
24
|
+
editorPlaceholder: { color: 0x00cc00, dims: [4, 0.5, 4] },
|
|
25
|
+
config: {
|
|
26
|
+
waypoints: [[0, 0, 0]],
|
|
27
|
+
speed: 5,
|
|
28
|
+
waitTime: 1
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
trigger: {
|
|
33
|
+
displayName: 'Trigger Volume',
|
|
34
|
+
model: null,
|
|
35
|
+
collider: { type: 'box', size: [2, 3, 2] },
|
|
36
|
+
physics: 'trigger',
|
|
37
|
+
editorPlaceholder: { color: 0xffff00, dims: [2, 3, 2] },
|
|
38
|
+
config: {
|
|
39
|
+
eventName: 'trigger',
|
|
40
|
+
oneshot: false
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
hazard: {
|
|
45
|
+
displayName: 'Hazard Zone',
|
|
46
|
+
model: null,
|
|
47
|
+
collider: { type: 'sphere', radius: 2 },
|
|
48
|
+
physics: 'trigger',
|
|
49
|
+
editorPlaceholder: { color: 0xff0000, dims: [2, 2, 2] },
|
|
50
|
+
config: {
|
|
51
|
+
damage: 10,
|
|
52
|
+
damageInterval: 0.5
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
lootBox: {
|
|
57
|
+
displayName: 'Loot Box',
|
|
58
|
+
model: null,
|
|
59
|
+
collider: { type: 'box', size: [1, 1.5, 1] },
|
|
60
|
+
physics: 'dynamic',
|
|
61
|
+
editorPlaceholder: { color: 0x885533, dims: [1, 1.5, 1] },
|
|
62
|
+
config: {
|
|
63
|
+
lootType: 'ammo',
|
|
64
|
+
quantity: 30,
|
|
65
|
+
openSound: 'open'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
pillar: {
|
|
70
|
+
displayName: 'Pillar/Column',
|
|
71
|
+
model: null,
|
|
72
|
+
collider: { type: 'capsule', radius: 0.5, halfHeight: 2 },
|
|
73
|
+
physics: 'static',
|
|
74
|
+
editorPlaceholder: { color: 0x888888, dims: [1, 4, 1] },
|
|
75
|
+
config: {
|
|
76
|
+
decorative: true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Editor placeholder color mapping
|
|
82
|
+
export const PLACEHOLDER_COLORS = {
|
|
83
|
+
door: 0x0066ff, // Blue
|
|
84
|
+
platform: 0x00cc00, // Green
|
|
85
|
+
trigger: 0xffff00, // Yellow
|
|
86
|
+
hazard: 0xff0000, // Red
|
|
87
|
+
lootBox: 0x885533, // Brown
|
|
88
|
+
pillar: 0x888888, // Gray
|
|
89
|
+
unknown: 0xcccccc // Light gray
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Get placeholder color for a template
|
|
93
|
+
export function getPlaceholderColor(templateName) {
|
|
94
|
+
return PLACEHOLDER_COLORS[templateName] || PLACEHOLDER_COLORS.unknown
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Get placeholder dimensions for a template
|
|
98
|
+
export function getPlaceholderDimensions(templateName) {
|
|
99
|
+
const template = SMART_OBJECT_TEMPLATES[templateName]
|
|
100
|
+
if (!template || !template.editorPlaceholder) {
|
|
101
|
+
return [1, 1, 1]
|
|
102
|
+
}
|
|
103
|
+
return template.editorPlaceholder.dims
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate template exists and is accessible
|
|
107
|
+
export function isValidTemplate(templateName) {
|
|
108
|
+
return templateName in SMART_OBJECT_TEMPLATES
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Get template definition
|
|
112
|
+
export function getTemplate(templateName) {
|
|
113
|
+
return SMART_OBJECT_TEMPLATES[templateName] || null
|
|
114
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
server: {
|
|
3
|
+
setup(ctx) {
|
|
4
|
+
ctx.entity.custom = { mesh: 'box', color: 0x00ff88, sx: 1.5, sy: 0.5, sz: 1.5, label: 'INTERACT' }
|
|
5
|
+
ctx.state.interactionRadius = 3.5
|
|
6
|
+
ctx.state.interactionCooldown = new Map()
|
|
7
|
+
ctx.state.interactionCount = 0
|
|
8
|
+
|
|
9
|
+
ctx.physics.setStatic(true)
|
|
10
|
+
ctx.physics.addBoxCollider([0.75, 0.25, 0.75])
|
|
11
|
+
|
|
12
|
+
ctx.time.every(0.1, () => {
|
|
13
|
+
const nearby = ctx.players.getNearest(ctx.entity.position, ctx.state.interactionRadius)
|
|
14
|
+
if (!nearby) return
|
|
15
|
+
|
|
16
|
+
const now = Date.now()
|
|
17
|
+
const playerId = nearby.id
|
|
18
|
+
const lastInteract = ctx.state.interactionCooldown.get(playerId) || 0
|
|
19
|
+
|
|
20
|
+
if (nearby.state?.interact && now - lastInteract > 500) {
|
|
21
|
+
ctx.state.interactionCooldown.set(playerId, now)
|
|
22
|
+
ctx.state.interactionCount++
|
|
23
|
+
|
|
24
|
+
const messages = [
|
|
25
|
+
'Hello there!',
|
|
26
|
+
'You found the interact button!',
|
|
27
|
+
'Nice to meet you!',
|
|
28
|
+
'This button works!',
|
|
29
|
+
`Interacted ${ctx.state.interactionCount} times total`
|
|
30
|
+
]
|
|
31
|
+
const msg = messages[ctx.state.interactionCount % messages.length]
|
|
32
|
+
|
|
33
|
+
ctx.players.send(playerId, {
|
|
34
|
+
type: 'interact_response',
|
|
35
|
+
message: msg,
|
|
36
|
+
count: ctx.state.interactionCount
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
ctx.network.broadcast({
|
|
40
|
+
type: 'interact_effect',
|
|
41
|
+
position: ctx.entity.position,
|
|
42
|
+
playerId: playerId
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
teardown(ctx) {
|
|
49
|
+
ctx.state.interactionCooldown?.clear()
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
onMessage(ctx, msg) {
|
|
53
|
+
if (msg.type === 'player_leave') {
|
|
54
|
+
ctx.state.interactionCooldown?.delete(msg.playerId)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
client: {
|
|
60
|
+
setup(engine) {
|
|
61
|
+
this._lastMessage = null
|
|
62
|
+
this._messageExpire = 0
|
|
63
|
+
this._showingInteract = false
|
|
64
|
+
this._isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
|
65
|
+
this._wasRegistered = false
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
onFrame(dt, engine) {
|
|
69
|
+
const ent = engine.client?.state?.entities?.find(e => e.app === 'interactable')
|
|
70
|
+
const pos = ent?.position
|
|
71
|
+
const local = engine.client?.state?.players?.find(p => p.id === engine.playerId)
|
|
72
|
+
if (!pos || !local?.position) {
|
|
73
|
+
if (this._wasRegistered) {
|
|
74
|
+
engine.mobileControls?.unregisterInteractable(ent?.id || 'interactable')
|
|
75
|
+
this._wasRegistered = false
|
|
76
|
+
}
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dx = pos[0] - local.position[0]
|
|
81
|
+
const dy = pos[1] - local.position[1]
|
|
82
|
+
const dz = pos[2] - local.position[2]
|
|
83
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
84
|
+
const canInteract = dist < 3.5
|
|
85
|
+
|
|
86
|
+
if (canInteract && !this._wasRegistered) {
|
|
87
|
+
engine.mobileControls?.registerInteractable(ent.id, 'INTERACT')
|
|
88
|
+
this._wasRegistered = ent.id
|
|
89
|
+
} else if (!canInteract && this._wasRegistered) {
|
|
90
|
+
engine.mobileControls?.unregisterInteractable(this._wasRegistered)
|
|
91
|
+
this._wasRegistered = false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this._canInteract = canInteract
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
teardown(engine) {
|
|
98
|
+
if (this._wasRegistered) {
|
|
99
|
+
engine.mobileControls?.unregisterInteractable('interactable')
|
|
100
|
+
this._wasRegistered = false
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
onInput(input, engine) {
|
|
105
|
+
if (input.interact && !this._wasInteracting) {
|
|
106
|
+
this._showingInteract = true
|
|
107
|
+
this._messageExpire = Date.now() + 2000
|
|
108
|
+
}
|
|
109
|
+
this._wasInteracting = input.interact
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
onEvent(payload, engine) {
|
|
113
|
+
if (payload.type === 'interact_response') {
|
|
114
|
+
this._lastMessage = payload.message
|
|
115
|
+
this._messageExpire = Date.now() + 3000
|
|
116
|
+
}
|
|
117
|
+
if (payload.type === 'interact_effect') {
|
|
118
|
+
this._lastMessage = 'Someone interacted!'
|
|
119
|
+
this._messageExpire = Date.now() + 1500
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
render(ctx) {
|
|
124
|
+
const h = ctx.h
|
|
125
|
+
const pos = ctx.entity.position
|
|
126
|
+
if (!h || !pos) return { position: pos }
|
|
127
|
+
|
|
128
|
+
const ui = []
|
|
129
|
+
|
|
130
|
+
if (this._lastMessage && Date.now() < this._messageExpire) {
|
|
131
|
+
const timeLeft = (this._messageExpire - Date.now()) / 3000
|
|
132
|
+
const opacity = Math.min(1, timeLeft * 2)
|
|
133
|
+
ui.push(
|
|
134
|
+
h('div', {
|
|
135
|
+
style: `position:fixed;top:30%;left:50%;transform:translate(-50%,-50%);padding:16px 32px;background:rgba(0,0,0,0.8);border-radius:12px;color:#0f0;font-weight:bold;font-size:20px;text-align:center;border:2px solid #0f0;opacity:${opacity}`
|
|
136
|
+
}, this._lastMessage)
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const custom = { ...ctx.entity.custom }
|
|
141
|
+
if (this._canInteract) {
|
|
142
|
+
custom.glow = true
|
|
143
|
+
custom.glowColor = 0x00ff88
|
|
144
|
+
custom.glowIntensity = 0.5
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
position: pos,
|
|
149
|
+
rotation: ctx.entity.rotation,
|
|
150
|
+
custom,
|
|
151
|
+
ui: ui.length > 0 ? h('div', null, ...ui) : null
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
server: {
|
|
3
3
|
setup(ctx) {
|
|
4
|
+
ctx.entity.custom = { mesh: 'box', color: 0xff8800, sx: 1, sy: 1, sz: 1 }
|
|
4
5
|
ctx.physics.setDynamic(true)
|
|
5
6
|
ctx.physics.setMass(10)
|
|
6
|
-
ctx.physics.addBoxCollider([
|
|
7
|
+
ctx.physics.addBoxCollider([0.5, 0.5, 0.5])
|
|
7
8
|
},
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
teardown(ctx) {
|
|
10
|
+
const ent = ctx._entity
|
|
11
|
+
if (ent?._physicsBodyId && ctx._runtime?._physics) {
|
|
12
|
+
ctx._runtime._physics.removeBody(ent._physicsBodyId)
|
|
13
|
+
ent._physicsBodyId = null
|
|
11
14
|
}
|
|
15
|
+
},
|
|
16
|
+
update(ctx, dt) {
|
|
17
|
+
const ent = ctx._entity
|
|
18
|
+
if (!ent?._physicsBodyId || !ctx._runtime?._physics) return
|
|
19
|
+
const pw = ctx._runtime._physics
|
|
20
|
+
ent.position = pw.getBodyPosition(ent._physicsBodyId)
|
|
21
|
+
ent.rotation = pw.getBodyRotation(ent._physicsBodyId)
|
|
12
22
|
}
|
|
13
23
|
},
|
|
14
24
|
client: {
|
|
15
25
|
render(ctx) {
|
|
16
|
-
return {
|
|
17
|
-
model: ctx.entity.model,
|
|
18
|
-
position: ctx.entity.position,
|
|
19
|
-
rotation: ctx.entity.rotation
|
|
20
|
-
}
|
|
26
|
+
return { position: ctx.entity.position, rotation: ctx.entity.rotation, custom: ctx.entity.custom }
|
|
21
27
|
}
|
|
22
28
|
}
|
|
23
29
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const CONFIG = {
|
|
2
|
-
spawnInterval:
|
|
2
|
+
spawnInterval: 30,
|
|
3
3
|
pickupRadius: 2.5,
|
|
4
4
|
pickupLifetime: 30,
|
|
5
|
+
maxCrates: 20,
|
|
5
6
|
crateHitRadius: 1.5,
|
|
6
7
|
buffDuration: 45,
|
|
7
8
|
speedMultiplier: 1.2,
|
|
@@ -19,7 +20,7 @@ const CONFIG = {
|
|
|
19
20
|
export default {
|
|
20
21
|
server: {
|
|
21
22
|
setup(ctx) {
|
|
22
|
-
ctx.state.crates = ctx.state.crates || new
|
|
23
|
+
ctx.state.crates = ctx.state.crates || new Set()
|
|
23
24
|
ctx.state.pickups = ctx.state.pickups || new Map()
|
|
24
25
|
ctx.state.spawnPoints = findSpawnPoints(ctx)
|
|
25
26
|
ctx.state.nextCrateId = ctx.state.nextCrateId || 0
|
|
@@ -30,6 +31,8 @@ export default {
|
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
console.log(`[power-crate] ${ctx.state.spawnPoints.length} spawn points, interval ${CONFIG.spawnInterval}s`)
|
|
34
|
+
const initialCount = Math.min(5, ctx.state.spawnPoints.length)
|
|
35
|
+
for (let i = 0; i < initialCount; i++) spawnCrate(ctx)
|
|
33
36
|
},
|
|
34
37
|
|
|
35
38
|
update(ctx, dt) {
|
|
@@ -44,7 +47,7 @@ export default {
|
|
|
44
47
|
},
|
|
45
48
|
|
|
46
49
|
teardown(ctx) {
|
|
47
|
-
for (const id of ctx.state.crates
|
|
50
|
+
for (const id of ctx.state.crates) ctx.world.destroy(id)
|
|
48
51
|
for (const id of ctx.state.pickups.keys()) ctx.world.destroy(id)
|
|
49
52
|
ctx.state.crates.clear()
|
|
50
53
|
ctx.state.pickups.clear()
|
|
@@ -78,14 +81,12 @@ function findSpawnPoints(ctx) {
|
|
|
78
81
|
function spawnCrate(ctx) {
|
|
79
82
|
const sp = ctx.state.spawnPoints
|
|
80
83
|
if (sp.length === 0) return
|
|
84
|
+
if (ctx.state.crates.size >= CONFIG.maxCrates) return
|
|
81
85
|
const pos = sp[Math.floor(Math.random() * sp.length)]
|
|
82
86
|
const id = `power_crate_${ctx.state.nextCrateId++}`
|
|
83
|
-
ctx.world.spawn(id, {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
app: 'physics-crate'
|
|
87
|
-
})
|
|
88
|
-
ctx.state.crates.set(id, [...pos])
|
|
87
|
+
const e = ctx.world.spawn(id, { position: [...pos] })
|
|
88
|
+
if (e) e.custom = { mesh: 'box', color: 0xff8800, sx: 1, sy: 1, sz: 1, hover: 0.15, spin: 1 }
|
|
89
|
+
ctx.state.crates.add(id)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function handleFireEvent(ctx, data) {
|
|
@@ -93,7 +94,10 @@ function handleFireEvent(ctx, data) {
|
|
|
93
94
|
const origin = data.origin
|
|
94
95
|
const direction = data.direction
|
|
95
96
|
|
|
96
|
-
for (const
|
|
97
|
+
for (const crateId of ctx.state.crates) {
|
|
98
|
+
const ent = ctx.world.getEntity(crateId)
|
|
99
|
+
if (!ent) continue
|
|
100
|
+
const cratePos = ent.position
|
|
97
101
|
const toTarget = [
|
|
98
102
|
cratePos[0] - origin[0],
|
|
99
103
|
cratePos[1] - origin[1],
|
|
@@ -113,16 +117,18 @@ function handleFireEvent(ctx, data) {
|
|
|
113
117
|
)
|
|
114
118
|
if (dist > CONFIG.crateHitRadius) continue
|
|
115
119
|
|
|
120
|
+
const hitPos = [cratePos[0], cratePos[1], cratePos[2]]
|
|
116
121
|
ctx.world.destroy(crateId)
|
|
117
122
|
ctx.state.crates.delete(crateId)
|
|
118
|
-
spawnPickup(ctx,
|
|
123
|
+
spawnPickup(ctx, hitPos)
|
|
119
124
|
break
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
function spawnPickup(ctx, pos) {
|
|
124
129
|
const id = `powerup_${ctx.state.nextCrateId++}`
|
|
125
|
-
ctx.world.spawn(id, { position: [...pos] })
|
|
130
|
+
const e = ctx.world.spawn(id, { position: [...pos] })
|
|
131
|
+
if (e) e.custom = { mesh: 'cylinder', r: 0.4, h: 0.1, seg: 16, color: 0xffd700, roughness: 0.3, metalness: 0.8, emissive: 0xffa000, emissiveIntensity: 0.3, rotZ: Math.PI / 2, light: 0xffd700, lightIntensity: 1, lightRange: 4, spin: 3, hover: 0.3 }
|
|
126
132
|
ctx.state.pickups.set(id, { position: [...pos], lifetime: CONFIG.pickupLifetime })
|
|
127
133
|
}
|
|
128
134
|
|
|
Binary file
|
|
Binary file
|