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};