diff --git a/server/server.js b/server/server.js
index fe73fdc..1653266 100644
--- a/server/server.js
+++ b/server/server.js
@@ -4,7 +4,7 @@ const path = require('path');
// --- Configuration ---
-const PORT = process.env.PORT || 80;
+const PORT = process.env.PORT || 3000;
const DATA_DIR = path.join(__dirname, 'data');
// --- Data Helpers ---
@@ -560,7 +560,6 @@ addRoute('GET', '/game-data/:gid', (req, res, params) => {
// Helper: convert storage cache to API response format
const toCacheApi = (cache, gid) => ({
id: cache.CacheId,
- name: cache.Name || '',
coordinates: { latitude: cache.Latitude, longitude: cache.Longitude },
radius: cache.TriggerMeters,
clue: cache.Title,
@@ -573,7 +572,6 @@ const toCacheApi = (cache, gid) => ({
// Helper: convert API payload to storage cache format
const toCacheStorage = (data, cacheId) => ({
CacheId: cacheId,
- Name: data.name || data.Name || '',
Title: data.clue || data.Title || '',
Latitude: data.latitude != null ? data.latitude : (data.Latitude || 0),
Longitude: data.longitude != null ? data.longitude : (data.Longitude || 0),
diff --git a/src/components/gameplay/PlayerMapView.js b/src/components/gameplay/PlayerMapView.js
index b4f3dfb..7ac9e0c 100644
--- a/src/components/gameplay/PlayerMapView.js
+++ b/src/components/gameplay/PlayerMapView.js
@@ -1,6 +1,6 @@
import React from 'react';
-import {StyleSheet, View} from 'react-native';
-import MapView, {Circle, Marker} from 'react-native-maps';
+import MapView, {Circle, Polygon} from 'react-native-maps';
+import {getFovCone} from '../../utils/geoMath';
const PlayerMapView = ({ userLocation, visibleCache, heading }) => {
// Initialisation -------------
@@ -10,6 +10,10 @@ const PlayerMapView = ({ userLocation, visibleCache, heading }) => {
if (!userLocation) return null;
+ const coneCoords = (heading !== null && heading !== undefined)
+ ? getFovCone(userLocation, heading)
+ : null;
+
return (
{
strokeColor="rgba(250, 204, 21, 0.90)"
/>
) : null}
- {heading !== null && heading !== undefined && (
-
-
-
-
-
+ {coneCoords && (
+
)}
);
};
-const styles = StyleSheet.create({
- coneWrap: {width: 22, height: 24, alignItems: 'center'},
- cone: {
- width: 0,
- height: 0,
- borderTopWidth: 24,
- borderLeftWidth: 11,
- borderRightWidth: 11,
- borderTopColor: 'rgba(37,99,235,0.70)',
- borderLeftColor: 'transparent',
- borderRightColor: 'transparent',
- },
-});
export default PlayerMapView;
diff --git a/src/components/screens/ExpandedMapScreen.js b/src/components/screens/ExpandedMapScreen.js
index 148e829..4f3d41e 100644
--- a/src/components/screens/ExpandedMapScreen.js
+++ b/src/components/screens/ExpandedMapScreen.js
@@ -1,10 +1,10 @@
import React, {useEffect, useState} from 'react';
import {StyleSheet, View} from 'react-native';
-import MapView, {Circle, Marker} from 'react-native-maps';
+import MapView, {Circle, Marker, Polygon} from 'react-native-maps';
import * as Location from 'expo-location';
import {Magnetometer} from 'expo-sensors';
import Screen from '../layout/Screen';
-import {toHeading} from '../../utils/geoMath';
+import {getFovCone, toHeading} from '../../utils/geoMath';
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.01, longitudeDelta: 0.01};
@@ -54,6 +54,10 @@ const ExpandedMapScreen = ({route}) => {
// View -----------------------
+ const coneCoords = (userLocation && heading !== null)
+ ? getFovCone(userLocation, heading)
+ : null;
+
return (
@@ -78,19 +82,14 @@ const ExpandedMapScreen = ({route}) => {
/>
))}
- {/* Heading cone for both admin and player */}
- {userLocation && heading !== null && (
-
-
-
-
-
+ {/* Heading FOV cone */}
+ {coneCoords && (
+
)}
@@ -101,17 +100,6 @@ const ExpandedMapScreen = ({route}) => {
const styles = StyleSheet.create({
container: {padding: 0},
mapWrap: {flex: 1},
- coneWrap: {width: 22, height: 24, alignItems: 'center'},
- cone: {
- width: 0,
- height: 0,
- borderTopWidth: 24,
- borderLeftWidth: 11,
- borderRightWidth: 11,
- borderTopColor: 'rgba(37,99,235,0.70)',
- borderLeftColor: 'transparent',
- borderRightColor: 'transparent',
- },
});
export default ExpandedMapScreen;
diff --git a/src/components/screens/MapScreen.js b/src/components/screens/MapScreen.js
index bd8f710..5fe5878 100644
--- a/src/components/screens/MapScreen.js
+++ b/src/components/screens/MapScreen.js
@@ -1,6 +1,6 @@
import React, {useCallback, useEffect, useState} from 'react';
import {ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, TextInput, View} from 'react-native';
-import MapView, {Circle, Marker} from 'react-native-maps';
+import MapView, {Circle, Marker, Polygon} from 'react-native-maps';
import * as Location from 'expo-location';
import {Magnetometer} from 'expo-sensors';
import Screen from '../layout/Screen';
@@ -11,7 +11,7 @@ import PlayerMapView from '../gameplay/PlayerMapView';
import useGameHook from '../../hooks/useGameHook';
import usePlayerGame from '../../hooks/usePlayerGame';
import {getSession, setSessionGroup, setSessionUser} from '../../hooks/SessionStore';
-import {toHeading} from '../../utils/geoMath';
+import {getFovCone, toHeading} from '../../utils/geoMath';
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05};
@@ -378,17 +378,12 @@ const MapScreen = ({navigation}) => {
))}
{userLocation && heading !== null && (
-
-
-
-
-
+
)}
@@ -563,18 +558,6 @@ const styles = StyleSheet.create({
cacheSection: {flex: 1, backgroundColor: '#f3f4f6', paddingHorizontal: 12, paddingTop: 10},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 20, fontSize: 14},
mapWrap: {flex: 1},
- // Heading direction cone (like Google Maps)
- headingConeWrap: {width: 22, height: 24, alignItems: 'center'},
- headingCone: {
- width: 0,
- height: 0,
- borderTopWidth: 24,
- borderLeftWidth: 11,
- borderRightWidth: 11,
- borderTopColor: 'rgba(37,99,235,0.70)',
- borderLeftColor: 'transparent',
- borderRightColor: 'transparent',
- },
});
export default MapScreen;
diff --git a/src/utils/geoMath.js b/src/utils/geoMath.js
index b0c11ad..748d5a6 100644
--- a/src/utils/geoMath.js
+++ b/src/utils/geoMath.js
@@ -1,9 +1,34 @@
import * as geolib from 'geolib';
-// Convert raw Magnetometer {x, y} reading to a 0-360° heading
+// --- FOV Configuration ---
+const FOV_DISTANCE = 20; // metres — radius of arc + length of straight edges
+const FOV_ANGLE = 45; // degrees — total field of view (halved for each side)
+
+// Convert raw Magnetometer {x, y} reading to a 0-360° heading (0° = north)
const toHeading = ({x, y}) => {
const angle = Math.atan2(y, x) * (180 / Math.PI);
- return angle < 0 ? angle + 360 : angle;
+ const heading = 90 - angle;
+ return heading < 0 ? heading + 360 : heading % 360;
+};
+
+// Build a pie-shaped FOV sector as polygon coordinates
+const getFovCone = (location, headingDeg, radiusMeters = FOV_DISTANCE, halfAngle = FOV_ANGLE / 2, arcSegments = 20) => {
+ const origin = {latitude: location.latitude, longitude: location.longitude};
+ const startBearing = headingDeg - halfAngle;
+ const endBearing = headingDeg + halfAngle;
+ const step = (endBearing - startBearing) / arcSegments;
+
+ // Tip of the sector
+ const points = [location];
+
+ // Arc points at radiusMeters distance from origin
+ for (let i = 0; i <= arcSegments; i++) {
+ const bearing = startBearing + step * i;
+ const dest = geolib.computeDestinationPoint(origin, radiusMeters, bearing);
+ points.push({latitude: dest.latitude, longitude: dest.longitude});
+ }
+
+ return points;
};
const isWithinRadius = (playerCoords, cacheCoords, radius) => {
@@ -20,5 +45,5 @@ const isLookingAtCache = (playerHeading, playerCoords, cacheCoords, tolerance =
return diff <= tolerance || diff >= 360 - tolerance;
};
-export { toHeading, isWithinRadius, isLookingAtCache };
+export { toHeading, getFovCone, isWithinRadius, isLookingAtCache };