mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 02:10:18 +01:00
Remove out of zone penality + upgrades
This commit is contained in:
21
doc/TODO.md
21
doc/TODO.md
@@ -23,6 +23,8 @@
|
|||||||
- [ ] Permettre le changement du style de la carte (schéma, satellite, relief etc)
|
- [ ] Permettre le changement du style de la carte (schéma, satellite, relief etc)
|
||||||
- [ ] Ajouter imprécision de la position au besoin (comme sur google maps)
|
- [ ] Ajouter imprécision de la position au besoin (comme sur google maps)
|
||||||
- [ ] Synchroniser les horloges sur l'interface
|
- [ ] Synchroniser les horloges sur l'interface
|
||||||
|
- [ ] Avoir un récap des évènement de la partie
|
||||||
|
- [ ] Publier sur le playstore
|
||||||
|
|
||||||
### Admin (Pageweb)
|
### Admin (Pageweb)
|
||||||
|
|
||||||
@@ -30,12 +32,27 @@
|
|||||||
- [x] Clarifier qui chasse qui sur l'interface.
|
- [x] Clarifier qui chasse qui sur l'interface.
|
||||||
- [x] Ajouter timer du rétrécissement des zones.
|
- [x] Ajouter timer du rétrécissement des zones.
|
||||||
- [x] Pouvoir changer les paramètres du jeu pendant une partie.
|
- [x] Pouvoir changer les paramètres du jeu pendant une partie.
|
||||||
- [ ] Implémenter les wireframes
|
- [x] Implémenter les wireframes
|
||||||
|
- [ ] Ajouter une région par défaut si pas de position
|
||||||
|
- [ ] Pouvoir faire pause dans la partie
|
||||||
|
- [ ] Voir les traces et évènements des teams
|
||||||
|
- [ ] Voir l'incertitude de position des teams
|
||||||
|
- [ ] Focus une team cliquée
|
||||||
|
- [ ] Refaire les flèches de chasse sur la map
|
||||||
|
- [ ] Mettre en évidence le menu paramètre (configuration)
|
||||||
|
- [ ] Afficher un feedback quand un paramètre est sauvegardé
|
||||||
|
- [ ] Pouvoir définir la zone de départ de chaque équipe
|
||||||
|
- [ ] Nommer les polygons par des lettres de l'alphabet
|
||||||
|
- [ ] Faire un menu quand on arrive sur la traque
|
||||||
|
- [ ] Pouvoir load des paramètres enregistrés
|
||||||
|
- [ ] Améliorer le système de création zone (cercle et polygone)
|
||||||
|
- [ ] Améliorer la sélection du système de zone
|
||||||
|
- [ ] Penser l'affichage en fin de traque
|
||||||
|
|
||||||
### Améliorations du jeu de la traque
|
### Améliorations du jeu de la traque
|
||||||
|
|
||||||
- [x] Supprimer la pénalité de non envoi de position : envoyer la position automatiquement à la fin du timer.
|
- [x] Supprimer la pénalité de non envoi de position : envoyer la position automatiquement à la fin du timer.
|
||||||
- [ ] Supprimer la pénalité d'hors zone : révéler la position de la team hors zone au bout d'un certain temps.
|
- [x] Supprimer la pénalité d'hors zone : révéler la position de la team hors zone au bout d'un certain temps.
|
||||||
- [x] Changer le système de zone de jeu pour qu'il soit fait d'un pavage de zones qui se ferment successivement.
|
- [x] Changer le système de zone de jeu pour qu'il soit fait d'un pavage de zones qui se ferment successivement.
|
||||||
|
|
||||||
### Autres idées
|
### Autres idées
|
||||||
|
|||||||
@@ -14,22 +14,27 @@ import LinearGradient from 'react-native-linear-gradient';
|
|||||||
import { useSocket } from '../context/socketContext';
|
import { useSocket } from '../context/socketContext';
|
||||||
import { useTeamContext } from '../context/teamContext';
|
import { useTeamContext } from '../context/teamContext';
|
||||||
import { useTeamConnexion } from '../context/teamConnexionContext';
|
import { useTeamConnexion } from '../context/teamConnexionContext';
|
||||||
import { useDeadline, useTimeDifference } from '../hook/useTimeDifference';
|
import { useTimeDifference } from '../hook/useTimeDifference';
|
||||||
import { GameState } from '../util/gameState';
|
import { GameState } from '../util/gameState';
|
||||||
import useGame from '../hook/useGame';
|
import useGame from '../hook/useGame';
|
||||||
|
|
||||||
const backgroundColor = '#f5f5f5';
|
const backgroundColor = '#f5f5f5';
|
||||||
const initialRegion = {latitude: 48.864, longitude: 2.342, latitudeDelta: 0, longitudeDelta: 50} // France centrée sur Paris
|
const initialRegion = {latitude: 48.864, longitude: 2.342, latitudeDelta: 0, longitudeDelta: 50} // France centrée sur Paris
|
||||||
|
|
||||||
|
const zoneTypes = {
|
||||||
|
circle: "circle",
|
||||||
|
polygon: "polygon"
|
||||||
|
}
|
||||||
|
|
||||||
export default function Display() {
|
export default function Display() {
|
||||||
const arrowUp = require('../assets/images/arrow.png');
|
const arrowUp = require('../assets/images/arrow.png');
|
||||||
const [collapsibleState, setCollapsibleState] = useState(true);
|
const [collapsibleState, setCollapsibleState] = useState(true);
|
||||||
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
|
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {SERVER_URL} = useSocket();
|
const {SERVER_URL} = useSocket();
|
||||||
const {gameSettings, zoneExtremities, nextZoneDate, isShrinking, location, startLocationTracking, stopLocationTracking, gameState, zone} = useTeamContext();
|
const {gameSettings, zoneType, zoneExtremities, nextZoneDate, isShrinking, location, startLocationTracking, stopLocationTracking, gameState} = useTeamContext();
|
||||||
const {loggedIn, logout, loading} = useTeamConnexion();
|
const {loggedIn, logout, loading} = useTeamConnexion();
|
||||||
const {sendCurrentPosition, capture, enemyLocation, enemyName, startingArea, captureCode, name, ready, captured, lastSentLocation, locationSendDeadline, penalties, teamId, outOfZone, outOfZoneDeadline, distance, startDate, finishDate, nCaptures, nSentLocation} = useGame();
|
const {sendCurrentPosition, capture, enemyLocation, enemyName, startingArea, captureCode, name, ready, captured, lastSentLocation, locationSendDeadline, teamId, outOfZone, outOfZoneDeadline, distance, startDate, finishDate, nCaptures, nSentLocation} = useGame();
|
||||||
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
|
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
|
||||||
const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000);
|
const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000);
|
||||||
const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000);
|
const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000);
|
||||||
@@ -234,12 +239,32 @@ export default function Display() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Zones = () => {
|
||||||
|
switch (zoneType) {
|
||||||
|
case zoneTypes.circle:
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{ zoneExtremities.begin && <Circle center={zoneExtremities.begin.center} radius={zoneExtremities.begin.radius} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} />}
|
||||||
|
{ zoneExtremities.end && <Circle center={zoneExtremities.end.center} radius={zoneExtremities.end.radius} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} />}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
case zoneTypes.polygon:
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{ zoneExtremities.begin && <Polygon coordinates={zoneExtremities.begin.points} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} /> }
|
||||||
|
{ zoneExtremities.end && <Polygon coordinates={zoneExtremities.end.points} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} /> }
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Map = () => {
|
const Map = () => {
|
||||||
return (
|
return (
|
||||||
<MapView ref={mapRef} style={{flex: 1}} initialRegion={initialRegion} mapType="standard" onTouchMove={() => setCenterMap(false)}>
|
<MapView ref={mapRef} style={{flex: 1}} initialRegion={initialRegion} mapType="standard" onTouchMove={() => setCenterMap(false)}>
|
||||||
{ gameState == GameState.PLACEMENT && startingArea && circle("0, 0, 255", startingArea)}
|
{ gameState == GameState.PLACEMENT && startingArea && circle("0, 0, 255", startingArea)}
|
||||||
{ gameState == GameState.PLAYING && zoneExtremities && <Polygon coordinates={zoneExtremities.begin.points} strokeColor="red" fillColor="rgba(255,0,0,0.1)" strokeWidth={2} /> }
|
{ gameState == GameState.PLAYING && zoneExtremities && <Zones/>}
|
||||||
{ gameState == GameState.PLAYING && zoneExtremities && <Polygon coordinates={zoneExtremities.end.points} strokeColor="green" fillColor="rgba(0,255,0,0.1)" strokeWidth={2} /> }
|
|
||||||
{ location &&
|
{ location &&
|
||||||
<Marker coordinate={{ latitude: location[0], longitude: location[1] }} anchor={{ x: 0.33, y: 0.33 }}>
|
<Marker coordinate={{ latitude: location[0], longitude: location[1] }} anchor={{ x: 0.33, y: 0.33 }}>
|
||||||
<Image source={require("../assets/images/marker/blue.png")} style={{width: 24, height: 24}} resizeMode="contain"/>
|
<Image source={require("../assets/images/marker/blue.png")} style={{width: 24, height: 24}} resizeMode="contain"/>
|
||||||
@@ -345,9 +370,6 @@ export default function Display() {
|
|||||||
<View style={styles.topContainer}>
|
<View style={styles.topContainer}>
|
||||||
<View style={styles.topheadContainer}>
|
<View style={styles.topheadContainer}>
|
||||||
{ Logout() }
|
{ Logout() }
|
||||||
{ penalties > 0 && gameState == GameState.PLAYING &&
|
|
||||||
<Text style={{marginTop: 15, fontSize: 15}}>Pénalités : {penalties}</Text>
|
|
||||||
}
|
|
||||||
{ false && Settings() }
|
{ false && Settings() }
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.teamNameContainer}>
|
<View style={styles.teamNameContainer}>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSocket } from "./socketContext";
|
|||||||
import { useSocketAuth } from "../hook/useSocketAuth";
|
import { useSocketAuth } from "../hook/useSocketAuth";
|
||||||
|
|
||||||
const teamConnexionContext = createContext();
|
const teamConnexionContext = createContext();
|
||||||
|
|
||||||
const TeamConnexionProvider = ({ children }) => {
|
const TeamConnexionProvider = ({ children }) => {
|
||||||
const { teamSocket } = useSocket();
|
const { teamSocket } = useSocket();
|
||||||
const { login, password: teamId, loggedIn, loading, logout } = useSocketAuth(teamSocket, "team_password");
|
const { login, password: teamId, loggedIn, loading, logout } = useSocketAuth(teamSocket, "team_password");
|
||||||
|
|||||||
@@ -4,13 +4,21 @@ import { createContext, useContext, useMemo, useRef, useState } from "react";
|
|||||||
import { useSocket } from "./socketContext";
|
import { useSocket } from "./socketContext";
|
||||||
import { GameState } from "../util/gameState";
|
import { GameState } from "../util/gameState";
|
||||||
import useSendDeviceInfo from "../hook/useSendDeviceInfo";
|
import useSendDeviceInfo from "../hook/useSendDeviceInfo";
|
||||||
|
import { useTeamConnexion } from "./teamConnexionContext";
|
||||||
|
|
||||||
|
const teamContext = createContext();
|
||||||
|
|
||||||
|
const zoneTypes = {
|
||||||
|
circle: "circle",
|
||||||
|
polygon: "polygon"
|
||||||
|
}
|
||||||
|
|
||||||
const teamContext = createContext()
|
|
||||||
function TeamProvider({children}) {
|
function TeamProvider({children}) {
|
||||||
|
const { logout } = useTeamConnexion();
|
||||||
const [teamInfos, setTeamInfos] = useState({});
|
const [teamInfos, setTeamInfos] = useState({});
|
||||||
const [gameState, setGameState] = useState(GameState.SETUP);
|
const [gameState, setGameState] = useState(GameState.SETUP);
|
||||||
const [gameSettings, setGameSettings] = useState(null);
|
const [gameSettings, setGameSettings] = useState(null);
|
||||||
|
const [zoneType, setZoneType] = useState(null);
|
||||||
const [zoneExtremities, setZoneExtremities] = useState(null);
|
const [zoneExtremities, setZoneExtremities] = useState(null);
|
||||||
const [nextZoneDate, setNextZoneDate] = useState(null);
|
const [nextZoneDate, setNextZoneDate] = useState(null);
|
||||||
const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10);
|
const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10);
|
||||||
@@ -21,21 +29,38 @@ function TeamProvider({children}) {
|
|||||||
|
|
||||||
teamInfosRef.current = teamInfos;
|
teamInfosRef.current = teamInfos;
|
||||||
|
|
||||||
function setCurrentZone(data) {
|
function setZone(data) {
|
||||||
const newBegin = {points : data.begin.points.map( p => ({latitude: p.lat,longitude: p.lng}) ), duration: data.begin.duration};
|
setZoneType(data.type);
|
||||||
const newEnd = {points : data.end.points.map( p => ({latitude: p.lat,longitude: p.lng}) ), duration: data.end.duration};
|
switch (data.type) {
|
||||||
setZoneExtremities({begin: newBegin, end: newEnd});
|
case zoneTypes.circle:
|
||||||
|
setZoneExtremities({
|
||||||
|
begin: {...data.begin, ...{center : {latitude: data.begin.center.lat, longitude: data.begin.center.lng} }},
|
||||||
|
end: {...data.end, ...{center : {latitude: data.end.center.lat, longitude: data.end.center.lng} }}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case zoneTypes.polygon:
|
||||||
|
setZoneExtremities({
|
||||||
|
begin: {...data.begin, ...{points : data.begin.points.map( p => ({latitude: p.lat,longitude: p.lng}) )}},
|
||||||
|
end: {...data.end, ...{points : data.end.points.map( p => ({latitude: p.lat,longitude: p.lng}) )}}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setZoneExtremities({begin: data.begin, end: data.end});
|
||||||
|
break;
|
||||||
|
}
|
||||||
setNextZoneDate(data.endDate);
|
setNextZoneDate(data.endDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
useSocketListener(teamSocket, "update_team", (newTeamInfos) => {setTeamInfos({...teamInfosRef.current, ...newTeamInfos})});
|
useSocketListener(teamSocket, "update_team", (newTeamInfos) => {setTeamInfos({...teamInfosRef.current, ...newTeamInfos})});
|
||||||
useSocketListener(teamSocket, "game_state", setGameState);
|
useSocketListener(teamSocket, "game_state", setGameState);
|
||||||
useSocketListener(teamSocket, "current_zone", setCurrentZone);
|
useSocketListener(teamSocket, "zone", setZone);
|
||||||
useSocketListener(teamSocket, "game_settings", setGameSettings);
|
useSocketListener(teamSocket, "game_settings", setGameSettings);
|
||||||
|
useSocketListener(teamSocket, "logout", logout);
|
||||||
|
|
||||||
const value = useMemo(() => (
|
const value = useMemo(() => (
|
||||||
{teamInfos, gameState, zoneExtremities, nextZoneDate, gameSettings, location, getLocationAuthorization, startLocationTracking, stopLocationTracking}
|
{teamInfos, gameState, zoneType, zoneExtremities, nextZoneDate, gameSettings, location, getLocationAuthorization, startLocationTracking, stopLocationTracking}
|
||||||
), [teamInfos, gameState, zoneExtremities, nextZoneDate, gameSettings, location]);
|
), [teamInfos, gameState, zoneType, zoneExtremities, nextZoneDate, gameSettings, location]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<teamContext.Provider value={value}>
|
<teamContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
@@ -47,4 +72,4 @@ function useTeamContext() {
|
|||||||
return useContext(teamContext);
|
return useContext(teamContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TeamProvider, useTeamContext };
|
export { TeamProvider, useTeamContext };
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ export default function useSendDeviceInfo() {
|
|||||||
const brand = DeviceInfo.getBrand();
|
const brand = DeviceInfo.getBrand();
|
||||||
const model = DeviceInfo.getModel();
|
const model = DeviceInfo.getModel();
|
||||||
const name = await DeviceInfo.getDeviceName();
|
const name = await DeviceInfo.getDeviceName();
|
||||||
teamSocket.emit('deviceInfo', {model: brand + " " + model, name: name});
|
teamSocket.emit('device_info', {model: brand + " " + model, name: name});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendBattery = async () => {
|
const sendBattery = async () => {
|
||||||
const level = await DeviceInfo.getBatteryLevel();
|
const level = await DeviceInfo.getBatteryLevel();
|
||||||
teamSocket.emit('batteryUpdate', Math.round(level * 100));
|
teamSocket.emit('battery_update', Math.round(level * 100));
|
||||||
};
|
};
|
||||||
|
|
||||||
sendInfo();
|
sendInfo();
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ This module also exposes functions to send messages via socket to all admins
|
|||||||
import { io } from "./index.js";
|
import { io } from "./index.js";
|
||||||
import game from "./game.js"
|
import game from "./game.js"
|
||||||
import zoneManager from "./zone_manager.js"
|
import zoneManager from "./zone_manager.js"
|
||||||
import penaltyController from "./penalty_controller.js";
|
|
||||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH;
|
const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,163 +27,81 @@ export function secureAdminBroadcast(event, data) {
|
|||||||
|
|
||||||
// Array of logged in sockets
|
// Array of logged in sockets
|
||||||
let loggedInSockets = [];
|
let loggedInSockets = [];
|
||||||
|
|
||||||
export function initAdminSocketHandler() {
|
export function initAdminSocketHandler() {
|
||||||
// Admin namespace
|
|
||||||
io.of("admin").on("connection", (socket) => {
|
io.of("admin").on("connection", (socket) => {
|
||||||
// Flag to check if the user is logged in, defined for each socket
|
|
||||||
console.log("Connection of an admin");
|
console.log("Connection of an admin");
|
||||||
let loggedIn = false;
|
let loggedIn = false;
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
console.log("Disconnection of an admin");
|
console.log("Disconnection of an admin");
|
||||||
// Remove the socket from the logged in sockets array
|
|
||||||
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
||||||
|
loggedIn = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("logout", () => {
|
socket.on("logout", () => {
|
||||||
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
||||||
|
loggedIn = false;
|
||||||
})
|
})
|
||||||
|
|
||||||
// User is attempting to log in
|
|
||||||
socket.on("login", (password) => {
|
socket.on("login", (password) => {
|
||||||
const hash = createHash('sha256').update(password).digest('hex');
|
const hash = createHash('sha256').update(password).digest('hex');
|
||||||
if (hash === ADMIN_PASSWORD_HASH && !loggedIn) {
|
if (hash === ADMIN_PASSWORD_HASH && !loggedIn) {
|
||||||
// Attempt successful
|
|
||||||
socket.emit("login_response", true);
|
|
||||||
loggedInSockets.push(socket.id);
|
loggedInSockets.push(socket.id);
|
||||||
loggedIn = true;
|
loggedIn = true;
|
||||||
// Send the current state
|
socket.emit("teams", game.teams);
|
||||||
socket.emit("game_state", {state: game.state, startDate: game.startDate})
|
socket.emit("game_state", {
|
||||||
// Other settings that need initialization
|
state: game.state,
|
||||||
socket.emit("penalty_settings", penaltyController.settings)
|
date: game.stateDate
|
||||||
socket.emit("game_settings", game.settings)
|
});
|
||||||
socket.emit("zone_settings", zoneManager.settings)
|
|
||||||
socket.emit("current_zone", {
|
socket.emit("current_zone", {
|
||||||
begin: zoneManager.getCurrentZone(),
|
begin: zoneManager.getCurrentZone(),
|
||||||
end: zoneManager.getNextZone(),
|
end: zoneManager.getNextZone(),
|
||||||
endDate: zoneManager.currentZoneEndDate,
|
endDate: zoneManager.currentZoneEndDate,
|
||||||
})
|
});
|
||||||
} else {
|
socket.emit("settings", game.getSettings());
|
||||||
// Attempt unsuccessful
|
|
||||||
socket.emit("login_response", false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("set_game_settings", (settings) => {
|
socket.on("update_settings", (settings) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.changeSettings(settings);
|
||||||
return;
|
secureAdminBroadcast("settings", game.getSettings());
|
||||||
}
|
|
||||||
if (!game.changeSettings(settings)) {
|
|
||||||
socket.emit("error", "Invalid settings");
|
|
||||||
socket.emit("game_settings", penaltyController.settings)
|
|
||||||
} else {
|
|
||||||
secureAdminBroadcast("game_settings", game.settings);
|
|
||||||
playersBroadcast("game_settings", game.settings);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("set_zone_settings", (settings) => {
|
|
||||||
if (!loggedIn) {
|
|
||||||
socket.emit("error", "Not logged in");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!zoneManager.changeSettings(settings)) {
|
|
||||||
socket.emit("error", "Error changing zone");
|
|
||||||
socket.emit("zone_settings", settings)
|
|
||||||
} else {
|
|
||||||
secureAdminBroadcast("zone_settings", settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on("set_penalty_settings", (settings) => {
|
|
||||||
if (!loggedIn) {
|
|
||||||
socket.emit("error", "Not logged in");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!penaltyController.updateSettings(settings)) {
|
|
||||||
socket.emit("error", "Invalid settings");
|
|
||||||
socket.emit("penalty_settings", penaltyController.settings)
|
|
||||||
} else {
|
|
||||||
secureAdminBroadcast("penalty_settings", penaltyController.settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// User is attempting to add a new team
|
|
||||||
socket.on("add_team", (teamName) => {
|
socket.on("add_team", (teamName) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.addTeam(teamName);
|
||||||
return;
|
secureAdminBroadcast("teams", game.teams);
|
||||||
}
|
|
||||||
if (game.addTeam(teamName)) {
|
|
||||||
secureAdminBroadcast("teams", game.teams);
|
|
||||||
} else {
|
|
||||||
socket.emit("error", "Error adding team");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// User is attempting to remove a team
|
|
||||||
socket.on("remove_team", (teamId) => {
|
socket.on("remove_team", (teamId) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.removeTeam(teamId);
|
||||||
return;
|
secureAdminBroadcast("teams", game.teams);
|
||||||
}
|
|
||||||
if (game.removeTeam(teamId)) {
|
|
||||||
secureAdminBroadcast("teams", game.teams);
|
|
||||||
} else {
|
|
||||||
socket.emit("error", "Error removing team");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// User is attempting to change the game state
|
|
||||||
socket.on("change_state", (state) => {
|
socket.on("change_state", (state) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.setState(state);
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!game.setState(state)) {
|
|
||||||
socket.emit("error", "Error setting state");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use is sending a new list containing the new order of the teams
|
// Use is sending a new list containing the new order of the teams
|
||||||
// Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function
|
// Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function
|
||||||
// But the frontend should always send the same teams in a different order
|
// But the frontend should always send the same teams in a different order
|
||||||
socket.on("reorder_teams", (newOrder) => {
|
socket.on("reorder_teams", (newOrder) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.reorderTeams(newOrder);
|
||||||
return;
|
secureAdminBroadcast("teams", game.teams);
|
||||||
}
|
game.teams.forEach(t => sendUpdatedTeamInformations(t.id));
|
||||||
if (game.reorderTeams(newOrder)) {
|
|
||||||
secureAdminBroadcast("teams", game.teams);
|
|
||||||
game.teams.forEach(t => sendUpdatedTeamInformations(t.id))
|
|
||||||
} else {
|
|
||||||
socket.emit("error", "Error reordering teams");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("update_team", (teamId, newTeam) => {
|
socket.on("update_team", (teamId, newTeam) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) return;
|
||||||
socket.emit("error", "Not logged in");
|
game.updateTeam(teamId, newTeam);
|
||||||
return;
|
secureAdminBroadcast("teams", game.teams);
|
||||||
}
|
sendUpdatedTeamInformations(teamId);
|
||||||
if (game.updateTeam(teamId, newTeam)) {
|
sendUpdatedTeamInformations(game.getTeam(teamId).chased);
|
||||||
secureAdminBroadcast("teams", game.teams);
|
|
||||||
sendUpdatedTeamInformations(teamId)
|
|
||||||
sendUpdatedTeamInformations(game.getTeam(teamId).chased)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Request an update of the team list
|
|
||||||
// We only reply to the sender to prevent spam
|
|
||||||
socket.on("get_teams", () => {
|
|
||||||
if (!loggedIn) {
|
|
||||||
socket.emit("error", "Not logged in");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
socket.emit("teams", game.teams);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
This module manages the main game state, the teams, the settings and the game logic
|
This module manages the main game state, the teams, the settings and the game logic
|
||||||
*/
|
*/
|
||||||
import { secureAdminBroadcast } from "./admin_socket.js";
|
import { secureAdminBroadcast } from "./admin_socket.js";
|
||||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
import { teamBroadcast, playersBroadcast, sendUpdatedTeamInformations, } from "./team_socket.js";
|
||||||
import timeoutHandler from "./timeoutHandler.js";
|
import { sendPositionTimeouts, outOfZoneTimeouts } from "./timeout_handler.js";
|
||||||
import penaltyController from "./penalty_controller.js";
|
|
||||||
import zoneManager from "./zone_manager.js";
|
import zoneManager from "./zone_manager.js";
|
||||||
import trajectory from "./trajectory.js";
|
import trajectory from "./trajectory.js";
|
||||||
|
|
||||||
@@ -52,34 +51,43 @@ export const GameState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
//List of teams, as objects. To see the fields see the addTeam methods
|
// List of teams, as objects. To see the fields see the addTeam methods
|
||||||
teams: [],
|
teams: [],
|
||||||
//Current state of the game
|
// Current state of the game
|
||||||
state: GameState.SETUP,
|
state: GameState.SETUP,
|
||||||
// Date since gameState switched to PLAYING
|
// Date since the state changed
|
||||||
startDate: null,
|
stateDate: Date.now(),
|
||||||
//Settings of the game
|
// Messages
|
||||||
settings: {
|
messages: {
|
||||||
loserEndGameMessage: "",
|
waiting: "",
|
||||||
winnerEndGameMessage: "",
|
captured: "",
|
||||||
capturedMessage: "",
|
winner: "",
|
||||||
waitingMessage: ""
|
loser: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return {
|
||||||
|
messages: this.messages,
|
||||||
|
zone: zoneManager.settings,
|
||||||
|
sendPositionDelay: sendPositionTimeouts.delay,
|
||||||
|
outOfZoneDelay: outOfZoneTimeouts.delay
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the game settings
|
* Update the game settings
|
||||||
* @param {Object} newSettings settings to be updated, can be partial
|
* @param {Object} newSettings settings to be updated, can be partial
|
||||||
* @returns true if the settings are applied
|
|
||||||
*/
|
*/
|
||||||
changeSettings(newSettings) {
|
changeSettings(newSettings) {
|
||||||
this.settings = { ...this.settings, ...newSettings };
|
if ("messages" in newSettings) this.messages = {...this.messages, ...newSettings.messages};
|
||||||
return true;
|
if ("zone" in newSettings) zoneManager.changeSettings(newSettings.zone);
|
||||||
|
if ("sendPositionDelay" in newSettings) sendPositionTimeouts.setDelay(newSettings.sendPositionDelay);
|
||||||
|
if ("outOfZoneDelay" in newSettings) outOfZoneTimeouts.setDelay(newSettings.outOfZoneDelay);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the state of the game to newState and start the necessary processes
|
* Change the state of the game to newState and start the necessary processes
|
||||||
* @param {String} newState
|
* @param {String} newState
|
||||||
* @returns true if the state has been changed
|
|
||||||
*/
|
*/
|
||||||
setState(newState) {
|
setState(newState) {
|
||||||
// Checks is the newState is a Gamestate
|
// Checks is the newState is a Gamestate
|
||||||
@@ -89,8 +97,8 @@ export default {
|
|||||||
case GameState.SETUP:
|
case GameState.SETUP:
|
||||||
trajectory.stop();
|
trajectory.stop();
|
||||||
zoneManager.stop();
|
zoneManager.stop();
|
||||||
penaltyController.stop();
|
sendPositionTimeouts.clearAll();
|
||||||
timeoutHandler.endAllSendPositionTimeout();
|
outOfZoneTimeouts.clearAll();
|
||||||
for (let team of this.teams) {
|
for (let team of this.teams) {
|
||||||
// Chasing
|
// Chasing
|
||||||
team.captured = false;
|
team.captured = false;
|
||||||
@@ -103,7 +111,6 @@ export default {
|
|||||||
// Placement
|
// Placement
|
||||||
team.ready = false;
|
team.ready = false;
|
||||||
// Zone
|
// Zone
|
||||||
team.penalties = 0;
|
|
||||||
team.outOfZone = false;
|
team.outOfZone = false;
|
||||||
team.outOfZoneDeadline = null;
|
team.outOfZoneDeadline = null;
|
||||||
// Stats
|
// Stats
|
||||||
@@ -113,34 +120,33 @@ export default {
|
|||||||
team.nObserved = 0;
|
team.nObserved = 0;
|
||||||
team.finishDate = null;
|
team.finishDate = null;
|
||||||
}
|
}
|
||||||
this.startDate = null;
|
this.stateDate = Date.now();
|
||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
break;
|
break;
|
||||||
case GameState.PLACEMENT:
|
case GameState.PLACEMENT:
|
||||||
if (this.teams.length < 3) {
|
if (this.teams.length < 3) {
|
||||||
secureAdminBroadcast("game_state", {state: this.state, startDate: this.startDate});
|
secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
trajectory.stop();
|
trajectory.stop();
|
||||||
zoneManager.stop();
|
zoneManager.stop();
|
||||||
penaltyController.stop();
|
sendPositionTimeouts.clearAll();
|
||||||
timeoutHandler.endAllSendPositionTimeout();
|
outOfZoneTimeouts.clearAll();
|
||||||
this.startDate = null;
|
this.stateDate = Date.now();
|
||||||
break;
|
break;
|
||||||
case GameState.PLAYING:
|
case GameState.PLAYING:
|
||||||
if (this.teams.length < 3) {
|
if (this.teams.length < 3) {
|
||||||
secureAdminBroadcast("game_state", {state: this.state, startDate: this.startDate});
|
secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
trajectory.start();
|
trajectory.start();
|
||||||
zoneManager.start();
|
zoneManager.start();
|
||||||
penaltyController.start();
|
|
||||||
this.initLastSentLocations();
|
this.initLastSentLocations();
|
||||||
this.startDate = Date.now();
|
this.stateDate = Date.now();
|
||||||
break;
|
break;
|
||||||
case GameState.FINISHED:
|
case GameState.FINISHED:
|
||||||
if (this.state != GameState.PLAYING) {
|
if (this.state != GameState.PLAYING) {
|
||||||
secureAdminBroadcast("game_state", {state: this.state, startDate: this.startDate});
|
secureAdminBroadcast("game_state", {state: this.state, stateDate: this.stateDate});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const team of this.teams) {
|
for (const team of this.teams) {
|
||||||
@@ -148,16 +154,15 @@ export default {
|
|||||||
}
|
}
|
||||||
trajectory.stop();
|
trajectory.stop();
|
||||||
zoneManager.stop();
|
zoneManager.stop();
|
||||||
penaltyController.stop();
|
sendPositionTimeouts.clearAll();
|
||||||
timeoutHandler.endAllSendPositionTimeout();
|
outOfZoneTimeouts.clearAll();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Update the state
|
// Update the state
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
secureAdminBroadcast("game_state", {state: newState, startDate: this.startDate});
|
secureAdminBroadcast("game_state", {state: newState, stateDate: this.stateDate});
|
||||||
playersBroadcast("game_state", newState);
|
playersBroadcast("game_state", newState);
|
||||||
secureAdminBroadcast("teams", this.teams);
|
secureAdminBroadcast("teams", this.teams);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,7 +188,6 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* Add a new team to the game
|
* Add a new team to the game
|
||||||
* @param {String} teamName the name of the team
|
* @param {String} teamName the name of the team
|
||||||
* @returns true if the team has been added
|
|
||||||
*/
|
*/
|
||||||
addTeam(teamName) {
|
addTeam(teamName) {
|
||||||
this.teams.push({
|
this.teams.push({
|
||||||
@@ -206,7 +210,6 @@ export default {
|
|||||||
startingArea: null,
|
startingArea: null,
|
||||||
ready: false,
|
ready: false,
|
||||||
// Zone
|
// Zone
|
||||||
penalties: 0,
|
|
||||||
outOfZone: false,
|
outOfZone: false,
|
||||||
outOfZoneDeadline: null,
|
outOfZoneDeadline: null,
|
||||||
// Stats
|
// Stats
|
||||||
@@ -221,35 +224,18 @@ export default {
|
|||||||
battery: null,
|
battery: null,
|
||||||
});
|
});
|
||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of teams that are not captured
|
|
||||||
* @returns the number of teams that are not captured
|
|
||||||
*/
|
|
||||||
playingTeamCount() {
|
|
||||||
let res = 0;
|
|
||||||
this.teams.forEach((t) => {
|
|
||||||
if (!t.captured) {
|
|
||||||
res++;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return res;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the chasing chain of the teams based of the ordre of the teams array
|
* Update the chasing chain of the teams based of the ordre of the teams array
|
||||||
* If there are only 2 teams left, the game will end
|
* If there are only 2 teams left, the game will end
|
||||||
* This function will update the chasing and chased values of each teams
|
* This function will update the chasing and chased values of each teams
|
||||||
* @returns true if successful
|
|
||||||
*/
|
*/
|
||||||
updateTeamChasing() {
|
updateTeamChasing() {
|
||||||
if (this.playingTeamCount() <= 2) {
|
const playingTeams = this.teams.reduce((count, team) => count + (!team.captured ? 1 : 0), 0);
|
||||||
if (this.state == GameState.PLAYING) {
|
if (playingTeams <= 2) {
|
||||||
this.setState(GameState.FINISHED);
|
if (this.state == GameState.PLAYING) this.setState(GameState.FINISHED);
|
||||||
}
|
return;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
let firstTeam = null;
|
let firstTeam = null;
|
||||||
let previousTeam = null
|
let previousTeam = null
|
||||||
@@ -267,17 +253,15 @@ export default {
|
|||||||
this.getTeam(firstTeam).chased = previousTeam;
|
this.getTeam(firstTeam).chased = previousTeam;
|
||||||
this.getTeam(previousTeam).chasing = firstTeam;
|
this.getTeam(previousTeam).chasing = firstTeam;
|
||||||
secureAdminBroadcast("teams", this.teams);
|
secureAdminBroadcast("teams", this.teams);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rearrange the order of the teams and update the chasing chain
|
* Rearrange the order of the teams and update the chasing chain
|
||||||
* @param {Array} newOrder An array of teams in the new order
|
* @param {Array} newOrder An array of teams in the new order
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
reorderTeams(newOrder) {
|
reorderTeams(newOrder) {
|
||||||
this.teams = newOrder;
|
this.teams = newOrder;
|
||||||
return this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,7 +277,6 @@ export default {
|
|||||||
* Update a team's values
|
* Update a team's values
|
||||||
* @param {Number} teamId The id of the team to update
|
* @param {Number} teamId The id of the team to update
|
||||||
* @param {Object} newTeam An object containing the new values of the team, can be partial
|
* @param {Object} newTeam An object containing the new values of the team, can be partial
|
||||||
* @returns true if the team has been updated
|
|
||||||
*/
|
*/
|
||||||
updateTeam(teamId, newTeam) {
|
updateTeam(teamId, newTeam) {
|
||||||
this.teams = this.teams.map((t) => {
|
this.teams = this.teams.map((t) => {
|
||||||
@@ -304,20 +287,17 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
penaltyController.checkPenalties();
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Number} teamId The ID of the team which location will be updated
|
* @param {Number} teamId The ID of the team which location will be updated
|
||||||
* @param {Array} location An array containing in order the latitude and longitude of the new location
|
* @param {Array} location An array containing in order the latitude and longitude of the new location
|
||||||
* @returns true if the location has been updated
|
|
||||||
*/
|
*/
|
||||||
updateLocation(teamId, location) {
|
updateLocation(teamId, location) {
|
||||||
const team = this.getTeam(teamId);
|
const team = this.getTeam(teamId);
|
||||||
if (!team || !location) {
|
if (!team || !location) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
if (team.currentLocation) team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]}));
|
if (team.currentLocation) team.distance += Math.floor(getDistanceFromLatLon({lat: location[0], lng: location[1]}, {lat: team.currentLocation[0], lng: team.currentLocation[1]}));
|
||||||
// Update of events of the game
|
// Update of events of the game
|
||||||
@@ -328,9 +308,19 @@ export default {
|
|||||||
if (this.state == GameState.PLACEMENT && team.startingArea && team.startingArea && location) {
|
if (this.state == GameState.PLACEMENT && team.startingArea && team.startingArea && location) {
|
||||||
team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius);
|
team.ready = isInCircle({ lat: location[0], lng: location[1] }, team.startingArea.center, team.startingArea.radius);
|
||||||
}
|
}
|
||||||
|
// Verify zone
|
||||||
|
const teamCurrentlyOutOfZone = !zoneManager.isInZone({ lat: location[0], lng: location[1] })
|
||||||
|
if (teamCurrentlyOutOfZone && !team.outOfZone) {
|
||||||
|
team.outOfZone = true;
|
||||||
|
team.outOfZoneDeadline = Date.now() + outOfZoneTimeouts.duration * 60 * 1000;
|
||||||
|
outOfZoneTimeouts.set(teamId);
|
||||||
|
} else if (!teamCurrentlyOutOfZone && team.outOfZone) {
|
||||||
|
team.outOfZone = false;
|
||||||
|
team.outOfZoneDeadline = null;
|
||||||
|
outOfZoneTimeouts.clear(teamId);
|
||||||
|
}
|
||||||
// Sending new infos to the team
|
// Sending new infos to the team
|
||||||
sendUpdatedTeamInformations(team.id);
|
sendUpdatedTeamInformations(team.id);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -340,8 +330,8 @@ export default {
|
|||||||
// Update of lastSentLocation
|
// Update of lastSentLocation
|
||||||
for (const team of this.teams) {
|
for (const team of this.teams) {
|
||||||
team.lastSentLocation = team.currentLocation;
|
team.lastSentLocation = team.currentLocation;
|
||||||
team.locationSendDeadline = Date.now() + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000;
|
team.locationSendDeadline = Date.now() + sendPositionTimeouts.duration * 60 * 1000;
|
||||||
timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline);
|
sendPositionTimeouts.set(team.id);
|
||||||
sendUpdatedTeamInformations(team.id);
|
sendUpdatedTeamInformations(team.id);
|
||||||
}
|
}
|
||||||
// Update of enemyLocation now we have the lastSentLocation of the enemy
|
// Update of enemyLocation now we have the lastSentLocation of the enemy
|
||||||
@@ -354,22 +344,21 @@ export default {
|
|||||||
/**
|
/**
|
||||||
* Get the most recent enemy team's location as well as setting the latest accessible location to the current one
|
* Get the most recent enemy team's location as well as setting the latest accessible location to the current one
|
||||||
* @param {Number} teamId The ID of the team that will send its location
|
* @param {Number} teamId The ID of the team that will send its location
|
||||||
* @returns true if the location has been sent
|
|
||||||
*/
|
*/
|
||||||
sendLocation(teamId) {
|
sendLocation(teamId) {
|
||||||
const team = this.getTeam(teamId);
|
const team = this.getTeam(teamId);
|
||||||
const enemyTeam = this.getTeam(team.chasing);
|
|
||||||
if (!team || !team.currentLocation) {
|
if (!team || !team.currentLocation) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
const enemyTeam = this.getTeam(team.chasing);
|
||||||
team.nSentLocation++;
|
team.nSentLocation++;
|
||||||
enemyTeam.nObserved++;
|
enemyTeam.nObserved++;
|
||||||
const dateNow = Date.now();
|
const dateNow = Date.now();
|
||||||
// Update of events of the game
|
// Update of events of the game
|
||||||
trajectory.writeSeePosition(dateNow, teamId, team.chasing);
|
trajectory.writeSeePosition(dateNow, teamId, team.chasing);
|
||||||
// Update of locationSendDeadline
|
// Update of locationSendDeadline
|
||||||
team.locationSendDeadline = dateNow + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000;
|
team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000;
|
||||||
timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline);
|
sendPositionTimeouts.set(team.id);
|
||||||
// Update of lastSentLocation
|
// Update of lastSentLocation
|
||||||
team.lastSentLocation = team.currentLocation;
|
team.lastSentLocation = team.currentLocation;
|
||||||
// Update of enemyLocation
|
// Update of enemyLocation
|
||||||
@@ -378,22 +367,21 @@ export default {
|
|||||||
// Sending new infos to the team
|
// Sending new infos to the team
|
||||||
sendUpdatedTeamInformations(team.id);
|
sendUpdatedTeamInformations(team.id);
|
||||||
sendUpdatedTeamInformations(enemyTeam.id);
|
sendUpdatedTeamInformations(enemyTeam.id);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a team by its ID
|
* Remove a team by its ID
|
||||||
* @param {Number} teamId The id of the team to remove
|
* @param {Number} teamId The id of the team to remove
|
||||||
* @returns true if the team has been deleted
|
|
||||||
*/
|
*/
|
||||||
removeTeam(teamId) {
|
removeTeam(teamId) {
|
||||||
if (!this.getTeam(teamId)) {
|
if (!this.getTeam(teamId)) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
teamBroadcast("logout");
|
||||||
this.teams = this.teams.filter(t => t.id !== teamId);
|
this.teams = this.teams.filter(t => t.id !== teamId);
|
||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
timeoutHandler.endSendPositionTimeout(teamId);
|
sendPositionTimeouts.clear(teamId);
|
||||||
return true;
|
outOfZoneTimeouts.clear(teamId);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -402,13 +390,12 @@ export default {
|
|||||||
* And the chase chain will be updated
|
* And the chase chain will be updated
|
||||||
* @param {Number} teamId The id of the capturing team
|
* @param {Number} teamId The id of the capturing team
|
||||||
* @param {Number} captureCode The code sent by the capturing that only the captured team know, used to verify the authenticity of the capture
|
* @param {Number} captureCode The code sent by the capturing that only the captured team know, used to verify the authenticity of the capture
|
||||||
* @returns {Boolean} if the capture has been successfull or not
|
|
||||||
*/
|
*/
|
||||||
requestCapture(teamId, captureCode) {
|
requestCapture(teamId, captureCode) {
|
||||||
const team = this.getTeam(teamId);
|
const team = this.getTeam(teamId);
|
||||||
const enemyTeam = this.getTeam(team.chasing);
|
const enemyTeam = this.getTeam(team.chasing);
|
||||||
if (!enemyTeam || enemyTeam.captureCode != captureCode) {
|
if (!enemyTeam || enemyTeam.captureCode != captureCode) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
team.nCaptures++;
|
team.nCaptures++;
|
||||||
// Update of events of the game
|
// Update of events of the game
|
||||||
@@ -418,7 +405,6 @@ export default {
|
|||||||
// Sending new infos to the teams
|
// Sending new infos to the teams
|
||||||
sendUpdatedTeamInformations(team.id);
|
sendUpdatedTeamInformations(team.id);
|
||||||
sendUpdatedTeamInformations(enemyTeam.id);
|
sendUpdatedTeamInformations(enemyTeam.id);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -429,7 +415,12 @@ export default {
|
|||||||
const team = this.getTeam(teamId);
|
const team = this.getTeam(teamId);
|
||||||
team.captured = true;
|
team.captured = true;
|
||||||
team.finishDate = Date.now();
|
team.finishDate = Date.now();
|
||||||
timeoutHandler.endSendPositionTimeout(teamId);
|
sendPositionTimeouts.clear(teamId);
|
||||||
|
outOfZoneTimeouts.clear(teamId);
|
||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
},
|
},
|
||||||
}
|
|
||||||
|
handicapTeam(teamId) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
/*
|
|
||||||
This module manages the verification of the game rules and the penalties.
|
|
||||||
*/
|
|
||||||
import { sendUpdatedTeamInformations, teamBroadcast } from "./team_socket.js";
|
|
||||||
import { secureAdminBroadcast } from "./admin_socket.js";
|
|
||||||
import game, { GameState } from "./game.js";
|
|
||||||
import zoneManager from "./zone_manager.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
// Object mapping team id to the date they left the zone as a UNIX millisecond timestamp
|
|
||||||
outOfBoundsSince: {},
|
|
||||||
// Id of the interval checking the rules
|
|
||||||
checkIntervalId: null,
|
|
||||||
settings: {
|
|
||||||
//Time in minutes before a team is penalized for not updating their position
|
|
||||||
allowedTimeOutOfZone: 10,
|
|
||||||
//Time in minutes before a team is penalized for not updating their position
|
|
||||||
allowedTimeBetweenPositionUpdate: 10,
|
|
||||||
//Number of penalties needed to be eliminated
|
|
||||||
maxPenalties: 3
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the penalty controller, watch the team positions and apply penalties if necessary
|
|
||||||
*/
|
|
||||||
start() {
|
|
||||||
this.outOfBoundsSince = {};
|
|
||||||
if (this.checkIntervalId) {
|
|
||||||
clearInterval(this.checkIntervalId)
|
|
||||||
}
|
|
||||||
//Watch periodically if all teams need are following the rules
|
|
||||||
this.checkIntervalId = setInterval(() => {
|
|
||||||
if (game.state == GameState.PLAYING) {
|
|
||||||
//this.watchPositionUpdate();
|
|
||||||
this.watchZone();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop the penalty controller
|
|
||||||
*/
|
|
||||||
stop() {
|
|
||||||
this.outOfBoundsSince = {};
|
|
||||||
if (this.checkIntervalId) {
|
|
||||||
clearInterval(this.checkIntervalId)
|
|
||||||
this.checkIntervalId = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the penalty controller settings
|
|
||||||
* @param {Object} newSettings the object containing the settings to be udpated, can be partial
|
|
||||||
* @returns true if the settings were updated, false otherwise
|
|
||||||
*/
|
|
||||||
updateSettings(newSettings) {
|
|
||||||
//Sanitize input
|
|
||||||
if (newSettings.maxPenalties && (isNaN(parseInt(newSettings.maxPenalties)) || newSettings.maxPenalties < 0)) { return false }
|
|
||||||
if (newSettings.allowedTimeBetweenPositionUpdate && (isNaN(parseFloat(newSettings.allowedTimeBetweenPositionUpdate)) || newSettings.allowedTimeBetweenPositionUpdate < 0)) { return false }
|
|
||||||
if (newSettings.allowedTimeOutOfZone && (isNaN(parseFloat(newSettings.allowedTimeOutOfZone)) || newSettings.allowedTimeOutOfZone < 0)) { return false }
|
|
||||||
|
|
||||||
this.settings = { ...this.settings, ...newSettings };
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the penalty score of a team, send a message to the team and eliminated if necessary
|
|
||||||
* @param {Number} teamId The team that will recieve a penalty
|
|
||||||
*/
|
|
||||||
addPenalty(teamId) {
|
|
||||||
let team = game.getTeam(teamId);
|
|
||||||
if (!team) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (team.captured) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
team.penalties++;
|
|
||||||
if (team.penalties >= this.settings.maxPenalties) {
|
|
||||||
game.capture(team.id);
|
|
||||||
sendUpdatedTeamInformations(teamId);
|
|
||||||
sendUpdatedTeamInformations(team.chased);
|
|
||||||
teamBroadcast(teamId, "warning", "You have been eliminated (reason: too many penalties)")
|
|
||||||
teamBroadcast(team.chased, "success", "The team you were chasing has been eliminated")
|
|
||||||
} else {
|
|
||||||
teamBroadcast(teamId, "warning", `You recieved a penalty (${team.penalties}/${this.settings.maxPenalties})`)
|
|
||||||
sendUpdatedTeamInformations(teamId);
|
|
||||||
}
|
|
||||||
secureAdminBroadcast("teams", game.teams)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if each team has too many penalties and eliminate them if necessary
|
|
||||||
* Also send a socket message to the team and the chased team
|
|
||||||
*/
|
|
||||||
checkPenalties() {
|
|
||||||
for (let team of game.teams) {
|
|
||||||
if (team.penalties >= this.settings.maxPenalties) {
|
|
||||||
game.capture(team.id);
|
|
||||||
sendUpdatedTeamInformations(team.id);
|
|
||||||
sendUpdatedTeamInformations(team.chased);
|
|
||||||
teamBroadcast(team.id, "warning", "You have been eliminated (reason: too many penalties)")
|
|
||||||
teamBroadcast(team.chased, "success", "The team you were chasing has been eliminated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch the position of each team and apply penalties if necessary
|
|
||||||
* If a team is out of the zone, a warning will be sent to them
|
|
||||||
* A warning is also sent one minute before the penalty is applied
|
|
||||||
*/
|
|
||||||
watchZone() {
|
|
||||||
game.teams.forEach((team) => {
|
|
||||||
if (team.captured) { return }
|
|
||||||
//All the informations are not ready yet
|
|
||||||
if (team.currentLocation == null || !zoneManager.isRunning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!zoneManager.isInZone({ lat: team.currentLocation[0], lng: team.currentLocation[1] })) {
|
|
||||||
//The team was not previously out of the zone
|
|
||||||
if (!this.outOfBoundsSince[team.id]) {
|
|
||||||
this.outOfBoundsSince[team.id] = Date.now();
|
|
||||||
team.outOfZone = true;
|
|
||||||
team.outOfZoneDeadline = this.outOfBoundsSince[team.id] + this.settings.allowedTimeOutOfZone * 60 * 1000;
|
|
||||||
secureAdminBroadcast("teams", game.teams)
|
|
||||||
} else if (Date.now() - this.outOfBoundsSince[team.id] > this.settings.allowedTimeOutOfZone * 60 * 1000) {
|
|
||||||
this.addPenalty(team.id);
|
|
||||||
this.outOfBoundsSince[team.id] = Date.now();
|
|
||||||
team.outOfZoneDeadline = this.outOfBoundsSince[team.id] + this.settings.allowedTimeOutOfZone * 60 * 1000;
|
|
||||||
secureAdminBroadcast("teams", game.teams)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.outOfBoundsSince[team.id]) {
|
|
||||||
team.outOfZone = false;
|
|
||||||
delete this.outOfBoundsSince[team.id];
|
|
||||||
secureAdminBroadcast("teams", game.teams)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch the date of the last position update of each team and apply penalties if necessary
|
|
||||||
* Also send a message one minute before the penalty is applied
|
|
||||||
*/
|
|
||||||
watchPositionUpdate() {
|
|
||||||
game.teams.forEach((team) => {
|
|
||||||
//If the team has not sent their location for more than the allowed period, automatically send it and add a penalty
|
|
||||||
if (team.captured) { return }
|
|
||||||
if (team.locationSendDeadline == null) {
|
|
||||||
team.locationSendDeadline = Number(Date.now()) + this.settings.allowedTimeBetweenPositionUpdate * 60 * 1000;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Date.now() > team.locationSendDeadline) {
|
|
||||||
this.addPenalty(team.id);
|
|
||||||
game.sendLocation(team.id);
|
|
||||||
sendUpdatedTeamInformations(team.id);
|
|
||||||
secureAdminBroadcast("teams", game.teams)
|
|
||||||
} else if (Math.abs(Date.now() - team.locationSendDeadline - 60 * 1000) < 100) {
|
|
||||||
teamBroadcast(team.id, "warning", `You have one minute left to udpate your location.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -15,9 +15,7 @@ import zoneManager from "./zone_manager.js";
|
|||||||
* @param {*} data The payload
|
* @param {*} data The payload
|
||||||
*/
|
*/
|
||||||
export function teamBroadcast(teamId, event, data) {
|
export function teamBroadcast(teamId, event, data) {
|
||||||
for (const socketId of game.getTeam(teamId).sockets) {
|
game.getTeam(teamId).sockets.forEach(socketId => io.of("player").to(socketId).emit(event, data));
|
||||||
io.of("player").to(socketId).emit(event, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +50,6 @@ export function sendUpdatedTeamInformations(teamId) {
|
|||||||
startingArea: team.startingArea,
|
startingArea: team.startingArea,
|
||||||
ready: team.ready,
|
ready: team.ready,
|
||||||
// Constraints
|
// Constraints
|
||||||
penalties: team.penalties,
|
|
||||||
outOfZone: team.outOfZone,
|
outOfZone: team.outOfZone,
|
||||||
outOfZoneDeadline: team.outOfZoneDeadline,
|
outOfZoneDeadline: team.outOfZoneDeadline,
|
||||||
locationSendDeadline: team.locationSendDeadline,
|
locationSendDeadline: team.locationSendDeadline,
|
||||||
@@ -66,66 +63,60 @@ export function sendUpdatedTeamInformations(teamId) {
|
|||||||
secureAdminBroadcast("teams", game.teams);
|
secureAdminBroadcast("teams", game.teams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a player from the list of logged in players
|
|
||||||
* @param {Number} id The id of the player to log out
|
|
||||||
*/
|
|
||||||
function logoutPlayer(id) {
|
|
||||||
for (const team of game.teams) {
|
|
||||||
if (team.sockets.indexOf(id) == 0) {
|
|
||||||
team.battery = null;
|
|
||||||
team.phoneModel = null;
|
|
||||||
team.phoneName = null;
|
|
||||||
}
|
|
||||||
team.sockets = team.sockets.filter((sid) => sid != id);
|
|
||||||
}
|
|
||||||
secureAdminBroadcast("teams", game.teams);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initTeamSocket() {
|
export function initTeamSocket() {
|
||||||
io.of("player").on("connection", (socket) => {
|
io.of("player").on("connection", (socket) => {
|
||||||
console.log("Connection of a player");
|
console.log("Connection of a player");
|
||||||
let teamId = null;
|
let teamId = null;
|
||||||
|
|
||||||
|
const logoutPlayer = () => {
|
||||||
|
if (!teamId) return;
|
||||||
|
const team = game.getTeam(teamId);
|
||||||
|
if (team.sockets.indexOf(socket.id) == 0) {
|
||||||
|
team.battery = null;
|
||||||
|
team.phoneModel = null;
|
||||||
|
team.phoneName = null;
|
||||||
|
}
|
||||||
|
// Delete the player from the team
|
||||||
|
team.sockets = team.sockets.filter((sid) => sid != socket.id);
|
||||||
|
secureAdminBroadcast("teams", game.teams);
|
||||||
|
socket.emit("logout");
|
||||||
|
teamId = null;
|
||||||
|
}
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("disconnect", () => {
|
||||||
console.log("Disconnection of a player");
|
console.log("Disconnection of a player");
|
||||||
logoutPlayer(socket.id);
|
logoutPlayer();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("logout", () => {
|
||||||
|
logoutPlayer();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("login", (loginTeamId, callback) => {
|
socket.on("login", (loginTeamId, callback) => {
|
||||||
|
logoutPlayer();
|
||||||
const team = game.getTeam(loginTeamId);
|
const team = game.getTeam(loginTeamId);
|
||||||
if (!team) {
|
if (!team) {
|
||||||
callback({ isLoggedIn: false, message: "Login denied" });
|
callback({ isLoggedIn: false, message: "Login denied" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logoutPlayer(socket.id);
|
|
||||||
team.sockets.push(socket.id);
|
|
||||||
teamId = loginTeamId;
|
teamId = loginTeamId;
|
||||||
|
team.sockets.push(socket.id);
|
||||||
sendUpdatedTeamInformations(loginTeamId);
|
sendUpdatedTeamInformations(loginTeamId);
|
||||||
socket.emit("login_response", true);
|
|
||||||
socket.emit("game_state", game.state);
|
socket.emit("game_state", game.state);
|
||||||
socket.emit("game_settings", game.settings);
|
socket.emit("game_settings", game.settings);
|
||||||
socket.emit("zone", {
|
socket.emit("zone", {
|
||||||
|
type: zoneManager.settings.type,
|
||||||
begin: zoneManager.getCurrentZone(),
|
begin: zoneManager.getCurrentZone(),
|
||||||
end: zoneManager.getNextZone(),
|
end: zoneManager.getNextZone(),
|
||||||
endDate: zoneManager.currentZoneEndDate,
|
endDate: zoneManager.currentZoneEndDate,
|
||||||
})
|
});
|
||||||
callback({ isLoggedIn : true, message: "Logged in"});
|
callback({ isLoggedIn : true, message: "Logged in"});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("logout", () => {
|
|
||||||
logoutPlayer(socket.id);
|
|
||||||
teamId = null;
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on("update_position", (position) => {
|
socket.on("update_position", (position) => {
|
||||||
// Only the first player to connect to the team socket can update the current position
|
if (!teamId) return;
|
||||||
// This is done to prevent multiple clients from sending slightly different prosition back and forth
|
|
||||||
// Making the point jitter on the map
|
|
||||||
if (!teamId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const team = game.getTeam(teamId);
|
const team = game.getTeam(teamId);
|
||||||
|
// Only the first socket can update the current position since he is the one whose location is tracked
|
||||||
if (team.sockets.indexOf(socket.id) == 0) {
|
if (team.sockets.indexOf(socket.id) == 0) {
|
||||||
game.updateLocation(teamId, position);
|
game.updateLocation(teamId, position);
|
||||||
team.lastCurrentLocationDate = Date.now();
|
team.lastCurrentLocationDate = Date.now();
|
||||||
@@ -134,27 +125,18 @@ export function initTeamSocket() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("send_position", () => {
|
socket.on("send_position", () => {
|
||||||
if (!teamId) {
|
if (!teamId) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
game.sendLocation(teamId);
|
game.sendLocation(teamId);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("capture", (captureCode, callback) => {
|
socket.on("capture", (captureCode, callback) => {
|
||||||
if (!teamId) {
|
if (!teamId) return;
|
||||||
return;
|
game.requestCapture(teamId, captureCode);
|
||||||
}
|
|
||||||
if (!game.requestCapture(teamId, captureCode)) {
|
|
||||||
callback({ hasCaptured : false, message: "Capture failed" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback({ hasCaptured : true, message: "Capture successful" });
|
callback({ hasCaptured : true, message: "Capture successful" });
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("deviceInfo", (infos) => {
|
socket.on("device_info", (infos) => {
|
||||||
if (!teamId) {
|
if (!teamId) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const team = game.getTeam(teamId);
|
const team = game.getTeam(teamId);
|
||||||
// Only the first socket shares its infos since he is the one whose location is tracked
|
// Only the first socket shares its infos since he is the one whose location is tracked
|
||||||
if (team.sockets.indexOf(socket.id) == 0) {
|
if (team.sockets.indexOf(socket.id) == 0) {
|
||||||
@@ -163,10 +145,8 @@ export function initTeamSocket() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("batteryUpdate", (batteryLevel) => {
|
socket.on("battery_update", (batteryLevel) => {
|
||||||
if (!teamId) {
|
if (!teamId) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const team = game.getTeam(teamId);
|
const team = game.getTeam(teamId);
|
||||||
// Only the first socket shares its infos since he is the one whose location is tracked
|
// Only the first socket shares its infos since he is the one whose location is tracked
|
||||||
if (team.sockets.indexOf(socket.id) == 0) {
|
if (team.sockets.indexOf(socket.id) == 0) {
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import game from "./game.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
teams: [],
|
|
||||||
|
|
||||||
setSendPositionTimeout(teamID, deadline) {
|
|
||||||
const foundTeam = this.teams.find(t => t.teamID === teamID);
|
|
||||||
if (!foundTeam) {
|
|
||||||
this.teams.push({teamID: teamID, timeoutID: setTimeout(() => game.sendLocation(teamID), deadline - Date.now())});
|
|
||||||
} else {
|
|
||||||
clearTimeout(foundTeam.timeoutID);
|
|
||||||
foundTeam.timeoutID = setTimeout(() => game.sendLocation(teamID), deadline - Date.now());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
endSendPositionTimeout(teamID) {
|
|
||||||
const foundTeam = this.teams.find(t => t.teamID === teamID);
|
|
||||||
if (foundTeam) {
|
|
||||||
clearTimeout(foundTeam.timeoutID);
|
|
||||||
this.teams = this.teams.filter(t => t.teamID !== teamID);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
endAllSendPositionTimeout() {
|
|
||||||
for (const team of this.teams) {
|
|
||||||
clearTimeout(team.timeoutID);
|
|
||||||
}
|
|
||||||
this.teams = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
80
traque-back/timeout_handler.js
Normal file
80
traque-back/timeout_handler.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import game from "./game.js";
|
||||||
|
|
||||||
|
class TimeoutManager {
|
||||||
|
constructor() {
|
||||||
|
this.timeouts = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, callback, delay) {
|
||||||
|
const newCallback = () => {
|
||||||
|
this.timeouts.delete(key);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timeouts.has(key)) clearTimeout(this.timeouts.get(key));
|
||||||
|
this.timeouts.set(key, setTimeout(newCallback, delay));
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(key) {
|
||||||
|
if (this.timeouts.has(key)) {
|
||||||
|
clearTimeout(this.timeouts.get(key));
|
||||||
|
this.timeouts.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
this.timeouts.forEach(timeout => clearTimeout(timeout));
|
||||||
|
this.timeouts = new Map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendPositionTimeouts = {
|
||||||
|
timeoutManager: new TimeoutManager(),
|
||||||
|
delay: 10, // Minutes
|
||||||
|
|
||||||
|
set(teamID) {
|
||||||
|
const callback = () => {
|
||||||
|
game.sendLocation(teamID);
|
||||||
|
this.set(teamID);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutManager.set(teamID, callback, this.delay * 60 * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear(teamID) {
|
||||||
|
this.timeoutManager.clear(teamID);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
this.timeoutManager.clearAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
setDelay(delay) {
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const outOfZoneTimeouts = {
|
||||||
|
timeoutManager: new TimeoutManager(),
|
||||||
|
delay: 10, // Minutes
|
||||||
|
|
||||||
|
set(teamID) {
|
||||||
|
const callback = () => {
|
||||||
|
game.handicapTeam(teamID);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutManager.set(teamID, callback, this.delay * 60 * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear(teamID) {
|
||||||
|
this.timeoutManager.clear(teamID);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
this.timeoutManager.clearAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
setDelay(delay) {
|
||||||
|
this.delay = delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -233,11 +233,12 @@ export default {
|
|||||||
|
|
||||||
zoneBroadcast() {
|
zoneBroadcast() {
|
||||||
const zone = {
|
const zone = {
|
||||||
|
type: this.settings.type,
|
||||||
begin: this.getCurrentZone(),
|
begin: this.getCurrentZone(),
|
||||||
end: this.getNextZone(),
|
end: this.getNextZone(),
|
||||||
endDate:this.currentZone.endDate,
|
endDate:this.currentZone.endDate,
|
||||||
};
|
};
|
||||||
playersBroadcast("current_zone", zone);
|
playersBroadcast("zone", zone);
|
||||||
secureAdminBroadcast("current_zone", zone);
|
secureAdminBroadcast("current_zone", zone);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const zoneTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
||||||
const { zoneSettings, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin();
|
const { zoneType, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin();
|
||||||
const [timeLeftNextZone, setTimeLeftNextZone] = useState(null);
|
const [timeLeftNextZone, setTimeLeftNextZone] = useState(null);
|
||||||
|
|
||||||
// Remaining time before sending position
|
// Remaining time before sending position
|
||||||
@@ -48,16 +48,16 @@ export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
|||||||
if (pos1 && pos2) {
|
if (pos1 && pos2) {
|
||||||
return (
|
return (
|
||||||
<Polyline positions={[pos1, pos2]} pathOptions={{ color: 'black', weight: 3 }}/>
|
<Polyline positions={[pos1, pos2]} pathOptions={{ color: 'black', weight: 3 }}/>
|
||||||
)
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Zones() {
|
function Zones() {
|
||||||
if (!(showZones && gameState == GameState.PLAYING && zoneSettings)) return null;
|
if (!(showZones && gameState == GameState.PLAYING && zoneType)) return null;
|
||||||
|
|
||||||
switch (zoneSettings.type) {
|
switch (zoneType) {
|
||||||
case zoneTypes.circle:
|
case zoneTypes.circle:
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -83,10 +83,12 @@ export default function LiveMap({mapStyle, showZones, showNames, showArrows}) {
|
|||||||
<CustomMapContainer mapStyle={mapStyle}>
|
<CustomMapContainer mapStyle={mapStyle}>
|
||||||
<Zones/>
|
<Zones/>
|
||||||
{teams.map((team) => team.currentLocation && !team.captured &&
|
{teams.map((team) => team.currentLocation && !team.captured &&
|
||||||
<Marker key={team.id} position={team.currentLocation} icon={positionIcon}>
|
<div>
|
||||||
{showNames && <Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{team.name}</Tooltip>}
|
<Marker key={team.id} position={team.currentLocation} icon={positionIcon}>
|
||||||
{showArrows && <Arrow pos1={team.currentLocation} pos2={getTeam(team.chasing).currentLocation}/>}
|
{showNames && <Tooltip permanent direction="top" offset={[0.5, -15]} className="custom-tooltip">{team.name}</Tooltip>}
|
||||||
</Marker>
|
</Marker>
|
||||||
|
{showArrows && <Arrow key={team.id} pos1={team.currentLocation} pos2={getTeam(team.chased).currentLocation}/>}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CustomMapContainer>
|
</CustomMapContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ function getStatus(team, gamestate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TeamSidePanel({ selectedTeamId, onClose }) {
|
export default function TeamSidePanel({ selectedTeamId, onClose }) {
|
||||||
const { getTeam, getTeamName, startDate, gameState } = useAdmin();
|
const { getTeam, startDate, gameState } = useAdmin();
|
||||||
const [imgSrc, setImgSrc] = useState("");
|
const [imgSrc, setImgSrc] = useState("");
|
||||||
const team = getTeam(selectedTeamId);
|
const team = getTeam(selectedTeamId);
|
||||||
const NO_VALUE = "XX";
|
const NO_VALUE = "XX";
|
||||||
@@ -115,8 +115,8 @@ export default function TeamSidePanel({ selectedTeamId, onClose }) {
|
|||||||
<DotLine label="ID de capture" value={String(team.captureCode).padStart(4, '0')} />
|
<DotLine label="ID de capture" value={String(team.captureCode).padStart(4, '0')} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<DotLine label="Chasse" value={getTeamName(team.chasing) ?? NO_VALUE} />
|
<DotLine label="Chasse" value={getTeam(team.chasing).name ?? NO_VALUE} />
|
||||||
<DotLine label="Chassé par" value={getTeamName(team.chased) ?? NO_VALUE} />
|
<DotLine label="Chassé par" value={getTeam(team.chased).name ?? NO_VALUE} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<DotLine label="Distance" value={formatDistance(team.distance)} />
|
<DotLine label="Distance" value={formatDistance(team.distance)} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { CustomMapContainer, MapEventListener } from "@/components/map";
|
|||||||
import { TextInput } from "@/components/input";
|
import { TextInput } from "@/components/input";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import useMapCircleDraw from "@/hook/useMapCircleDraw";
|
import useMapCircleDraw from "@/hook/useMapCircleDraw";
|
||||||
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
|
||||||
const EditMode = {
|
const EditMode = {
|
||||||
MIN: 0,
|
MIN: 0,
|
||||||
@@ -43,38 +44,36 @@ function Drawings({ minZone, setMinZone, maxZone, setMaxZone, editMode }) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<MapEventListener onLeftClick={handleLeftClick} onRightClick={handleRightClick} onMouseMove={handleMouseMove}/>
|
<MapEventListener onLeftClick={handleLeftClick} onRightClick={handleRightClick} onMouseMove={handleMouseMove}/>
|
||||||
{minCenter && minRadius && <Circle center={minCenter} radius={minRadius} color="blue" fillColor="blue" />}
|
{minCenter && minRadius && <Circle center={minCenter} radius={minRadius} color="red" fillColor="red" />}
|
||||||
{maxCenter && maxRadius && <Circle center={maxCenter} radius={maxRadius} color="red" fillColor="red" />}
|
{maxCenter && maxRadius && <Circle center={maxCenter} radius={maxRadius} color="blue" fillColor="blue" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CircleZoneSelector({zoneSettings, updateZoneSettings, applyZoneSettings}) {
|
export default function CircleZoneSelector({zoneSettings, modifyZoneSettings, applyZoneSettings}) {
|
||||||
const {penaltySettings, changePenaltySettings} = useAdmin();
|
const {outOfZoneDelay, updateSettings} = useAdmin();
|
||||||
const [allowedTimeOutOfZone, setAllowedTimeOutOfZone] = useState("");
|
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
|
||||||
const [editMode, setEditMode] = useState(EditMode.MIN);
|
const [editMode, setEditMode] = useState(EditMode.MIN);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditMode(editMode == EditMode.MIN ? EditMode.MAX : EditMode.MIN);
|
setEditMode(editMode == EditMode.MIN ? EditMode.MAX : EditMode.MIN);
|
||||||
}, [zoneSettings.min, zoneSettings.max])
|
}, [zoneSettings.min, zoneSettings.max]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (penaltySettings) {
|
|
||||||
setAllowedTimeOutOfZone(penaltySettings.allowedTimeOutOfZone.toString());
|
|
||||||
}
|
|
||||||
}, [penaltySettings]);
|
|
||||||
|
|
||||||
function handleSettingsSubmit() {
|
function handleSettingsSubmit() {
|
||||||
console.log(zoneSettings)
|
|
||||||
applyZoneSettings();
|
applyZoneSettings();
|
||||||
changePenaltySettings({allowedTimeOutOfZone: Number(allowedTimeOutOfZone)});
|
applyLocalOutOfZoneDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
function customStringToInt(e) {
|
||||||
|
const res = parseInt(e, 10);
|
||||||
|
return isNaN(res) ? null : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full w-full bg-white p-3 gap-3 flex flex-row shadow-2xl'>
|
<div className='h-full w-full gap-3 flex flex-row'>
|
||||||
<div className="h-full flex-1">
|
<div className="h-full flex-1">
|
||||||
<CustomMapContainer>
|
<CustomMapContainer>
|
||||||
<Drawings minZone={zoneSettings.min} setMinZone={(e) => updateZoneSettings("min", e)} maxZone={zoneSettings.max} setMaxZone={(e) => updateZoneSettings("max", e)} editMode={editMode} />
|
<Drawings minZone={zoneSettings.min} setMinZone={(e) => modifyZoneSettings("min", e)} maxZone={zoneSettings.max} setMaxZone={(e) => modifyZoneSettings("max", e)} editMode={editMode} />
|
||||||
</CustomMapContainer>
|
</CustomMapContainer>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-1/6 flex flex-col gap-3">
|
<div className="h-full w-1/6 flex flex-col gap-3">
|
||||||
@@ -83,21 +82,21 @@ export default function CircleZoneSelector({zoneSettings, updateZoneSettings, ap
|
|||||||
{editMode == EditMode.MAX && <RedButton onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</RedButton>}
|
{editMode == EditMode.MAX && <RedButton onClick={() => setEditMode(EditMode.MIN)}>Click to edit last zone</RedButton>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Number</p>
|
<p>Reduction number</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={zoneSettings.reductionCount} onChange={(e) => updateZoneSettings("reductionCount", e.target.value)} />
|
<TextInput id="reduction-number" value={zoneSettings?.reductionCount ?? ""} onChange={(e) => modifyZoneSettings("reductionCount", customStringToInt(e.target.value))} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Duration</p>
|
<p>Zone duration</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={zoneSettings.duration} onChange={(e) => updateZoneSettings("duration", e.target.value)} />
|
<TextInput id="duration" value={zoneSettings?.duration ?? ""} onChange={(e) => modifyZoneSettings("duration", customStringToInt(e.target.value))} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Timeout</p>
|
<p>Timeout</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={allowedTimeOutOfZone} onChange={(e) => setAllowedTimeOutOfZone(e.target.value)} />
|
<TextInput id="timeout" value={localOutOfZoneDelay ?? ""} onChange={(e) => setLocalOutOfZoneDelay(customStringToInt(e.target.value))} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-15">
|
<div className="w-full h-15">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Section } from "@/components/section";
|
import { Section } from "@/components/section";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
|
||||||
function MessageInput({title, ...props}) {
|
function MessageInput({title, ...props}) {
|
||||||
return (
|
return (
|
||||||
@@ -12,33 +12,19 @@ function MessageInput({title, ...props}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Messages() {
|
export default function Messages() {
|
||||||
const {gameSettings, changeGameSettings} = useAdmin();
|
const {messages, updateSettings} = useAdmin();
|
||||||
const [capturedMessage, setCapturedMessage] = useState("");
|
const [localGameSettings, setLocalGameSettings, applyLocalGameSettings] = useLocalVariable(messages, (e) => updateSettings({messages: e}));
|
||||||
const [winnerEndMessage, setWinnerEndMessage] = useState("");
|
|
||||||
const [loserEndMessage, setLoserEndMessage] = useState("");
|
|
||||||
const [waitingMessage, setWaitingMessage] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
function modifyLocalZoneSettings(key, value) {
|
||||||
if (gameSettings) {
|
setLocalGameSettings(prev => ({...prev, [key]: value}));
|
||||||
setCapturedMessage(gameSettings.capturedMessage);
|
};
|
||||||
setWinnerEndMessage(gameSettings.winnerEndGameMessage);
|
|
||||||
setLoserEndMessage(gameSettings.loserEndGameMessage);
|
|
||||||
setWaitingMessage(gameSettings.waitingMessage);
|
|
||||||
}
|
|
||||||
}, [gameSettings]);
|
|
||||||
|
|
||||||
function applySettings() {
|
|
||||||
changeGameSettings({capturedMessage: capturedMessage, winnerEndGameMessage: winnerEndMessage, loserEndGameMessage: loserEndMessage, waitingMessage: waitingMessage});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section title="Message">
|
<Section title="Message" innerClassName="w-full h-full flex flex-col gap-3 items-center">
|
||||||
<div className="w-full h-full flex flex-col gap-3 items-center">
|
<MessageInput id="waiting" title="Attente :" value={localGameSettings?.waiting ?? ""} onChange={(e) => modifyLocalZoneSettings("waiting", e.target.value)} onBlur={applyLocalGameSettings}/>
|
||||||
<MessageInput title="Attente :" value={waitingMessage} onChange={(e) => setWaitingMessage(e.target.value)} onBlur={applySettings}/>
|
<MessageInput id="captured" title="Capture :" value={localGameSettings?.captured ?? ""} onChange={(e) => modifyLocalZoneSettings("captured", e.target.value)} onBlur={applyLocalGameSettings}/>
|
||||||
<MessageInput title="Capture :" value={capturedMessage} onChange={(e) => setCapturedMessage(e.target.value)} onBlur={applySettings}/>
|
<MessageInput id="winner" title="Victoire :" value={localGameSettings?.winner ?? ""} onChange={(e) => modifyLocalZoneSettings("winner", e.target.value)} onBlur={applyLocalGameSettings}/>
|
||||||
<MessageInput title="Victoire :" value={winnerEndMessage} onChange={(e) => setWinnerEndMessage(e.target.value)} onBlur={applySettings}/>
|
<MessageInput id="loser" title="Défaite :" value={localGameSettings?.loser ?? ""} onChange={(e) => modifyLocalZoneSettings("loser", e.target.value)} onBlur={applyLocalGameSettings}/>
|
||||||
<MessageInput title="Défaite :" value={loserEndMessage} onChange={(e) => setLoserEndMessage(e.target.value)} onBlur={applySettings}/>
|
|
||||||
</div>
|
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CustomMapContainer, MapEventListener } from "@/components/map";
|
|||||||
import { TextInput } from "@/components/input";
|
import { TextInput } from "@/components/input";
|
||||||
import useAdmin from "@/hook/useAdmin";
|
import useAdmin from "@/hook/useAdmin";
|
||||||
import useMapPolygonDraw from "@/hook/useMapPolygonDraw";
|
import useMapPolygonDraw from "@/hook/useMapPolygonDraw";
|
||||||
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
|
||||||
function Drawings({ polygons, addPolygon, removePolygon }) {
|
function Drawings({ polygons, addPolygon, removePolygon }) {
|
||||||
const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addPolygon, removePolygon);
|
const { currentPolygon, highlightNodes, handleLeftClick, handleRightClick, handleMouseMove } = useMapPolygonDraw(polygons, addPolygon, removePolygon);
|
||||||
@@ -94,51 +95,44 @@ function Drawings({ polygons, addPolygon, removePolygon }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PolygonZoneSelector({zoneSettings, updateZoneSettings, applyZoneSettings}) {
|
export default function PolygonZoneSelector({zoneSettings, modifyZoneSettings, applyZoneSettings}) {
|
||||||
const defaultDuration = 10;
|
const defaultDuration = 10;
|
||||||
const [polygons, setPolygons] = useState([]);
|
const [polygons, setPolygons] = useState([]);
|
||||||
const {penaltySettings, changePenaltySettings} = useAdmin();
|
const {outOfZoneDelay, updateSettings} = useAdmin();
|
||||||
const [allowedTimeOutOfZone, setAllowedTimeOutOfZone] = useState("");
|
const [localOutOfZoneDelay, setLocalOutOfZoneDelay, applyLocalOutOfZoneDelay] = useLocalVariable(outOfZoneDelay, (e) => updateSettings({outOfZoneDelay: e}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (zoneSettings) {
|
if (zoneSettings) {
|
||||||
const newPolygons = zoneSettings.polygons.map((zone) => ({id: idFromPolygon(zone.polygon), polygon: zone.polygon, duration: zone.duration}));
|
setPolygons(zoneSettings.polygons.map((zone) => zone.polygon));
|
||||||
setPolygons(newPolygons.map((zone) => zone.polygon));
|
|
||||||
}
|
}
|
||||||
}, [zoneSettings]);
|
}, [zoneSettings]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (penaltySettings) {
|
|
||||||
setAllowedTimeOutOfZone(penaltySettings.allowedTimeOutOfZone.toString());
|
|
||||||
}
|
|
||||||
}, [penaltySettings]);
|
|
||||||
|
|
||||||
function idFromPolygon(polygon) {
|
function idFromPolygon(polygon) {
|
||||||
return (polygon[0].lat + polygon[1].lat + polygon[2].lat).toString() + (polygon[0].lng + polygon[1].lng + polygon[2].lng).toString();
|
return (polygon[0].lat + polygon[1].lat + polygon[2].lat).toString() + (polygon[0].lng + polygon[1].lng + polygon[2].lng).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPolygon(polygon) {
|
function addPolygon(polygon) {
|
||||||
const newPolygons = [...zoneSettings.polygons, {id: idFromPolygon(polygon), polygon: polygon, duration: defaultDuration}];
|
const newPolygons = [...zoneSettings.polygons, {id: idFromPolygon(polygon), polygon: polygon, duration: defaultDuration}];
|
||||||
updateZoneSettings("polygons", newPolygons);
|
modifyZoneSettings("polygons", newPolygons);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removePolygon(i) {
|
function removePolygon(i) {
|
||||||
const newPolygons = zoneSettings.polygons.filter((_, index) => index !== i);
|
const newPolygons = zoneSettings.polygons.filter((_, index) => index !== i);
|
||||||
updateZoneSettings("polygons", newPolygons);
|
modifyZoneSettings("polygons", newPolygons);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDuration(i, duration) {
|
function updateDuration(i, duration) {
|
||||||
const newPolygons = zoneSettings.polygons.map((zone, index) => index === i ? {id: zone.id, polygon: zone.polygon, duration: duration} : zone);
|
const newPolygons = zoneSettings.polygons.map((zone, index) => index === i ? {id: zone.id, polygon: zone.polygon, duration: duration} : zone);
|
||||||
updateZoneSettings("polygons", newPolygons);
|
modifyZoneSettings("polygons", newPolygons);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSettingsSubmit() {
|
function handleSettingsSubmit() {
|
||||||
applyZoneSettings();
|
applyZoneSettings();
|
||||||
changePenaltySettings({allowedTimeOutOfZone: Number(allowedTimeOutOfZone)});
|
applyLocalOutOfZoneDelay();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full w-full bg-white p-3 gap-3 flex flex-row shadow-2xl'>
|
<div className='h-full w-full gap-3 flex flex-row'>
|
||||||
<div className="h-full flex-1">
|
<div className="h-full flex-1">
|
||||||
<CustomMapContainer>
|
<CustomMapContainer>
|
||||||
<Drawings polygons={polygons} addPolygon={addPolygon} removePolygon={removePolygon} />
|
<Drawings polygons={polygons} addPolygon={addPolygon} removePolygon={removePolygon} />
|
||||||
@@ -148,12 +142,12 @@ export default function PolygonZoneSelector({zoneSettings, updateZoneSettings, a
|
|||||||
<div className="w-full text-center">
|
<div className="w-full text-center">
|
||||||
<h2 className="text-xl">Reduction order</h2>
|
<h2 className="text-xl">Reduction order</h2>
|
||||||
</div>
|
</div>
|
||||||
<ReorderList droppableId="zones-order" array={zoneSettings.polygons} setArray={(polygons) => updateZoneSettings("polygons", polygons)}>
|
<ReorderList droppableId="zones-order" array={zoneSettings.polygons} setArray={(polygons) => modifyZoneSettings("polygons", polygons)}>
|
||||||
{ (zone, i) =>
|
{ (zone, i) =>
|
||||||
<div className="w-full p-2 bg-white flex flex-row gap-2 items-center justify-between">
|
<div className="w-full p-2 bg-white flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Zone {i+1}</p>
|
<p>Zone {i+1}</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={zone.duration} onChange={(e) => updateDuration(i, e.target.value)}/>
|
<TextInput value={zone?.duration || ""} onChange={(e) => updateDuration(i, parseInt(e.target.value, 10))}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -161,7 +155,7 @@ export default function PolygonZoneSelector({zoneSettings, updateZoneSettings, a
|
|||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Timeout</p>
|
<p>Timeout</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={allowedTimeOutOfZone} onChange={(e) => setAllowedTimeOutOfZone(e.target.value)} />
|
<TextInput id="timeout" value={localOutOfZoneDelay ?? ""} onChange={(e) => setLocalOutOfZoneDelay(parseInt(e.target.value, 10))} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full h-15">
|
<div className="w-full h-15">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState, useEffect } from "react";
|
import { useState } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { TextInput } from "@/components/input";
|
import { TextInput } from "@/components/input";
|
||||||
@@ -9,6 +9,7 @@ import { useAdminConnexion } from "@/context/adminConnexionContext";
|
|||||||
import useAdmin from '@/hook/useAdmin';
|
import useAdmin from '@/hook/useAdmin';
|
||||||
import Messages from "./components/messages";
|
import Messages from "./components/messages";
|
||||||
import TeamManager from './components/teamManager';
|
import TeamManager from './components/teamManager';
|
||||||
|
import useLocalVariable from "@/hook/useLocalVariable";
|
||||||
|
|
||||||
// Imported at runtime and not at compile time
|
// Imported at runtime and not at compile time
|
||||||
const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
|
const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false });
|
||||||
@@ -23,41 +24,23 @@ const defaultCircleSettings = {type: zoneTypes.circle, min: null, max: null, red
|
|||||||
const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []}
|
const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []}
|
||||||
|
|
||||||
export default function ConfigurationPage() {
|
export default function ConfigurationPage() {
|
||||||
const {zoneSettings, changeZoneSettings, penaltySettings, changePenaltySettings, addTeam} = useAdmin();
|
|
||||||
const { useProtect } = useAdminConnexion();
|
const { useProtect } = useAdminConnexion();
|
||||||
const [allowedTimeBetweenUpdates, setAllowedTimeBetweenUpdates] = useState("");
|
const {zoneSettings, sendPositionDelay, updateSettings, addTeam} = useAdmin();
|
||||||
const [teamName, setTeamName] = useState('');
|
const [teamName, setTeamName] = useState('');
|
||||||
const [localZoneSettings, setLocalZoneSettings] = useState(zoneSettings);
|
const [localZoneSettings, setLocalZoneSettings, applyLocalZoneSettings] = useLocalVariable(zoneSettings, (e) => updateSettings({zone: e}));
|
||||||
|
const [localSendPositionDelay, setLocalSendPositionDelay, applyLocalSendPositionDelay] = useLocalVariable(sendPositionDelay, (e) => updateSettings({sendPositionDelay: e}));
|
||||||
|
|
||||||
useProtect();
|
useProtect();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (penaltySettings) {
|
|
||||||
setAllowedTimeBetweenUpdates(penaltySettings.allowedTimeBetweenPositionUpdate.toString());
|
|
||||||
}
|
|
||||||
}, [penaltySettings]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (zoneSettings) {
|
|
||||||
setLocalZoneSettings(zoneSettings);
|
|
||||||
}
|
|
||||||
}, [zoneSettings]);
|
|
||||||
|
|
||||||
function updateLocalZoneSettings(key, value) {
|
function modifyLocalZoneSettings(key, value) {
|
||||||
setLocalZoneSettings(prev => ({...prev, [key]: value}));
|
setLocalZoneSettings(prev => ({...prev, [key]: value}));
|
||||||
};
|
};
|
||||||
|
|
||||||
function applySettings() {
|
|
||||||
if (Number(allowedTimeBetweenUpdates) != penaltySettings.allowedTimeBetweenPositionUpdate) {
|
|
||||||
changePenaltySettings({allowedTimeBetweenPositionUpdate: Number(allowedTimeBetweenUpdates)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleChangeZoneType() {
|
function handleChangeZoneType() {
|
||||||
setLocalZoneSettings(localZoneSettings.type == zoneTypes.circle ? defaultPolygonSettings : defaultCircleSettings)
|
setLocalZoneSettings(localZoneSettings.type == zoneTypes.circle ? defaultPolygonSettings : defaultCircleSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e) {
|
function handleTeamSubmit(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (teamName !== "") {
|
if (teamName !== "") {
|
||||||
addTeam(teamName);
|
addTeam(teamName);
|
||||||
@@ -76,7 +59,7 @@ export default function ConfigurationPage() {
|
|||||||
</div>
|
</div>
|
||||||
<Messages/>
|
<Messages/>
|
||||||
<Section title="Équipe" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3">
|
<Section title="Équipe" outerClassName="flex-1 min-h-0" innerClassName="flex flex-col items-center gap-3">
|
||||||
<form className='w-full flex flex-row gap-3' onSubmit={handleSubmit}>
|
<form className='w-full flex flex-row gap-3' onSubmit={handleTeamSubmit}>
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
<input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" />
|
<input name="teamName" label='Team name' value={teamName} onChange={(e) => setTeamName(e.target.value)} type="text" className="w-full h-full p-4 ring-1 ring-inset ring-gray-300" />
|
||||||
</div>
|
</div>
|
||||||
@@ -90,21 +73,21 @@ export default function ConfigurationPage() {
|
|||||||
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
<div className="w-full flex flex-row gap-2 items-center justify-between">
|
||||||
<p>Interval between position updates</p>
|
<p>Interval between position updates</p>
|
||||||
<div className="w-16 h-10">
|
<div className="w-16 h-10">
|
||||||
<TextInput value={allowedTimeBetweenUpdates} onChange={(e) => setAllowedTimeBetweenUpdates(e.target.value)} onBlur={applySettings} />
|
<TextInput id="position-update" value={localSendPositionDelay ?? ""} onChange={(e) => setLocalSendPositionDelay(parseInt(e.target.value, 10))} onBlur={applyLocalSendPositionDelay} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full flex-1 flex flex-col">
|
<div className="h-full flex-1 flex flex-col p-3 gap-3 bg-white shadow-2xl">
|
||||||
<div className="w-full h-20">
|
<div className="w-full h-15">
|
||||||
{localZoneSettings && <BlueButton onClick={handleChangeZoneType}>Change zone type</BlueButton>}
|
{localZoneSettings && <BlueButton onClick={handleChangeZoneType}>Change zone type</BlueButton>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex-1">
|
<div className="w-full flex-1">
|
||||||
{localZoneSettings && localZoneSettings.type == zoneTypes.circle &&
|
{localZoneSettings && localZoneSettings.type == zoneTypes.circle &&
|
||||||
<CircleZoneSelector zoneSettings={localZoneSettings} updateZoneSettings={updateLocalZoneSettings} applyZoneSettings={() => changeZoneSettings(localZoneSettings)}/>
|
<CircleZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
||||||
}
|
}
|
||||||
{localZoneSettings && localZoneSettings.type == zoneTypes.polygon &&
|
{localZoneSettings && localZoneSettings.type == zoneTypes.polygon &&
|
||||||
<PolygonZoneSelector zoneSettings={localZoneSettings} updateZoneSettings={updateLocalZoneSettings} applyZoneSettings={() => changeZoneSettings(localZoneSettings)}/>
|
<PolygonZoneSelector zoneSettings={localZoneSettings} modifyZoneSettings={modifyLocalZoneSettings} applyZoneSettings={applyLocalZoneSettings}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import React from 'react'
|
const className = "block w-full h-full p-4 rounded text-center ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600";
|
||||||
|
|
||||||
export function TextInput({...props}) {
|
export function TextInput({...props}) {
|
||||||
return (
|
return (
|
||||||
<input {...props} type="text" className="block w-full h-full p-4 rounded text-center ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600" />
|
<input {...props} type="text" className={className} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextArea({...props}) {
|
export function TextArea({...props}) {
|
||||||
return (
|
return (
|
||||||
<textarea {...props} className="block w-full h-full p-4 rounded text-center ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600" />
|
<textarea {...props} className={className} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,52 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
import { createContext, useContext, useMemo, useState } from "react";
|
||||||
import { useSocket } from "./socketContext";
|
import { useSocket } from "./socketContext";
|
||||||
import useSocketListener from "@/hook/useSocketListener";
|
import useSocketListener from "@/hook/useSocketListener";
|
||||||
import { useAdminConnexion } from "./adminConnexionContext";
|
|
||||||
import { GameState } from "@/util/gameState";
|
import { GameState } from "@/util/gameState";
|
||||||
|
|
||||||
const adminContext = createContext();
|
const adminContext = createContext();
|
||||||
|
|
||||||
export function AdminProvider({ children }) {
|
export function AdminProvider({ children }) {
|
||||||
const [teams, setTeams] = useState([]);
|
|
||||||
const [zoneSettings, setZoneSettings] = useState(null)
|
|
||||||
const [penaltySettings, setPenaltySettings] = useState(null);
|
|
||||||
const [gameSettings, setGameSettings] = useState(null);
|
|
||||||
const [zoneExtremities, setZoneExtremities] = useState(null);
|
|
||||||
const [nextZoneDate, setNextZoneDate] = useState(null);
|
|
||||||
const { adminSocket } = useSocket();
|
const { adminSocket } = useSocket();
|
||||||
const { loggedIn } = useAdminConnexion();
|
// teams
|
||||||
|
const [teams, setTeams] = useState([]);
|
||||||
|
// game_state
|
||||||
const [gameState, setGameState] = useState(GameState.SETUP);
|
const [gameState, setGameState] = useState(GameState.SETUP);
|
||||||
const [startDate, setStartDate] = useState(null);
|
const [startDate, setStartDate] = useState(null);
|
||||||
|
// current_zone
|
||||||
|
const [zoneType, setZoneType] = useState(null);
|
||||||
|
const [zoneExtremities, setZoneExtremities] = useState(null);
|
||||||
|
const [nextZoneDate, setNextZoneDate] = useState(null);
|
||||||
|
// settings
|
||||||
|
const [messages, setMessages] = useState(null);
|
||||||
|
const [zoneSettings, setZoneSettings] = useState(null)
|
||||||
|
const [sendPositionDelay, setSendPositionDelay] = useState(null);
|
||||||
|
const [outOfZoneDelay, setOutOfZoneDelay] = useState(null);
|
||||||
|
|
||||||
// Send a request to get the teams when the user logs in
|
useSocketListener(adminSocket, "teams", setTeams);
|
||||||
useEffect(() => {
|
|
||||||
adminSocket.emit("get_teams");
|
|
||||||
}, [loggedIn]);
|
|
||||||
|
|
||||||
function setCurrentZone(data) {
|
useSocketListener(adminSocket, "game_state", (data) => {
|
||||||
|
setGameState(data.state);
|
||||||
|
setStartDate(data.date)
|
||||||
|
});
|
||||||
|
|
||||||
|
useSocketListener(adminSocket, "current_zone", (data) => {
|
||||||
|
setZoneType(data.type);
|
||||||
setZoneExtremities({begin: data.begin, end: data.end});
|
setZoneExtremities({begin: data.begin, end: data.end});
|
||||||
setNextZoneDate(data.endDate);
|
setNextZoneDate(data.endDate);
|
||||||
}
|
});
|
||||||
|
|
||||||
useSocketListener(adminSocket, "game_state", (data) => {setGameState(data.state); setStartDate(data.startDate)});
|
useSocketListener(adminSocket, "settings", (data) => {
|
||||||
useSocketListener(adminSocket, "teams", setTeams);
|
setMessages(data.messages);
|
||||||
useSocketListener(adminSocket, "zone_settings", setZoneSettings);
|
setZoneSettings(data.zone);
|
||||||
useSocketListener(adminSocket, "game_settings", setGameSettings);
|
setSendPositionDelay(data.sendPositionDelay);
|
||||||
useSocketListener(adminSocket, "penalty_settings", setPenaltySettings);
|
setOutOfZoneDelay(data.outOfZoneDelay);
|
||||||
useSocketListener(adminSocket, "current_zone", setCurrentZone);
|
});
|
||||||
|
|
||||||
const value = useMemo(() => (
|
const value = useMemo(() => (
|
||||||
{ zoneExtremities, teams, zoneSettings, penaltySettings, gameSettings, gameState, nextZoneDate, startDate }
|
{ zoneSettings, teams, gameState, zoneType, zoneExtremities, sendPositionDelay, outOfZoneDelay, messages, nextZoneDate, startDate }
|
||||||
), [zoneSettings, teams, gameState, zoneExtremities, penaltySettings, gameSettings, nextZoneDate, startDate]);
|
), [zoneSettings, teams, gameState, zoneType, zoneExtremities, sendPositionDelay, outOfZoneDelay, messages, nextZoneDate, startDate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<adminContext.Provider value={value}>
|
<adminContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -7,19 +7,10 @@ export default function useAdmin() {
|
|||||||
const { teams } = adminContext;
|
const { teams } = adminContext;
|
||||||
const { adminSocket } = useSocket();
|
const { adminSocket } = useSocket();
|
||||||
|
|
||||||
function pollTeams() {
|
|
||||||
adminSocket.emit("get_teams");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTeam(teamId) {
|
function getTeam(teamId) {
|
||||||
return teams.find(team => team.id === teamId);
|
return teams.find(team => team.id === teamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTeamName(teamId) {
|
|
||||||
let team = getTeam(teamId);
|
|
||||||
return team ? team.name : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function reorderTeams(newOrder) {
|
function reorderTeams(newOrder) {
|
||||||
adminSocket.emit("reorder_teams", newOrder);
|
adminSocket.emit("reorder_teams", newOrder);
|
||||||
}
|
}
|
||||||
@@ -40,17 +31,9 @@ export default function useAdmin() {
|
|||||||
adminSocket.emit("change_state", state);
|
adminSocket.emit("change_state", state);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeZoneSettings(zone) {
|
function updateSettings(settings) {
|
||||||
adminSocket.emit("set_zone_settings", zone);
|
adminSocket.emit("update_settings", settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePenaltySettings(penalties) {
|
return { ...adminContext, getTeam, reorderTeams, addTeam, removeTeam, updateTeam, changeState, updateSettings };
|
||||||
adminSocket.emit("set_penalty_settings", penalties);
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeGameSettings(settings) {
|
|
||||||
adminSocket.emit("set_game_settings", settings);
|
|
||||||
}
|
|
||||||
return { ...adminContext, changeGameSettings, changeZoneSettings, changePenaltySettings, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, changeState, updateTeam };
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
traque-front/hook/useLocalVariable.jsx
Normal file
15
traque-front/hook/useLocalVariable.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export default function useLocalVariable(variable, setVariable) {
|
||||||
|
const [localVariable, setLocalVariable] = useState(variable);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalVariable(variable);
|
||||||
|
}, [variable]);
|
||||||
|
|
||||||
|
function applyLocalVariable() {
|
||||||
|
setVariable(localVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [localVariable, setLocalVariable, applyLocalVariable];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user