JavaScript · 86159 bytes Raw Blame History
1 // Item catalog for dougk shop
2 // Defines all purchasable outfits and buildings
3
4 import * as THREE from 'three'
5
6 // Outfit types
7 export const OUTFIT_TYPES = {
8 COLOR_BODY: 'color_body',
9 COLOR_ACCENT: 'color_accent',
10 ACCESSORY_HEAD: 'accessory_head',
11 ACCESSORY_FACE: 'accessory_face',
12 ACCESSORY_HELD: 'accessory_held',
13 CLOTHING_BODY: 'clothing_body' // Actual rendered clothing meshes
14 }
15
16 // Character IDs
17 export const CHARACTERS = {
18 DOUG: 'doug',
19 DONNY: 'donny',
20 OLLIE: 'ollie'
21 }
22
23 // Outfit definitions
24 export const OUTFITS = {
25 // Doug outfits - starter tier (cheap)
26 doug_mint: {
27 id: 'doug_mint',
28 name: 'Mint Fresh',
29 character: CHARACTERS.DOUG,
30 type: OUTFIT_TYPES.COLOR_BODY,
31 price: 5,
32 colors: { body: 0x98fb98, highlight: 0xb0ffb0 }
33 },
34 doug_bubblegum: {
35 id: 'doug_bubblegum',
36 name: 'Bubblegum',
37 character: CHARACTERS.DOUG,
38 type: OUTFIT_TYPES.COLOR_BODY,
39 price: 5,
40 colors: { body: 0xffb6c1, highlight: 0xffd1dc }
41 },
42 // Doug outfits - mid tier
43 doug_golden: {
44 id: 'doug_golden',
45 name: 'Golden Glow',
46 character: CHARACTERS.DOUG,
47 type: OUTFIT_TYPES.COLOR_BODY,
48 price: 12,
49 colors: { body: 0xffd700, highlight: 0xffec8b }
50 },
51 doug_sunset: {
52 id: 'doug_sunset',
53 name: 'Sunset Orange',
54 character: CHARACTERS.DOUG,
55 type: OUTFIT_TYPES.COLOR_BODY,
56 price: 12,
57 colors: { body: 0xff6b35, highlight: 0xffa07a }
58 },
59 doug_tophat: {
60 id: 'doug_tophat',
61 name: 'Top Hat',
62 character: CHARACTERS.DOUG,
63 type: OUTFIT_TYPES.ACCESSORY_HEAD,
64 price: 25,
65 meshFactory: (gradientMap) => {
66 const group = new THREE.Group()
67 const material = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
68
69 // Hat brim
70 const brimGeom = new THREE.CylinderGeometry(0.25, 0.25, 0.03, 12)
71 const brim = new THREE.Mesh(brimGeom, material)
72 group.add(brim)
73
74 // Hat top
75 const topGeom = new THREE.CylinderGeometry(0.15, 0.15, 0.25, 12)
76 const top = new THREE.Mesh(topGeom, material)
77 top.position.y = 0.14
78 group.add(top)
79
80 // Hat band
81 const bandMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
82 const bandGeom = new THREE.CylinderGeometry(0.155, 0.155, 0.04, 12)
83 const band = new THREE.Mesh(bandGeom, bandMat)
84 band.position.y = 0.04
85 group.add(band)
86
87 return group
88 }
89 },
90 doug_shades: {
91 id: 'doug_shades',
92 name: 'Cool Shades',
93 character: CHARACTERS.DOUG,
94 type: OUTFIT_TYPES.ACCESSORY_FACE,
95 price: 20,
96 meshFactory: (gradientMap) => {
97 const group = new THREE.Group()
98 const frameMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
99 const lensMat = new THREE.MeshBasicMaterial({ color: 0x222222, transparent: true, opacity: 0.7 })
100
101 // Left lens
102 const lensGeom = new THREE.CircleGeometry(0.08, 8)
103 const leftLens = new THREE.Mesh(lensGeom, lensMat)
104 leftLens.position.set(-0.1, 0, 0.01)
105 group.add(leftLens)
106
107 // Right lens
108 const rightLens = new THREE.Mesh(lensGeom, lensMat)
109 rightLens.position.set(0.1, 0, 0.01)
110 group.add(rightLens)
111
112 // Bridge
113 const bridgeGeom = new THREE.BoxGeometry(0.06, 0.02, 0.02)
114 const bridge = new THREE.Mesh(bridgeGeom, frameMat)
115 group.add(bridge)
116
117 // Frames
118 const frameGeom = new THREE.TorusGeometry(0.08, 0.01, 4, 12)
119 const leftFrame = new THREE.Mesh(frameGeom, frameMat)
120 leftFrame.position.set(-0.1, 0, 0)
121 group.add(leftFrame)
122
123 const rightFrame = new THREE.Mesh(frameGeom, frameMat)
124 rightFrame.position.set(0.1, 0, 0)
125 group.add(rightFrame)
126
127 return group
128 }
129 },
130
131 doug_aviator_goggles: {
132 id: 'doug_aviator_goggles',
133 name: 'Aviator Goggles',
134 character: CHARACTERS.DOUG,
135 type: OUTFIT_TYPES.ACCESSORY_FACE,
136 price: 35,
137 meshFactory: (gradientMap) => {
138 const group = new THREE.Group()
139 const brassMat = new THREE.MeshToonMaterial({ color: 0xb8860b, gradientMap })
140 const leatherMat = new THREE.MeshToonMaterial({ color: 0x5c4033, gradientMap })
141 const lensMat = new THREE.MeshBasicMaterial({ color: 0x4a90d9, transparent: true, opacity: 0.6 })
142
143 // Large goggle lenses (round, pilot-style)
144 const lensGeom = new THREE.CircleGeometry(0.1, 10)
145 const leftLens = new THREE.Mesh(lensGeom, lensMat)
146 leftLens.position.set(-0.12, 0, 0.02)
147 group.add(leftLens)
148
149 const rightLens = new THREE.Mesh(lensGeom, lensMat)
150 rightLens.position.set(0.12, 0, 0.02)
151 group.add(rightLens)
152
153 // Brass frames
154 const frameGeom = new THREE.TorusGeometry(0.1, 0.015, 6, 16)
155 const leftFrame = new THREE.Mesh(frameGeom, brassMat)
156 leftFrame.position.set(-0.12, 0, 0)
157 group.add(leftFrame)
158
159 const rightFrame = new THREE.Mesh(frameGeom, brassMat)
160 rightFrame.position.set(0.12, 0, 0)
161 group.add(rightFrame)
162
163 // Brass bridge with rivets
164 const bridgeGeom = new THREE.BoxGeometry(0.06, 0.025, 0.025)
165 const bridge = new THREE.Mesh(bridgeGeom, brassMat)
166 group.add(bridge)
167
168 // Rivets on bridge
169 const rivetGeom = new THREE.SphereGeometry(0.008, 4, 4)
170 const rivet1 = new THREE.Mesh(rivetGeom, brassMat)
171 rivet1.position.set(-0.02, 0, 0.015)
172 group.add(rivet1)
173
174 const rivet2 = new THREE.Mesh(rivetGeom, brassMat)
175 rivet2.position.set(0.02, 0, 0.015)
176 group.add(rivet2)
177
178 // Leather padding around lenses
179 const padGeom = new THREE.TorusGeometry(0.11, 0.02, 4, 16)
180 const leftPad = new THREE.Mesh(padGeom, leatherMat)
181 leftPad.position.set(-0.12, 0, -0.01)
182 group.add(leftPad)
183
184 const rightPad = new THREE.Mesh(padGeom, leatherMat)
185 rightPad.position.set(0.12, 0, -0.01)
186 group.add(rightPad)
187
188 // Leather strap connectors
189 const strapGeom = new THREE.BoxGeometry(0.04, 0.03, 0.015)
190 const leftStrap = new THREE.Mesh(strapGeom, leatherMat)
191 leftStrap.position.set(-0.22, 0, 0)
192 group.add(leftStrap)
193
194 const rightStrap = new THREE.Mesh(strapGeom, leatherMat)
195 rightStrap.position.set(0.22, 0, 0)
196 group.add(rightStrap)
197
198 return group
199 }
200 },
201
202 doug_propeller_beanie: {
203 id: 'doug_propeller_beanie',
204 name: 'Propeller Beanie',
205 character: CHARACTERS.DOUG,
206 type: OUTFIT_TYPES.ACCESSORY_HEAD,
207 price: 30,
208 meshFactory: (gradientMap) => {
209 const group = new THREE.Group()
210 const redMat = new THREE.MeshToonMaterial({ color: 0xdd3333, gradientMap })
211 const yellowMat = new THREE.MeshToonMaterial({ color: 0xffdd00, gradientMap })
212 const blueMat = new THREE.MeshToonMaterial({ color: 0x3366cc, gradientMap })
213
214 // Beanie cap base
215 const capGeom = new THREE.SphereGeometry(0.18, 10, 8, 0, Math.PI * 2, 0, Math.PI / 2)
216 const cap = new THREE.Mesh(capGeom, redMat)
217 group.add(cap)
218
219 // Striped panels (alternating colors)
220 for (let i = 0; i < 6; i++) {
221 const panelGeom = new THREE.SphereGeometry(0.182, 2, 8,
222 i * Math.PI / 3, Math.PI / 3, 0, Math.PI / 2)
223 const material = i % 2 === 0 ? yellowMat : blueMat
224 const panel = new THREE.Mesh(panelGeom, material)
225 group.add(panel)
226 }
227
228 // Brim
229 const brimGeom = new THREE.TorusGeometry(0.18, 0.02, 4, 16)
230 const brim = new THREE.Mesh(brimGeom, redMat)
231 brim.rotation.x = Math.PI / 2
232 brim.position.y = 0
233 group.add(brim)
234
235 // Button on top
236 const buttonGeom = new THREE.CylinderGeometry(0.03, 0.03, 0.02, 8)
237 const button = new THREE.Mesh(buttonGeom, yellowMat)
238 button.position.y = 0.17
239 group.add(button)
240
241 // Propeller mount
242 const mountGeom = new THREE.CylinderGeometry(0.015, 0.02, 0.04, 6)
243 const mount = new THREE.Mesh(mountGeom, blueMat)
244 mount.position.y = 0.19
245 group.add(mount)
246
247 // Propeller blades (will spin in animation)
248 const propGroup = new THREE.Group()
249 propGroup.userData.isPropeller = true
250 propGroup.position.y = 0.22
251
252 const bladeGeom = new THREE.BoxGeometry(0.15, 0.01, 0.03)
253 const blade1 = new THREE.Mesh(bladeGeom, redMat)
254 blade1.rotation.z = 0.1
255 propGroup.add(blade1)
256
257 const blade2 = new THREE.Mesh(bladeGeom, yellowMat)
258 blade2.rotation.y = Math.PI / 2
259 blade2.rotation.z = -0.1
260 propGroup.add(blade2)
261
262 // Center hub
263 const hubGeom = new THREE.SphereGeometry(0.02, 6, 4)
264 const hub = new THREE.Mesh(hubGeom, blueMat)
265 propGroup.add(hub)
266
267 group.add(propGroup)
268
269 return group
270 }
271 },
272
273 // === DOUG CLOTHING - Actual rendered garments ===
274
275 // Simple tier - basic clothing
276 doug_tee_red: {
277 id: 'doug_tee_red',
278 name: 'Red T-Shirt',
279 character: CHARACTERS.DOUG,
280 type: OUTFIT_TYPES.CLOTHING_BODY,
281 price: 35,
282 meshFactory: (gradientMap) => {
283 const group = new THREE.Group()
284 const fabricMat = new THREE.MeshToonMaterial({ color: 0xcc3333, gradientMap })
285
286 // Main shirt body - wraps around duck torso
287 const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8)
288 torsoGeom.scale(1.35, 0.72, 0.88)
289 const torso = new THREE.Mesh(torsoGeom, fabricMat)
290 torso.position.set(0, 0.28, 0)
291 group.add(torso)
292
293 // Collar (V-neck)
294 const collarMat = new THREE.MeshToonMaterial({ color: 0xaa2222, gradientMap })
295 const collarGeom = new THREE.TorusGeometry(0.12, 0.025, 4, 8, Math.PI)
296 const collar = new THREE.Mesh(collarGeom, collarMat)
297 collar.position.set(0.35, 0.42, 0)
298 collar.rotation.z = -Math.PI / 2
299 collar.rotation.y = Math.PI / 2
300 group.add(collar)
301
302 // Short sleeves
303 const sleeveGeom = new THREE.CylinderGeometry(0.08, 0.1, 0.12, 6)
304 const leftSleeve = new THREE.Mesh(sleeveGeom, fabricMat)
305 leftSleeve.position.set(0.05, 0.32, 0.36)
306 leftSleeve.rotation.x = Math.PI / 2
307 leftSleeve.rotation.z = 0.3
308 group.add(leftSleeve)
309
310 const rightSleeve = new THREE.Mesh(sleeveGeom, fabricMat)
311 rightSleeve.position.set(0.05, 0.32, -0.36)
312 rightSleeve.rotation.x = -Math.PI / 2
313 rightSleeve.rotation.z = 0.3
314 group.add(rightSleeve)
315
316 return group
317 }
318 },
319
320 doug_sailor_shirt: {
321 id: 'doug_sailor_shirt',
322 name: 'Sailor Stripes',
323 character: CHARACTERS.DOUG,
324 type: OUTFIT_TYPES.CLOTHING_BODY,
325 price: 55,
326 meshFactory: (gradientMap) => {
327 const group = new THREE.Group()
328 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
329 const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap })
330
331 // Main shirt body
332 const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8)
333 torsoGeom.scale(1.35, 0.72, 0.88)
334 const torso = new THREE.Mesh(torsoGeom, whiteMat)
335 torso.position.set(0, 0.28, 0)
336 group.add(torso)
337
338 // Horizontal stripes (rings around body)
339 for (let i = 0; i < 4; i++) {
340 const stripeGeom = new THREE.TorusGeometry(0.38 - i * 0.02, 0.02, 4, 16)
341 const stripe = new THREE.Mesh(stripeGeom, blueMat)
342 stripe.position.set(-0.05 + i * 0.12, 0.18 + i * 0.06, 0)
343 stripe.rotation.y = Math.PI / 2
344 stripe.rotation.z = 0.1
345 group.add(stripe)
346 }
347
348 // Sailor collar (big square back collar)
349 const collarGeom = new THREE.BoxGeometry(0.35, 0.02, 0.4)
350 const collar = new THREE.Mesh(collarGeom, blueMat)
351 collar.position.set(-0.15, 0.48, 0)
352 group.add(collar)
353
354 // Collar flaps hanging down back
355 const flapGeom = new THREE.BoxGeometry(0.18, 0.2, 0.02)
356 const leftFlap = new THREE.Mesh(flapGeom, blueMat)
357 leftFlap.position.set(-0.2, 0.38, 0.18)
358 leftFlap.rotation.x = 0.2
359 group.add(leftFlap)
360
361 const rightFlap = new THREE.Mesh(flapGeom, blueMat)
362 rightFlap.position.set(-0.2, 0.38, -0.18)
363 rightFlap.rotation.x = -0.2
364 group.add(rightFlap)
365
366 // Red neckerchief
367 const tieMat = new THREE.MeshToonMaterial({ color: 0xcc2222, gradientMap })
368 const tieGeom = new THREE.ConeGeometry(0.06, 0.15, 4)
369 const tie = new THREE.Mesh(tieGeom, tieMat)
370 tie.position.set(0.32, 0.32, 0)
371 tie.rotation.z = Math.PI
372 group.add(tie)
373
374 return group
375 }
376 },
377
378 doug_cozy_sweater: {
379 id: 'doug_cozy_sweater',
380 name: 'Cozy Sweater',
381 character: CHARACTERS.DOUG,
382 type: OUTFIT_TYPES.CLOTHING_BODY,
383 price: 70,
384 meshFactory: (gradientMap) => {
385 const group = new THREE.Group()
386 const woolMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
387 const trimMat = new THREE.MeshToonMaterial({ color: 0xdaa520, gradientMap })
388
389 // Chunky sweater body
390 const torsoGeom = new THREE.SphereGeometry(0.46, 10, 8)
391 torsoGeom.scale(1.38, 0.75, 0.92)
392 const torso = new THREE.Mesh(torsoGeom, woolMat)
393 torso.position.set(0, 0.28, 0)
394 group.add(torso)
395
396 // Ribbed collar (turtleneck)
397 const collarGeom = new THREE.CylinderGeometry(0.14, 0.16, 0.1, 8)
398 const collar = new THREE.Mesh(collarGeom, woolMat)
399 collar.position.set(0.38, 0.52, 0)
400 collar.rotation.z = -0.1
401 group.add(collar)
402
403 // Cable knit pattern (raised lines)
404 for (let i = 0; i < 3; i++) {
405 const cableGeom = new THREE.CylinderGeometry(0.015, 0.015, 0.5, 4)
406 const cable = new THREE.Mesh(cableGeom, trimMat)
407 cable.position.set(0.1, 0.28, -0.25 + i * 0.25)
408 cable.rotation.x = Math.PI / 2
409 cable.rotation.z = 0.2
410 group.add(cable)
411 }
412
413 // Long sleeves
414 const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.2, 6)
415 const leftSleeve = new THREE.Mesh(sleeveGeom, woolMat)
416 leftSleeve.position.set(-0.05, 0.30, 0.38)
417 leftSleeve.rotation.x = Math.PI / 2
418 leftSleeve.rotation.z = 0.4
419 group.add(leftSleeve)
420
421 const rightSleeve = new THREE.Mesh(sleeveGeom, woolMat)
422 rightSleeve.position.set(-0.05, 0.30, -0.38)
423 rightSleeve.rotation.x = -Math.PI / 2
424 rightSleeve.rotation.z = 0.4
425 group.add(rightSleeve)
426
427 // Bottom ribbing
428 const ribGeom = new THREE.TorusGeometry(0.42, 0.03, 4, 16)
429 const rib = new THREE.Mesh(ribGeom, trimMat)
430 rib.position.set(-0.1, 0.08, 0)
431 rib.rotation.y = Math.PI / 2
432 group.add(rib)
433
434 return group
435 }
436 },
437
438 doug_captain_jacket: {
439 id: 'doug_captain_jacket',
440 name: "Captain's Jacket",
441 character: CHARACTERS.DOUG,
442 type: OUTFIT_TYPES.CLOTHING_BODY,
443 price: 95,
444 meshFactory: (gradientMap) => {
445 const group = new THREE.Group()
446 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
447 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
448 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
449
450 // Jacket body
451 const torsoGeom = new THREE.SphereGeometry(0.45, 10, 8)
452 torsoGeom.scale(1.4, 0.76, 0.9)
453 const torso = new THREE.Mesh(torsoGeom, navyMat)
454 torso.position.set(0, 0.28, 0)
455 group.add(torso)
456
457 // Jacket front panels (double-breasted)
458 const panelGeom = new THREE.BoxGeometry(0.08, 0.35, 0.25)
459 const leftPanel = new THREE.Mesh(panelGeom, navyMat)
460 leftPanel.position.set(0.38, 0.28, 0.12)
461 group.add(leftPanel)
462
463 const rightPanel = new THREE.Mesh(panelGeom, navyMat)
464 rightPanel.position.set(0.38, 0.28, -0.12)
465 group.add(rightPanel)
466
467 // Gold buttons (double row)
468 const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4)
469 const buttonPositions = [
470 { x: 0.42, y: 0.38, z: 0.08 }, { x: 0.42, y: 0.28, z: 0.08 }, { x: 0.42, y: 0.18, z: 0.08 },
471 { x: 0.42, y: 0.38, z: -0.08 }, { x: 0.42, y: 0.28, z: -0.08 }, { x: 0.42, y: 0.18, z: -0.08 }
472 ]
473 for (const pos of buttonPositions) {
474 const button = new THREE.Mesh(buttonGeom, goldMat)
475 button.position.set(pos.x, pos.y, pos.z)
476 group.add(button)
477 }
478
479 // Epaulettes (shoulder boards)
480 const epauletteGeom = new THREE.BoxGeometry(0.08, 0.02, 0.12)
481 const leftEpaulette = new THREE.Mesh(epauletteGeom, goldMat)
482 leftEpaulette.position.set(0.1, 0.45, 0.32)
483 group.add(leftEpaulette)
484
485 const rightEpaulette = new THREE.Mesh(epauletteGeom, goldMat)
486 rightEpaulette.position.set(0.1, 0.45, -0.32)
487 group.add(rightEpaulette)
488
489 // High collar
490 const collarGeom = new THREE.CylinderGeometry(0.13, 0.15, 0.08, 8, 1, true)
491 const collar = new THREE.Mesh(collarGeom, navyMat)
492 collar.position.set(0.36, 0.50, 0)
493 collar.rotation.z = -0.1
494 group.add(collar)
495
496 // Gold collar trim
497 const collarTrimGeom = new THREE.TorusGeometry(0.14, 0.012, 4, 12)
498 const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat)
499 collarTrim.position.set(0.36, 0.54, 0)
500 collarTrim.rotation.x = Math.PI / 2
501 collarTrim.rotation.z = -0.1
502 group.add(collarTrim)
503
504 // Sleeves with gold cuff trim
505 const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.1, 0.18, 6)
506 const leftSleeve = new THREE.Mesh(sleeveGeom, navyMat)
507 leftSleeve.position.set(-0.02, 0.30, 0.38)
508 leftSleeve.rotation.x = Math.PI / 2
509 leftSleeve.rotation.z = 0.35
510 group.add(leftSleeve)
511
512 const rightSleeve = new THREE.Mesh(sleeveGeom, navyMat)
513 rightSleeve.position.set(-0.02, 0.30, -0.38)
514 rightSleeve.rotation.x = -Math.PI / 2
515 rightSleeve.rotation.z = 0.35
516 group.add(rightSleeve)
517
518 // Gold cuff rings
519 const cuffGeom = new THREE.TorusGeometry(0.095, 0.015, 4, 8)
520 const leftCuff = new THREE.Mesh(cuffGeom, goldMat)
521 leftCuff.position.set(-0.08, 0.26, 0.44)
522 leftCuff.rotation.y = Math.PI / 2 + 0.35
523 group.add(leftCuff)
524
525 const rightCuff = new THREE.Mesh(cuffGeom, goldMat)
526 rightCuff.position.set(-0.08, 0.26, -0.44)
527 rightCuff.rotation.y = Math.PI / 2 - 0.35
528 group.add(rightCuff)
529
530 return group
531 }
532 },
533
534 doug_pirate_captain: {
535 id: 'doug_pirate_captain',
536 name: 'Pirate Captain',
537 character: CHARACTERS.DOUG,
538 type: OUTFIT_TYPES.CLOTHING_BODY,
539 price: 150,
540 meshFactory: (gradientMap) => {
541 const group = new THREE.Group()
542 const coatMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
543 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
544 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
545 const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
546 const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap })
547
548 // Inner white shirt (ruffle front)
549 const shirtGeom = new THREE.SphereGeometry(0.42, 10, 8)
550 shirtGeom.scale(1.3, 0.7, 0.85)
551 const shirt = new THREE.Mesh(shirtGeom, whiteMat)
552 shirt.position.set(0, 0.28, 0)
553 group.add(shirt)
554
555 // Ruffle details on shirt front
556 for (let i = 0; i < 4; i++) {
557 const ruffleGeom = new THREE.TorusGeometry(0.06 - i * 0.008, 0.015, 4, 8, Math.PI)
558 const ruffle = new THREE.Mesh(ruffleGeom, whiteMat)
559 ruffle.position.set(0.36, 0.35 - i * 0.06, 0)
560 ruffle.rotation.y = Math.PI / 2
561 ruffle.rotation.z = -Math.PI / 2
562 group.add(ruffle)
563 }
564
565 // Long pirate coat (open front)
566 const coatLeftGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35)
567 const coatLeft = new THREE.Mesh(coatLeftGeom, coatMat)
568 coatLeft.position.set(0.15, 0.22, 0.25)
569 coatLeft.rotation.y = 0.15
570 group.add(coatLeft)
571
572 const coatRightGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35)
573 const coatRight = new THREE.Mesh(coatRightGeom, coatMat)
574 coatRight.position.set(0.15, 0.22, -0.25)
575 coatRight.rotation.y = -0.15
576 group.add(coatRight)
577
578 // Coat back
579 const coatBackGeom = new THREE.BoxGeometry(0.08, 0.5, 0.55)
580 const coatBack = new THREE.Mesh(coatBackGeom, coatMat)
581 coatBack.position.set(-0.25, 0.25, 0)
582 group.add(coatBack)
583
584 // Coat tails (flowing behind)
585 const tailGeom = new THREE.BoxGeometry(0.06, 0.25, 0.2)
586 const leftTail = new THREE.Mesh(tailGeom, coatMat)
587 leftTail.position.set(-0.35, 0.08, 0.15)
588 leftTail.rotation.x = 0.2
589 leftTail.rotation.z = 0.1
590 group.add(leftTail)
591
592 const rightTail = new THREE.Mesh(tailGeom, coatMat)
593 rightTail.position.set(-0.35, 0.08, -0.15)
594 rightTail.rotation.x = -0.2
595 rightTail.rotation.z = 0.1
596 group.add(rightTail)
597
598 // Gold trim on coat edges
599 const trimGeom = new THREE.BoxGeometry(0.02, 0.4, 0.02)
600 const trimPositions = [
601 { x: 0.22, y: 0.22, z: 0.42 }, { x: 0.22, y: 0.22, z: -0.42 },
602 { x: 0.22, y: 0.22, z: 0.08 }, { x: 0.22, y: 0.22, z: -0.08 }
603 ]
604 for (const pos of trimPositions) {
605 const trim = new THREE.Mesh(trimGeom, goldMat)
606 trim.position.set(pos.x, pos.y, pos.z)
607 group.add(trim)
608 }
609
610 // Wide leather belt
611 const beltGeom = new THREE.TorusGeometry(0.4, 0.035, 4, 16)
612 const belt = new THREE.Mesh(beltGeom, beltMat)
613 belt.position.set(0, 0.15, 0)
614 belt.rotation.y = Math.PI / 2
615 belt.rotation.z = 0.05
616 group.add(belt)
617
618 // Belt buckle (skull shape simplified)
619 const buckleGeom = new THREE.BoxGeometry(0.08, 0.08, 0.03)
620 const buckle = new THREE.Mesh(buckleGeom, goldMat)
621 buckle.position.set(0.38, 0.15, 0)
622 group.add(buckle)
623
624 // Skull detail on buckle
625 const skullGeom = new THREE.SphereGeometry(0.025, 6, 4)
626 const skull = new THREE.Mesh(skullGeom, whiteMat)
627 skull.position.set(0.40, 0.15, 0)
628 group.add(skull)
629
630 // Diagonal bandolier strap
631 const strapGeom = new THREE.BoxGeometry(0.5, 0.04, 0.03)
632 const strap = new THREE.Mesh(strapGeom, beltMat)
633 strap.position.set(0.05, 0.32, 0)
634 strap.rotation.z = 0.6
635 group.add(strap)
636
637 // Coat sleeves
638 const sleeveGeom = new THREE.CylinderGeometry(0.1, 0.11, 0.22, 6)
639 const leftSleeve = new THREE.Mesh(sleeveGeom, coatMat)
640 leftSleeve.position.set(-0.05, 0.30, 0.4)
641 leftSleeve.rotation.x = Math.PI / 2
642 leftSleeve.rotation.z = 0.4
643 group.add(leftSleeve)
644
645 const rightSleeve = new THREE.Mesh(sleeveGeom, coatMat)
646 rightSleeve.position.set(-0.05, 0.30, -0.4)
647 rightSleeve.rotation.x = -Math.PI / 2
648 rightSleeve.rotation.z = 0.4
649 group.add(rightSleeve)
650
651 // Sleeve cuff ruffles
652 const cuffGeom = new THREE.TorusGeometry(0.1, 0.02, 4, 8)
653 const leftCuff = new THREE.Mesh(cuffGeom, whiteMat)
654 leftCuff.position.set(-0.12, 0.26, 0.48)
655 leftCuff.rotation.y = Math.PI / 2 + 0.4
656 group.add(leftCuff)
657
658 const rightCuff = new THREE.Mesh(cuffGeom, whiteMat)
659 rightCuff.position.set(-0.12, 0.26, -0.48)
660 rightCuff.rotation.y = Math.PI / 2 - 0.4
661 group.add(rightCuff)
662
663 // Gold buttons on coat
664 const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
665 for (let i = 0; i < 3; i++) {
666 const leftBtn = new THREE.Mesh(buttonGeom, goldMat)
667 leftBtn.position.set(0.2, 0.35 - i * 0.08, 0.1)
668 group.add(leftBtn)
669
670 const rightBtn = new THREE.Mesh(buttonGeom, goldMat)
671 rightBtn.position.set(0.2, 0.35 - i * 0.08, -0.1)
672 group.add(rightBtn)
673 }
674
675 return group
676 }
677 },
678
679 // Doug outfits - full costumes with accessories
680 doug_chef: {
681 id: 'doug_chef',
682 name: 'Chef Doug',
683 character: CHARACTERS.DOUG,
684 type: OUTFIT_TYPES.CLOTHING_BODY,
685 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
686 price: 65,
687 meshFactory: (gradientMap) => {
688 const group = new THREE.Group()
689 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
690 const apronMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap })
691 const strapMat = new THREE.MeshToonMaterial({ color: 0xe8e8e8, gradientMap })
692
693 // Chef's toque (tall puffy hat)
694 const toqueBase = new THREE.CylinderGeometry(0.18, 0.2, 0.08, 12)
695 const toqueBaseMesh = new THREE.Mesh(toqueBase, whiteMat)
696 toqueBaseMesh.position.set(0.1, 0.75, 0)
697 group.add(toqueBaseMesh)
698
699 // Puffy top of toque (multiple spheres for volume)
700 const puffGeom = new THREE.SphereGeometry(0.14, 8, 6)
701 const puff1 = new THREE.Mesh(puffGeom, whiteMat)
702 puff1.position.set(0.1, 0.9, 0)
703 group.add(puff1)
704
705 const puff2 = new THREE.Mesh(puffGeom, whiteMat)
706 puff2.position.set(0.08, 0.98, 0.05)
707 puff2.scale.set(0.9, 0.85, 0.9)
708 group.add(puff2)
709
710 const puff3 = new THREE.Mesh(puffGeom, whiteMat)
711 puff3.position.set(0.12, 0.95, -0.05)
712 puff3.scale.set(0.85, 0.8, 0.85)
713 group.add(puff3)
714
715 // Apron body
716 const apronBodyGeom = new THREE.BoxGeometry(0.08, 0.4, 0.45)
717 const apronBody = new THREE.Mesh(apronBodyGeom, apronMat)
718 apronBody.position.set(0.38, 0.2, 0)
719 group.add(apronBody)
720
721 // Apron bib (upper part)
722 const bibGeom = new THREE.BoxGeometry(0.06, 0.2, 0.3)
723 const bib = new THREE.Mesh(bibGeom, apronMat)
724 bib.position.set(0.4, 0.45, 0)
725 group.add(bib)
726
727 // Neck strap
728 const neckStrapGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12, Math.PI)
729 const neckStrap = new THREE.Mesh(neckStrapGeom, strapMat)
730 neckStrap.position.set(0.25, 0.55, 0)
731 neckStrap.rotation.z = Math.PI
732 neckStrap.rotation.y = Math.PI / 2
733 group.add(neckStrap)
734
735 // Waist ties (hanging at sides)
736 const tieGeom = new THREE.BoxGeometry(0.2, 0.02, 0.03)
737 const leftTie = new THREE.Mesh(tieGeom, strapMat)
738 leftTie.position.set(0.25, 0.25, 0.25)
739 leftTie.rotation.y = 0.3
740 group.add(leftTie)
741
742 const rightTie = new THREE.Mesh(tieGeom, strapMat)
743 rightTie.position.set(0.25, 0.25, -0.25)
744 rightTie.rotation.y = -0.3
745 group.add(rightTie)
746
747 // Apron pocket
748 const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.15)
749 const pocket = new THREE.Mesh(pocketGeom, strapMat)
750 pocket.position.set(0.42, 0.18, 0)
751 group.add(pocket)
752
753 return group
754 }
755 },
756
757 doug_superhero: {
758 id: 'doug_superhero',
759 name: 'Super Duck',
760 character: CHARACTERS.DOUG,
761 type: OUTFIT_TYPES.CLOTHING_BODY,
762 price: 80,
763 meshFactory: (gradientMap) => {
764 const group = new THREE.Group()
765 const capeMat = new THREE.MeshToonMaterial({ color: 0xcc0000, gradientMap, side: THREE.DoubleSide })
766 const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap, side: THREE.DoubleSide })
767 const maskMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
768 const emblemMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap })
769
770 // Cape - draped from shoulders
771 const capeShape = new THREE.Shape()
772 capeShape.moveTo(-0.35, 0)
773 capeShape.lineTo(-0.4, -0.5)
774 capeShape.quadraticCurveTo(0, -0.6, 0.4, -0.5)
775 capeShape.lineTo(0.35, 0)
776 capeShape.quadraticCurveTo(0, 0.05, -0.35, 0)
777
778 const capeGeom = new THREE.ShapeGeometry(capeShape)
779 const cape = new THREE.Mesh(capeGeom, capeMat)
780 cape.position.set(-0.2, 0.4, 0)
781 cape.rotation.y = Math.PI / 2
782 cape.rotation.x = 0.2
783 group.add(cape)
784
785 // Cape inner lining (yellow)
786 const capeInner = new THREE.Mesh(capeGeom, capeInnerMat)
787 capeInner.position.set(-0.22, 0.4, 0)
788 capeInner.rotation.y = Math.PI / 2
789 capeInner.rotation.x = 0.2
790 group.add(capeInner)
791
792 // Cape clasp at neck
793 const claspGeom = new THREE.SphereGeometry(0.04, 8, 6)
794 const claspL = new THREE.Mesh(claspGeom, emblemMat)
795 claspL.position.set(0.15, 0.52, 0.18)
796 group.add(claspL)
797
798 const claspR = new THREE.Mesh(claspGeom, emblemMat)
799 claspR.position.set(0.15, 0.52, -0.18)
800 group.add(claspR)
801
802 // Domino mask
803 const maskGeom = new THREE.BoxGeometry(0.06, 0.08, 0.35)
804 const mask = new THREE.Mesh(maskGeom, maskMat)
805 mask.position.set(0.48, 0.58, 0)
806 group.add(mask)
807
808 // Eye holes in mask
809 const eyeHoleGeom = new THREE.CircleGeometry(0.04, 8)
810 const eyeHoleMat = new THREE.MeshBasicMaterial({ color: 0x000000 })
811 const eyeL = new THREE.Mesh(eyeHoleGeom, eyeHoleMat)
812 eyeL.position.set(0.52, 0.58, 0.08)
813 eyeL.rotation.y = Math.PI / 2
814 group.add(eyeL)
815
816 const eyeR = new THREE.Mesh(eyeHoleGeom, eyeHoleMat)
817 eyeR.position.set(0.52, 0.58, -0.08)
818 eyeR.rotation.y = Math.PI / 2
819 group.add(eyeR)
820
821 // Chest emblem - "D" shield
822 const shieldGeom = new THREE.CircleGeometry(0.1, 6)
823 const shield = new THREE.Mesh(shieldGeom, emblemMat)
824 shield.position.set(0.42, 0.38, 0)
825 shield.rotation.y = Math.PI / 2
826 group.add(shield)
827
828 // D letter on shield
829 const dGeom = new THREE.TorusGeometry(0.04, 0.015, 4, 8, Math.PI)
830 const dLetter = new THREE.Mesh(dGeom, capeMat)
831 dLetter.position.set(0.44, 0.38, 0)
832 dLetter.rotation.y = Math.PI / 2
833 dLetter.rotation.z = -Math.PI / 2
834 group.add(dLetter)
835
836 return group
837 }
838 },
839
840 doug_hoodie: {
841 id: 'doug_hoodie',
842 name: 'Cozy Hoodie',
843 character: CHARACTERS.DOUG,
844 type: OUTFIT_TYPES.CLOTHING_BODY,
845 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
846 price: 55,
847 meshFactory: (gradientMap) => {
848 const group = new THREE.Group()
849 const hoodieMat = new THREE.MeshToonMaterial({ color: 0x4a5568, gradientMap })
850 const hoodInnerMat = new THREE.MeshToonMaterial({ color: 0x2d3748, gradientMap })
851 const stringMat = new THREE.MeshToonMaterial({ color: 0xe2e8f0, gradientMap })
852
853 // Hoodie body
854 const bodyGeom = new THREE.SphereGeometry(0.46, 10, 8)
855 bodyGeom.scale(1.38, 0.76, 0.92)
856 const body = new THREE.Mesh(bodyGeom, hoodieMat)
857 body.position.set(0, 0.28, 0)
858 group.add(body)
859
860 // Hood (up, around head)
861 const hoodGeom = new THREE.SphereGeometry(0.28, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.6)
862 const hood = new THREE.Mesh(hoodGeom, hoodieMat)
863 hood.position.set(0.15, 0.58, 0)
864 hood.rotation.z = -0.3
865 hood.rotation.x = 0.1
866 group.add(hood)
867
868 // Hood inner lining
869 const hoodInnerGeom = new THREE.SphereGeometry(0.26, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.55)
870 const hoodInner = new THREE.Mesh(hoodInnerGeom, hoodInnerMat)
871 hoodInner.position.set(0.16, 0.58, 0)
872 hoodInner.rotation.z = -0.3
873 hoodInner.rotation.x = 0.1
874 group.add(hoodInner)
875
876 // Hoodie opening for face
877 const openingGeom = new THREE.TorusGeometry(0.18, 0.025, 6, 12)
878 const opening = new THREE.Mesh(openingGeom, hoodInnerMat)
879 opening.position.set(0.38, 0.6, 0)
880 opening.rotation.y = Math.PI / 2
881 opening.rotation.z = -0.1
882 group.add(opening)
883
884 // Drawstrings
885 const stringGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.2, 4)
886 const stringL = new THREE.Mesh(stringGeom, stringMat)
887 stringL.position.set(0.42, 0.42, 0.1)
888 stringL.rotation.z = 0.3
889 group.add(stringL)
890
891 const stringR = new THREE.Mesh(stringGeom, stringMat)
892 stringR.position.set(0.42, 0.42, -0.1)
893 stringR.rotation.z = 0.3
894 group.add(stringR)
895
896 // String tips (little plastic aglets)
897 const agletGeom = new THREE.CylinderGeometry(0.012, 0.008, 0.03, 4)
898 const agletL = new THREE.Mesh(agletGeom, stringMat)
899 agletL.position.set(0.48, 0.32, 0.1)
900 group.add(agletL)
901
902 const agletR = new THREE.Mesh(agletGeom, stringMat)
903 agletR.position.set(0.48, 0.32, -0.1)
904 group.add(agletR)
905
906 // Front pocket (kangaroo pouch)
907 const pocketGeom = new THREE.SphereGeometry(0.2, 8, 6, 0, Math.PI, 0, Math.PI)
908 pocketGeom.scale(1.2, 0.5, 0.8)
909 const pocket = new THREE.Mesh(pocketGeom, hoodInnerMat)
910 pocket.position.set(0.4, 0.15, 0)
911 pocket.rotation.y = Math.PI / 2
912 pocket.rotation.z = Math.PI
913 group.add(pocket)
914
915 // Pocket opening
916 const pocketOpenGeom = new THREE.TorusGeometry(0.12, 0.015, 4, 12, Math.PI)
917 const pocketOpen = new THREE.Mesh(pocketOpenGeom, hoodieMat)
918 pocketOpen.position.set(0.42, 0.18, 0)
919 pocketOpen.rotation.y = Math.PI / 2
920 pocketOpen.rotation.x = Math.PI / 2
921 group.add(pocketOpen)
922
923 // Sleeves
924 const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.18, 6)
925 const leftSleeve = new THREE.Mesh(sleeveGeom, hoodieMat)
926 leftSleeve.position.set(0.05, 0.3, 0.38)
927 leftSleeve.rotation.x = Math.PI / 2
928 leftSleeve.rotation.z = 0.3
929 group.add(leftSleeve)
930
931 const rightSleeve = new THREE.Mesh(sleeveGeom, hoodieMat)
932 rightSleeve.position.set(0.05, 0.3, -0.38)
933 rightSleeve.rotation.x = -Math.PI / 2
934 rightSleeve.rotation.z = 0.3
935 group.add(rightSleeve)
936
937 // Ribbed cuffs
938 const cuffGeom = new THREE.TorusGeometry(0.085, 0.02, 4, 8)
939 const leftCuff = new THREE.Mesh(cuffGeom, hoodInnerMat)
940 leftCuff.position.set(0.0, 0.28, 0.46)
941 leftCuff.rotation.y = Math.PI / 2
942 group.add(leftCuff)
943
944 const rightCuff = new THREE.Mesh(cuffGeom, hoodInnerMat)
945 rightCuff.position.set(0.0, 0.28, -0.46)
946 rightCuff.rotation.y = Math.PI / 2
947 group.add(rightCuff)
948
949 return group
950 }
951 },
952
953 // Donny outfits - starter tier
954 donny_seafoam: {
955 id: 'donny_seafoam',
956 name: 'Seafoam',
957 character: CHARACTERS.DONNY,
958 type: OUTFIT_TYPES.COLOR_BODY,
959 price: 8,
960 colors: { body: 0x5f9ea0, belly: 0x98d8d8 }
961 },
962 // Donny outfits - mid tier
963 donny_royal: {
964 id: 'donny_royal',
965 name: 'Royal Purple',
966 character: CHARACTERS.DONNY,
967 type: OUTFIT_TYPES.COLOR_BODY,
968 price: 15,
969 colors: { body: 0x6b3fa0, belly: 0x9b7bc0 }
970 },
971 donny_arctic: {
972 id: 'donny_arctic',
973 name: 'Arctic White',
974 character: CHARACTERS.DONNY,
975 type: OUTFIT_TYPES.COLOR_BODY,
976 price: 18,
977 colors: { body: 0xe8e8e8, belly: 0xffffff }
978 },
979 donny_ruby_monocle: {
980 id: 'donny_ruby_monocle',
981 name: 'Ruby Monocle',
982 character: CHARACTERS.DONNY,
983 type: OUTFIT_TYPES.ACCESSORY_FACE,
984 price: 25,
985 colors: { rim: 0xb22222, glass: 0xff6666 }
986 },
987 donny_bowler: {
988 id: 'donny_bowler',
989 name: 'Bowler Hat',
990 character: CHARACTERS.DONNY,
991 type: OUTFIT_TYPES.ACCESSORY_HEAD,
992 price: 25,
993 meshFactory: (gradientMap) => {
994 const group = new THREE.Group()
995 const material = new THREE.MeshToonMaterial({ color: 0x2f2f2f, gradientMap })
996
997 // Hat dome
998 const domeGeom = new THREE.SphereGeometry(0.15, 12, 8, 0, Math.PI * 2, 0, Math.PI / 2)
999 const dome = new THREE.Mesh(domeGeom, material)
1000 group.add(dome)
1001
1002 // Hat brim
1003 const brimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.025, 12)
1004 const brim = new THREE.Mesh(brimGeom, material)
1005 brim.position.y = -0.01
1006 group.add(brim)
1007
1008 return group
1009 }
1010 },
1011
1012 donny_bicorn: {
1013 id: 'donny_bicorn',
1014 name: "Captain's Bicorn",
1015 character: CHARACTERS.DONNY,
1016 type: OUTFIT_TYPES.ACCESSORY_HEAD,
1017 price: 40,
1018 meshFactory: (gradientMap) => {
1019 const group = new THREE.Group()
1020 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap })
1021 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1022 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1023
1024 // Main hat body (curved rectangular shape)
1025 const bodyGeom = new THREE.BoxGeometry(0.35, 0.08, 0.2)
1026 const body = new THREE.Mesh(bodyGeom, navyMat)
1027 body.position.y = 0.04
1028 group.add(body)
1029
1030 // Bicorn points (turned up sides)
1031 const pointGeom = new THREE.BoxGeometry(0.08, 0.15, 0.18)
1032 const leftPoint = new THREE.Mesh(pointGeom, navyMat)
1033 leftPoint.position.set(-0.15, 0.1, 0)
1034 leftPoint.rotation.z = 0.3
1035 group.add(leftPoint)
1036
1037 const rightPoint = new THREE.Mesh(pointGeom, navyMat)
1038 rightPoint.position.set(0.15, 0.1, 0)
1039 rightPoint.rotation.z = -0.3
1040 group.add(rightPoint)
1041
1042 // Gold trim along edges
1043 const trimGeom = new THREE.BoxGeometry(0.36, 0.015, 0.01)
1044 const frontTrim = new THREE.Mesh(trimGeom, goldMat)
1045 frontTrim.position.set(0, 0.08, 0.1)
1046 group.add(frontTrim)
1047
1048 const backTrim = new THREE.Mesh(trimGeom, goldMat)
1049 backTrim.position.set(0, 0.08, -0.1)
1050 group.add(backTrim)
1051
1052 // Cockade (rosette) decoration
1053 const cockadeGeom = new THREE.CircleGeometry(0.04, 8)
1054 const cockade = new THREE.Mesh(cockadeGeom, whiteMat)
1055 cockade.position.set(0, 0.1, 0.11)
1056 group.add(cockade)
1057
1058 // Gold button on cockade
1059 const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
1060 const button = new THREE.Mesh(buttonGeom, goldMat)
1061 button.position.set(0, 0.1, 0.12)
1062 group.add(button)
1063
1064 // Feather plume
1065 const featherGeom = new THREE.ConeGeometry(0.02, 0.15, 4)
1066 const feather = new THREE.Mesh(featherGeom, whiteMat)
1067 feather.position.set(0.02, 0.18, 0.08)
1068 feather.rotation.z = -0.3
1069 feather.rotation.x = 0.2
1070 group.add(feather)
1071
1072 return group
1073 }
1074 },
1075
1076 donny_opera_glasses: {
1077 id: 'donny_opera_glasses',
1078 name: 'Opera Glasses',
1079 character: CHARACTERS.DONNY,
1080 type: OUTFIT_TYPES.ACCESSORY_FACE,
1081 price: 45,
1082 meshFactory: (gradientMap) => {
1083 const group = new THREE.Group()
1084 const goldMat = new THREE.MeshToonMaterial({ color: 0xd4af37, gradientMap })
1085 const pearlMat = new THREE.MeshToonMaterial({ color: 0xfdf5e6, gradientMap })
1086 const lensMat = new THREE.MeshBasicMaterial({ color: 0x87ceeb, transparent: true, opacity: 0.4 })
1087
1088 // Twin barrels (binocular style)
1089 const barrelGeom = new THREE.CylinderGeometry(0.05, 0.06, 0.12, 8)
1090
1091 const leftBarrel = new THREE.Mesh(barrelGeom, pearlMat)
1092 leftBarrel.position.set(-0.07, 0, 0)
1093 leftBarrel.rotation.x = Math.PI / 2
1094 group.add(leftBarrel)
1095
1096 const rightBarrel = new THREE.Mesh(barrelGeom, pearlMat)
1097 rightBarrel.position.set(0.07, 0, 0)
1098 rightBarrel.rotation.x = Math.PI / 2
1099 group.add(rightBarrel)
1100
1101 // Lenses (front)
1102 const lensGeom = new THREE.CircleGeometry(0.05, 10)
1103 const leftLens = new THREE.Mesh(lensGeom, lensMat)
1104 leftLens.position.set(-0.07, 0, 0.07)
1105 group.add(leftLens)
1106
1107 const rightLens = new THREE.Mesh(lensGeom, lensMat)
1108 rightLens.position.set(0.07, 0, 0.07)
1109 group.add(rightLens)
1110
1111 // Gold rims
1112 const rimGeom = new THREE.TorusGeometry(0.05, 0.008, 4, 12)
1113 const leftRim = new THREE.Mesh(rimGeom, goldMat)
1114 leftRim.position.set(-0.07, 0, 0.06)
1115 group.add(leftRim)
1116
1117 const rightRim = new THREE.Mesh(rimGeom, goldMat)
1118 rightRim.position.set(0.07, 0, 0.06)
1119 group.add(rightRim)
1120
1121 // Bridge connecting barrels
1122 const bridgeGeom = new THREE.BoxGeometry(0.04, 0.02, 0.03)
1123 const bridge = new THREE.Mesh(bridgeGeom, goldMat)
1124 bridge.position.set(0, 0, 0.02)
1125 group.add(bridge)
1126
1127 // Ornate handle
1128 const handleGeom = new THREE.CylinderGeometry(0.012, 0.015, 0.15, 6)
1129 const handle = new THREE.Mesh(handleGeom, goldMat)
1130 handle.position.set(0.12, -0.06, 0)
1131 handle.rotation.z = Math.PI / 4
1132 group.add(handle)
1133
1134 // Handle end knob
1135 const knobGeom = new THREE.SphereGeometry(0.02, 6, 4)
1136 const knob = new THREE.Mesh(knobGeom, goldMat)
1137 knob.position.set(0.18, -0.12, 0)
1138 group.add(knob)
1139
1140 // Decorative filigree on barrels
1141 const filigreeGeom = new THREE.TorusGeometry(0.055, 0.005, 3, 12)
1142 const leftFiligree = new THREE.Mesh(filigreeGeom, goldMat)
1143 leftFiligree.position.set(-0.07, 0, 0)
1144 group.add(leftFiligree)
1145
1146 const rightFiligree = new THREE.Mesh(filigreeGeom, goldMat)
1147 rightFiligree.position.set(0.07, 0, 0)
1148 group.add(rightFiligree)
1149
1150 return group
1151 }
1152 },
1153
1154 // === DONNY CLOTHING - Narwhal garments ===
1155
1156 donny_sailor_vest: {
1157 id: 'donny_sailor_vest',
1158 name: 'Sailor Vest',
1159 character: CHARACTERS.DONNY,
1160 type: OUTFIT_TYPES.CLOTHING_BODY,
1161 price: 45,
1162 meshFactory: (gradientMap) => {
1163 const group = new THREE.Group()
1164 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1165 const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap })
1166
1167 // Vest body - fits narwhal's torpedo shape
1168 const vestGeom = new THREE.CylinderGeometry(0.38, 0.42, 0.6, 10)
1169 const vest = new THREE.Mesh(vestGeom, whiteMat)
1170 vest.position.set(0, 0.15, 0)
1171 vest.rotation.x = Math.PI / 2
1172 group.add(vest)
1173
1174 // Blue stripes on vest
1175 for (let i = 0; i < 3; i++) {
1176 const stripeGeom = new THREE.TorusGeometry(0.39 + i * 0.01, 0.02, 4, 16)
1177 const stripe = new THREE.Mesh(stripeGeom, blueMat)
1178 stripe.position.set(0, -0.1 + i * 0.15, 0)
1179 stripe.rotation.x = Math.PI / 2
1180 group.add(stripe)
1181 }
1182
1183 // Collar
1184 const collarGeom = new THREE.TorusGeometry(0.36, 0.04, 4, 12, Math.PI)
1185 const collar = new THREE.Mesh(collarGeom, blueMat)
1186 collar.position.set(0, 0.42, 0.1)
1187 collar.rotation.x = Math.PI / 2 + 0.3
1188 group.add(collar)
1189
1190 return group
1191 }
1192 },
1193
1194 donny_admiral_coat: {
1195 id: 'donny_admiral_coat',
1196 name: 'Admiral Coat',
1197 character: CHARACTERS.DONNY,
1198 type: OUTFIT_TYPES.CLOTHING_BODY,
1199 price: 100,
1200 meshFactory: (gradientMap) => {
1201 const group = new THREE.Group()
1202 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
1203 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1204 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1205
1206 // Main coat body
1207 const coatGeom = new THREE.CylinderGeometry(0.4, 0.45, 0.7, 10)
1208 const coat = new THREE.Mesh(coatGeom, navyMat)
1209 coat.position.set(0, 0.12, 0)
1210 coat.rotation.x = Math.PI / 2
1211 group.add(coat)
1212
1213 // High collar
1214 const collarGeom = new THREE.CylinderGeometry(0.38, 0.35, 0.12, 10, 1, true)
1215 const collar = new THREE.Mesh(collarGeom, navyMat)
1216 collar.position.set(0, 0.48, 0)
1217 collar.rotation.x = Math.PI / 2
1218 group.add(collar)
1219
1220 // Gold collar trim
1221 const collarTrimGeom = new THREE.TorusGeometry(0.36, 0.015, 4, 12)
1222 const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat)
1223 collarTrim.position.set(0, 0.54, 0)
1224 collarTrim.rotation.x = Math.PI / 2
1225 group.add(collarTrim)
1226
1227 // Epaulettes
1228 const epGeom = new THREE.BoxGeometry(0.15, 0.03, 0.1)
1229 const leftEp = new THREE.Mesh(epGeom, goldMat)
1230 leftEp.position.set(0.35, 0.35, 0.2)
1231 leftEp.rotation.z = 0.3
1232 group.add(leftEp)
1233
1234 const rightEp = new THREE.Mesh(epGeom, goldMat)
1235 rightEp.position.set(0.35, 0.35, -0.2)
1236 rightEp.rotation.z = 0.3
1237 group.add(rightEp)
1238
1239 // Gold buttons (row down front)
1240 const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4)
1241 for (let i = 0; i < 4; i++) {
1242 const button = new THREE.Mesh(buttonGeom, goldMat)
1243 button.position.set(0.1 - i * 0.12, 0.42, 0)
1244 group.add(button)
1245 }
1246
1247 // Medals/decorations
1248 const medalGeom = new THREE.CircleGeometry(0.04, 6)
1249 const medal1 = new THREE.Mesh(medalGeom, goldMat)
1250 medal1.position.set(0.25, 0.38, 0.15)
1251 medal1.rotation.y = -0.5
1252 group.add(medal1)
1253
1254 const medal2 = new THREE.Mesh(medalGeom, goldMat)
1255 medal2.position.set(0.2, 0.38, 0.12)
1256 medal2.rotation.y = -0.5
1257 group.add(medal2)
1258
1259 return group
1260 }
1261 },
1262
1263 donny_pirate_vest: {
1264 id: 'donny_pirate_vest',
1265 name: 'Pirate Vest',
1266 character: CHARACTERS.DONNY,
1267 type: OUTFIT_TYPES.CLOTHING_BODY,
1268 price: 130,
1269 meshFactory: (gradientMap) => {
1270 const group = new THREE.Group()
1271 const redMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
1272 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1273 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1274 const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap })
1275 const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
1276
1277 // Inner shirt
1278 const shirtGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.55, 10)
1279 const shirt = new THREE.Mesh(shirtGeom, whiteMat)
1280 shirt.position.set(0, 0.15, 0)
1281 shirt.rotation.x = Math.PI / 2
1282 group.add(shirt)
1283
1284 // Vest panels (open front)
1285 const panelGeom = new THREE.BoxGeometry(0.4, 0.08, 0.25)
1286 const leftPanel = new THREE.Mesh(panelGeom, redMat)
1287 leftPanel.position.set(0.1, 0.3, 0.2)
1288 leftPanel.rotation.z = 0.2
1289 group.add(leftPanel)
1290
1291 const rightPanel = new THREE.Mesh(panelGeom, redMat)
1292 rightPanel.position.set(0.1, 0.3, -0.2)
1293 rightPanel.rotation.z = 0.2
1294 group.add(rightPanel)
1295
1296 // Vest back
1297 const backGeom = new THREE.BoxGeometry(0.5, 0.08, 0.35)
1298 const back = new THREE.Mesh(backGeom, redMat)
1299 back.position.set(-0.15, 0.2, 0)
1300 group.add(back)
1301
1302 // Gold trim on vest
1303 const trimGeom = new THREE.BoxGeometry(0.35, 0.02, 0.02)
1304 const leftTrim = new THREE.Mesh(trimGeom, goldMat)
1305 leftTrim.position.set(0.12, 0.32, 0.32)
1306 leftTrim.rotation.z = 0.2
1307 group.add(leftTrim)
1308
1309 const rightTrim = new THREE.Mesh(trimGeom, goldMat)
1310 rightTrim.position.set(0.12, 0.32, -0.32)
1311 rightTrim.rotation.z = 0.2
1312 group.add(rightTrim)
1313
1314 // Wide belt with skull buckle
1315 const beltGeom = new THREE.TorusGeometry(0.4, 0.05, 4, 16)
1316 const belt = new THREE.Mesh(beltGeom, beltMat)
1317 belt.position.set(0, -0.1, 0)
1318 belt.rotation.x = Math.PI / 2
1319 group.add(belt)
1320
1321 // Buckle
1322 const buckleGeom = new THREE.BoxGeometry(0.1, 0.08, 0.04)
1323 const buckle = new THREE.Mesh(buckleGeom, goldMat)
1324 buckle.position.set(0.38, -0.1, 0)
1325 group.add(buckle)
1326
1327 // Skull on buckle
1328 const skullGeom = new THREE.SphereGeometry(0.03, 6, 4)
1329 const skull = new THREE.Mesh(skullGeom, whiteMat)
1330 skull.position.set(0.40, -0.1, 0)
1331 group.add(skull)
1332
1333 // Bandolier strap
1334 const strapGeom = new THREE.BoxGeometry(0.6, 0.04, 0.03)
1335 const strap = new THREE.Mesh(strapGeom, beltMat)
1336 strap.position.set(0, 0.2, 0)
1337 strap.rotation.z = 0.5
1338 strap.rotation.y = Math.PI / 2
1339 group.add(strap)
1340
1341 return group
1342 }
1343 },
1344
1345 // Donny outfits - full costumes
1346 donny_tuxedo: {
1347 id: 'donny_tuxedo',
1348 name: 'Tuxedo',
1349 character: CHARACTERS.DONNY,
1350 type: OUTFIT_TYPES.CLOTHING_BODY,
1351 price: 90,
1352 meshFactory: (gradientMap) => {
1353 const group = new THREE.Group()
1354 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1355 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1356 const bowMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1357
1358 // White shirt front
1359 const shirtGeom = new THREE.CylinderGeometry(0.32, 0.36, 0.5, 10, 1, false, -Math.PI * 0.3, Math.PI * 0.6)
1360 const shirt = new THREE.Mesh(shirtGeom, whiteMat)
1361 shirt.position.set(0, 0.2, 0.15)
1362 shirt.rotation.x = Math.PI / 2
1363 group.add(shirt)
1364
1365 // Tuxedo jacket body
1366 const jacketGeom = new THREE.CylinderGeometry(0.38, 0.44, 0.65, 10)
1367 const jacket = new THREE.Mesh(jacketGeom, blackMat)
1368 jacket.position.set(0, 0.15, 0)
1369 jacket.rotation.x = Math.PI / 2
1370 group.add(jacket)
1371
1372 // Jacket lapels (satin)
1373 const lapelGeom = new THREE.BoxGeometry(0.35, 0.04, 0.12)
1374 const leftLapel = new THREE.Mesh(lapelGeom, blackMat)
1375 leftLapel.position.set(0.08, 0.4, 0.2)
1376 leftLapel.rotation.z = 0.3
1377 leftLapel.rotation.y = 0.2
1378 group.add(leftLapel)
1379
1380 const rightLapel = new THREE.Mesh(lapelGeom, blackMat)
1381 rightLapel.position.set(0.08, 0.4, -0.2)
1382 rightLapel.rotation.z = 0.3
1383 rightLapel.rotation.y = -0.2
1384 group.add(rightLapel)
1385
1386 // Bow tie
1387 const bowWingGeom = new THREE.BoxGeometry(0.08, 0.04, 0.03)
1388 const bowL = new THREE.Mesh(bowWingGeom, bowMat)
1389 bowL.position.set(0.05, 0.52, 0.06)
1390 bowL.rotation.z = 0.2
1391 group.add(bowL)
1392
1393 const bowR = new THREE.Mesh(bowWingGeom, bowMat)
1394 bowR.position.set(0.05, 0.52, -0.06)
1395 bowR.rotation.z = -0.2
1396 group.add(bowR)
1397
1398 const bowKnotGeom = new THREE.SphereGeometry(0.025, 6, 4)
1399 const bowKnot = new THREE.Mesh(bowKnotGeom, bowMat)
1400 bowKnot.position.set(0.05, 0.52, 0)
1401 group.add(bowKnot)
1402
1403 // Jacket tails (back)
1404 const tailGeom = new THREE.BoxGeometry(0.3, 0.06, 0.15)
1405 const leftTail = new THREE.Mesh(tailGeom, blackMat)
1406 leftTail.position.set(-0.25, -0.05, 0.12)
1407 leftTail.rotation.x = -0.3
1408 group.add(leftTail)
1409
1410 const rightTail = new THREE.Mesh(tailGeom, blackMat)
1411 rightTail.position.set(-0.25, -0.05, -0.12)
1412 rightTail.rotation.x = 0.3
1413 group.add(rightTail)
1414
1415 // Shirt buttons
1416 const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
1417 for (let i = 0; i < 3; i++) {
1418 const btn = new THREE.Mesh(buttonGeom, blackMat)
1419 btn.position.set(0.08, 0.35 - i * 0.1, 0.28)
1420 group.add(btn)
1421 }
1422
1423 return group
1424 }
1425 },
1426
1427 donny_captain: {
1428 id: 'donny_captain',
1429 name: 'Sea Captain',
1430 character: CHARACTERS.DONNY,
1431 type: OUTFIT_TYPES.CLOTHING_BODY,
1432 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
1433 price: 110,
1434 meshFactory: (gradientMap) => {
1435 const group = new THREE.Group()
1436 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
1437 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1438 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1439 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1440
1441 // Captain's coat
1442 const coatGeom = new THREE.CylinderGeometry(0.4, 0.46, 0.7, 10)
1443 const coat = new THREE.Mesh(coatGeom, navyMat)
1444 coat.position.set(0, 0.12, 0)
1445 coat.rotation.x = Math.PI / 2
1446 group.add(coat)
1447
1448 // High collar
1449 const collarGeom = new THREE.CylinderGeometry(0.36, 0.34, 0.1, 10, 1, true)
1450 const collar = new THREE.Mesh(collarGeom, navyMat)
1451 collar.position.set(0, 0.5, 0)
1452 collar.rotation.x = Math.PI / 2
1453 group.add(collar)
1454
1455 // Gold collar trim
1456 const trimGeom = new THREE.TorusGeometry(0.35, 0.015, 4, 12)
1457 const trim = new THREE.Mesh(trimGeom, goldMat)
1458 trim.position.set(0, 0.55, 0)
1459 trim.rotation.x = Math.PI / 2
1460 group.add(trim)
1461
1462 // Epaulettes
1463 const epGeom = new THREE.BoxGeometry(0.12, 0.03, 0.1)
1464 const epL = new THREE.Mesh(epGeom, goldMat)
1465 epL.position.set(0, 0.45, 0.38)
1466 group.add(epL)
1467
1468 const epR = new THREE.Mesh(epGeom, goldMat)
1469 epR.position.set(0, 0.45, -0.38)
1470 group.add(epR)
1471
1472 // Gold fringe on epaulettes
1473 for (let i = 0; i < 3; i++) {
1474 const fringeGeom = new THREE.CylinderGeometry(0.008, 0.005, 0.06, 4)
1475 const fringeL = new THREE.Mesh(fringeGeom, goldMat)
1476 fringeL.position.set(-0.02, 0.42, 0.35 + i * 0.03)
1477 group.add(fringeL)
1478
1479 const fringeR = new THREE.Mesh(fringeGeom, goldMat)
1480 fringeR.position.set(-0.02, 0.42, -0.35 - i * 0.03)
1481 group.add(fringeR)
1482 }
1483
1484 // Gold buttons
1485 const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
1486 for (let i = 0; i < 4; i++) {
1487 const btn = new THREE.Mesh(buttonGeom, goldMat)
1488 btn.position.set(0.08, 0.35 - i * 0.1, 0.15)
1489 group.add(btn)
1490 }
1491
1492 // Captain's peaked cap
1493 const capBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12)
1494 const capBrim = new THREE.Mesh(capBrimGeom, blackMat)
1495 capBrim.position.set(0.2, 0.7, 0)
1496 capBrim.rotation.z = -0.2
1497 group.add(capBrim)
1498
1499 // Cap crown
1500 const capCrownGeom = new THREE.CylinderGeometry(0.18, 0.2, 0.12, 12)
1501 const capCrown = new THREE.Mesh(capCrownGeom, navyMat)
1502 capCrown.position.set(0.18, 0.78, 0)
1503 capCrown.rotation.z = -0.2
1504 group.add(capCrown)
1505
1506 // Cap badge
1507 const badgeGeom = new THREE.CircleGeometry(0.06, 8)
1508 const badge = new THREE.Mesh(badgeGeom, goldMat)
1509 badge.position.set(0.32, 0.78, 0)
1510 badge.rotation.y = Math.PI / 2
1511 badge.rotation.z = -0.2
1512 group.add(badge)
1513
1514 // Anchor on badge
1515 const anchorGeom = new THREE.TorusGeometry(0.025, 0.006, 4, 8, Math.PI)
1516 const anchor = new THREE.Mesh(anchorGeom, whiteMat)
1517 anchor.position.set(0.34, 0.78, 0)
1518 anchor.rotation.y = Math.PI / 2
1519 anchor.rotation.x = Math.PI
1520 group.add(anchor)
1521
1522 return group
1523 }
1524 },
1525
1526 donny_magician: {
1527 id: 'donny_magician',
1528 name: 'Magician',
1529 character: CHARACTERS.DONNY,
1530 type: OUTFIT_TYPES.CLOTHING_BODY,
1531 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
1532 price: 95,
1533 meshFactory: (gradientMap) => {
1534 const group = new THREE.Group()
1535 const capeMat = new THREE.MeshToonMaterial({ color: 0x2d0a4e, gradientMap, side: THREE.DoubleSide })
1536 const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap, side: THREE.DoubleSide })
1537 const hatMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1538 const starMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1539 const vestMat = new THREE.MeshToonMaterial({ color: 0x4a0080, gradientMap })
1540
1541 // Vest underneath
1542 const vestGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.5, 10)
1543 const vest = new THREE.Mesh(vestGeom, vestMat)
1544 vest.position.set(0, 0.18, 0)
1545 vest.rotation.x = Math.PI / 2
1546 group.add(vest)
1547
1548 // Cape (draped around shoulders)
1549 const capeShape = new THREE.Shape()
1550 capeShape.moveTo(-0.4, 0)
1551 capeShape.lineTo(-0.45, -0.55)
1552 capeShape.quadraticCurveTo(0, -0.65, 0.45, -0.55)
1553 capeShape.lineTo(0.4, 0)
1554 capeShape.quadraticCurveTo(0, 0.05, -0.4, 0)
1555
1556 const capeGeom = new THREE.ShapeGeometry(capeShape)
1557 const cape = new THREE.Mesh(capeGeom, capeMat)
1558 cape.position.set(-0.15, 0.35, 0)
1559 cape.rotation.y = Math.PI / 2
1560 group.add(cape)
1561
1562 // Cape inner (gold lining)
1563 const capeInner = new THREE.Mesh(capeGeom, capeInnerMat)
1564 capeInner.position.set(-0.17, 0.35, 0)
1565 capeInner.rotation.y = Math.PI / 2
1566 group.add(capeInner)
1567
1568 // Stars on cape
1569 const starPositions = [
1570 { x: -0.2, y: 0.25, z: 0.2 },
1571 { x: -0.25, y: 0.1, z: -0.15 },
1572 { x: -0.22, y: 0.0, z: 0.25 }
1573 ]
1574 for (const pos of starPositions) {
1575 const starGeom = new THREE.CircleGeometry(0.03, 5)
1576 const star = new THREE.Mesh(starGeom, starMat)
1577 star.position.set(pos.x, pos.y, pos.z)
1578 star.rotation.y = Math.PI / 2
1579 star.rotation.z = Math.PI / 10
1580 group.add(star)
1581 }
1582
1583 // Cape clasp (gem)
1584 const claspGeom = new THREE.OctahedronGeometry(0.04)
1585 const claspMat = new THREE.MeshToonMaterial({ color: 0xff0066, gradientMap })
1586 const clasp = new THREE.Mesh(claspGeom, claspMat)
1587 clasp.position.set(0.08, 0.5, 0)
1588 group.add(clasp)
1589
1590 // Top hat
1591 const hatBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12)
1592 const hatBrim = new THREE.Mesh(hatBrimGeom, hatMat)
1593 hatBrim.position.set(0.15, 0.68, 0)
1594 hatBrim.rotation.z = -0.15
1595 group.add(hatBrim)
1596
1597 // Hat crown (tall)
1598 const hatCrownGeom = new THREE.CylinderGeometry(0.14, 0.14, 0.25, 12)
1599 const hatCrown = new THREE.Mesh(hatCrownGeom, hatMat)
1600 hatCrown.position.set(0.13, 0.82, 0)
1601 hatCrown.rotation.z = -0.15
1602 group.add(hatCrown)
1603
1604 // Hat band
1605 const bandGeom = new THREE.TorusGeometry(0.14, 0.015, 4, 12)
1606 const band = new THREE.Mesh(bandGeom, starMat)
1607 band.position.set(0.14, 0.72, 0)
1608 band.rotation.x = Math.PI / 2
1609 band.rotation.y = 0.15
1610 group.add(band)
1611
1612 // Star on hat
1613 const hatStarGeom = new THREE.CircleGeometry(0.035, 5)
1614 const hatStar = new THREE.Mesh(hatStarGeom, starMat)
1615 hatStar.position.set(0.27, 0.82, 0)
1616 hatStar.rotation.y = Math.PI / 2
1617 hatStar.rotation.z = Math.PI / 10
1618 group.add(hatStar)
1619
1620 return group
1621 }
1622 },
1623
1624 // Ollie outfits
1625 ollie_coral: {
1626 id: 'ollie_coral',
1627 name: 'Coral Pink',
1628 character: CHARACTERS.OLLIE,
1629 type: OUTFIT_TYPES.COLOR_BODY,
1630 price: 20,
1631 colors: { body: 0xff7f7f, belly: 0xffb3b3, suckers: 0xffcccc }
1632 },
1633 ollie_deepsea: {
1634 id: 'ollie_deepsea',
1635 name: 'Deep Sea Blue',
1636 character: CHARACTERS.OLLIE,
1637 type: OUTFIT_TYPES.COLOR_BODY,
1638 price: 20,
1639 colors: { body: 0x1e3a5f, belly: 0x4a6fa5, suckers: 0x6b8cae }
1640 },
1641 ollie_golden_mag: {
1642 id: 'ollie_golden_mag',
1643 name: 'Golden Magnifier',
1644 character: CHARACTERS.OLLIE,
1645 type: OUTFIT_TYPES.ACCESSORY_HELD,
1646 price: 30,
1647 colors: { rim: 0xffd700, glass: 0xffffcc }
1648 },
1649 ollie_detective: {
1650 id: 'ollie_detective',
1651 name: 'Detective Cap',
1652 character: CHARACTERS.OLLIE,
1653 type: OUTFIT_TYPES.ACCESSORY_HEAD,
1654 price: 25,
1655 meshFactory: (gradientMap) => {
1656 const group = new THREE.Group()
1657 const material = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
1658
1659 // Cap body
1660 const capGeom = new THREE.SphereGeometry(0.18, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2)
1661 const cap = new THREE.Mesh(capGeom, material)
1662 cap.scale.y = 0.5
1663 group.add(cap)
1664
1665 // Front brim
1666 const brimGeom = new THREE.CylinderGeometry(0.12, 0.15, 0.02, 8, 1, false, -Math.PI/3, Math.PI * 2/3)
1667 const brim = new THREE.Mesh(brimGeom, material)
1668 brim.position.set(0.12, -0.02, 0)
1669 brim.rotation.z = -0.3
1670 group.add(brim)
1671
1672 return group
1673 }
1674 },
1675
1676 ollie_jeweler_loupe: {
1677 id: 'ollie_jeweler_loupe',
1678 name: "Jeweler's Loupe",
1679 character: CHARACTERS.OLLIE,
1680 type: OUTFIT_TYPES.ACCESSORY_FACE,
1681 price: 35,
1682 meshFactory: (gradientMap) => {
1683 const group = new THREE.Group()
1684 const brassMat = new THREE.MeshToonMaterial({ color: 0xb8860b, gradientMap })
1685 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1686 const lensMat = new THREE.MeshBasicMaterial({ color: 0xaaffaa, transparent: true, opacity: 0.5 })
1687
1688 // Main loupe barrel
1689 const barrelGeom = new THREE.CylinderGeometry(0.06, 0.08, 0.1, 10)
1690 const barrel = new THREE.Mesh(barrelGeom, blackMat)
1691 barrel.rotation.x = Math.PI / 2
1692 barrel.position.z = 0.03
1693 group.add(barrel)
1694
1695 // Lens (front)
1696 const lensGeom = new THREE.CircleGeometry(0.06, 12)
1697 const lens = new THREE.Mesh(lensGeom, lensMat)
1698 lens.position.z = 0.09
1699 group.add(lens)
1700
1701 // Brass rim
1702 const rimGeom = new THREE.TorusGeometry(0.06, 0.01, 4, 16)
1703 const rim = new THREE.Mesh(rimGeom, brassMat)
1704 rim.position.z = 0.08
1705 group.add(rim)
1706
1707 // Eyepiece (back)
1708 const eyepieceGeom = new THREE.CylinderGeometry(0.05, 0.06, 0.03, 10)
1709 const eyepiece = new THREE.Mesh(eyepieceGeom, brassMat)
1710 eyepiece.rotation.x = Math.PI / 2
1711 eyepiece.position.z = -0.03
1712 group.add(eyepiece)
1713
1714 // Headband attachment (wraps around head)
1715 const bandGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 16, Math.PI)
1716 const band = new THREE.Mesh(bandGeom, blackMat)
1717 band.position.set(0, 0.05, -0.05)
1718 band.rotation.x = Math.PI / 2
1719 band.rotation.z = Math.PI / 2
1720 group.add(band)
1721
1722 // Adjustment knob
1723 const knobGeom = new THREE.CylinderGeometry(0.015, 0.02, 0.03, 6)
1724 const knob = new THREE.Mesh(knobGeom, brassMat)
1725 knob.position.set(0.07, 0, 0.02)
1726 knob.rotation.z = Math.PI / 2
1727 group.add(knob)
1728
1729 // Knob ridges
1730 for (let i = 0; i < 6; i++) {
1731 const ridgeGeom = new THREE.BoxGeometry(0.002, 0.025, 0.005)
1732 const ridge = new THREE.Mesh(ridgeGeom, brassMat)
1733 const angle = (i / 6) * Math.PI * 2
1734 ridge.position.set(0.085, Math.sin(angle) * 0.015, 0.02 + Math.cos(angle) * 0.015)
1735 group.add(ridge)
1736 }
1737
1738 return group
1739 }
1740 },
1741
1742 ollie_wizard_hat: {
1743 id: 'ollie_wizard_hat',
1744 name: 'Wizard Hat',
1745 character: CHARACTERS.OLLIE,
1746 type: OUTFIT_TYPES.ACCESSORY_HEAD,
1747 price: 45,
1748 meshFactory: (gradientMap) => {
1749 const group = new THREE.Group()
1750 const purpleMat = new THREE.MeshToonMaterial({ color: 0x4b0082, gradientMap })
1751 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1752 const silverMat = new THREE.MeshToonMaterial({ color: 0xc0c0c0, gradientMap })
1753
1754 // Main cone
1755 const coneGeom = new THREE.ConeGeometry(0.2, 0.45, 8)
1756 const cone = new THREE.Mesh(coneGeom, purpleMat)
1757 cone.position.y = 0.22
1758 group.add(cone)
1759
1760 // Bent tip (wizards hats droop!)
1761 const tipGeom = new THREE.ConeGeometry(0.05, 0.12, 6)
1762 const tip = new THREE.Mesh(tipGeom, purpleMat)
1763 tip.position.set(0.08, 0.42, 0.05)
1764 tip.rotation.z = -0.8
1765 tip.rotation.x = 0.3
1766 group.add(tip)
1767
1768 // Wide brim
1769 const brimGeom = new THREE.CylinderGeometry(0.28, 0.3, 0.03, 12)
1770 const brim = new THREE.Mesh(brimGeom, purpleMat)
1771 brim.position.y = 0
1772 group.add(brim)
1773
1774 // Gold band at base
1775 const bandGeom = new THREE.TorusGeometry(0.2, 0.02, 4, 16)
1776 const band = new THREE.Mesh(bandGeom, goldMat)
1777 band.position.y = 0.02
1778 band.rotation.x = Math.PI / 2
1779 group.add(band)
1780
1781 // Stars on hat (scattered)
1782 const starPositions = [
1783 { x: 0.12, y: 0.25, z: 0.1, scale: 0.8 },
1784 { x: -0.08, y: 0.35, z: 0.12, scale: 0.6 },
1785 { x: 0.05, y: 0.15, z: 0.15, scale: 0.5 },
1786 { x: -0.1, y: 0.2, z: -0.1, scale: 0.7 },
1787 { x: 0.08, y: 0.38, z: -0.05, scale: 0.5 }
1788 ]
1789
1790 for (const star of starPositions) {
1791 // Simple 4-point star shape
1792 const starGroup = new THREE.Group()
1793
1794 const point1Geom = new THREE.ConeGeometry(0.015, 0.04, 4)
1795 const point1 = new THREE.Mesh(point1Geom, silverMat)
1796 starGroup.add(point1)
1797
1798 const point2 = new THREE.Mesh(point1Geom, silverMat)
1799 point2.rotation.z = Math.PI
1800 starGroup.add(point2)
1801
1802 const point3 = new THREE.Mesh(point1Geom, silverMat)
1803 point3.rotation.z = Math.PI / 2
1804 starGroup.add(point3)
1805
1806 const point4 = new THREE.Mesh(point1Geom, silverMat)
1807 point4.rotation.z = -Math.PI / 2
1808 starGroup.add(point4)
1809
1810 starGroup.position.set(star.x, star.y, star.z)
1811 starGroup.scale.setScalar(star.scale)
1812 group.add(starGroup)
1813 }
1814
1815 // Moon crescent
1816 const moonGeom = new THREE.TorusGeometry(0.03, 0.01, 4, 12, Math.PI * 1.5)
1817 const moon = new THREE.Mesh(moonGeom, goldMat)
1818 moon.position.set(-0.12, 0.3, 0.08)
1819 moon.rotation.z = 0.5
1820 group.add(moon)
1821
1822 return group
1823 }
1824 },
1825
1826 // === OLLIE CLOTHING - Octopus garments ===
1827
1828 ollie_bowtie: {
1829 id: 'ollie_bowtie',
1830 name: 'Fancy Bow Tie',
1831 character: CHARACTERS.OLLIE,
1832 type: OUTFIT_TYPES.CLOTHING_BODY,
1833 price: 40,
1834 meshFactory: (gradientMap) => {
1835 const group = new THREE.Group()
1836 const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
1837 const knotMat = new THREE.MeshToonMaterial({ color: 0x660000, gradientMap })
1838
1839 // Bow tie wings
1840 const wingGeom = new THREE.BoxGeometry(0.12, 0.06, 0.04)
1841 const leftWing = new THREE.Mesh(wingGeom, silkMat)
1842 leftWing.position.set(0, 0.65, 0.08)
1843 leftWing.rotation.z = 0.2
1844 group.add(leftWing)
1845
1846 const rightWing = new THREE.Mesh(wingGeom, silkMat)
1847 rightWing.position.set(0, 0.65, -0.08)
1848 rightWing.rotation.z = -0.2
1849 group.add(rightWing)
1850
1851 // Center knot
1852 const knotGeom = new THREE.SphereGeometry(0.03, 6, 4)
1853 const knot = new THREE.Mesh(knotGeom, knotMat)
1854 knot.position.set(0, 0.65, 0)
1855 knot.scale.set(1, 1, 1.5)
1856 group.add(knot)
1857
1858 // Small dangling ribbon ends
1859 const ribbonGeom = new THREE.BoxGeometry(0.02, 0.08, 0.03)
1860 const ribbon1 = new THREE.Mesh(ribbonGeom, silkMat)
1861 ribbon1.position.set(0, 0.60, 0.02)
1862 ribbon1.rotation.z = 0.1
1863 group.add(ribbon1)
1864
1865 const ribbon2 = new THREE.Mesh(ribbonGeom, silkMat)
1866 ribbon2.position.set(0, 0.60, -0.02)
1867 ribbon2.rotation.z = -0.1
1868 group.add(ribbon2)
1869
1870 return group
1871 }
1872 },
1873
1874 ollie_dapper_vest: {
1875 id: 'ollie_dapper_vest',
1876 name: 'Dapper Vest',
1877 character: CHARACTERS.OLLIE,
1878 type: OUTFIT_TYPES.CLOTHING_BODY,
1879 price: 75,
1880 meshFactory: (gradientMap) => {
1881 const group = new THREE.Group()
1882 const vestMat = new THREE.MeshToonMaterial({ color: 0x4a4a4a, gradientMap })
1883 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1884 const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
1885
1886 // Vest body - wraps around octopus mantle
1887 const vestGeom = new THREE.SphereGeometry(0.42, 10, 8)
1888 vestGeom.scale(1, 0.8, 1)
1889 const vest = new THREE.Mesh(vestGeom, vestMat)
1890 vest.position.set(0, 0.35, 0)
1891 group.add(vest)
1892
1893 // Vest front opening (shows body)
1894 const openingGeom = new THREE.PlaneGeometry(0.15, 0.4)
1895 const openingMat = new THREE.MeshBasicMaterial({ color: 0xe07850, transparent: true, opacity: 0.3 })
1896 const opening = new THREE.Mesh(openingGeom, openingMat)
1897 opening.position.set(0.42, 0.35, 0)
1898 opening.rotation.y = Math.PI / 2
1899 group.add(opening)
1900
1901 // Lapels
1902 const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.05)
1903 const leftLapel = new THREE.Mesh(lapelGeom, silkMat)
1904 leftLapel.position.set(0.4, 0.45, 0.1)
1905 leftLapel.rotation.z = -0.2
1906 leftLapel.rotation.y = 0.3
1907 group.add(leftLapel)
1908
1909 const rightLapel = new THREE.Mesh(lapelGeom, silkMat)
1910 rightLapel.position.set(0.4, 0.45, -0.1)
1911 rightLapel.rotation.z = -0.2
1912 rightLapel.rotation.y = -0.3
1913 group.add(rightLapel)
1914
1915 // Gold buttons
1916 const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
1917 for (let i = 0; i < 3; i++) {
1918 const button = new THREE.Mesh(buttonGeom, goldMat)
1919 button.position.set(0.42, 0.5 - i * 0.1, 0)
1920 group.add(button)
1921 }
1922
1923 // Watch chain
1924 const chainGeom = new THREE.TorusGeometry(0.08, 0.008, 4, 8, Math.PI)
1925 const chain = new THREE.Mesh(chainGeom, goldMat)
1926 chain.position.set(0.35, 0.32, 0)
1927 chain.rotation.y = Math.PI / 2
1928 chain.rotation.x = Math.PI / 2
1929 group.add(chain)
1930
1931 // Watch fob
1932 const fobGeom = new THREE.SphereGeometry(0.02, 6, 4)
1933 const fob = new THREE.Mesh(fobGeom, goldMat)
1934 fob.position.set(0.35, 0.24, 0)
1935 group.add(fob)
1936
1937 return group
1938 }
1939 },
1940
1941 ollie_gentleman_suit: {
1942 id: 'ollie_gentleman_suit',
1943 name: "Gentleman's Suit",
1944 character: CHARACTERS.OLLIE,
1945 type: OUTFIT_TYPES.CLOTHING_BODY,
1946 price: 120,
1947 meshFactory: (gradientMap) => {
1948 const group = new THREE.Group()
1949 const suitMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap })
1950 const shirtMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1951 const tieMat = new THREE.MeshToonMaterial({ color: 0x4a0000, gradientMap })
1952 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1953
1954 // White shirt underneath
1955 const shirtGeom = new THREE.SphereGeometry(0.38, 10, 8)
1956 shirtGeom.scale(1, 0.78, 1)
1957 const shirt = new THREE.Mesh(shirtGeom, shirtMat)
1958 shirt.position.set(0, 0.35, 0)
1959 group.add(shirt)
1960
1961 // Suit jacket
1962 const jacketGeom = new THREE.SphereGeometry(0.44, 10, 8)
1963 jacketGeom.scale(1, 0.82, 1)
1964 const jacket = new THREE.Mesh(jacketGeom, suitMat)
1965 jacket.position.set(0, 0.35, 0)
1966 group.add(jacket)
1967
1968 // Jacket front cutaway (shows shirt and tie)
1969 const cutawayGeom = new THREE.PlaneGeometry(0.2, 0.45)
1970 const cutawayMat = new THREE.MeshBasicMaterial({ visible: false })
1971 const cutaway = new THREE.Mesh(cutawayGeom, cutawayMat)
1972 cutaway.position.set(0.45, 0.35, 0)
1973 cutaway.rotation.y = Math.PI / 2
1974 group.add(cutaway)
1975
1976 // Lapels
1977 const lapelGeom = new THREE.BoxGeometry(0.12, 0.25, 0.04)
1978 const leftLapel = new THREE.Mesh(lapelGeom, suitMat)
1979 leftLapel.position.set(0.42, 0.48, 0.12)
1980 leftLapel.rotation.z = -0.25
1981 leftLapel.rotation.y = 0.4
1982 group.add(leftLapel)
1983
1984 const rightLapel = new THREE.Mesh(lapelGeom, suitMat)
1985 rightLapel.position.set(0.42, 0.48, -0.12)
1986 rightLapel.rotation.z = -0.25
1987 rightLapel.rotation.y = -0.4
1988 group.add(rightLapel)
1989
1990 // Collar
1991 const collarGeom = new THREE.BoxGeometry(0.06, 0.08, 0.05)
1992 const leftCollar = new THREE.Mesh(collarGeom, shirtMat)
1993 leftCollar.position.set(0.38, 0.62, 0.06)
1994 leftCollar.rotation.z = -0.4
1995 group.add(leftCollar)
1996
1997 const rightCollar = new THREE.Mesh(collarGeom, shirtMat)
1998 rightCollar.position.set(0.38, 0.62, -0.06)
1999 rightCollar.rotation.z = -0.4
2000 group.add(rightCollar)
2001
2002 // Tie
2003 const tieKnotGeom = new THREE.BoxGeometry(0.04, 0.04, 0.03)
2004 const tieKnot = new THREE.Mesh(tieKnotGeom, tieMat)
2005 tieKnot.position.set(0.40, 0.58, 0)
2006 group.add(tieKnot)
2007
2008 const tieBodyGeom = new THREE.BoxGeometry(0.06, 0.25, 0.02)
2009 const tieBody = new THREE.Mesh(tieBodyGeom, tieMat)
2010 tieBody.position.set(0.42, 0.42, 0)
2011 tieBody.rotation.z = -0.05
2012 group.add(tieBody)
2013
2014 // Tie point
2015 const tiePointGeom = new THREE.ConeGeometry(0.035, 0.06, 4)
2016 const tiePoint = new THREE.Mesh(tiePointGeom, tieMat)
2017 tiePoint.position.set(0.43, 0.28, 0)
2018 tiePoint.rotation.z = Math.PI
2019 group.add(tiePoint)
2020
2021 // Pocket square
2022 const squareGeom = new THREE.BoxGeometry(0.04, 0.05, 0.02)
2023 const squareMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
2024 const square = new THREE.Mesh(squareGeom, squareMat)
2025 square.position.set(0.4, 0.5, -0.2)
2026 group.add(square)
2027
2028 // Buttons
2029 const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
2030 const button1 = new THREE.Mesh(buttonGeom, goldMat)
2031 button1.position.set(0.44, 0.35, 0.15)
2032 group.add(button1)
2033
2034 const button2 = new THREE.Mesh(buttonGeom, goldMat)
2035 button2.position.set(0.44, 0.35, -0.15)
2036 group.add(button2)
2037
2038 return group
2039 }
2040 },
2041
2042 // Ollie outfits - full costumes
2043 ollie_scientist: {
2044 id: 'ollie_scientist',
2045 name: 'Mad Scientist',
2046 character: CHARACTERS.OLLIE,
2047 type: OUTFIT_TYPES.CLOTHING_BODY,
2048 conflictsWith: [OUTFIT_TYPES.ACCESSORY_FACE],
2049 price: 85,
2050 meshFactory: (gradientMap) => {
2051 const group = new THREE.Group()
2052 const labCoatMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap })
2053 const goggleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2054 const lensMat = new THREE.MeshBasicMaterial({ color: 0x66ffff, transparent: true, opacity: 0.7 })
2055 const flaskMat = new THREE.MeshBasicMaterial({ color: 0x88ff88, transparent: true, opacity: 0.6 })
2056 const glassMat = new THREE.MeshBasicMaterial({ color: 0xccffcc, transparent: true, opacity: 0.4 })
2057
2058 // Lab coat body
2059 const coatGeom = new THREE.SphereGeometry(0.46, 10, 8)
2060 coatGeom.scale(1, 0.85, 1)
2061 const coat = new THREE.Mesh(coatGeom, labCoatMat)
2062 coat.position.set(0, 0.35, 0)
2063 group.add(coat)
2064
2065 // Coat collar
2066 const collarGeom = new THREE.TorusGeometry(0.28, 0.04, 4, 12, Math.PI)
2067 const collar = new THREE.Mesh(collarGeom, labCoatMat)
2068 collar.position.set(0, 0.62, 0.15)
2069 collar.rotation.x = Math.PI / 2 + 0.3
2070 group.add(collar)
2071
2072 // Coat lapels
2073 const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.06)
2074 const lapelL = new THREE.Mesh(lapelGeom, labCoatMat)
2075 lapelL.position.set(0.38, 0.5, 0.12)
2076 lapelL.rotation.z = -0.2
2077 group.add(lapelL)
2078
2079 const lapelR = new THREE.Mesh(lapelGeom, labCoatMat)
2080 lapelR.position.set(0.38, 0.5, -0.12)
2081 lapelR.rotation.z = -0.2
2082 group.add(lapelR)
2083
2084 // Pocket with pens
2085 const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.08)
2086 const pocket = new THREE.Mesh(pocketGeom, labCoatMat)
2087 pocket.position.set(0.45, 0.45, 0.15)
2088 group.add(pocket)
2089
2090 // Pens in pocket
2091 const penMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
2092 const penGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.08, 4)
2093 const pen1 = new THREE.Mesh(penGeom, penMat)
2094 pen1.position.set(0.46, 0.52, 0.13)
2095 group.add(pen1)
2096
2097 const pen2Mat = new THREE.MeshToonMaterial({ color: 0x0000aa, gradientMap })
2098 const pen2 = new THREE.Mesh(penGeom, pen2Mat)
2099 pen2.position.set(0.46, 0.51, 0.17)
2100 pen2.rotation.z = 0.1
2101 group.add(pen2)
2102
2103 // Wild goggles (multiple lenses!)
2104 const goggleStrapGeom = new THREE.TorusGeometry(0.22, 0.025, 6, 16)
2105 const goggleStrap = new THREE.Mesh(goggleStrapGeom, goggleMat)
2106 goggleStrap.position.set(0.15, 0.7, 0)
2107 goggleStrap.rotation.y = Math.PI / 2
2108 group.add(goggleStrap)
2109
2110 // Main lens (large center)
2111 const mainLensGeom = new THREE.CylinderGeometry(0.08, 0.08, 0.04, 12)
2112 const mainLensFrame = new THREE.Mesh(mainLensGeom, goggleMat)
2113 mainLensFrame.position.set(0.35, 0.7, 0)
2114 mainLensFrame.rotation.z = Math.PI / 2
2115 group.add(mainLensFrame)
2116
2117 const mainLens = new THREE.Mesh(new THREE.CircleGeometry(0.07, 12), lensMat)
2118 mainLens.position.set(0.38, 0.7, 0)
2119 mainLens.rotation.y = Math.PI / 2
2120 group.add(mainLens)
2121
2122 // Extra lens (smaller, offset)
2123 const extraLensGeom = new THREE.CylinderGeometry(0.04, 0.04, 0.03, 10)
2124 const extraLensFrame = new THREE.Mesh(extraLensGeom, goggleMat)
2125 extraLensFrame.position.set(0.38, 0.78, 0.08)
2126 extraLensFrame.rotation.z = Math.PI / 2
2127 group.add(extraLensFrame)
2128
2129 const extraLens = new THREE.Mesh(new THREE.CircleGeometry(0.035, 10), lensMat)
2130 extraLens.position.set(0.40, 0.78, 0.08)
2131 extraLens.rotation.y = Math.PI / 2
2132 group.add(extraLens)
2133
2134 // Bubbling flask (held by tentacle)
2135 const flaskGeom = new THREE.CylinderGeometry(0.02, 0.06, 0.15, 8)
2136 const flask = new THREE.Mesh(flaskGeom, glassMat)
2137 flask.position.set(0.25, 0.1, 0.35)
2138 group.add(flask)
2139
2140 // Flask neck
2141 const neckGeom = new THREE.CylinderGeometry(0.02, 0.02, 0.05, 6)
2142 const neck = new THREE.Mesh(neckGeom, glassMat)
2143 neck.position.set(0.25, 0.2, 0.35)
2144 group.add(neck)
2145
2146 // Bubbling liquid
2147 const liquidGeom = new THREE.SphereGeometry(0.045, 8, 6)
2148 const liquid = new THREE.Mesh(liquidGeom, flaskMat)
2149 liquid.position.set(0.25, 0.08, 0.35)
2150 group.add(liquid)
2151
2152 // Bubbles
2153 const bubbleGeom = new THREE.SphereGeometry(0.012, 6, 4)
2154 const bubbleMat = new THREE.MeshBasicMaterial({ color: 0xaaffaa, transparent: true, opacity: 0.5 })
2155 for (let i = 0; i < 3; i++) {
2156 const bubble = new THREE.Mesh(bubbleGeom, bubbleMat)
2157 bubble.position.set(0.25 + (Math.random() - 0.5) * 0.03, 0.12 + i * 0.03, 0.35)
2158 group.add(bubble)
2159 }
2160
2161 return group
2162 }
2163 },
2164
2165 ollie_explorer: {
2166 id: 'ollie_explorer',
2167 name: 'Explorer',
2168 character: CHARACTERS.OLLIE,
2169 type: OUTFIT_TYPES.CLOTHING_BODY,
2170 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
2171 price: 75,
2172 meshFactory: (gradientMap) => {
2173 const group = new THREE.Group()
2174 const vestMat = new THREE.MeshToonMaterial({ color: 0xc4a574, gradientMap })
2175 const pocketMat = new THREE.MeshToonMaterial({ color: 0xa08050, gradientMap })
2176 const helmetMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
2177 const bandMat = new THREE.MeshToonMaterial({ color: 0x4a3728, gradientMap })
2178 const buttonMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2179
2180 // Safari vest body
2181 const vestGeom = new THREE.SphereGeometry(0.44, 10, 8)
2182 vestGeom.scale(1, 0.82, 1)
2183 const vest = new THREE.Mesh(vestGeom, vestMat)
2184 vest.position.set(0, 0.35, 0)
2185 group.add(vest)
2186
2187 // Multiple pockets (it's a safari vest!)
2188 const pocketGeom = new THREE.BoxGeometry(0.03, 0.1, 0.1)
2189
2190 // Front pockets
2191 const pocket1 = new THREE.Mesh(pocketGeom, pocketMat)
2192 pocket1.position.set(0.44, 0.3, 0.15)
2193 group.add(pocket1)
2194
2195 const pocket2 = new THREE.Mesh(pocketGeom, pocketMat)
2196 pocket2.position.set(0.44, 0.3, -0.15)
2197 group.add(pocket2)
2198
2199 const pocket3 = new THREE.Mesh(pocketGeom, pocketMat)
2200 pocket3.position.set(0.44, 0.45, 0.1)
2201 pocket3.scale.set(1, 0.7, 0.8)
2202 group.add(pocket3)
2203
2204 // Pocket flaps
2205 const flapGeom = new THREE.BoxGeometry(0.02, 0.02, 0.1)
2206 const flap1 = new THREE.Mesh(flapGeom, pocketMat)
2207 flap1.position.set(0.45, 0.36, 0.15)
2208 group.add(flap1)
2209
2210 const flap2 = new THREE.Mesh(flapGeom, pocketMat)
2211 flap2.position.set(0.45, 0.36, -0.15)
2212 group.add(flap2)
2213
2214 // Buttons on pockets
2215 const buttonGeom = new THREE.SphereGeometry(0.012, 6, 4)
2216 const btn1 = new THREE.Mesh(buttonGeom, buttonMat)
2217 btn1.position.set(0.46, 0.36, 0.15)
2218 group.add(btn1)
2219
2220 const btn2 = new THREE.Mesh(buttonGeom, buttonMat)
2221 btn2.position.set(0.46, 0.36, -0.15)
2222 group.add(btn2)
2223
2224 // Vest collar
2225 const collarGeom = new THREE.TorusGeometry(0.2, 0.03, 4, 10, Math.PI)
2226 const collar = new THREE.Mesh(collarGeom, vestMat)
2227 collar.position.set(0.1, 0.6, 0.1)
2228 collar.rotation.x = Math.PI / 2 + 0.3
2229 collar.rotation.z = 0.2
2230 group.add(collar)
2231
2232 // Pith helmet
2233 const helmetDomeGeom = new THREE.SphereGeometry(0.2, 12, 8, 0, Math.PI * 2, 0, Math.PI * 0.6)
2234 const helmetDome = new THREE.Mesh(helmetDomeGeom, helmetMat)
2235 helmetDome.position.set(0.12, 0.72, 0)
2236 group.add(helmetDome)
2237
2238 // Helmet brim
2239 const brimGeom = new THREE.TorusGeometry(0.2, 0.04, 4, 16)
2240 const brim = new THREE.Mesh(brimGeom, helmetMat)
2241 brim.position.set(0.12, 0.68, 0)
2242 brim.rotation.x = Math.PI / 2
2243 group.add(brim)
2244
2245 // Helmet band
2246 const helmetBandGeom = new THREE.TorusGeometry(0.17, 0.02, 4, 16)
2247 const helmetBand = new THREE.Mesh(helmetBandGeom, bandMat)
2248 helmetBand.position.set(0.12, 0.72, 0)
2249 helmetBand.rotation.x = Math.PI / 2
2250 group.add(helmetBand)
2251
2252 // Helmet top button
2253 const topButtonGeom = new THREE.SphereGeometry(0.025, 6, 4)
2254 const topButton = new THREE.Mesh(topButtonGeom, bandMat)
2255 topButton.position.set(0.12, 0.88, 0)
2256 group.add(topButton)
2257
2258 return group
2259 }
2260 },
2261
2262 ollie_artist: {
2263 id: 'ollie_artist',
2264 name: 'Artist',
2265 character: CHARACTERS.OLLIE,
2266 type: OUTFIT_TYPES.CLOTHING_BODY,
2267 conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD],
2268 price: 70,
2269 meshFactory: (gradientMap) => {
2270 const group = new THREE.Group()
2271 const smockMat = new THREE.MeshToonMaterial({ color: 0x4a4a6a, gradientMap })
2272 const beretMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
2273 const paletteMat = new THREE.MeshToonMaterial({ color: 0xdeb887, gradientMap })
2274
2275 // Paint splatter colors
2276 const paintColors = [0xff4444, 0x44ff44, 0x4444ff, 0xffff44, 0xff44ff]
2277
2278 // Smock body
2279 const smockGeom = new THREE.SphereGeometry(0.46, 10, 8)
2280 smockGeom.scale(1, 0.85, 1)
2281 const smock = new THREE.Mesh(smockGeom, smockMat)
2282 smock.position.set(0, 0.35, 0)
2283 group.add(smock)
2284
2285 // Paint splatters on smock
2286 const splatPositions = [
2287 { x: 0.4, y: 0.4, z: 0.2 },
2288 { x: 0.42, y: 0.25, z: -0.15 },
2289 { x: 0.38, y: 0.45, z: -0.1 },
2290 { x: 0.35, y: 0.3, z: 0.25 },
2291 { x: 0.4, y: 0.2, z: 0.1 }
2292 ]
2293 for (let i = 0; i < splatPositions.length; i++) {
2294 const splatGeom = new THREE.SphereGeometry(0.03 + Math.random() * 0.02, 6, 4)
2295 splatGeom.scale(1, 0.3, 1)
2296 const splatMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap })
2297 const splat = new THREE.Mesh(splatGeom, splatMat)
2298 splat.position.set(splatPositions[i].x, splatPositions[i].y, splatPositions[i].z)
2299 splat.rotation.y = Math.random() * Math.PI
2300 group.add(splat)
2301 }
2302
2303 // Smock collar/neckline
2304 const collarGeom = new THREE.TorusGeometry(0.22, 0.03, 4, 12)
2305 const collar = new THREE.Mesh(collarGeom, smockMat)
2306 collar.position.set(0.05, 0.6, 0)
2307 collar.rotation.x = Math.PI / 2
2308 collar.rotation.y = 0.2
2309 group.add(collar)
2310
2311 // Beret
2312 const beretGeom = new THREE.SphereGeometry(0.18, 10, 8)
2313 beretGeom.scale(1.3, 0.5, 1.3)
2314 const beret = new THREE.Mesh(beretGeom, beretMat)
2315 beret.position.set(0.15, 0.78, 0.05)
2316 beret.rotation.z = -0.3
2317 group.add(beret)
2318
2319 // Beret stem/tip
2320 const stemGeom = new THREE.SphereGeometry(0.03, 6, 4)
2321 const stem = new THREE.Mesh(stemGeom, beretMat)
2322 stem.position.set(0.15, 0.85, 0.05)
2323 group.add(stem)
2324
2325 // Beret band
2326 const bandGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12)
2327 const band = new THREE.Mesh(bandGeom, beretMat)
2328 band.position.set(0.13, 0.72, 0)
2329 band.rotation.x = Math.PI / 2
2330 band.rotation.y = 0.3
2331 group.add(band)
2332
2333 // Painter's palette (held by tentacle)
2334 const paletteShape = new THREE.Shape()
2335 paletteShape.moveTo(0, 0)
2336 paletteShape.quadraticCurveTo(0.1, 0.05, 0.12, 0)
2337 paletteShape.quadraticCurveTo(0.14, -0.06, 0.08, -0.1)
2338 paletteShape.quadraticCurveTo(0, -0.12, -0.08, -0.1)
2339 paletteShape.quadraticCurveTo(-0.12, -0.04, -0.1, 0.02)
2340 paletteShape.quadraticCurveTo(-0.06, 0.06, 0, 0)
2341
2342 const paletteGeom = new THREE.ExtrudeGeometry(paletteShape, {
2343 steps: 1, depth: 0.015, bevelEnabled: false
2344 })
2345 const palette = new THREE.Mesh(paletteGeom, paletteMat)
2346 palette.position.set(0.2, 0.15, 0.35)
2347 palette.rotation.x = -0.5
2348 palette.rotation.y = 0.3
2349 group.add(palette)
2350
2351 // Thumb hole
2352 const holeGeom = new THREE.TorusGeometry(0.02, 0.008, 4, 8)
2353 const holeMat = new THREE.MeshToonMaterial({ color: 0x000000, gradientMap })
2354 const hole = new THREE.Mesh(holeGeom, holeMat)
2355 hole.position.set(0.12, 0.15, 0.35)
2356 hole.rotation.x = -0.5 + Math.PI / 2
2357 hole.rotation.z = 0.3
2358 group.add(hole)
2359
2360 // Paint blobs on palette
2361 const blobPositions = [
2362 { x: 0.22, y: 0.18, z: 0.32 },
2363 { x: 0.18, y: 0.17, z: 0.38 },
2364 { x: 0.25, y: 0.16, z: 0.36 },
2365 { x: 0.2, y: 0.19, z: 0.34 }
2366 ]
2367 for (let i = 0; i < blobPositions.length; i++) {
2368 const blobGeom = new THREE.SphereGeometry(0.015, 6, 4)
2369 blobGeom.scale(1, 0.4, 1)
2370 const blobMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap })
2371 const blob = new THREE.Mesh(blobGeom, blobMat)
2372 blob.position.set(blobPositions[i].x, blobPositions[i].y, blobPositions[i].z)
2373 group.add(blob)
2374 }
2375
2376 // Paintbrush (tiny, held)
2377 const brushHandleGeom = new THREE.CylinderGeometry(0.008, 0.01, 0.12, 4)
2378 const brushHandleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
2379 const brushHandle = new THREE.Mesh(brushHandleGeom, brushHandleMat)
2380 brushHandle.position.set(0.15, 0.08, 0.4)
2381 brushHandle.rotation.z = 0.8
2382 brushHandle.rotation.x = 0.3
2383 group.add(brushHandle)
2384
2385 // Brush bristles
2386 const bristleGeom = new THREE.ConeGeometry(0.015, 0.03, 6)
2387 const bristleMat = new THREE.MeshToonMaterial({ color: 0x4444ff, gradientMap })
2388 const bristles = new THREE.Mesh(bristleGeom, bristleMat)
2389 bristles.position.set(0.1, 0.12, 0.42)
2390 bristles.rotation.z = 0.8 + Math.PI
2391 bristles.rotation.x = 0.3
2392 group.add(bristles)
2393
2394 return group
2395 }
2396 }
2397 }
2398
2399 // Building definitions
2400 export const BUILDINGS = {
2401 dock_wooden: {
2402 id: 'dock_wooden',
2403 buildingType: 'dock_wooden',
2404 name: 'Wooden Dock',
2405 price: 40,
2406 zoneType: 'waterEdge',
2407 forbiddenRadius: 0.8
2408 },
2409 fishing_hut: {
2410 id: 'fishing_hut',
2411 buildingType: 'fishing_hut',
2412 name: 'Fishing Hut',
2413 price: 50,
2414 zoneType: 'waterEdge',
2415 forbiddenRadius: 0.9
2416 },
2417 lighthouse: {
2418 id: 'lighthouse',
2419 buildingType: 'lighthouse',
2420 name: 'Mini Lighthouse',
2421 price: 50,
2422 zoneType: 'shore',
2423 forbiddenRadius: 0.5
2424 },
2425 reeds: {
2426 id: 'reeds',
2427 buildingType: 'reeds',
2428 name: 'Reed Cluster',
2429 price: 25,
2430 zoneType: 'water',
2431 forbiddenRadius: 0.4
2432 },
2433 fence: {
2434 id: 'fence',
2435 buildingType: 'fence',
2436 name: 'Fence Segment',
2437 price: 25,
2438 zoneType: 'shore',
2439 forbiddenRadius: 0.3
2440 },
2441 onion_house: {
2442 id: 'onion_house',
2443 buildingType: 'onion_house',
2444 name: 'Onion House',
2445 price: 45,
2446 zoneType: 'shore',
2447 forbiddenRadius: 0.6
2448 },
2449 boot_house: {
2450 id: 'boot_house',
2451 buildingType: 'boot_house',
2452 name: 'Boot House',
2453 price: 100,
2454 zoneType: 'shore',
2455 forbiddenRadius: 1.2
2456 }
2457 }
2458
2459 // Get all outfits for a character
2460 export function getOutfitsForCharacter(character) {
2461 return Object.values(OUTFITS).filter(o => o.character === character)
2462 }
2463
2464 // Get all buildings
2465 export function getAllBuildings() {
2466 return Object.values(BUILDINGS)
2467 }
2468
2469 // Get item by ID (outfit or building)
2470 export function getItem(itemId) {
2471 return OUTFITS[itemId] || BUILDINGS[itemId] || null
2472 }
2473
2474 // Get all purchasable items (outfits + buildings)
2475 export function getAllItems() {
2476 return [...Object.values(OUTFITS), ...Object.values(BUILDINGS)]
2477 }