zeroed-some/dougk / eef876c

Browse files

add boot house

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
eef876c1a18ccda7126fe61b7da9f67a9bf5b883
Parents
95a35f3
Tree
60ff0a0

3 changed files

StatusFile+-
M src/renderers/three/buildingPlacement.js 71 3
M src/renderers/three/buildings.js 377 0
M src/renderers/three/shop/items.js 8 0
src/renderers/three/buildingPlacement.jsmodified
@@ -28,7 +28,8 @@ const BUILDING_TERRAIN = {
2828
   lighthouse: [TERRAIN.SHORE],
2929
   reeds: [TERRAIN.WATER, TERRAIN.WATER_EDGE],
3030
   fence: [TERRAIN.SHORE],
31
-  onion_house: [TERRAIN.SHORE]
31
+  onion_house: [TERRAIN.SHORE],
32
+  boot_house: [TERRAIN.SHORE]
3233
 }
3334
 
3435
 // Building collision radii (for overlap detection)
@@ -38,7 +39,8 @@ const BUILDING_COLLISION_RADIUS = {
3839
   lighthouse: 0.5,
3940
   reeds: 0.3,
4041
   fence: 0.25,
41
-  onion_house: 0.5
42
+  onion_house: 0.5,
43
+  boot_house: 1.0
4244
 }
4345
 
4446
 export class PlacementManager {
@@ -89,7 +91,8 @@ export class PlacementManager {
8991
       lighthouse: 0.5,
9092
       reeds: 0.4,
9193
       fence: 0.3,
92
-      onion_house: 0.6
94
+      onion_house: 0.6,
95
+      boot_house: 1.2
9396
     }
9497
 
9598
     // Placed building positions for collision detection
@@ -515,6 +518,71 @@ export class PlacementManager {
515518
           }
516519
         })
517520
       }
521
+
522
+      // Boot house animations
523
+      if (building.userData.buildingType === 'boot_house') {
524
+        building.traverse((child) => {
525
+          // Smoke puffs from chimney
526
+          if (child.userData.isSmokePuff) {
527
+            const phase = child.userData.phase
528
+            const cycleTime = 3.5
529
+            const t = ((elapsed * 0.4 + phase * cycleTime) % cycleTime) / cycleTime
530
+
531
+            child.position.y = t * 0.4
532
+            const scale = 1 + t * 1.0
533
+            child.scale.set(scale, scale, scale)
534
+
535
+            if (child.material) {
536
+              child.material.opacity = 0.5 * (1 - t * 0.85)
537
+            }
538
+
539
+            child.position.x += Math.sin(elapsed * 0.7 + phase * 4) * 0.001
540
+            child.position.z += Math.cos(elapsed * 0.5 + phase * 3) * 0.001
541
+          }
542
+
543
+          // Grass blade swaying
544
+          if (child.userData.isGrassBlade) {
545
+            const phase = child.userData.phase
546
+            const baseX = child.userData.baseRotX
547
+            const baseZ = child.userData.baseRotZ
548
+            child.rotation.x = baseX + Math.sin(elapsed * 1.8 + phase) * 0.12
549
+            child.rotation.z = baseZ + Math.cos(elapsed * 1.4 + phase) * 0.08
550
+          }
551
+
552
+          // Sprite running around the yard
553
+          if (child.userData.isBootSprite) {
554
+            const orbitRadius = child.userData.orbitRadius
555
+            const orbitSpeed = child.userData.orbitSpeed
556
+            const orbitPhase = child.userData.orbitPhase
557
+            const bobPhase = child.userData.bobPhase
558
+            const centerX = child.userData.orbitCenterX
559
+            const centerZ = child.userData.orbitCenterZ
560
+
561
+            // Orbit position
562
+            const angle = elapsed * orbitSpeed + orbitPhase
563
+            child.position.x = centerX + Math.cos(angle) * orbitRadius
564
+            child.position.z = centerZ + Math.sin(angle) * orbitRadius
565
+
566
+            // Bobbing up and down while running
567
+            child.position.y = Math.abs(Math.sin(elapsed * 8 + bobPhase)) * 0.02
568
+
569
+            // Face direction of movement
570
+            child.rotation.y = angle + Math.PI / 2
571
+
572
+            // Animate legs
573
+            child.traverse((part) => {
574
+              if (part.userData.isLeg) {
575
+                const legSwing = Math.sin(elapsed * 12 + bobPhase) * 0.4
576
+                if (part.userData.legSide === 'left') {
577
+                  part.rotation.x = legSwing
578
+                } else {
579
+                  part.rotation.x = -legSwing
580
+                }
581
+              }
582
+            })
583
+          }
584
+        })
585
+      }
518586
     }
519587
   }
520588
 
src/renderers/three/buildings.jsmodified
@@ -528,6 +528,381 @@ export function createOnionHouse(gradientMap) {
528528
   return group
529529
 }
530530
 
531
+// Create a whimsical boot house with yard, sprites, and bendy chimney
532
+export function createBootHouse(gradientMap) {
533
+  const group = new THREE.Group()
534
+
535
+  // Boot leather material - warm brown
536
+  const leatherMaterial = new THREE.MeshToonMaterial({
537
+    color: 0x8b4513,
538
+    gradientMap
539
+  })
540
+
541
+  // Darker leather for details
542
+  const darkLeatherMaterial = new THREE.MeshToonMaterial({
543
+    color: 0x5c3317,
544
+    gradientMap
545
+  })
546
+
547
+  // Sole material - darker rubber-like
548
+  const soleMaterial = new THREE.MeshToonMaterial({
549
+    color: 0x2d1f14,
550
+    gradientMap
551
+  })
552
+
553
+  // Lace/tongue material
554
+  const laceMaterial = new THREE.MeshToonMaterial({
555
+    color: 0xdaa520,
556
+    gradientMap
557
+  })
558
+
559
+  // Chimney brick
560
+  const brickMaterial = new THREE.MeshToonMaterial({
561
+    color: 0x8b4513,
562
+    gradientMap
563
+  })
564
+
565
+  // Window glow
566
+  const windowMaterial = new THREE.MeshBasicMaterial({
567
+    color: 0xffffcc,
568
+    transparent: true,
569
+    opacity: 0.8
570
+  })
571
+
572
+  // Grass material
573
+  const grassMaterial = new THREE.MeshToonMaterial({
574
+    color: 0x4a7c3f,
575
+    gradientMap
576
+  })
577
+
578
+  // Sprite body colors
579
+  const spriteColors = [0xff6b9d, 0x9b59b6, 0x3498db, 0x2ecc71]
580
+
581
+  // === BOOT STRUCTURE ===
582
+
583
+  // Boot sole (the base)
584
+  const soleGeom = new THREE.BoxGeometry(1.0, 0.12, 0.6)
585
+  soleGeom.translate(0.1, 0, 0) // Offset for toe curve
586
+  const sole = new THREE.Mesh(soleGeom, soleMaterial)
587
+  sole.position.y = 0.06
588
+  group.add(sole)
589
+
590
+  // Toe section - curved front of boot (rounded box)
591
+  const toeGeom = new THREE.SphereGeometry(0.35, 10, 8)
592
+  toeGeom.scale(1.2, 0.7, 1.0)
593
+  const toe = new THREE.Mesh(toeGeom, leatherMaterial)
594
+  toe.position.set(-0.35, 0.25, 0)
595
+  group.add(toe)
596
+
597
+  // Main boot body (the ankle/shaft part) - this is the house
598
+  const shaftGeom = new THREE.CylinderGeometry(0.32, 0.38, 0.9, 10)
599
+  const shaft = new THREE.Mesh(shaftGeom, leatherMaterial)
600
+  shaft.position.set(0.25, 0.6, 0)
601
+  group.add(shaft)
602
+
603
+  // Boot rim/collar at top
604
+  const rimGeom = new THREE.TorusGeometry(0.34, 0.05, 6, 16)
605
+  const rim = new THREE.Mesh(rimGeom, darkLeatherMaterial)
606
+  rim.position.set(0.25, 1.05, 0)
607
+  rim.rotation.x = Math.PI / 2
608
+  group.add(rim)
609
+
610
+  // Tongue (extending from shaft toward toe)
611
+  const tongueGeom = new THREE.BoxGeometry(0.18, 0.4, 0.08)
612
+  const tongue = new THREE.Mesh(tongueGeom, laceMaterial)
613
+  tongue.position.set(0.05, 0.5, 0.32)
614
+  tongue.rotation.x = 0.3
615
+  group.add(tongue)
616
+
617
+  // Lace holes (decorative dots)
618
+  const holeGeom = new THREE.CylinderGeometry(0.025, 0.025, 0.02, 8)
619
+  for (let i = 0; i < 4; i++) {
620
+    const holeL = new THREE.Mesh(holeGeom, soleMaterial)
621
+    holeL.position.set(-0.05, 0.35 + i * 0.15, 0.36)
622
+    holeL.rotation.x = Math.PI / 2
623
+    group.add(holeL)
624
+
625
+    const holeR = new THREE.Mesh(holeGeom, soleMaterial)
626
+    holeR.position.set(0.15, 0.35 + i * 0.15, 0.36)
627
+    holeR.rotation.x = Math.PI / 2
628
+    group.add(holeR)
629
+  }
630
+
631
+  // Cross-laces between holes
632
+  const laceCordMaterial = new THREE.MeshToonMaterial({
633
+    color: 0xf5deb3,
634
+    gradientMap
635
+  })
636
+  for (let i = 0; i < 3; i++) {
637
+    const cordGeom = new THREE.CylinderGeometry(0.01, 0.01, 0.22, 4)
638
+    const cord = new THREE.Mesh(cordGeom, laceCordMaterial)
639
+    cord.position.set(0.05, 0.42 + i * 0.15, 0.37)
640
+    cord.rotation.z = Math.PI / 2
641
+    cord.rotation.x = 0.3
642
+    group.add(cord)
643
+  }
644
+
645
+  // Heel bump
646
+  const heelGeom = new THREE.SphereGeometry(0.18, 8, 6)
647
+  heelGeom.scale(1.0, 0.8, 0.6)
648
+  const heel = new THREE.Mesh(heelGeom, soleMaterial)
649
+  heel.position.set(0.55, 0.1, 0)
650
+  group.add(heel)
651
+
652
+  // === WINDOWS ===
653
+
654
+  // Round window on toe
655
+  const toeWindowGeom = new THREE.CircleGeometry(0.08, 10)
656
+  const toeWindow = new THREE.Mesh(toeWindowGeom, windowMaterial)
657
+  toeWindow.position.set(-0.58, 0.3, 0)
658
+  toeWindow.rotation.y = -Math.PI / 2
659
+  group.add(toeWindow)
660
+
661
+  // Window frame
662
+  const toeFrameGeom = new THREE.TorusGeometry(0.08, 0.015, 4, 16)
663
+  const toeFrame = new THREE.Mesh(toeFrameGeom, darkLeatherMaterial)
664
+  toeFrame.position.set(-0.57, 0.3, 0)
665
+  toeFrame.rotation.y = -Math.PI / 2
666
+  group.add(toeFrame)
667
+
668
+  // Windows on shaft (2 small ones)
669
+  for (let i = 0; i < 2; i++) {
670
+    const angle = (i === 0) ? Math.PI * 0.7 : -Math.PI * 0.7
671
+    const wx = 0.25 + Math.sin(angle) * 0.33
672
+    const wz = Math.cos(angle) * 0.33
673
+
674
+    const shaftWindowGeom = new THREE.PlaneGeometry(0.12, 0.15)
675
+    const shaftWindow = new THREE.Mesh(shaftWindowGeom, windowMaterial)
676
+    shaftWindow.position.set(wx, 0.6, wz)
677
+    shaftWindow.rotation.y = angle + Math.PI
678
+    group.add(shaftWindow)
679
+
680
+    // Frame
681
+    const frameGeom = new THREE.BoxGeometry(0.14, 0.17, 0.02)
682
+    const frame = new THREE.Mesh(frameGeom, darkLeatherMaterial)
683
+    frame.position.set(wx, 0.6, wz)
684
+    frame.rotation.y = angle + Math.PI
685
+    group.add(frame)
686
+  }
687
+
688
+  // === BENDY CHIMNEY ===
689
+
690
+  const chimneyGroup = new THREE.Group()
691
+  chimneyGroup.userData.isChimney = true
692
+
693
+  // Build a crooked/bendy chimney from segments
694
+  const segments = [
695
+    { y: 0, height: 0.15, radius: 0.06, tiltX: 0, tiltZ: 0.15 },
696
+    { y: 0.14, height: 0.12, radius: 0.055, tiltX: 0.1, tiltZ: -0.2 },
697
+    { y: 0.25, height: 0.12, radius: 0.05, tiltX: -0.15, tiltZ: 0.25 },
698
+    { y: 0.36, height: 0.1, radius: 0.045, tiltX: 0.05, tiltZ: -0.1 }
699
+  ]
700
+
701
+  let lastPos = new THREE.Vector3(0, 0, 0)
702
+  for (const seg of segments) {
703
+    const segGeom = new THREE.CylinderGeometry(seg.radius * 0.9, seg.radius, seg.height, 6)
704
+    const segMesh = new THREE.Mesh(segGeom, brickMaterial)
705
+    segMesh.position.copy(lastPos)
706
+    segMesh.position.y += seg.height / 2
707
+    segMesh.rotation.x = seg.tiltX
708
+    segMesh.rotation.z = seg.tiltZ
709
+    chimneyGroup.add(segMesh)
710
+
711
+    // Track where next segment starts (approximate offset from tilt)
712
+    lastPos.y += seg.height
713
+    lastPos.x += Math.sin(seg.tiltZ) * seg.height * 0.5
714
+    lastPos.z += Math.sin(seg.tiltX) * seg.height * 0.5
715
+  }
716
+
717
+  // Chimney cap
718
+  const capGeom = new THREE.CylinderGeometry(0.06, 0.04, 0.04, 6)
719
+  const cap = new THREE.Mesh(capGeom, darkLeatherMaterial)
720
+  cap.position.copy(lastPos)
721
+  cap.position.y += 0.02
722
+  chimneyGroup.add(cap)
723
+
724
+  // Smoke puffs
725
+  const smokeGroup = new THREE.Group()
726
+  const smokeMaterial = new THREE.MeshBasicMaterial({
727
+    color: 0xdddddd,
728
+    transparent: true,
729
+    opacity: 0.5
730
+  })
731
+
732
+  for (let i = 0; i < 4; i++) {
733
+    const puffGeom = new THREE.SphereGeometry(0.035, 6, 4)
734
+    const puff = new THREE.Mesh(puffGeom, smokeMaterial.clone())
735
+    puff.userData.isSmokePuff = true
736
+    puff.userData.phase = i * 0.25
737
+    puff.position.set(
738
+      lastPos.x + (Math.random() - 0.5) * 0.02,
739
+      lastPos.y + 0.05,
740
+      lastPos.z + (Math.random() - 0.5) * 0.02
741
+    )
742
+    smokeGroup.add(puff)
743
+  }
744
+  chimneyGroup.add(smokeGroup)
745
+
746
+  chimneyGroup.position.set(0.1, 1.0, -0.15)
747
+  group.add(chimneyGroup)
748
+
749
+  // === DOOR ===
750
+
751
+  const doorGroup = new THREE.Group()
752
+
753
+  // Arched door on the toe
754
+  const doorGeom = new THREE.BoxGeometry(0.15, 0.25, 0.03)
755
+  const door = new THREE.Mesh(doorGeom, darkLeatherMaterial)
756
+  door.position.y = 0.125
757
+  doorGroup.add(door)
758
+
759
+  // Door arch
760
+  const doorArchGeom = new THREE.SphereGeometry(0.075, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2)
761
+  const doorArch = new THREE.Mesh(doorArchGeom, darkLeatherMaterial)
762
+  doorArch.position.y = 0.25
763
+  doorArch.rotation.x = Math.PI
764
+  doorGroup.add(doorArch)
765
+
766
+  // Door knob
767
+  const knobGeom = new THREE.SphereGeometry(0.018, 6, 4)
768
+  const knobMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap })
769
+  const knob = new THREE.Mesh(knobGeom, knobMat)
770
+  knob.position.set(0.05, 0.12, 0.02)
771
+  doorGroup.add(knob)
772
+
773
+  doorGroup.position.set(-0.25, 0.12, 0.35)
774
+  group.add(doorGroup)
775
+
776
+  // === YARD WITH TALL GRASS PATCHES ===
777
+
778
+  const yardGroup = new THREE.Group()
779
+  yardGroup.userData.isYard = true
780
+
781
+  // Create grass patches around the boot
782
+  const grassPositions = [
783
+    { x: -0.7, z: 0.4, count: 8 },
784
+    { x: -0.6, z: -0.35, count: 6 },
785
+    { x: 0.7, z: 0.35, count: 7 },
786
+    { x: 0.65, z: -0.3, count: 5 },
787
+    { x: -0.1, z: -0.5, count: 6 },
788
+    { x: 0.4, z: 0.5, count: 5 }
789
+  ]
790
+
791
+  for (const patch of grassPositions) {
792
+    for (let i = 0; i < patch.count; i++) {
793
+      const bladeHeight = 0.12 + Math.random() * 0.15
794
+      const bladeGeom = new THREE.ConeGeometry(0.015, bladeHeight, 4)
795
+      const blade = new THREE.Mesh(bladeGeom, grassMaterial)
796
+
797
+      const offsetX = (Math.random() - 0.5) * 0.2
798
+      const offsetZ = (Math.random() - 0.5) * 0.2
799
+
800
+      blade.position.set(
801
+        patch.x + offsetX,
802
+        bladeHeight / 2,
803
+        patch.z + offsetZ
804
+      )
805
+
806
+      // Random lean
807
+      blade.rotation.x = (Math.random() - 0.5) * 0.3
808
+      blade.rotation.z = (Math.random() - 0.5) * 0.3
809
+
810
+      blade.userData.isGrassBlade = true
811
+      blade.userData.phase = Math.random() * Math.PI * 2
812
+      blade.userData.baseRotX = blade.rotation.x
813
+      blade.userData.baseRotZ = blade.rotation.z
814
+
815
+      yardGroup.add(blade)
816
+    }
817
+  }
818
+
819
+  group.add(yardGroup)
820
+
821
+  // === TINY SPRITES ===
822
+
823
+  const spritesGroup = new THREE.Group()
824
+  spritesGroup.userData.isSpritesGroup = true
825
+
826
+  // Create 4 tiny sprites that will run around
827
+  for (let i = 0; i < 4; i++) {
828
+    const sprite = new THREE.Group()
829
+    sprite.userData.isBootSprite = true
830
+
831
+    // Sprite body (small sphere)
832
+    const bodyGeom = new THREE.SphereGeometry(0.04, 8, 6)
833
+    const bodyMat = new THREE.MeshToonMaterial({
834
+      color: spriteColors[i],
835
+      gradientMap
836
+    })
837
+    const body = new THREE.Mesh(bodyGeom, bodyMat)
838
+    body.position.y = 0.06
839
+    sprite.add(body)
840
+
841
+    // Tiny legs (2 little cylinders)
842
+    const legGeom = new THREE.CylinderGeometry(0.008, 0.01, 0.04, 4)
843
+    const legMat = new THREE.MeshToonMaterial({ color: 0x333333, gradientMap })
844
+
845
+    const legL = new THREE.Mesh(legGeom, legMat)
846
+    legL.position.set(-0.02, 0.02, 0)
847
+    legL.userData.isLeg = true
848
+    legL.userData.legSide = 'left'
849
+    sprite.add(legL)
850
+
851
+    const legR = new THREE.Mesh(legGeom, legMat)
852
+    legR.position.set(0.02, 0.02, 0)
853
+    legR.userData.isLeg = true
854
+    legR.userData.legSide = 'right'
855
+    sprite.add(legR)
856
+
857
+    // Little eyes (2 white dots)
858
+    const eyeGeom = new THREE.SphereGeometry(0.012, 6, 4)
859
+    const eyeMat = new THREE.MeshBasicMaterial({ color: 0xffffff })
860
+    const pupilGeom = new THREE.SphereGeometry(0.006, 4, 3)
861
+    const pupilMat = new THREE.MeshBasicMaterial({ color: 0x000000 })
862
+
863
+    const eyeL = new THREE.Mesh(eyeGeom, eyeMat)
864
+    eyeL.position.set(-0.015, 0.07, 0.03)
865
+    sprite.add(eyeL)
866
+
867
+    const pupilL = new THREE.Mesh(pupilGeom, pupilMat)
868
+    pupilL.position.set(-0.015, 0.07, 0.04)
869
+    sprite.add(pupilL)
870
+
871
+    const eyeR = new THREE.Mesh(eyeGeom, eyeMat)
872
+    eyeR.position.set(0.015, 0.07, 0.03)
873
+    sprite.add(eyeR)
874
+
875
+    const pupilR = new THREE.Mesh(pupilGeom, pupilMat)
876
+    pupilR.position.set(0.015, 0.07, 0.04)
877
+    sprite.add(pupilR)
878
+
879
+    // Animation data
880
+    sprite.userData.orbitRadius = 0.5 + Math.random() * 0.3
881
+    sprite.userData.orbitSpeed = 0.8 + Math.random() * 0.6
882
+    sprite.userData.orbitPhase = (i / 4) * Math.PI * 2
883
+    sprite.userData.bobPhase = Math.random() * Math.PI * 2
884
+    sprite.userData.orbitCenterX = 0.1 // Roughly center of boot
885
+    sprite.userData.orbitCenterZ = 0
886
+
887
+    // Initial position
888
+    const angle = sprite.userData.orbitPhase
889
+    sprite.position.set(
890
+      sprite.userData.orbitCenterX + Math.cos(angle) * sprite.userData.orbitRadius,
891
+      0,
892
+      sprite.userData.orbitCenterZ + Math.sin(angle) * sprite.userData.orbitRadius
893
+    )
894
+
895
+    spritesGroup.add(sprite)
896
+  }
897
+
898
+  group.add(spritesGroup)
899
+
900
+  // Mark for building identification
901
+  group.userData.isBootHouse = true
902
+
903
+  return group
904
+}
905
+
531906
 // Factory function to create building by type
532907
 export function createBuilding(type, gradientMap) {
533908
   switch (type) {
@@ -543,6 +918,8 @@ export function createBuilding(type, gradientMap) {
543918
       return createFence(gradientMap)
544919
     case 'onion_house':
545920
       return createOnionHouse(gradientMap)
921
+    case 'boot_house':
922
+      return createBootHouse(gradientMap)
546923
     default:
547924
       console.warn('Unknown building type:', type)
548925
       return new THREE.Group()
src/renderers/three/shop/items.jsmodified
@@ -1536,6 +1536,14 @@ export const BUILDINGS = {
15361536
     price: 45,
15371537
     zoneType: 'shore',
15381538
     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
15391547
   }
15401548
 }
15411549