diff --git a/src/components/gameplay/PlayerMapView.js b/src/components/gameplay/PlayerMapView.js index 9617411..2a405c3 100644 --- a/src/components/gameplay/PlayerMapView.js +++ b/src/components/gameplay/PlayerMapView.js @@ -11,8 +11,9 @@ const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) => if (!userLocation) return null; - const hasHeading = heading !== null && heading !== undefined; - const coneCoords = hasHeading ? getFovCone(userLocation, heading) : null; + const coneCoords = (heading !== null && heading !== undefined) + ? getFovCone(userLocation, heading) + : null; return ( /> ))} - {/* FOV cone when heading is available */} + {/* FOV heading cone */} {coneCoords && ( strokeWidth={1} /> )} - {/* Proximity circle when heading is NOT available (iOS fallback) */} - {!hasHeading && claimDistance > 0 && ( - - )} ); }; diff --git a/src/components/screens/ExpandedMapScreen.js b/src/components/screens/ExpandedMapScreen.js index 1530841..289eb6b 100644 --- a/src/components/screens/ExpandedMapScreen.js +++ b/src/components/screens/ExpandedMapScreen.js @@ -3,7 +3,7 @@ import {Platform, StyleSheet, View} from 'react-native'; import MapView, {Circle, Marker, Polygon} from 'react-native-maps'; import * as Location from 'expo-location'; import Screen from '../layout/Screen'; -import {getFovCone, isInClaimCone, isWithinRadius} from '../../utils/geoMath'; +import {getFovCone, isInClaimCone} from '../../utils/geoMath'; const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.01, longitudeDelta: 0.01}; @@ -71,14 +71,10 @@ const ExpandedMapScreen = ({route}) => { ? getFovCone(userLocation, heading) : null; - // For players — when heading is available use FOV cone, otherwise fall back - // to proximity-only so the expanded map works on iPhones without compass data - const visiblePlayerCaches = (!isAdmin && userLocation) - ? caches.filter((c) => - hasHeading - ? isInClaimCone(heading, userLocation, c.coordinates, claimDistance) - : isWithinRadius(userLocation, c.coordinates, claimDistance), - ) + // For players — heading is required so the player must point their device + // towards a cache for it to appear (same logic as the main MapScreen) + const visiblePlayerCaches = (!isAdmin && userLocation && hasHeading) + ? caches.filter((c) => isInClaimCone(heading, userLocation, c.coordinates, claimDistance)) : []; return ( @@ -140,15 +136,6 @@ const ExpandedMapScreen = ({route}) => { strokeWidth={1} /> )} - {/* Proximity circle when heading is unavailable (iOS fallback) */} - {!isAdmin && userLocation && !hasHeading && claimDistance > 0 && ( - - )} diff --git a/src/hooks/usePlayerGame.js b/src/hooks/usePlayerGame.js index 401020d..44fbb8c 100644 --- a/src/hooks/usePlayerGame.js +++ b/src/hooks/usePlayerGame.js @@ -1,5 +1,5 @@ import {useEffect, useRef, useState} from 'react'; -import {isInClaimCone, isWithinRadius} from '../utils/geoMath'; +import {isInClaimCone} from '../utils/geoMath'; const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance) => { // State ---------------------- @@ -11,8 +11,9 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc // Handlers ------------------- useEffect(() => { - // No location at all — clear everything - if (!playerLocation) { + // 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([]); @@ -21,24 +22,17 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc return; } - const hasHeading = playerHeading !== null && playerHeading !== undefined; - - // When heading is available use the full FOV cone check. - // When heading is NOT available (common on iOS / Apple Maps where the - // compass may be delayed or unavailable) fall back to proximity-only so - // iPhone users can still discover and claim caches within range. - const nearby = (activeCaches || []).filter((cache) => - hasHeading - ? isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance) - : isWithinRadius(playerLocation, cache.coordinates, claimDistance), + // Find all caches that fall inside the claim cone + const inCone = (activeCaches || []).filter((cache) => + isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance), ); // Only update state when the set of visible caches actually changes - const newIds = nearby.map((c) => c.id).join(','); + const newIds = inCone.map((c) => c.id).join(','); if (newIds !== prevIdsRef.current) { prevIdsRef.current = newIds; - setVisibleCaches(nearby); - setIsClaiming(nearby.length > 0); + setVisibleCaches(inCone); + setIsClaiming(inCone.length > 0); } }, [playerLocation, playerHeading, activeCaches, claimDistance]);