|
2 | 2 |
|
3 | 3 | import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs' |
4 | 4 | import { resolve, join, dirname } from 'node:path' |
| 5 | +import { getTemplateContent } from './templates.js' |
5 | 6 | import { fileURLToPath } from 'node:url' |
6 | 7 |
|
7 | 8 | const __dirname = import.meta.dirname || dirname(fileURLToPath(import.meta.url)) |
@@ -47,238 +48,6 @@ function parseArgs(argv) { |
47 | 48 | return args |
48 | 49 | } |
49 | 50 |
|
50 | | -function getTemplateContent(templateType) { |
51 | | - const templates = { |
52 | | - simple: `export default { |
53 | | - server: { |
54 | | - setup(ctx) { |
55 | | - ctx.entity.custom = { |
56 | | - mesh: 'box', |
57 | | - color: 0x00ff00 |
58 | | - } |
59 | | - ctx.physics.setStatic(true) |
60 | | - ctx.physics.addBoxCollider([0.5, 0.5, 0.5]) |
61 | | - }, |
62 | | -
|
63 | | - update(ctx, dt) { |
64 | | - // Your update logic here |
65 | | - }, |
66 | | -
|
67 | | - teardown(ctx) { |
68 | | - // Cleanup resources |
69 | | - } |
70 | | - }, |
71 | | -
|
72 | | - client: { |
73 | | - render(ctx) { |
74 | | - return { |
75 | | - position: ctx.entity.position, |
76 | | - rotation: ctx.entity.rotation, |
77 | | - custom: ctx.entity.custom |
78 | | - } |
79 | | - } |
80 | | - } |
81 | | -}`, |
82 | | - |
83 | | - physics: `export default { |
84 | | - server: { |
85 | | - setup(ctx) { |
86 | | - ctx.entity.custom = { |
87 | | - mesh: 'box', |
88 | | - color: 0xff8800, |
89 | | - sx: 1, |
90 | | - sy: 1, |
91 | | - sz: 1 |
92 | | - } |
93 | | - ctx.physics.setDynamic(true) |
94 | | - ctx.physics.setMass(5) |
95 | | - ctx.physics.addBoxCollider([0.5, 0.5, 0.5]) |
96 | | - } |
97 | | - }, |
98 | | -
|
99 | | - client: { |
100 | | - render(ctx) { |
101 | | - return { |
102 | | - position: ctx.entity.position, |
103 | | - rotation: ctx.entity.rotation, |
104 | | - custom: ctx.entity.custom |
105 | | - } |
106 | | - } |
107 | | - } |
108 | | -}`, |
109 | | - |
110 | | - interactive: `export default { |
111 | | - server: { |
112 | | - setup(ctx) { |
113 | | - ctx.entity.custom = { |
114 | | - mesh: 'box', |
115 | | - color: 0x00ff88, |
116 | | - sx: 1.5, |
117 | | - sy: 0.5, |
118 | | - sz: 1.5, |
119 | | - label: 'INTERACT' |
120 | | - } |
121 | | - ctx.physics.setStatic(true) |
122 | | - ctx.physics.addBoxCollider([0.75, 0.25, 0.75]) |
123 | | - ctx.state.interactionCount = 0 |
124 | | - ctx.state.interactionRadius = 3.5 |
125 | | - ctx.state.interactionCooldown = new Map() |
126 | | - }, |
127 | | -
|
128 | | - update(ctx, dt) { |
129 | | - const nearby = ctx.players.getNearest(ctx.entity.position, ctx.state.interactionRadius) |
130 | | - if (!nearby?.state?.interact) return |
131 | | -
|
132 | | - const now = Date.now() |
133 | | - const playerId = nearby.id |
134 | | - const lastInteract = ctx.state.interactionCooldown.get(playerId) || 0 |
135 | | -
|
136 | | - if (now - lastInteract > 500) { |
137 | | - ctx.state.interactionCooldown.set(playerId, now) |
138 | | - ctx.state.interactionCount++ |
139 | | -
|
140 | | - ctx.players.send(playerId, { |
141 | | - type: 'interact_response', |
142 | | - message: 'You interacted!', |
143 | | - count: ctx.state.interactionCount |
144 | | - }) |
145 | | -
|
146 | | - ctx.network.broadcast({ |
147 | | - type: 'interact_effect', |
148 | | - position: ctx.entity.position |
149 | | - }) |
150 | | - } |
151 | | - }, |
152 | | -
|
153 | | - teardown(ctx) { |
154 | | - ctx.state.interactionCooldown?.clear() |
155 | | - } |
156 | | - }, |
157 | | -
|
158 | | - client: { |
159 | | - setup(engine) { |
160 | | - this._lastMessage = null |
161 | | - this._messageExpire = 0 |
162 | | - this._canInteract = false |
163 | | - this._entityPos = null |
164 | | - }, |
165 | | -
|
166 | | - onFrame(dt, engine) { |
167 | | - const local = engine.client?.state?.players?.find(p => p.id === engine.playerId) |
168 | | - if (!this._entityPos || !local?.position) { |
169 | | - this._canInteract = false |
170 | | - return |
171 | | - } |
172 | | -
|
173 | | - const dx = this._entityPos[0] - local.position[0] |
174 | | - const dy = this._entityPos[1] - local.position[1] |
175 | | - const dz = this._entityPos[2] - local.position[2] |
176 | | - const dist = Math.sqrt(dx * dx + dy * dy + dz * dz) |
177 | | - this._canInteract = dist < 3.5 |
178 | | - }, |
179 | | -
|
180 | | - onEvent(payload, engine) { |
181 | | - if (payload.type === 'interact_response') { |
182 | | - this._lastMessage = payload.message |
183 | | - this._messageExpire = Date.now() + 2000 |
184 | | - } |
185 | | - }, |
186 | | -
|
187 | | - render(ctx) { |
188 | | - this._entityPos = ctx.entity.position |
189 | | - const custom = { ...ctx.entity.custom } |
190 | | - if (this._canInteract) { |
191 | | - custom.glow = true |
192 | | - custom.glowColor = 0x00ff88 |
193 | | - } |
194 | | -
|
195 | | - const ui = [] |
196 | | - if (this._lastMessage && Date.now() < this._messageExpire) { |
197 | | - const opacity = Math.max(0, (this._messageExpire - Date.now()) / 2000) |
198 | | - if (ctx.h) { |
199 | | - ui.push( |
200 | | - ctx.h('div', { |
201 | | - 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;opacity:\${opacity}\` |
202 | | - }, this._lastMessage) |
203 | | - ) |
204 | | - } |
205 | | - } |
206 | | -
|
207 | | - return { |
208 | | - position: ctx.entity.position, |
209 | | - rotation: ctx.entity.rotation, |
210 | | - custom, |
211 | | - ui: ui.length > 0 ? ctx.h('div', null, ...ui) : null |
212 | | - } |
213 | | - } |
214 | | - } |
215 | | -}`, |
216 | | - |
217 | | - spawner: `const CONFIG = { |
218 | | - spawnInterval: 5, |
219 | | - maxEntities: 10, |
220 | | - entityApp: 'physics-crate' |
221 | | -} |
222 | | -
|
223 | | -export default { |
224 | | - server: { |
225 | | - setup(ctx) { |
226 | | - ctx.state.entities = new Set() |
227 | | - ctx.state.nextId = 0 |
228 | | -
|
229 | | - ctx.entity.custom = { |
230 | | - mesh: 'box', |
231 | | - color: 0x4488ff, |
232 | | - sx: 1.5, |
233 | | - sy: 1.5, |
234 | | - sz: 1.5, |
235 | | - label: 'SPAWNER' |
236 | | - } |
237 | | -
|
238 | | - ctx.time.every(CONFIG.spawnInterval, () => { |
239 | | - if (ctx.state.entities.size >= CONFIG.maxEntities) return |
240 | | -
|
241 | | - const id = \`spawned_\${ctx.state.nextId++}\` |
242 | | - const pos = [ |
243 | | - ctx.entity.position[0] + (Math.random() - 0.5) * 4, |
244 | | - ctx.entity.position[1] + 2, |
245 | | - ctx.entity.position[2] + (Math.random() - 0.5) * 4 |
246 | | - ] |
247 | | -
|
248 | | - ctx.world.spawn(id, { |
249 | | - position: pos, |
250 | | - app: CONFIG.entityApp |
251 | | - }) |
252 | | - ctx.state.entities.add(id) |
253 | | - }) |
254 | | - }, |
255 | | -
|
256 | | - onMessage(ctx, msg) { |
257 | | - if (msg.type === 'entity_destroyed') { |
258 | | - ctx.state.entities.delete(msg.entityId) |
259 | | - } |
260 | | - }, |
261 | | -
|
262 | | - teardown(ctx) { |
263 | | - ctx.state.entities.forEach(id => ctx.world.destroy(id)) |
264 | | - ctx.state.entities.clear() |
265 | | - } |
266 | | - }, |
267 | | -
|
268 | | - client: { |
269 | | - render(ctx) { |
270 | | - return { |
271 | | - position: ctx.entity.position, |
272 | | - rotation: ctx.entity.rotation, |
273 | | - custom: ctx.entity.custom |
274 | | - } |
275 | | - } |
276 | | - } |
277 | | -}` |
278 | | - } |
279 | | - |
280 | | - return templates[templateType] || templates.simple |
281 | | -} |
282 | 51 |
|
283 | 52 | function createApp(name, template) { |
284 | 53 | const appsDir = resolve('apps') |
|
0 commit comments