Skip to content

Commit e4ee799

Browse files
Merge branch 'main' into update-socials
2 parents d266235 + c95f41c commit e4ee799

File tree

7 files changed

+131
-22
lines changed

7 files changed

+131
-22
lines changed

src/components/game/engine/GameEngine.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,16 +739,22 @@ export class GameEngine {
739739
this.coinSystem.update(delta, this.coins)
740740
this.cameraSystem.update(delta, time)
741741

742+
// Respawn coins that have been collected for long enough
743+
useGameStore.getState().respawnCoins()
744+
742745
if (state.stage === 'battle') {
743746
this.aiSystem.update(delta)
744747
this.cannonballSystem.update(delta, this.cannonballs)
745748
this.aiDebug.update()
746749

747750
// Health regeneration
748-
const { boatHealth, shipStats, setBoatHealth } = useGameStore.getState()
749-
if (boatHealth < shipStats.maxHealth && shipStats.healthRegen > 0) {
751+
const { boatHealth, shipStats, purchasedBoosts, setBoatHealth } =
752+
useGameStore.getState()
753+
const totalMaxHealth =
754+
shipStats.maxHealth + purchasedBoosts.permHealth * 25
755+
if (boatHealth < totalMaxHealth && shipStats.healthRegen > 0) {
750756
const newHealth = Math.min(
751-
shipStats.maxHealth,
757+
totalMaxHealth,
752758
boatHealth + shipStats.healthRegen * delta,
753759
)
754760
setBoatHealth(newHealth)

src/components/game/engine/systems/AISystem.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ const AI_STATS: Record<AIDifficulty, AIStats> = {
6868
aggroDistance: 90,
6969
attackDistance: 38,
7070
},
71-
// Corner boss - absolutely insane
71+
// Corner boss - tough but beatable
7272
boss: {
73-
health: 500,
74-
fireCooldown: 200, // Extremely fast fire
75-
speed: 12,
76-
turnSpeed: 5.0,
77-
accuracy: 0.98,
73+
health: 350,
74+
fireCooldown: 350, // Fast fire but not overwhelming
75+
speed: 10,
76+
turnSpeed: 4.0,
77+
accuracy: 0.9,
7878
aggroDistance: 30, // Tiny aggro range - must get close
7979
attackDistance: 25,
8080
},

src/components/game/hooks/useGameStore.ts

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const STORAGE_KEY = 'tanstack-island-explorer'
1818
interface PurchasedBoosts {
1919
permSpeed: number // Number of permanent speed boosts purchased
2020
permAccel: number // Number of permanent acceleration boosts purchased
21+
permHealth: number // Number of permanent health boosts purchased (+25 each, max 8)
2122
rapidFire: boolean // Whether rapid fire (auto-loader) is purchased
2223
}
2324

@@ -119,6 +120,7 @@ export interface CoinData {
119120
id: number
120121
position: [number, number, number]
121122
collected: boolean
123+
collectedAt?: number // Timestamp when collected (for respawn timer)
122124
}
123125

124126
export type BoatType = 'dinghy' | 'ship'
@@ -233,6 +235,7 @@ interface GameState {
233235
coinsCollected: number
234236
setCoins: (coins: CoinData[]) => void
235237
collectCoin: (id: number) => void
238+
respawnCoins: () => void // Respawn coins that have been collected for long enough
236239

237240
// Boundary collision (which edges are being hit)
238241
boundaryEdges: {
@@ -351,6 +354,7 @@ const initialState = {
351354
purchasedBoosts: {
352355
permSpeed: 0,
353356
permAccel: 0,
357+
permHealth: 0,
354358
rapidFire: false,
355359
} as PurchasedBoosts,
356360
}
@@ -636,12 +640,31 @@ export const useGameStore = create<GameState>()((set, get) => ({
636640
const coin = coins.find((c) => c.id === id)
637641
if (coin && !coin.collected) {
638642
const newCoins = coins.map((c) =>
639-
c.id === id ? { ...c, collected: true } : c,
643+
c.id === id ? { ...c, collected: true, collectedAt: Date.now() } : c,
640644
)
641645
set({ coins: newCoins, coinsCollected: coinsCollected + 1 })
642646
}
643647
},
644648

649+
respawnCoins: () => {
650+
const { coins } = get()
651+
const now = Date.now()
652+
const RESPAWN_DELAY = 60000 // 60 seconds to respawn
653+
654+
let hasChanges = false
655+
const newCoins = coins.map((c) => {
656+
if (c.collected && c.collectedAt && now - c.collectedAt > RESPAWN_DELAY) {
657+
hasChanges = true
658+
return { ...c, collected: false, collectedAt: undefined }
659+
}
660+
return c
661+
})
662+
663+
if (hasChanges) {
664+
set({ coins: newCoins })
665+
}
666+
},
667+
645668
toggleMute: () => set((state) => ({ isMuted: !state.isMuted })),
646669

647670
setShowCollisionDebug: (show) => set({ showCollisionDebug: show }),
@@ -835,8 +858,9 @@ export const useGameStore = create<GameState>()((set, get) => ({
835858
break
836859
}
837860
case 'healthPack': {
838-
// Restore 50 health, up to max
839-
const newHealth = Math.min(boatHealth + 50, shipStats.maxHealth)
861+
// Restore 50 health, up to max (including purchased health boosts)
862+
const maxHealth = shipStats.maxHealth + purchasedBoosts.permHealth * 25
863+
const newHealth = Math.min(boatHealth + 50, maxHealth)
840864
set({ boatHealth: newHealth })
841865
break
842866
}
@@ -860,6 +884,20 @@ export const useGameStore = create<GameState>()((set, get) => ({
860884
})
861885
break
862886
}
887+
case 'permHealth': {
888+
// Permanent +25 max health boost
889+
const newPermHealth = purchasedBoosts.permHealth + 1
890+
const healthBonus = 25 // Each purchase adds 25 max health
891+
set({
892+
purchasedBoosts: {
893+
...purchasedBoosts,
894+
permHealth: newPermHealth,
895+
},
896+
// Also increase current health by the bonus amount
897+
boatHealth: boatHealth + healthBonus,
898+
})
899+
break
900+
}
863901
case 'rapidFire': {
864902
// Unlock auto-loader (rapid fire side cannons)
865903
set({
@@ -930,19 +968,58 @@ export const useGameStore = create<GameState>()((set, get) => ({
930968
unlockedUpgrades,
931969
shipStats,
932970
purchasedBoosts,
971+
boatPosition,
933972
} = get()
934973

935974
// World boundary depends on showcase unlock status
936975
const worldBoundary = showcaseUnlocked ? 520 : EXPANDED_WORLD_BOUNDARY
937976

977+
// Find nearest discovered island to respawn at
978+
const allIslands = [
979+
...islands,
980+
...expandedIslands,
981+
...showcaseIslands,
982+
...cornerIslands,
983+
]
984+
const discoveredIslandsList = allIslands.filter((i) =>
985+
discoveredIslands.has(i.id),
986+
)
987+
988+
let spawnPosition: [number, number, number] = [0, 0, 0]
989+
if (discoveredIslandsList.length > 0) {
990+
// Find nearest discovered island to death position
991+
let nearestIsland = discoveredIslandsList[0]
992+
let nearestDist = Infinity
993+
for (const island of discoveredIslandsList) {
994+
const dx = island.position[0] - boatPosition[0]
995+
const dz = island.position[2] - boatPosition[2]
996+
const dist = dx * dx + dz * dz
997+
if (dist < nearestDist) {
998+
nearestDist = dist
999+
nearestIsland = island
1000+
}
1001+
}
1002+
// Spawn slightly away from the island (not inside it)
1003+
const spawnOffset = 8
1004+
const angle = Math.random() * Math.PI * 2
1005+
spawnPosition = [
1006+
nearestIsland.position[0] + Math.cos(angle) * spawnOffset,
1007+
0,
1008+
nearestIsland.position[2] + Math.sin(angle) * spawnOffset,
1009+
]
1010+
}
1011+
1012+
// Calculate total max health (upgrades + purchased boosts)
1013+
const totalMaxHealth = shipStats.maxHealth + purchasedBoosts.permHealth * 25
1014+
9381015
set({
9391016
phase: 'playing',
9401017
stage: 'battle',
9411018
boatType: 'ship',
942-
boatPosition: [0, 0, 0],
1019+
boatPosition: spawnPosition,
9431020
boatRotation: 0,
9441021
boatVelocity: 0,
945-
boatHealth: shipStats.maxHealth,
1022+
boatHealth: totalMaxHealth,
9461023
isMovingForward: false,
9471024
isMovingBackward: false,
9481025
isTurningLeft: false,

src/components/game/ui/GameHUD.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function GameHUD() {
1717
coinsCollected,
1818
boatHealth,
1919
shipStats,
20+
purchasedBoosts,
2021
lastFireTime,
2122
lastUnlockedUpgrade,
2223
clearLastUnlockedUpgrade,
@@ -25,6 +26,9 @@ export function GameHUD() {
2526
fireCannon,
2627
} = useGameStore()
2728

29+
// Calculate total max health including purchased boosts
30+
const totalMaxHealth = shipStats.maxHealth + purchasedBoosts.permHealth * 25
31+
2832
// Cooldown progress (0-1, 1 = ready)
2933
const [cooldownProgress, setCooldownProgress] = useState(1)
3034
const [showUpgradeNotification, setShowUpgradeNotification] = useState(false)
@@ -194,12 +198,12 @@ export function GameHUD() {
194198
<div
195199
className="h-full bg-gradient-to-r from-red-600 to-red-400"
196200
style={{
197-
width: `${(boatHealth / shipStats.maxHealth) * 100}%`,
201+
width: `${(boatHealth / totalMaxHealth) * 100}%`,
198202
}}
199203
/>
200204
</div>
201205
<span className="text-white text-sm font-mono w-12 max-md:text-xs max-md:w-10">
202-
{Math.round(boatHealth)}/{shipStats.maxHealth}
206+
{Math.round(boatHealth)}/{totalMaxHealth}
203207
</span>
204208
</div>
205209

src/components/game/ui/Minimap.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function Minimap() {
2828
islands,
2929
expandedIslands,
3030
showcaseIslands,
31+
cornerIslands,
3132
discoveredIslands,
3233
} = useGameStore()
3334
const [minimapSize, setMinimapSize] = useState(getMinimapSize)
@@ -77,8 +78,13 @@ export function Minimap() {
7778

7879
const { width: W, height: H } = minimapSize
7980

80-
// All islands (core + expanded + showcase)
81-
const allIslands = [...islands, ...expandedIslands, ...showcaseIslands]
81+
// All islands (core + expanded + showcase + corners)
82+
const allIslands = [
83+
...islands,
84+
...expandedIslands,
85+
...showcaseIslands,
86+
...cornerIslands,
87+
]
8288

8389
// Convert world position to isometric minimap position
8490
const worldToMinimap = (worldX: number, worldZ: number) => {
@@ -197,14 +203,17 @@ export function Minimap() {
197203
{/* Discovered islands */}
198204
{discoveredIslandsList.map((island) => {
199205
const pos = worldToMinimap(island.position[0], island.position[2])
206+
const isCorner = island.id.startsWith('corner-')
200207
const color =
201208
island.type === 'library' && island.library
202209
? getLibraryColor(island.library.id)
203210
: island.type === 'partner'
204211
? '#f59e0b' // Amber for partners
205-
: island.type === 'showcase'
206-
? '#8b5cf6' // Purple for showcases
207-
: '#8b5cf6'
212+
: isCorner
213+
? '#ef4444' // Red for corner boss islands
214+
: island.type === 'showcase'
215+
? '#8b5cf6' // Purple for showcases
216+
: '#8b5cf6'
208217
const size = (1.5 + island.scale * 1) * scale
209218

210219
return (

src/components/game/ui/Shop.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export function Shop() {
3636
? purchasedBoosts.permSpeed
3737
: item.type === 'permAccel'
3838
? purchasedBoosts.permAccel
39-
: 0
39+
: item.type === 'permHealth'
40+
? purchasedBoosts.permHealth
41+
: 0
4042

4143
// Check if already owned (non-stackable)
4244
const alreadyOwned =

src/components/game/utils/shopItems.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type ShopItemType =
66
| 'healthPack'
77
| 'permSpeed'
88
| 'permAccel'
9+
| 'permHealth'
910
| 'rapidFire'
1011

1112
export interface ShopItem {
@@ -67,6 +68,16 @@ export const SHOP_ITEMS: ShopItem[] = [
6768
stackable: true,
6869
maxStacks: 0, // Unlimited
6970
},
71+
{
72+
type: 'permHealth',
73+
name: 'Hull Plating',
74+
description: 'Permanent +25 max health',
75+
cost: 30,
76+
icon: '🛡️',
77+
stages: ['battle'],
78+
stackable: true,
79+
maxStacks: 8, // Max 200 bonus health (300 total with base 100)
80+
},
7081
{
7182
type: 'rapidFire',
7283
name: 'Auto-Loader',

0 commit comments

Comments
 (0)