JavaScript · 52679 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 // Donny outfits - starter tier
680 donny_seafoam: {
681 id: 'donny_seafoam',
682 name: 'Seafoam',
683 character: CHARACTERS.DONNY,
684 type: OUTFIT_TYPES.COLOR_BODY,
685 price: 8,
686 colors: { body: 0x5f9ea0, belly: 0x98d8d8 }
687 },
688 // Donny outfits - mid tier
689 donny_royal: {
690 id: 'donny_royal',
691 name: 'Royal Purple',
692 character: CHARACTERS.DONNY,
693 type: OUTFIT_TYPES.COLOR_BODY,
694 price: 15,
695 colors: { body: 0x6b3fa0, belly: 0x9b7bc0 }
696 },
697 donny_arctic: {
698 id: 'donny_arctic',
699 name: 'Arctic White',
700 character: CHARACTERS.DONNY,
701 type: OUTFIT_TYPES.COLOR_BODY,
702 price: 18,
703 colors: { body: 0xe8e8e8, belly: 0xffffff }
704 },
705 donny_ruby_monocle: {
706 id: 'donny_ruby_monocle',
707 name: 'Ruby Monocle',
708 character: CHARACTERS.DONNY,
709 type: OUTFIT_TYPES.ACCESSORY_FACE,
710 price: 25,
711 colors: { rim: 0xb22222, glass: 0xff6666 }
712 },
713 donny_bowler: {
714 id: 'donny_bowler',
715 name: 'Bowler Hat',
716 character: CHARACTERS.DONNY,
717 type: OUTFIT_TYPES.ACCESSORY_HEAD,
718 price: 25,
719 meshFactory: (gradientMap) => {
720 const group = new THREE.Group()
721 const material = new THREE.MeshToonMaterial({ color: 0x2f2f2f, gradientMap })
722
723 // Hat dome
724 const domeGeom = new THREE.SphereGeometry(0.15, 12, 8, 0, Math.PI * 2, 0, Math.PI / 2)
725 const dome = new THREE.Mesh(domeGeom, material)
726 group.add(dome)
727
728 // Hat brim
729 const brimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.025, 12)
730 const brim = new THREE.Mesh(brimGeom, material)
731 brim.position.y = -0.01
732 group.add(brim)
733
734 return group
735 }
736 },
737
738 donny_bicorn: {
739 id: 'donny_bicorn',
740 name: "Captain's Bicorn",
741 character: CHARACTERS.DONNY,
742 type: OUTFIT_TYPES.ACCESSORY_HEAD,
743 price: 40,
744 meshFactory: (gradientMap) => {
745 const group = new THREE.Group()
746 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap })
747 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
748 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
749
750 // Main hat body (curved rectangular shape)
751 const bodyGeom = new THREE.BoxGeometry(0.35, 0.08, 0.2)
752 const body = new THREE.Mesh(bodyGeom, navyMat)
753 body.position.y = 0.04
754 group.add(body)
755
756 // Bicorn points (turned up sides)
757 const pointGeom = new THREE.BoxGeometry(0.08, 0.15, 0.18)
758 const leftPoint = new THREE.Mesh(pointGeom, navyMat)
759 leftPoint.position.set(-0.15, 0.1, 0)
760 leftPoint.rotation.z = 0.3
761 group.add(leftPoint)
762
763 const rightPoint = new THREE.Mesh(pointGeom, navyMat)
764 rightPoint.position.set(0.15, 0.1, 0)
765 rightPoint.rotation.z = -0.3
766 group.add(rightPoint)
767
768 // Gold trim along edges
769 const trimGeom = new THREE.BoxGeometry(0.36, 0.015, 0.01)
770 const frontTrim = new THREE.Mesh(trimGeom, goldMat)
771 frontTrim.position.set(0, 0.08, 0.1)
772 group.add(frontTrim)
773
774 const backTrim = new THREE.Mesh(trimGeom, goldMat)
775 backTrim.position.set(0, 0.08, -0.1)
776 group.add(backTrim)
777
778 // Cockade (rosette) decoration
779 const cockadeGeom = new THREE.CircleGeometry(0.04, 8)
780 const cockade = new THREE.Mesh(cockadeGeom, whiteMat)
781 cockade.position.set(0, 0.1, 0.11)
782 group.add(cockade)
783
784 // Gold button on cockade
785 const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
786 const button = new THREE.Mesh(buttonGeom, goldMat)
787 button.position.set(0, 0.1, 0.12)
788 group.add(button)
789
790 // Feather plume
791 const featherGeom = new THREE.ConeGeometry(0.02, 0.15, 4)
792 const feather = new THREE.Mesh(featherGeom, whiteMat)
793 feather.position.set(0.02, 0.18, 0.08)
794 feather.rotation.z = -0.3
795 feather.rotation.x = 0.2
796 group.add(feather)
797
798 return group
799 }
800 },
801
802 donny_opera_glasses: {
803 id: 'donny_opera_glasses',
804 name: 'Opera Glasses',
805 character: CHARACTERS.DONNY,
806 type: OUTFIT_TYPES.ACCESSORY_FACE,
807 price: 45,
808 meshFactory: (gradientMap) => {
809 const group = new THREE.Group()
810 const goldMat = new THREE.MeshToonMaterial({ color: 0xd4af37, gradientMap })
811 const pearlMat = new THREE.MeshToonMaterial({ color: 0xfdf5e6, gradientMap })
812 const lensMat = new THREE.MeshBasicMaterial({ color: 0x87ceeb, transparent: true, opacity: 0.4 })
813
814 // Twin barrels (binocular style)
815 const barrelGeom = new THREE.CylinderGeometry(0.05, 0.06, 0.12, 8)
816
817 const leftBarrel = new THREE.Mesh(barrelGeom, pearlMat)
818 leftBarrel.position.set(-0.07, 0, 0)
819 leftBarrel.rotation.x = Math.PI / 2
820 group.add(leftBarrel)
821
822 const rightBarrel = new THREE.Mesh(barrelGeom, pearlMat)
823 rightBarrel.position.set(0.07, 0, 0)
824 rightBarrel.rotation.x = Math.PI / 2
825 group.add(rightBarrel)
826
827 // Lenses (front)
828 const lensGeom = new THREE.CircleGeometry(0.05, 10)
829 const leftLens = new THREE.Mesh(lensGeom, lensMat)
830 leftLens.position.set(-0.07, 0, 0.07)
831 group.add(leftLens)
832
833 const rightLens = new THREE.Mesh(lensGeom, lensMat)
834 rightLens.position.set(0.07, 0, 0.07)
835 group.add(rightLens)
836
837 // Gold rims
838 const rimGeom = new THREE.TorusGeometry(0.05, 0.008, 4, 12)
839 const leftRim = new THREE.Mesh(rimGeom, goldMat)
840 leftRim.position.set(-0.07, 0, 0.06)
841 group.add(leftRim)
842
843 const rightRim = new THREE.Mesh(rimGeom, goldMat)
844 rightRim.position.set(0.07, 0, 0.06)
845 group.add(rightRim)
846
847 // Bridge connecting barrels
848 const bridgeGeom = new THREE.BoxGeometry(0.04, 0.02, 0.03)
849 const bridge = new THREE.Mesh(bridgeGeom, goldMat)
850 bridge.position.set(0, 0, 0.02)
851 group.add(bridge)
852
853 // Ornate handle
854 const handleGeom = new THREE.CylinderGeometry(0.012, 0.015, 0.15, 6)
855 const handle = new THREE.Mesh(handleGeom, goldMat)
856 handle.position.set(0.12, -0.06, 0)
857 handle.rotation.z = Math.PI / 4
858 group.add(handle)
859
860 // Handle end knob
861 const knobGeom = new THREE.SphereGeometry(0.02, 6, 4)
862 const knob = new THREE.Mesh(knobGeom, goldMat)
863 knob.position.set(0.18, -0.12, 0)
864 group.add(knob)
865
866 // Decorative filigree on barrels
867 const filigreeGeom = new THREE.TorusGeometry(0.055, 0.005, 3, 12)
868 const leftFiligree = new THREE.Mesh(filigreeGeom, goldMat)
869 leftFiligree.position.set(-0.07, 0, 0)
870 group.add(leftFiligree)
871
872 const rightFiligree = new THREE.Mesh(filigreeGeom, goldMat)
873 rightFiligree.position.set(0.07, 0, 0)
874 group.add(rightFiligree)
875
876 return group
877 }
878 },
879
880 // === DONNY CLOTHING - Narwhal garments ===
881
882 donny_sailor_vest: {
883 id: 'donny_sailor_vest',
884 name: 'Sailor Vest',
885 character: CHARACTERS.DONNY,
886 type: OUTFIT_TYPES.CLOTHING_BODY,
887 price: 45,
888 meshFactory: (gradientMap) => {
889 const group = new THREE.Group()
890 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
891 const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap })
892
893 // Vest body - fits narwhal's torpedo shape
894 const vestGeom = new THREE.CylinderGeometry(0.38, 0.42, 0.6, 10)
895 const vest = new THREE.Mesh(vestGeom, whiteMat)
896 vest.position.set(0, 0.15, 0)
897 vest.rotation.x = Math.PI / 2
898 group.add(vest)
899
900 // Blue stripes on vest
901 for (let i = 0; i < 3; i++) {
902 const stripeGeom = new THREE.TorusGeometry(0.39 + i * 0.01, 0.02, 4, 16)
903 const stripe = new THREE.Mesh(stripeGeom, blueMat)
904 stripe.position.set(0, -0.1 + i * 0.15, 0)
905 stripe.rotation.x = Math.PI / 2
906 group.add(stripe)
907 }
908
909 // Collar
910 const collarGeom = new THREE.TorusGeometry(0.36, 0.04, 4, 12, Math.PI)
911 const collar = new THREE.Mesh(collarGeom, blueMat)
912 collar.position.set(0, 0.42, 0.1)
913 collar.rotation.x = Math.PI / 2 + 0.3
914 group.add(collar)
915
916 return group
917 }
918 },
919
920 donny_admiral_coat: {
921 id: 'donny_admiral_coat',
922 name: 'Admiral Coat',
923 character: CHARACTERS.DONNY,
924 type: OUTFIT_TYPES.CLOTHING_BODY,
925 price: 100,
926 meshFactory: (gradientMap) => {
927 const group = new THREE.Group()
928 const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap })
929 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
930 const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
931
932 // Main coat body
933 const coatGeom = new THREE.CylinderGeometry(0.4, 0.45, 0.7, 10)
934 const coat = new THREE.Mesh(coatGeom, navyMat)
935 coat.position.set(0, 0.12, 0)
936 coat.rotation.x = Math.PI / 2
937 group.add(coat)
938
939 // High collar
940 const collarGeom = new THREE.CylinderGeometry(0.38, 0.35, 0.12, 10, 1, true)
941 const collar = new THREE.Mesh(collarGeom, navyMat)
942 collar.position.set(0, 0.48, 0)
943 collar.rotation.x = Math.PI / 2
944 group.add(collar)
945
946 // Gold collar trim
947 const collarTrimGeom = new THREE.TorusGeometry(0.36, 0.015, 4, 12)
948 const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat)
949 collarTrim.position.set(0, 0.54, 0)
950 collarTrim.rotation.x = Math.PI / 2
951 group.add(collarTrim)
952
953 // Epaulettes
954 const epGeom = new THREE.BoxGeometry(0.15, 0.03, 0.1)
955 const leftEp = new THREE.Mesh(epGeom, goldMat)
956 leftEp.position.set(0.35, 0.35, 0.2)
957 leftEp.rotation.z = 0.3
958 group.add(leftEp)
959
960 const rightEp = new THREE.Mesh(epGeom, goldMat)
961 rightEp.position.set(0.35, 0.35, -0.2)
962 rightEp.rotation.z = 0.3
963 group.add(rightEp)
964
965 // Gold buttons (row down front)
966 const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4)
967 for (let i = 0; i < 4; i++) {
968 const button = new THREE.Mesh(buttonGeom, goldMat)
969 button.position.set(0.1 - i * 0.12, 0.42, 0)
970 group.add(button)
971 }
972
973 // Medals/decorations
974 const medalGeom = new THREE.CircleGeometry(0.04, 6)
975 const medal1 = new THREE.Mesh(medalGeom, goldMat)
976 medal1.position.set(0.25, 0.38, 0.15)
977 medal1.rotation.y = -0.5
978 group.add(medal1)
979
980 const medal2 = new THREE.Mesh(medalGeom, goldMat)
981 medal2.position.set(0.2, 0.38, 0.12)
982 medal2.rotation.y = -0.5
983 group.add(medal2)
984
985 return group
986 }
987 },
988
989 donny_pirate_vest: {
990 id: 'donny_pirate_vest',
991 name: 'Pirate Vest',
992 character: CHARACTERS.DONNY,
993 type: OUTFIT_TYPES.CLOTHING_BODY,
994 price: 130,
995 meshFactory: (gradientMap) => {
996 const group = new THREE.Group()
997 const redMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
998 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
999 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1000 const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap })
1001 const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap })
1002
1003 // Inner shirt
1004 const shirtGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.55, 10)
1005 const shirt = new THREE.Mesh(shirtGeom, whiteMat)
1006 shirt.position.set(0, 0.15, 0)
1007 shirt.rotation.x = Math.PI / 2
1008 group.add(shirt)
1009
1010 // Vest panels (open front)
1011 const panelGeom = new THREE.BoxGeometry(0.4, 0.08, 0.25)
1012 const leftPanel = new THREE.Mesh(panelGeom, redMat)
1013 leftPanel.position.set(0.1, 0.3, 0.2)
1014 leftPanel.rotation.z = 0.2
1015 group.add(leftPanel)
1016
1017 const rightPanel = new THREE.Mesh(panelGeom, redMat)
1018 rightPanel.position.set(0.1, 0.3, -0.2)
1019 rightPanel.rotation.z = 0.2
1020 group.add(rightPanel)
1021
1022 // Vest back
1023 const backGeom = new THREE.BoxGeometry(0.5, 0.08, 0.35)
1024 const back = new THREE.Mesh(backGeom, redMat)
1025 back.position.set(-0.15, 0.2, 0)
1026 group.add(back)
1027
1028 // Gold trim on vest
1029 const trimGeom = new THREE.BoxGeometry(0.35, 0.02, 0.02)
1030 const leftTrim = new THREE.Mesh(trimGeom, goldMat)
1031 leftTrim.position.set(0.12, 0.32, 0.32)
1032 leftTrim.rotation.z = 0.2
1033 group.add(leftTrim)
1034
1035 const rightTrim = new THREE.Mesh(trimGeom, goldMat)
1036 rightTrim.position.set(0.12, 0.32, -0.32)
1037 rightTrim.rotation.z = 0.2
1038 group.add(rightTrim)
1039
1040 // Wide belt with skull buckle
1041 const beltGeom = new THREE.TorusGeometry(0.4, 0.05, 4, 16)
1042 const belt = new THREE.Mesh(beltGeom, beltMat)
1043 belt.position.set(0, -0.1, 0)
1044 belt.rotation.x = Math.PI / 2
1045 group.add(belt)
1046
1047 // Buckle
1048 const buckleGeom = new THREE.BoxGeometry(0.1, 0.08, 0.04)
1049 const buckle = new THREE.Mesh(buckleGeom, goldMat)
1050 buckle.position.set(0.38, -0.1, 0)
1051 group.add(buckle)
1052
1053 // Skull on buckle
1054 const skullGeom = new THREE.SphereGeometry(0.03, 6, 4)
1055 const skull = new THREE.Mesh(skullGeom, whiteMat)
1056 skull.position.set(0.40, -0.1, 0)
1057 group.add(skull)
1058
1059 // Bandolier strap
1060 const strapGeom = new THREE.BoxGeometry(0.6, 0.04, 0.03)
1061 const strap = new THREE.Mesh(strapGeom, beltMat)
1062 strap.position.set(0, 0.2, 0)
1063 strap.rotation.z = 0.5
1064 strap.rotation.y = Math.PI / 2
1065 group.add(strap)
1066
1067 return group
1068 }
1069 },
1070
1071 // Ollie outfits
1072 ollie_coral: {
1073 id: 'ollie_coral',
1074 name: 'Coral Pink',
1075 character: CHARACTERS.OLLIE,
1076 type: OUTFIT_TYPES.COLOR_BODY,
1077 price: 20,
1078 colors: { body: 0xff7f7f, belly: 0xffb3b3, suckers: 0xffcccc }
1079 },
1080 ollie_deepsea: {
1081 id: 'ollie_deepsea',
1082 name: 'Deep Sea Blue',
1083 character: CHARACTERS.OLLIE,
1084 type: OUTFIT_TYPES.COLOR_BODY,
1085 price: 20,
1086 colors: { body: 0x1e3a5f, belly: 0x4a6fa5, suckers: 0x6b8cae }
1087 },
1088 ollie_golden_mag: {
1089 id: 'ollie_golden_mag',
1090 name: 'Golden Magnifier',
1091 character: CHARACTERS.OLLIE,
1092 type: OUTFIT_TYPES.ACCESSORY_HELD,
1093 price: 30,
1094 colors: { rim: 0xffd700, glass: 0xffffcc }
1095 },
1096 ollie_detective: {
1097 id: 'ollie_detective',
1098 name: 'Detective Cap',
1099 character: CHARACTERS.OLLIE,
1100 type: OUTFIT_TYPES.ACCESSORY_HEAD,
1101 price: 25,
1102 meshFactory: (gradientMap) => {
1103 const group = new THREE.Group()
1104 const material = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap })
1105
1106 // Cap body
1107 const capGeom = new THREE.SphereGeometry(0.18, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2)
1108 const cap = new THREE.Mesh(capGeom, material)
1109 cap.scale.y = 0.5
1110 group.add(cap)
1111
1112 // Front brim
1113 const brimGeom = new THREE.CylinderGeometry(0.12, 0.15, 0.02, 8, 1, false, -Math.PI/3, Math.PI * 2/3)
1114 const brim = new THREE.Mesh(brimGeom, material)
1115 brim.position.set(0.12, -0.02, 0)
1116 brim.rotation.z = -0.3
1117 group.add(brim)
1118
1119 return group
1120 }
1121 },
1122
1123 ollie_jeweler_loupe: {
1124 id: 'ollie_jeweler_loupe',
1125 name: "Jeweler's Loupe",
1126 character: CHARACTERS.OLLIE,
1127 type: OUTFIT_TYPES.ACCESSORY_FACE,
1128 price: 35,
1129 meshFactory: (gradientMap) => {
1130 const group = new THREE.Group()
1131 const brassMat = new THREE.MeshToonMaterial({ color: 0xb8860b, gradientMap })
1132 const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap })
1133 const lensMat = new THREE.MeshBasicMaterial({ color: 0xaaffaa, transparent: true, opacity: 0.5 })
1134
1135 // Main loupe barrel
1136 const barrelGeom = new THREE.CylinderGeometry(0.06, 0.08, 0.1, 10)
1137 const barrel = new THREE.Mesh(barrelGeom, blackMat)
1138 barrel.rotation.x = Math.PI / 2
1139 barrel.position.z = 0.03
1140 group.add(barrel)
1141
1142 // Lens (front)
1143 const lensGeom = new THREE.CircleGeometry(0.06, 12)
1144 const lens = new THREE.Mesh(lensGeom, lensMat)
1145 lens.position.z = 0.09
1146 group.add(lens)
1147
1148 // Brass rim
1149 const rimGeom = new THREE.TorusGeometry(0.06, 0.01, 4, 16)
1150 const rim = new THREE.Mesh(rimGeom, brassMat)
1151 rim.position.z = 0.08
1152 group.add(rim)
1153
1154 // Eyepiece (back)
1155 const eyepieceGeom = new THREE.CylinderGeometry(0.05, 0.06, 0.03, 10)
1156 const eyepiece = new THREE.Mesh(eyepieceGeom, brassMat)
1157 eyepiece.rotation.x = Math.PI / 2
1158 eyepiece.position.z = -0.03
1159 group.add(eyepiece)
1160
1161 // Headband attachment (wraps around head)
1162 const bandGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 16, Math.PI)
1163 const band = new THREE.Mesh(bandGeom, blackMat)
1164 band.position.set(0, 0.05, -0.05)
1165 band.rotation.x = Math.PI / 2
1166 band.rotation.z = Math.PI / 2
1167 group.add(band)
1168
1169 // Adjustment knob
1170 const knobGeom = new THREE.CylinderGeometry(0.015, 0.02, 0.03, 6)
1171 const knob = new THREE.Mesh(knobGeom, brassMat)
1172 knob.position.set(0.07, 0, 0.02)
1173 knob.rotation.z = Math.PI / 2
1174 group.add(knob)
1175
1176 // Knob ridges
1177 for (let i = 0; i < 6; i++) {
1178 const ridgeGeom = new THREE.BoxGeometry(0.002, 0.025, 0.005)
1179 const ridge = new THREE.Mesh(ridgeGeom, brassMat)
1180 const angle = (i / 6) * Math.PI * 2
1181 ridge.position.set(0.085, Math.sin(angle) * 0.015, 0.02 + Math.cos(angle) * 0.015)
1182 group.add(ridge)
1183 }
1184
1185 return group
1186 }
1187 },
1188
1189 ollie_wizard_hat: {
1190 id: 'ollie_wizard_hat',
1191 name: 'Wizard Hat',
1192 character: CHARACTERS.OLLIE,
1193 type: OUTFIT_TYPES.ACCESSORY_HEAD,
1194 price: 45,
1195 meshFactory: (gradientMap) => {
1196 const group = new THREE.Group()
1197 const purpleMat = new THREE.MeshToonMaterial({ color: 0x4b0082, gradientMap })
1198 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1199 const silverMat = new THREE.MeshToonMaterial({ color: 0xc0c0c0, gradientMap })
1200
1201 // Main cone
1202 const coneGeom = new THREE.ConeGeometry(0.2, 0.45, 8)
1203 const cone = new THREE.Mesh(coneGeom, purpleMat)
1204 cone.position.y = 0.22
1205 group.add(cone)
1206
1207 // Bent tip (wizards hats droop!)
1208 const tipGeom = new THREE.ConeGeometry(0.05, 0.12, 6)
1209 const tip = new THREE.Mesh(tipGeom, purpleMat)
1210 tip.position.set(0.08, 0.42, 0.05)
1211 tip.rotation.z = -0.8
1212 tip.rotation.x = 0.3
1213 group.add(tip)
1214
1215 // Wide brim
1216 const brimGeom = new THREE.CylinderGeometry(0.28, 0.3, 0.03, 12)
1217 const brim = new THREE.Mesh(brimGeom, purpleMat)
1218 brim.position.y = 0
1219 group.add(brim)
1220
1221 // Gold band at base
1222 const bandGeom = new THREE.TorusGeometry(0.2, 0.02, 4, 16)
1223 const band = new THREE.Mesh(bandGeom, goldMat)
1224 band.position.y = 0.02
1225 band.rotation.x = Math.PI / 2
1226 group.add(band)
1227
1228 // Stars on hat (scattered)
1229 const starPositions = [
1230 { x: 0.12, y: 0.25, z: 0.1, scale: 0.8 },
1231 { x: -0.08, y: 0.35, z: 0.12, scale: 0.6 },
1232 { x: 0.05, y: 0.15, z: 0.15, scale: 0.5 },
1233 { x: -0.1, y: 0.2, z: -0.1, scale: 0.7 },
1234 { x: 0.08, y: 0.38, z: -0.05, scale: 0.5 }
1235 ]
1236
1237 for (const star of starPositions) {
1238 // Simple 4-point star shape
1239 const starGroup = new THREE.Group()
1240
1241 const point1Geom = new THREE.ConeGeometry(0.015, 0.04, 4)
1242 const point1 = new THREE.Mesh(point1Geom, silverMat)
1243 starGroup.add(point1)
1244
1245 const point2 = new THREE.Mesh(point1Geom, silverMat)
1246 point2.rotation.z = Math.PI
1247 starGroup.add(point2)
1248
1249 const point3 = new THREE.Mesh(point1Geom, silverMat)
1250 point3.rotation.z = Math.PI / 2
1251 starGroup.add(point3)
1252
1253 const point4 = new THREE.Mesh(point1Geom, silverMat)
1254 point4.rotation.z = -Math.PI / 2
1255 starGroup.add(point4)
1256
1257 starGroup.position.set(star.x, star.y, star.z)
1258 starGroup.scale.setScalar(star.scale)
1259 group.add(starGroup)
1260 }
1261
1262 // Moon crescent
1263 const moonGeom = new THREE.TorusGeometry(0.03, 0.01, 4, 12, Math.PI * 1.5)
1264 const moon = new THREE.Mesh(moonGeom, goldMat)
1265 moon.position.set(-0.12, 0.3, 0.08)
1266 moon.rotation.z = 0.5
1267 group.add(moon)
1268
1269 return group
1270 }
1271 },
1272
1273 // === OLLIE CLOTHING - Octopus garments ===
1274
1275 ollie_bowtie: {
1276 id: 'ollie_bowtie',
1277 name: 'Fancy Bow Tie',
1278 character: CHARACTERS.OLLIE,
1279 type: OUTFIT_TYPES.CLOTHING_BODY,
1280 price: 40,
1281 meshFactory: (gradientMap) => {
1282 const group = new THREE.Group()
1283 const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
1284 const knotMat = new THREE.MeshToonMaterial({ color: 0x660000, gradientMap })
1285
1286 // Bow tie wings
1287 const wingGeom = new THREE.BoxGeometry(0.12, 0.06, 0.04)
1288 const leftWing = new THREE.Mesh(wingGeom, silkMat)
1289 leftWing.position.set(0, 0.65, 0.08)
1290 leftWing.rotation.z = 0.2
1291 group.add(leftWing)
1292
1293 const rightWing = new THREE.Mesh(wingGeom, silkMat)
1294 rightWing.position.set(0, 0.65, -0.08)
1295 rightWing.rotation.z = -0.2
1296 group.add(rightWing)
1297
1298 // Center knot
1299 const knotGeom = new THREE.SphereGeometry(0.03, 6, 4)
1300 const knot = new THREE.Mesh(knotGeom, knotMat)
1301 knot.position.set(0, 0.65, 0)
1302 knot.scale.set(1, 1, 1.5)
1303 group.add(knot)
1304
1305 // Small dangling ribbon ends
1306 const ribbonGeom = new THREE.BoxGeometry(0.02, 0.08, 0.03)
1307 const ribbon1 = new THREE.Mesh(ribbonGeom, silkMat)
1308 ribbon1.position.set(0, 0.60, 0.02)
1309 ribbon1.rotation.z = 0.1
1310 group.add(ribbon1)
1311
1312 const ribbon2 = new THREE.Mesh(ribbonGeom, silkMat)
1313 ribbon2.position.set(0, 0.60, -0.02)
1314 ribbon2.rotation.z = -0.1
1315 group.add(ribbon2)
1316
1317 return group
1318 }
1319 },
1320
1321 ollie_dapper_vest: {
1322 id: 'ollie_dapper_vest',
1323 name: 'Dapper Vest',
1324 character: CHARACTERS.OLLIE,
1325 type: OUTFIT_TYPES.CLOTHING_BODY,
1326 price: 75,
1327 meshFactory: (gradientMap) => {
1328 const group = new THREE.Group()
1329 const vestMat = new THREE.MeshToonMaterial({ color: 0x4a4a4a, gradientMap })
1330 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1331 const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap })
1332
1333 // Vest body - wraps around octopus mantle
1334 const vestGeom = new THREE.SphereGeometry(0.42, 10, 8)
1335 vestGeom.scale(1, 0.8, 1)
1336 const vest = new THREE.Mesh(vestGeom, vestMat)
1337 vest.position.set(0, 0.35, 0)
1338 group.add(vest)
1339
1340 // Vest front opening (shows body)
1341 const openingGeom = new THREE.PlaneGeometry(0.15, 0.4)
1342 const openingMat = new THREE.MeshBasicMaterial({ color: 0xe07850, transparent: true, opacity: 0.3 })
1343 const opening = new THREE.Mesh(openingGeom, openingMat)
1344 opening.position.set(0.42, 0.35, 0)
1345 opening.rotation.y = Math.PI / 2
1346 group.add(opening)
1347
1348 // Lapels
1349 const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.05)
1350 const leftLapel = new THREE.Mesh(lapelGeom, silkMat)
1351 leftLapel.position.set(0.4, 0.45, 0.1)
1352 leftLapel.rotation.z = -0.2
1353 leftLapel.rotation.y = 0.3
1354 group.add(leftLapel)
1355
1356 const rightLapel = new THREE.Mesh(lapelGeom, silkMat)
1357 rightLapel.position.set(0.4, 0.45, -0.1)
1358 rightLapel.rotation.z = -0.2
1359 rightLapel.rotation.y = -0.3
1360 group.add(rightLapel)
1361
1362 // Gold buttons
1363 const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4)
1364 for (let i = 0; i < 3; i++) {
1365 const button = new THREE.Mesh(buttonGeom, goldMat)
1366 button.position.set(0.42, 0.5 - i * 0.1, 0)
1367 group.add(button)
1368 }
1369
1370 // Watch chain
1371 const chainGeom = new THREE.TorusGeometry(0.08, 0.008, 4, 8, Math.PI)
1372 const chain = new THREE.Mesh(chainGeom, goldMat)
1373 chain.position.set(0.35, 0.32, 0)
1374 chain.rotation.y = Math.PI / 2
1375 chain.rotation.x = Math.PI / 2
1376 group.add(chain)
1377
1378 // Watch fob
1379 const fobGeom = new THREE.SphereGeometry(0.02, 6, 4)
1380 const fob = new THREE.Mesh(fobGeom, goldMat)
1381 fob.position.set(0.35, 0.24, 0)
1382 group.add(fob)
1383
1384 return group
1385 }
1386 },
1387
1388 ollie_gentleman_suit: {
1389 id: 'ollie_gentleman_suit',
1390 name: "Gentleman's Suit",
1391 character: CHARACTERS.OLLIE,
1392 type: OUTFIT_TYPES.CLOTHING_BODY,
1393 price: 120,
1394 meshFactory: (gradientMap) => {
1395 const group = new THREE.Group()
1396 const suitMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap })
1397 const shirtMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1398 const tieMat = new THREE.MeshToonMaterial({ color: 0x4a0000, gradientMap })
1399 const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
1400
1401 // White shirt underneath
1402 const shirtGeom = new THREE.SphereGeometry(0.38, 10, 8)
1403 shirtGeom.scale(1, 0.78, 1)
1404 const shirt = new THREE.Mesh(shirtGeom, shirtMat)
1405 shirt.position.set(0, 0.35, 0)
1406 group.add(shirt)
1407
1408 // Suit jacket
1409 const jacketGeom = new THREE.SphereGeometry(0.44, 10, 8)
1410 jacketGeom.scale(1, 0.82, 1)
1411 const jacket = new THREE.Mesh(jacketGeom, suitMat)
1412 jacket.position.set(0, 0.35, 0)
1413 group.add(jacket)
1414
1415 // Jacket front cutaway (shows shirt and tie)
1416 const cutawayGeom = new THREE.PlaneGeometry(0.2, 0.45)
1417 const cutawayMat = new THREE.MeshBasicMaterial({ visible: false })
1418 const cutaway = new THREE.Mesh(cutawayGeom, cutawayMat)
1419 cutaway.position.set(0.45, 0.35, 0)
1420 cutaway.rotation.y = Math.PI / 2
1421 group.add(cutaway)
1422
1423 // Lapels
1424 const lapelGeom = new THREE.BoxGeometry(0.12, 0.25, 0.04)
1425 const leftLapel = new THREE.Mesh(lapelGeom, suitMat)
1426 leftLapel.position.set(0.42, 0.48, 0.12)
1427 leftLapel.rotation.z = -0.25
1428 leftLapel.rotation.y = 0.4
1429 group.add(leftLapel)
1430
1431 const rightLapel = new THREE.Mesh(lapelGeom, suitMat)
1432 rightLapel.position.set(0.42, 0.48, -0.12)
1433 rightLapel.rotation.z = -0.25
1434 rightLapel.rotation.y = -0.4
1435 group.add(rightLapel)
1436
1437 // Collar
1438 const collarGeom = new THREE.BoxGeometry(0.06, 0.08, 0.05)
1439 const leftCollar = new THREE.Mesh(collarGeom, shirtMat)
1440 leftCollar.position.set(0.38, 0.62, 0.06)
1441 leftCollar.rotation.z = -0.4
1442 group.add(leftCollar)
1443
1444 const rightCollar = new THREE.Mesh(collarGeom, shirtMat)
1445 rightCollar.position.set(0.38, 0.62, -0.06)
1446 rightCollar.rotation.z = -0.4
1447 group.add(rightCollar)
1448
1449 // Tie
1450 const tieKnotGeom = new THREE.BoxGeometry(0.04, 0.04, 0.03)
1451 const tieKnot = new THREE.Mesh(tieKnotGeom, tieMat)
1452 tieKnot.position.set(0.40, 0.58, 0)
1453 group.add(tieKnot)
1454
1455 const tieBodyGeom = new THREE.BoxGeometry(0.06, 0.25, 0.02)
1456 const tieBody = new THREE.Mesh(tieBodyGeom, tieMat)
1457 tieBody.position.set(0.42, 0.42, 0)
1458 tieBody.rotation.z = -0.05
1459 group.add(tieBody)
1460
1461 // Tie point
1462 const tiePointGeom = new THREE.ConeGeometry(0.035, 0.06, 4)
1463 const tiePoint = new THREE.Mesh(tiePointGeom, tieMat)
1464 tiePoint.position.set(0.43, 0.28, 0)
1465 tiePoint.rotation.z = Math.PI
1466 group.add(tiePoint)
1467
1468 // Pocket square
1469 const squareGeom = new THREE.BoxGeometry(0.04, 0.05, 0.02)
1470 const squareMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap })
1471 const square = new THREE.Mesh(squareGeom, squareMat)
1472 square.position.set(0.4, 0.5, -0.2)
1473 group.add(square)
1474
1475 // Buttons
1476 const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4)
1477 const button1 = new THREE.Mesh(buttonGeom, goldMat)
1478 button1.position.set(0.44, 0.35, 0.15)
1479 group.add(button1)
1480
1481 const button2 = new THREE.Mesh(buttonGeom, goldMat)
1482 button2.position.set(0.44, 0.35, -0.15)
1483 group.add(button2)
1484
1485 return group
1486 }
1487 }
1488 }
1489
1490 // Building definitions
1491 export const BUILDINGS = {
1492 dock_wooden: {
1493 id: 'dock_wooden',
1494 buildingType: 'dock_wooden',
1495 name: 'Wooden Dock',
1496 price: 40,
1497 zoneType: 'waterEdge',
1498 forbiddenRadius: 0.8
1499 },
1500 fishing_hut: {
1501 id: 'fishing_hut',
1502 buildingType: 'fishing_hut',
1503 name: 'Fishing Hut',
1504 price: 50,
1505 zoneType: 'waterEdge',
1506 forbiddenRadius: 0.9
1507 },
1508 lighthouse: {
1509 id: 'lighthouse',
1510 buildingType: 'lighthouse',
1511 name: 'Mini Lighthouse',
1512 price: 50,
1513 zoneType: 'shore',
1514 forbiddenRadius: 0.5
1515 },
1516 reeds: {
1517 id: 'reeds',
1518 buildingType: 'reeds',
1519 name: 'Reed Cluster',
1520 price: 25,
1521 zoneType: 'water',
1522 forbiddenRadius: 0.4
1523 },
1524 fence: {
1525 id: 'fence',
1526 buildingType: 'fence',
1527 name: 'Fence Segment',
1528 price: 25,
1529 zoneType: 'shore',
1530 forbiddenRadius: 0.3
1531 },
1532 onion_house: {
1533 id: 'onion_house',
1534 buildingType: 'onion_house',
1535 name: 'Onion House',
1536 price: 45,
1537 zoneType: 'shore',
1538 forbiddenRadius: 0.6
1539 },
1540 boot_house: {
1541 id: 'boot_house',
1542 buildingType: 'boot_house',
1543 name: 'Boot House',
1544 price: 100,
1545 zoneType: 'shore',
1546 forbiddenRadius: 1.2
1547 }
1548 }
1549
1550 // Get all outfits for a character
1551 export function getOutfitsForCharacter(character) {
1552 return Object.values(OUTFITS).filter(o => o.character === character)
1553 }
1554
1555 // Get all buildings
1556 export function getAllBuildings() {
1557 return Object.values(BUILDINGS)
1558 }
1559
1560 // Get item by ID (outfit or building)
1561 export function getItem(itemId) {
1562 return OUTFITS[itemId] || BUILDINGS[itemId] || null
1563 }
1564
1565 // Get all purchasable items (outfits + buildings)
1566 export function getAllItems() {
1567 return [...Object.values(OUTFITS), ...Object.values(BUILDINGS)]
1568 }