@@ -18,6 +18,7 @@ const STORAGE_KEY = 'tanstack-island-explorer'
1818interface 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
124126export 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 ,
0 commit comments