| 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 | } |
| 14 | |
| 15 | // Character IDs |
| 16 | export const CHARACTERS = { |
| 17 | DOUG: 'doug', |
| 18 | DONNY: 'donny', |
| 19 | OLLIE: 'ollie' |
| 20 | } |
| 21 | |
| 22 | // Outfit definitions |
| 23 | export const OUTFITS = { |
| 24 | // Doug outfits - starter tier (cheap) |
| 25 | doug_mint: { |
| 26 | id: 'doug_mint', |
| 27 | name: 'Mint Fresh', |
| 28 | character: CHARACTERS.DOUG, |
| 29 | type: OUTFIT_TYPES.COLOR_BODY, |
| 30 | price: 5, |
| 31 | colors: { body: 0x98fb98, highlight: 0xb0ffb0 } |
| 32 | }, |
| 33 | doug_bubblegum: { |
| 34 | id: 'doug_bubblegum', |
| 35 | name: 'Bubblegum', |
| 36 | character: CHARACTERS.DOUG, |
| 37 | type: OUTFIT_TYPES.COLOR_BODY, |
| 38 | price: 5, |
| 39 | colors: { body: 0xffb6c1, highlight: 0xffd1dc } |
| 40 | }, |
| 41 | // Doug outfits - mid tier |
| 42 | doug_golden: { |
| 43 | id: 'doug_golden', |
| 44 | name: 'Golden Glow', |
| 45 | character: CHARACTERS.DOUG, |
| 46 | type: OUTFIT_TYPES.COLOR_BODY, |
| 47 | price: 12, |
| 48 | colors: { body: 0xffd700, highlight: 0xffec8b } |
| 49 | }, |
| 50 | doug_sunset: { |
| 51 | id: 'doug_sunset', |
| 52 | name: 'Sunset Orange', |
| 53 | character: CHARACTERS.DOUG, |
| 54 | type: OUTFIT_TYPES.COLOR_BODY, |
| 55 | price: 12, |
| 56 | colors: { body: 0xff6b35, highlight: 0xffa07a } |
| 57 | }, |
| 58 | doug_tophat: { |
| 59 | id: 'doug_tophat', |
| 60 | name: 'Top Hat', |
| 61 | character: CHARACTERS.DOUG, |
| 62 | type: OUTFIT_TYPES.ACCESSORY_HEAD, |
| 63 | price: 25, |
| 64 | meshFactory: (gradientMap) => { |
| 65 | const group = new THREE.Group() |
| 66 | const material = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 67 | |
| 68 | // Hat brim |
| 69 | const brimGeom = new THREE.CylinderGeometry(0.25, 0.25, 0.03, 12) |
| 70 | const brim = new THREE.Mesh(brimGeom, material) |
| 71 | group.add(brim) |
| 72 | |
| 73 | // Hat top |
| 74 | const topGeom = new THREE.CylinderGeometry(0.15, 0.15, 0.25, 12) |
| 75 | const top = new THREE.Mesh(topGeom, material) |
| 76 | top.position.y = 0.14 |
| 77 | group.add(top) |
| 78 | |
| 79 | // Hat band |
| 80 | const bandMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| 81 | const bandGeom = new THREE.CylinderGeometry(0.155, 0.155, 0.04, 12) |
| 82 | const band = new THREE.Mesh(bandGeom, bandMat) |
| 83 | band.position.y = 0.04 |
| 84 | group.add(band) |
| 85 | |
| 86 | return group |
| 87 | } |
| 88 | }, |
| 89 | doug_shades: { |
| 90 | id: 'doug_shades', |
| 91 | name: 'Cool Shades', |
| 92 | character: CHARACTERS.DOUG, |
| 93 | type: OUTFIT_TYPES.ACCESSORY_FACE, |
| 94 | price: 20, |
| 95 | meshFactory: (gradientMap) => { |
| 96 | const group = new THREE.Group() |
| 97 | const frameMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 98 | const lensMat = new THREE.MeshBasicMaterial({ color: 0x222222, transparent: true, opacity: 0.7 }) |
| 99 | |
| 100 | // Left lens |
| 101 | const lensGeom = new THREE.CircleGeometry(0.08, 8) |
| 102 | const leftLens = new THREE.Mesh(lensGeom, lensMat) |
| 103 | leftLens.position.set(-0.1, 0, 0.01) |
| 104 | group.add(leftLens) |
| 105 | |
| 106 | // Right lens |
| 107 | const rightLens = new THREE.Mesh(lensGeom, lensMat) |
| 108 | rightLens.position.set(0.1, 0, 0.01) |
| 109 | group.add(rightLens) |
| 110 | |
| 111 | // Bridge |
| 112 | const bridgeGeom = new THREE.BoxGeometry(0.06, 0.02, 0.02) |
| 113 | const bridge = new THREE.Mesh(bridgeGeom, frameMat) |
| 114 | group.add(bridge) |
| 115 | |
| 116 | // Frames |
| 117 | const frameGeom = new THREE.TorusGeometry(0.08, 0.01, 4, 12) |
| 118 | const leftFrame = new THREE.Mesh(frameGeom, frameMat) |
| 119 | leftFrame.position.set(-0.1, 0, 0) |
| 120 | group.add(leftFrame) |
| 121 | |
| 122 | const rightFrame = new THREE.Mesh(frameGeom, frameMat) |
| 123 | rightFrame.position.set(0.1, 0, 0) |
| 124 | group.add(rightFrame) |
| 125 | |
| 126 | return group |
| 127 | } |
| 128 | }, |
| 129 | |
| 130 | // Donny outfits - starter tier |
| 131 | donny_seafoam: { |
| 132 | id: 'donny_seafoam', |
| 133 | name: 'Seafoam', |
| 134 | character: CHARACTERS.DONNY, |
| 135 | type: OUTFIT_TYPES.COLOR_BODY, |
| 136 | price: 8, |
| 137 | colors: { body: 0x5f9ea0, belly: 0x98d8d8 } |
| 138 | }, |
| 139 | // Donny outfits - mid tier |
| 140 | donny_royal: { |
| 141 | id: 'donny_royal', |
| 142 | name: 'Royal Purple', |
| 143 | character: CHARACTERS.DONNY, |
| 144 | type: OUTFIT_TYPES.COLOR_BODY, |
| 145 | price: 15, |
| 146 | colors: { body: 0x6b3fa0, belly: 0x9b7bc0 } |
| 147 | }, |
| 148 | donny_arctic: { |
| 149 | id: 'donny_arctic', |
| 150 | name: 'Arctic White', |
| 151 | character: CHARACTERS.DONNY, |
| 152 | type: OUTFIT_TYPES.COLOR_BODY, |
| 153 | price: 18, |
| 154 | colors: { body: 0xe8e8e8, belly: 0xffffff } |
| 155 | }, |
| 156 | donny_ruby_monocle: { |
| 157 | id: 'donny_ruby_monocle', |
| 158 | name: 'Ruby Monocle', |
| 159 | character: CHARACTERS.DONNY, |
| 160 | type: OUTFIT_TYPES.ACCESSORY_FACE, |
| 161 | price: 25, |
| 162 | colors: { rim: 0xb22222, glass: 0xff6666 } |
| 163 | }, |
| 164 | donny_bowler: { |
| 165 | id: 'donny_bowler', |
| 166 | name: 'Bowler Hat', |
| 167 | character: CHARACTERS.DONNY, |
| 168 | type: OUTFIT_TYPES.ACCESSORY_HEAD, |
| 169 | price: 25, |
| 170 | meshFactory: (gradientMap) => { |
| 171 | const group = new THREE.Group() |
| 172 | const material = new THREE.MeshToonMaterial({ color: 0x2f2f2f, gradientMap }) |
| 173 | |
| 174 | // Hat dome |
| 175 | const domeGeom = new THREE.SphereGeometry(0.15, 12, 8, 0, Math.PI * 2, 0, Math.PI / 2) |
| 176 | const dome = new THREE.Mesh(domeGeom, material) |
| 177 | group.add(dome) |
| 178 | |
| 179 | // Hat brim |
| 180 | const brimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.025, 12) |
| 181 | const brim = new THREE.Mesh(brimGeom, material) |
| 182 | brim.position.y = -0.01 |
| 183 | group.add(brim) |
| 184 | |
| 185 | return group |
| 186 | } |
| 187 | }, |
| 188 | |
| 189 | // Ollie outfits |
| 190 | ollie_coral: { |
| 191 | id: 'ollie_coral', |
| 192 | name: 'Coral Pink', |
| 193 | character: CHARACTERS.OLLIE, |
| 194 | type: OUTFIT_TYPES.COLOR_BODY, |
| 195 | price: 20, |
| 196 | colors: { body: 0xff7f7f, belly: 0xffb3b3, suckers: 0xffcccc } |
| 197 | }, |
| 198 | ollie_deepsea: { |
| 199 | id: 'ollie_deepsea', |
| 200 | name: 'Deep Sea Blue', |
| 201 | character: CHARACTERS.OLLIE, |
| 202 | type: OUTFIT_TYPES.COLOR_BODY, |
| 203 | price: 20, |
| 204 | colors: { body: 0x1e3a5f, belly: 0x4a6fa5, suckers: 0x6b8cae } |
| 205 | }, |
| 206 | ollie_golden_mag: { |
| 207 | id: 'ollie_golden_mag', |
| 208 | name: 'Golden Magnifier', |
| 209 | character: CHARACTERS.OLLIE, |
| 210 | type: OUTFIT_TYPES.ACCESSORY_HELD, |
| 211 | price: 30, |
| 212 | colors: { rim: 0xffd700, glass: 0xffffcc } |
| 213 | }, |
| 214 | ollie_detective: { |
| 215 | id: 'ollie_detective', |
| 216 | name: 'Detective Cap', |
| 217 | character: CHARACTERS.OLLIE, |
| 218 | type: OUTFIT_TYPES.ACCESSORY_HEAD, |
| 219 | price: 25, |
| 220 | meshFactory: (gradientMap) => { |
| 221 | const group = new THREE.Group() |
| 222 | const material = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap }) |
| 223 | |
| 224 | // Cap body |
| 225 | const capGeom = new THREE.SphereGeometry(0.18, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2) |
| 226 | const cap = new THREE.Mesh(capGeom, material) |
| 227 | cap.scale.y = 0.5 |
| 228 | group.add(cap) |
| 229 | |
| 230 | // Front brim |
| 231 | const brimGeom = new THREE.CylinderGeometry(0.12, 0.15, 0.02, 8, 1, false, -Math.PI/3, Math.PI * 2/3) |
| 232 | const brim = new THREE.Mesh(brimGeom, material) |
| 233 | brim.position.set(0.12, -0.02, 0) |
| 234 | brim.rotation.z = -0.3 |
| 235 | group.add(brim) |
| 236 | |
| 237 | return group |
| 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | // Building definitions |
| 243 | export const BUILDINGS = { |
| 244 | dock_wooden: { |
| 245 | id: 'dock_wooden', |
| 246 | buildingType: 'dock_wooden', |
| 247 | name: 'Wooden Dock', |
| 248 | price: 40, |
| 249 | zoneType: 'waterEdge', |
| 250 | forbiddenRadius: 0.8 |
| 251 | }, |
| 252 | fishing_hut: { |
| 253 | id: 'fishing_hut', |
| 254 | buildingType: 'fishing_hut', |
| 255 | name: 'Fishing Hut', |
| 256 | price: 50, |
| 257 | zoneType: 'waterEdge', |
| 258 | forbiddenRadius: 0.9 |
| 259 | }, |
| 260 | lighthouse: { |
| 261 | id: 'lighthouse', |
| 262 | buildingType: 'lighthouse', |
| 263 | name: 'Mini Lighthouse', |
| 264 | price: 50, |
| 265 | zoneType: 'shore', |
| 266 | forbiddenRadius: 0.5 |
| 267 | }, |
| 268 | reeds: { |
| 269 | id: 'reeds', |
| 270 | buildingType: 'reeds', |
| 271 | name: 'Reed Cluster', |
| 272 | price: 25, |
| 273 | zoneType: 'water', |
| 274 | forbiddenRadius: 0.4 |
| 275 | }, |
| 276 | fence: { |
| 277 | id: 'fence', |
| 278 | buildingType: 'fence', |
| 279 | name: 'Fence Segment', |
| 280 | price: 25, |
| 281 | zoneType: 'shore', |
| 282 | forbiddenRadius: 0.3 |
| 283 | }, |
| 284 | onion_house: { |
| 285 | id: 'onion_house', |
| 286 | buildingType: 'onion_house', |
| 287 | name: 'Onion House', |
| 288 | price: 45, |
| 289 | zoneType: 'shore', |
| 290 | forbiddenRadius: 0.6 |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | // Get all outfits for a character |
| 295 | export function getOutfitsForCharacter(character) { |
| 296 | return Object.values(OUTFITS).filter(o => o.character === character) |
| 297 | } |
| 298 | |
| 299 | // Get all buildings |
| 300 | export function getAllBuildings() { |
| 301 | return Object.values(BUILDINGS) |
| 302 | } |
| 303 | |
| 304 | // Get item by ID (outfit or building) |
| 305 | export function getItem(itemId) { |
| 306 | return OUTFITS[itemId] || BUILDINGS[itemId] || null |
| 307 | } |