mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
Refactor cache claim logic to use admin-defined claim distance; update related components for consistency
This commit is contained in:
1 parent
ecb61c4858
commit
2646c7b9bc
11 files changed
+71
-47
No files matched your search
@@ -19,7 +19,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"usesCleartextTraffic": true,
|
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/android-icon-monochrome.png",
|
"foregroundImage": "./assets/images/android-icon-monochrome.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ addRoute('POST', '/groups', async (req, res) => {
|
|||||||
CreatedByUid: payload.CreatedByUid,
|
CreatedByUid: payload.CreatedByUid,
|
||||||
MaxMemberSubgroups: payload.MaxMemberSubgroups || 1,
|
MaxMemberSubgroups: payload.MaxMemberSubgroups || 1,
|
||||||
TeamsEnabled: payload.TeamsEnabled !== undefined ? payload.TeamsEnabled : true,
|
TeamsEnabled: payload.TeamsEnabled !== undefined ? payload.TeamsEnabled : true,
|
||||||
|
CacheTriggerMeters: payload.CacheTriggerMeters || 20,
|
||||||
ApprovedAdmins: [payload.CreatedByUid],
|
ApprovedAdmins: [payload.CreatedByUid],
|
||||||
CreatedAt: new Date().toISOString(),
|
CreatedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MapView, {Circle, Marker} from 'react-native-maps';
|
import MapView, {Circle, Marker} from 'react-native-maps';
|
||||||
|
|
||||||
const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache }) => {
|
const AdminCacheEditorView = ({ userLocation, caches, claimDistance, onAddCache, onMoveCache }) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
@@ -34,7 +34,7 @@ const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache })
|
|||||||
/>
|
/>
|
||||||
<Circle
|
<Circle
|
||||||
center={cache.coordinates}
|
center={cache.coordinates}
|
||||||
radius={cache.radius}
|
radius={claimDistance}
|
||||||
fillColor="rgba(59, 130, 246, 0.15)"
|
fillColor="rgba(59, 130, 246, 0.15)"
|
||||||
strokeColor="rgba(59, 130, 246, 0.85)"
|
strokeColor="rgba(59, 130, 246, 0.85)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {StyleSheet, Text, View} from 'react-native';
|
import {StyleSheet, Text, View} from 'react-native';
|
||||||
|
import {CACHE_CLAIM_TIMER} from '../../utils/geoMath';
|
||||||
|
|
||||||
const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
const [timeLeft, setTimeLeft] = useState(5);
|
const [timeLeft, setTimeLeft] = useState(CACHE_CLAIM_TIMER);
|
||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
|||||||
} else if (isClaiming && timeLeft === 0 && cache) {
|
} else if (isClaiming && timeLeft === 0 && cache) {
|
||||||
onClaimSuccess(cache.id);
|
onClaimSuccess(cache.id);
|
||||||
} else {
|
} else {
|
||||||
setTimeLeft(5);
|
setTimeLeft(CACHE_CLAIM_TIMER);
|
||||||
}
|
}
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [isClaiming, timeLeft, cache, onClaimSuccess]);
|
}, [isClaiming, timeLeft, cache, onClaimSuccess]);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import MapView, {Circle, Polygon} from 'react-native-maps';
|
import MapView, {Circle, Polygon} from 'react-native-maps';
|
||||||
import {getFovCone} from '../../utils/geoMath';
|
import {getFovCone} from '../../utils/geoMath';
|
||||||
|
|
||||||
const PlayerMapView = ({ userLocation, visibleCache, heading }) => {
|
const PlayerMapView = ({ userLocation, visibleCache, heading, claimDistance }) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
@@ -31,7 +31,7 @@ const PlayerMapView = ({ userLocation, visibleCache, heading }) => {
|
|||||||
{visibleCache ? (
|
{visibleCache ? (
|
||||||
<Circle
|
<Circle
|
||||||
center={visibleCache.coordinates}
|
center={visibleCache.coordinates}
|
||||||
radius={visibleCache.radius}
|
radius={claimDistance}
|
||||||
fillColor="rgba(250, 204, 21, 0.20)"
|
fillColor="rgba(250, 204, 21, 0.20)"
|
||||||
strokeColor="rgba(250, 204, 21, 0.90)"
|
strokeColor="rgba(250, 204, 21, 0.90)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.
|
|||||||
const ExpandedMapScreen = ({route}) => {
|
const ExpandedMapScreen = ({route}) => {
|
||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const {isAdmin, cacheRecords: cacheStr} = route.params || {};
|
const {isAdmin, cacheRecords: cacheStr, claimDistance: routeClaimDistance} = route.params || {};
|
||||||
const caches = cacheStr ? JSON.parse(cacheStr) : [];
|
const caches = cacheStr ? JSON.parse(cacheStr) : [];
|
||||||
|
const claimDistance = routeClaimDistance || 20;
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ const ExpandedMapScreen = ({route}) => {
|
|||||||
/>
|
/>
|
||||||
<Circle
|
<Circle
|
||||||
center={cache.coordinates}
|
center={cache.coordinates}
|
||||||
radius={cache.radius}
|
radius={claimDistance}
|
||||||
fillColor="rgba(59,130,246,0.15)"
|
fillColor="rgba(59,130,246,0.15)"
|
||||||
strokeColor="rgba(59,130,246,0.85)"
|
strokeColor="rgba(59,130,246,0.85)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const GameSettingsScreen = () => {
|
|||||||
const [groupName, setGroupName] = useState('');
|
const [groupName, setGroupName] = useState('');
|
||||||
const [teamsEnabled, setTeamsEnabled] = useState(false);
|
const [teamsEnabled, setTeamsEnabled] = useState(false);
|
||||||
const [maxSubgroups, setMaxSubgroups] = useState('1');
|
const [maxSubgroups, setMaxSubgroups] = useState('1');
|
||||||
|
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
|
||||||
const [adminJoinCode, setAdminJoinCode] = useState('');
|
const [adminJoinCode, setAdminJoinCode] = useState('');
|
||||||
const [memberJoinCode, setMemberJoinCode] = useState('');
|
const [memberJoinCode, setMemberJoinCode] = useState('');
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ const GameSettingsScreen = () => {
|
|||||||
setGroupName(group.GroupName || '');
|
setGroupName(group.GroupName || '');
|
||||||
setTeamsEnabled(Boolean(group.TeamsEnabled));
|
setTeamsEnabled(Boolean(group.TeamsEnabled));
|
||||||
setMaxSubgroups(String(group.MaxMemberSubgroups || 1));
|
setMaxSubgroups(String(group.MaxMemberSubgroups || 1));
|
||||||
|
setCacheTriggerMeters(String(group.CacheTriggerMeters || 20));
|
||||||
setAdminJoinCode(group.AdminJoinCode || '');
|
setAdminJoinCode(group.AdminJoinCode || '');
|
||||||
}
|
}
|
||||||
const sgs = await getSubgroups(session.currentGid);
|
const sgs = await getSubgroups(session.currentGid);
|
||||||
@@ -52,6 +54,7 @@ const GameSettingsScreen = () => {
|
|||||||
GroupName: groupName.trim(),
|
GroupName: groupName.trim(),
|
||||||
TeamsEnabled: teamsEnabled,
|
TeamsEnabled: teamsEnabled,
|
||||||
MaxMemberSubgroups: parseInt(maxSubgroups) || 1,
|
MaxMemberSubgroups: parseInt(maxSubgroups) || 1,
|
||||||
|
CacheTriggerMeters: parseInt(cacheTriggerMeters) || 20,
|
||||||
});
|
});
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
@@ -112,6 +115,16 @@ const GameSettingsScreen = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Text style={styles.label}>Cache Claim Distance (metres)</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={cacheTriggerMeters}
|
||||||
|
onChangeText={setCacheTriggerMeters}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="20"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
/>
|
||||||
|
|
||||||
<View style={styles.codeSection}>
|
<View style={styles.codeSection}>
|
||||||
<Text style={styles.codeLabel}>Admin Join Code</Text>
|
<Text style={styles.codeLabel}>Admin Join Code</Text>
|
||||||
<Text style={styles.codeValue}>{adminJoinCode || '—'}</Text>
|
<Text style={styles.codeValue}>{adminJoinCode || '—'}</Text>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const ManageGameScreen = () => {
|
|||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const {getCaches, upsertCache} = useGameHook();
|
const {getCaches, upsertCache, getLobby} = useGameHook();
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ const ManageGameScreen = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [cacheRecords, setCacheRecords] = useState([]);
|
const [cacheRecords, setCacheRecords] = useState([]);
|
||||||
|
const [claimDistance, setClaimDistance] = useState(20);
|
||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|
||||||
@@ -32,6 +33,13 @@ const ManageGameScreen = () => {
|
|||||||
|
|
||||||
useEffect(() => { loadCaches(); }, [loadCaches]);
|
useEffect(() => { loadCaches(); }, [loadCaches]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
getLobby(session.currentGid).then((group) => {
|
||||||
|
if (group) setClaimDistance(group.CacheTriggerMeters || 20);
|
||||||
|
});
|
||||||
|
}, [session.currentGid]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let locationSub;
|
let locationSub;
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
@@ -62,7 +70,6 @@ const ManageGameScreen = () => {
|
|||||||
gid: session.currentGid,
|
gid: session.currentGid,
|
||||||
latitude: coordinate.latitude,
|
latitude: coordinate.latitude,
|
||||||
longitude: coordinate.longitude,
|
longitude: coordinate.longitude,
|
||||||
radius: 20,
|
|
||||||
clue: `Cache ${cacheRecords.length + 1}`,
|
clue: `Cache ${cacheRecords.length + 1}`,
|
||||||
subgroupId: 1,
|
subgroupId: 1,
|
||||||
});
|
});
|
||||||
@@ -78,7 +85,6 @@ const ManageGameScreen = () => {
|
|||||||
cacheId,
|
cacheId,
|
||||||
latitude: coordinate.latitude,
|
latitude: coordinate.latitude,
|
||||||
longitude: coordinate.longitude,
|
longitude: coordinate.longitude,
|
||||||
radius: current.radius,
|
|
||||||
clue: current.clue,
|
clue: current.clue,
|
||||||
subgroupId: current.subgroupId,
|
subgroupId: current.subgroupId,
|
||||||
});
|
});
|
||||||
@@ -125,6 +131,7 @@ const ManageGameScreen = () => {
|
|||||||
<AdminCacheEditorView
|
<AdminCacheEditorView
|
||||||
userLocation={userLocation}
|
userLocation={userLocation}
|
||||||
caches={cacheRecords}
|
caches={cacheRecords}
|
||||||
|
claimDistance={claimDistance}
|
||||||
onAddCache={handleAddCache}
|
onAddCache={handleAddCache}
|
||||||
onMoveCache={handleMoveCache}
|
onMoveCache={handleMoveCache}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -40,13 +40,16 @@ const MapScreen = ({navigation}) => {
|
|||||||
const [newCoord, setNewCoord] = useState(null);
|
const [newCoord, setNewCoord] = useState(null);
|
||||||
const [newName, setNewName] = useState('');
|
const [newName, setNewName] = useState('');
|
||||||
const [newClue, setNewClue] = useState('');
|
const [newClue, setNewClue] = useState('');
|
||||||
const [newRadius, setNewRadius] = useState('20');
|
|
||||||
|
// Global claim distance comes from the group's CacheTriggerMeters setting
|
||||||
|
const claimDistance = groupInfo?.CacheTriggerMeters || 20;
|
||||||
|
|
||||||
const isPlayer = inGame && !isAdmin;
|
const isPlayer = inGame && !isAdmin;
|
||||||
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(
|
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(
|
||||||
isPlayer ? userLocation : null,
|
isPlayer ? userLocation : null,
|
||||||
isPlayer ? heading : null,
|
isPlayer ? heading : null,
|
||||||
isPlayer ? cacheRecords : [],
|
isPlayer ? cacheRecords : [],
|
||||||
|
claimDistance,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapRegion = userLocation
|
const mapRegion = userLocation
|
||||||
@@ -151,7 +154,6 @@ const MapScreen = ({navigation}) => {
|
|||||||
setNewCoord(fallback);
|
setNewCoord(fallback);
|
||||||
setNewName('');
|
setNewName('');
|
||||||
setNewClue('');
|
setNewClue('');
|
||||||
setNewRadius('20');
|
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,7 +163,6 @@ const MapScreen = ({navigation}) => {
|
|||||||
setNewCoord(cache.coordinates);
|
setNewCoord(cache.coordinates);
|
||||||
setNewName(cache.name || '');
|
setNewName(cache.name || '');
|
||||||
setNewClue(cache.clue || '');
|
setNewClue(cache.clue || '');
|
||||||
setNewRadius(String(cache.radius || 20));
|
|
||||||
setIsCreating(true);
|
setIsCreating(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,7 +181,6 @@ const MapScreen = ({navigation}) => {
|
|||||||
name: newName.trim(),
|
name: newName.trim(),
|
||||||
latitude: newCoord.latitude,
|
latitude: newCoord.latitude,
|
||||||
longitude: newCoord.longitude,
|
longitude: newCoord.longitude,
|
||||||
radius: parseInt(newRadius) || 20,
|
|
||||||
clue: newClue.trim(),
|
clue: newClue.trim(),
|
||||||
subgroupId: 1,
|
subgroupId: 1,
|
||||||
};
|
};
|
||||||
@@ -202,6 +202,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
isAdmin,
|
isAdmin,
|
||||||
cacheRecords: JSON.stringify(cacheRecords),
|
cacheRecords: JSON.stringify(cacheRecords),
|
||||||
heading: heading || 0,
|
heading: heading || 0,
|
||||||
|
claimDistance,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
<Marker coordinate={cache.coordinates} pinColor="#9ca3af" title={cache.clue}/>
|
<Marker coordinate={cache.coordinates} pinColor="#9ca3af" title={cache.clue}/>
|
||||||
<Circle
|
<Circle
|
||||||
center={cache.coordinates}
|
center={cache.coordinates}
|
||||||
radius={cache.radius}
|
radius={claimDistance}
|
||||||
fillColor="rgba(156,163,175,0.15)"
|
fillColor="rgba(156,163,175,0.15)"
|
||||||
strokeColor="rgba(156,163,175,0.6)"
|
strokeColor="rgba(156,163,175,0.6)"
|
||||||
/>
|
/>
|
||||||
@@ -282,7 +283,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
/>
|
/>
|
||||||
<Circle
|
<Circle
|
||||||
center={newCoord}
|
center={newCoord}
|
||||||
radius={parseInt(newRadius) || 20}
|
radius={claimDistance}
|
||||||
fillColor="rgba(37,99,235,0.15)"
|
fillColor="rgba(37,99,235,0.15)"
|
||||||
strokeColor="rgba(37,99,235,0.85)"
|
strokeColor="rgba(37,99,235,0.85)"
|
||||||
/>
|
/>
|
||||||
@@ -305,8 +306,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
value={newClue}
|
value={newClue}
|
||||||
onChangeText={setNewClue}
|
onChangeText={setNewClue}
|
||||||
/>
|
/>
|
||||||
<View style={styles.coordRow}>
|
<View style={styles.coordRow}> <View style={styles.coordField}>
|
||||||
<View style={styles.coordField}>
|
|
||||||
<Text style={styles.coordLabel}>Latitude</Text>
|
<Text style={styles.coordLabel}>Latitude</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.formInput}
|
style={styles.formInput}
|
||||||
@@ -327,14 +327,6 @@ const MapScreen = ({navigation}) => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<TextInput
|
|
||||||
style={styles.formInput}
|
|
||||||
placeholder="Claim Radius (metres)"
|
|
||||||
placeholderTextColor="#9ca3af"
|
|
||||||
value={newRadius}
|
|
||||||
onChangeText={setNewRadius}
|
|
||||||
keyboardType="numeric"
|
|
||||||
/>
|
|
||||||
<ButtonTray>
|
<ButtonTray>
|
||||||
<Button
|
<Button
|
||||||
label={editingCacheId ? 'Update Cache' : 'Save Cache'}
|
label={editingCacheId ? 'Update Cache' : 'Save Cache'}
|
||||||
@@ -371,7 +363,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
<Marker coordinate={cache.coordinates} title={cache.name || cache.clue}/>
|
<Marker coordinate={cache.coordinates} title={cache.name || cache.clue}/>
|
||||||
<Circle
|
<Circle
|
||||||
center={cache.coordinates}
|
center={cache.coordinates}
|
||||||
radius={cache.radius}
|
radius={claimDistance}
|
||||||
fillColor="rgba(59,130,246,0.15)"
|
fillColor="rgba(59,130,246,0.15)"
|
||||||
strokeColor="rgba(59,130,246,0.85)"
|
strokeColor="rgba(59,130,246,0.85)"
|
||||||
/>
|
/>
|
||||||
@@ -450,6 +442,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
userLocation={userLocation}
|
userLocation={userLocation}
|
||||||
visibleCache={visibleCache}
|
visibleCache={visibleCache}
|
||||||
heading={heading}
|
heading={heading}
|
||||||
|
claimDistance={claimDistance}
|
||||||
/>
|
/>
|
||||||
<Pressable style={styles.expandButton} onPress={handleExpandMap}>
|
<Pressable style={styles.expandButton} onPress={handleExpandMap}>
|
||||||
<Text style={styles.expandIcon}>⛶</Text>
|
<Text style={styles.expandIcon}>⛶</Text>
|
||||||
|
|||||||
+10
-14
@@ -1,7 +1,7 @@
|
|||||||
import {useEffect, useState} from 'react';
|
import {useEffect, useState} from 'react';
|
||||||
import {isLookingAtCache, isWithinRadius} from '../utils/geoMath';
|
import {isInClaimCone} from '../utils/geoMath';
|
||||||
|
|
||||||
const usePlayerGame = (playerLocation, playerHeading, activeCaches) => {
|
const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance) => {
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
const [visibleCache, setVisibleCache] = useState(null);
|
const [visibleCache, setVisibleCache] = useState(null);
|
||||||
@@ -16,19 +16,15 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nearbyCache = (activeCaches || []).find((cache) =>
|
// Find the first cache that falls inside the invisible claim cone
|
||||||
isWithinRadius(playerLocation, cache.coordinates, cache.radius));
|
// (same FOV angle as the visible cone, but radius = admin-set claimDistance)
|
||||||
|
const cacheInCone = (activeCaches || []).find((cache) =>
|
||||||
|
isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance)
|
||||||
|
);
|
||||||
|
|
||||||
if (!nearbyCache) {
|
setVisibleCache(cacheInCone || null);
|
||||||
setVisibleCache(null);
|
setIsClaiming(Boolean(cacheInCone));
|
||||||
setIsClaiming(false);
|
}, [playerLocation, playerHeading, activeCaches, claimDistance]);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTargeted = isLookingAtCache(playerHeading, playerLocation, nearbyCache.coordinates);
|
|
||||||
setVisibleCache(nearbyCache);
|
|
||||||
setIsClaiming(isTargeted);
|
|
||||||
}, [playerLocation, playerHeading, activeCaches]);
|
|
||||||
|
|
||||||
// Return ---------------------
|
// Return ---------------------
|
||||||
|
|
||||||
|
|||||||
+17
-4
@@ -1,8 +1,9 @@
|
|||||||
import * as geolib from 'geolib';
|
import * as geolib from 'geolib';
|
||||||
|
|
||||||
// --- FOV Configuration ---
|
// --- FOV Configuration ---
|
||||||
const FOV_DISTANCE = 30; // metres — radius of arc + length of straight edges
|
const FOV_SIZE = 50; // metres — radius of the visible FOV cone drawn on screen
|
||||||
const FOV_ANGLE = 60; // degrees — total field of view (halved for each side)
|
const FOV_ANGLE = 60; // degrees — total field of view (halved for each side)
|
||||||
|
const CACHE_CLAIM_TIMER = 5; // seconds — how long the player must hold a cache in the claim cone to claim it
|
||||||
|
|
||||||
// Convert raw Magnetometer {x, y} reading to a 0-360° heading (0° = north)
|
// Convert raw Magnetometer {x, y} reading to a 0-360° heading (0° = north)
|
||||||
const toHeading = ({x, y}) => {
|
const toHeading = ({x, y}) => {
|
||||||
@@ -12,7 +13,8 @@ const toHeading = ({x, y}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Build a pie-shaped FOV sector as polygon coordinates
|
// Build a pie-shaped FOV sector as polygon coordinates
|
||||||
const getFovCone = (location, headingDeg, radiusMeters = FOV_DISTANCE, halfAngle = FOV_ANGLE / 2, arcSegments = 20) => {
|
// radiusMeters defaults to FOV_SIZE (the visible cone) — pass a different value to build the invisible claim cone
|
||||||
|
const getFovCone = (location, headingDeg, radiusMeters = FOV_SIZE, halfAngle = FOV_ANGLE / 2, arcSegments = 20) => {
|
||||||
const origin = {latitude: location.latitude, longitude: location.longitude};
|
const origin = {latitude: location.latitude, longitude: location.longitude};
|
||||||
const startBearing = headingDeg - halfAngle;
|
const startBearing = headingDeg - halfAngle;
|
||||||
const endBearing = headingDeg + halfAngle;
|
const endBearing = headingDeg + halfAngle;
|
||||||
@@ -36,7 +38,7 @@ const isWithinRadius = (playerCoords, cacheCoords, radius) => {
|
|||||||
return distance <= radius;
|
return distance <= radius;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLookingAtCache = (playerHeading, playerCoords, cacheCoords, tolerance = 20) => {
|
const isLookingAtCache = (playerHeading, playerCoords, cacheCoords, tolerance = FOV_ANGLE / 2) => {
|
||||||
if (playerHeading === null || playerHeading === undefined) {
|
if (playerHeading === null || playerHeading === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -45,5 +47,16 @@ const isLookingAtCache = (playerHeading, playerCoords, cacheCoords, tolerance =
|
|||||||
return diff <= tolerance || diff >= 360 - tolerance;
|
return diff <= tolerance || diff >= 360 - tolerance;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { toHeading, getFovCone, isWithinRadius, isLookingAtCache };
|
// Checks whether a cache falls within the invisible claim sector
|
||||||
|
// The claim sector uses the same FOV_ANGLE as the visible cone but extends to the
|
||||||
|
// admin-defined claimDistance rather than the visible FOV_SIZE.
|
||||||
|
// If the cache sits inside this sector the claiming countdown is triggered.
|
||||||
|
const isInClaimCone = (playerHeading, playerCoords, cacheCoords, claimDistance) => {
|
||||||
|
if (playerHeading === null || playerHeading === undefined) return false;
|
||||||
|
// Cache must be within the admin-configured claim distance
|
||||||
|
if (!isWithinRadius(playerCoords, cacheCoords, claimDistance)) return false;
|
||||||
|
// Cache must also fall within the same angular width as the visible cone
|
||||||
|
return isLookingAtCache(playerHeading, playerCoords, cacheCoords);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { toHeading, getFovCone, isWithinRadius, isLookingAtCache, isInClaimCone, FOV_ANGLE, FOV_SIZE, CACHE_CLAIM_TIMER };
|
||||||
Reference in new issue
Block a user