diff --git a/src/components/gameplay/PlayerMapView.js b/src/components/gameplay/PlayerMapView.js index 4476ae4..70eacf2 100644 --- a/src/components/gameplay/PlayerMapView.js +++ b/src/components/gameplay/PlayerMapView.js @@ -1,9 +1,9 @@ import React from 'react'; import {Platform, StyleSheet, View} from 'react-native'; -import MapView, {Circle, Marker, Polygon} from 'react-native-maps'; +import MapView, {Marker, Polygon} from 'react-native-maps'; import {getFovCone} from '../../utils/geoMath'; -const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) => { +const PlayerMapView = ({userLocation, visibleCaches, heading}) => { // Initialisation ------------- // State ---------------------- // Handlers ------------------- @@ -26,27 +26,21 @@ const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) => pitchEnabled={false} showsUserLocation > - {/* Cache markers — uses a custom View instead of pinColor so the - marker renders identically on Apple Maps (iOS) and Google Maps */} + {/* Cache pin markers — appear temporarily when the cache is in the FOV + cone AND within claim distance. Uses a custom View so the pin + renders identically on iOS (Google Maps) and Android */} {(visibleCaches || []).map((cache) => ( - - - - - - - - + + + + + ))} {/* FOV heading cone */} {coneCoords && ( diff --git a/src/components/screens/ExpandedMapScreen.js b/src/components/screens/ExpandedMapScreen.js index be7df57..6783678 100644 --- a/src/components/screens/ExpandedMapScreen.js +++ b/src/components/screens/ExpandedMapScreen.js @@ -107,26 +107,19 @@ const ExpandedMapScreen = ({route}) => { /> ))} - {/* Players — custom View markers for Apple Maps compatibility */} + {/* Players — pin markers appear when cache is in FOV cone + within range */} {!isAdmin && visiblePlayerCaches.map((cache) => ( - - - - - - - - + + + + + ))} {/* Heading FOV cone */} {coneCoords && ( diff --git a/src/components/screens/MapScreen.js b/src/components/screens/MapScreen.js index d199115..fb4dd55 100644 --- a/src/components/screens/MapScreen.js +++ b/src/components/screens/MapScreen.js @@ -73,12 +73,14 @@ const MapScreen = ({navigation}) => { }); }, [cacheRecords, isPlayer, session.currentTid, session.currentUid, EMPTY_CACHES]); - // When requiresTeam is true pass null/empty so usePlayerGame is inert + // When requiresTeam is true pass null/empty so usePlayerGame is inert. + // selectedCacheId is the 5th param — only that cache can trigger the claim countdown. const {visibleCaches, isClaiming, setIsClaiming} = usePlayerGame( (isPlayer && !requiresTeam) ? userLocation : null, (isPlayer && !requiresTeam) ? heading : null, requiresTeam ? EMPTY_CACHES : activeCachesForPlayer, claimDistance, + selectedCacheId, ); const mapRegion = userLocation @@ -521,8 +523,8 @@ const MapScreen = ({navigation}) => { ); } - // Player in game — use the first visible cache for the claim timer - const claimTarget = visibleCaches.length > 0 ? visibleCaches[0] : null; + // Player in game — the claim target is the selected cache (only it can trigger the countdown) + const claimTarget = visibleCaches.find((c) => c.id === selectedCacheId) || null; return ( @@ -534,7 +536,6 @@ const MapScreen = ({navigation}) => { userLocation={userLocation} visibleCaches={visibleCaches} heading={heading} - claimDistance={claimDistance} /> ⛶ diff --git a/src/hooks/usePlayerGame.js b/src/hooks/usePlayerGame.js index 44fbb8c..66b50e0 100644 --- a/src/hooks/usePlayerGame.js +++ b/src/hooks/usePlayerGame.js @@ -1,7 +1,7 @@ import {useEffect, useRef, useState} from 'react'; import {isInClaimCone} from '../utils/geoMath'; -const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance) => { +const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance, selectedCacheId) => { // State ---------------------- const [visibleCaches, setVisibleCaches] = useState([]); @@ -10,19 +10,16 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc // Handlers ------------------- + // Determine which caches fall inside the FOV claim cone useEffect(() => { - // Both location AND heading are required — the player must physically - // point their device towards a cache to trigger the claim countdown if (!playerLocation || playerHeading === null || playerHeading === undefined) { if (prevIdsRef.current !== '') { prevIdsRef.current = ''; setVisibleCaches([]); - setIsClaiming(false); } return; } - // Find all caches that fall inside the claim cone const inCone = (activeCaches || []).filter((cache) => isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance), ); @@ -32,10 +29,21 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc if (newIds !== prevIdsRef.current) { prevIdsRef.current = newIds; setVisibleCaches(inCone); - setIsClaiming(inCone.length > 0); } }, [playerLocation, playerHeading, activeCaches, claimDistance]); + // Claiming requires ALL four criteria: + // 1. Cache not already claimed by player's team (handled by activeCaches filter) + // 2. Cache within claim distance (handled by isInClaimCone) + // 3. Player looking at the cache (in FOV cone) (handled by isInClaimCone) + // 4. Cache is the one currently selected in list (checked here) + useEffect(() => { + const canClaim = Boolean( + selectedCacheId && visibleCaches.some((c) => c.id === selectedCacheId), + ); + setIsClaiming(canClaim); + }, [visibleCaches, selectedCacheId]); + // Return --------------------- return {visibleCaches, isClaiming, setIsClaiming};