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:
@@ -6,12 +6,12 @@ This module also exposes functions to send messages via socket to all admins
|
||||
import { io } from "./index.js";
|
||||
import game from "./game.js"
|
||||
import zoneManager from "./zone_manager.js"
|
||||
import penaltyController from "./penalty_controller.js";
|
||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
||||
import { createHash } from "crypto";
|
||||
import { config } from "dotenv";
|
||||
|
||||
config();
|
||||
|
||||
const ADMIN_PASSWORD_HASH = process.env.ADMIN_PASSWORD_HASH;
|
||||
|
||||
/**
|
||||
@@ -27,163 +27,81 @@ export function secureAdminBroadcast(event, data) {
|
||||
|
||||
// Array of logged in sockets
|
||||
let loggedInSockets = [];
|
||||
|
||||
export function initAdminSocketHandler() {
|
||||
// Admin namespace
|
||||
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");
|
||||
let loggedIn = false;
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("Disconnection of an admin");
|
||||
// Remove the socket from the logged in sockets array
|
||||
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
||||
loggedIn = false;
|
||||
});
|
||||
|
||||
socket.on("logout", () => {
|
||||
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
|
||||
loggedIn = false;
|
||||
})
|
||||
|
||||
// User is attempting to log in
|
||||
socket.on("login", (password) => {
|
||||
const hash = createHash('sha256').update(password).digest('hex');
|
||||
if (hash === ADMIN_PASSWORD_HASH && !loggedIn) {
|
||||
// Attempt successful
|
||||
socket.emit("login_response", true);
|
||||
loggedInSockets.push(socket.id);
|
||||
loggedIn = true;
|
||||
// Send the current state
|
||||
socket.emit("game_state", {state: game.state, startDate: game.startDate})
|
||||
// Other settings that need initialization
|
||||
socket.emit("penalty_settings", penaltyController.settings)
|
||||
socket.emit("game_settings", game.settings)
|
||||
socket.emit("zone_settings", zoneManager.settings)
|
||||
socket.emit("teams", game.teams);
|
||||
socket.emit("game_state", {
|
||||
state: game.state,
|
||||
date: game.stateDate
|
||||
});
|
||||
socket.emit("current_zone", {
|
||||
begin: zoneManager.getCurrentZone(),
|
||||
end: zoneManager.getNextZone(),
|
||||
endDate: zoneManager.currentZoneEndDate,
|
||||
})
|
||||
} else {
|
||||
// Attempt unsuccessful
|
||||
socket.emit("login_response", false);
|
||||
});
|
||||
socket.emit("settings", game.getSettings());
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("set_game_settings", (settings) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
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("update_settings", (settings) => {
|
||||
if (!loggedIn) return;
|
||||
game.changeSettings(settings);
|
||||
secureAdminBroadcast("settings", game.getSettings());
|
||||
})
|
||||
|
||||
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) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
if (game.addTeam(teamName)) {
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
} else {
|
||||
socket.emit("error", "Error adding team");
|
||||
}
|
||||
if (!loggedIn) return;
|
||||
game.addTeam(teamName);
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
});
|
||||
|
||||
// User is attempting to remove a team
|
||||
socket.on("remove_team", (teamId) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
if (game.removeTeam(teamId)) {
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
} else {
|
||||
socket.emit("error", "Error removing team");
|
||||
}
|
||||
if (!loggedIn) return;
|
||||
game.removeTeam(teamId);
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
});
|
||||
|
||||
// User is attempting to change the game state
|
||||
socket.on("change_state", (state) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
if (!game.setState(state)) {
|
||||
socket.emit("error", "Error setting state");
|
||||
}
|
||||
if (!loggedIn) return;
|
||||
game.setState(state);
|
||||
});
|
||||
|
||||
// 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
|
||||
// But the frontend should always send the same teams in a different order
|
||||
socket.on("reorder_teams", (newOrder) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
if (game.reorderTeams(newOrder)) {
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
game.teams.forEach(t => sendUpdatedTeamInformations(t.id))
|
||||
} else {
|
||||
socket.emit("error", "Error reordering teams");
|
||||
}
|
||||
if (!loggedIn) return;
|
||||
game.reorderTeams(newOrder);
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
game.teams.forEach(t => sendUpdatedTeamInformations(t.id));
|
||||
});
|
||||
|
||||
socket.on("update_team", (teamId, newTeam) => {
|
||||
if (!loggedIn) {
|
||||
socket.emit("error", "Not logged in");
|
||||
return;
|
||||
}
|
||||
if (game.updateTeam(teamId, newTeam)) {
|
||||
secureAdminBroadcast("teams", game.teams);
|
||||
sendUpdatedTeamInformations(teamId)
|
||||
sendUpdatedTeamInformations(game.getTeam(teamId).chased)
|
||||
}
|
||||
if (!loggedIn) return;
|
||||
game.updateTeam(teamId, newTeam);
|
||||
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
|
||||
*/
|
||||
import { secureAdminBroadcast } from "./admin_socket.js";
|
||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
||||
import timeoutHandler from "./timeoutHandler.js";
|
||||
import penaltyController from "./penalty_controller.js";
|
||||
import { teamBroadcast, playersBroadcast, sendUpdatedTeamInformations, } from "./team_socket.js";
|
||||
import { sendPositionTimeouts, outOfZoneTimeouts } from "./timeout_handler.js";
|
||||
import zoneManager from "./zone_manager.js";
|
||||
import trajectory from "./trajectory.js";
|
||||
|
||||
@@ -52,34 +51,43 @@ export const GameState = {
|
||||
}
|
||||
|
||||
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: [],
|
||||
//Current state of the game
|
||||
// Current state of the game
|
||||
state: GameState.SETUP,
|
||||
// Date since gameState switched to PLAYING
|
||||
startDate: null,
|
||||
//Settings of the game
|
||||
settings: {
|
||||
loserEndGameMessage: "",
|
||||
winnerEndGameMessage: "",
|
||||
capturedMessage: "",
|
||||
waitingMessage: ""
|
||||
// Date since the state changed
|
||||
stateDate: Date.now(),
|
||||
// Messages
|
||||
messages: {
|
||||
waiting: "",
|
||||
captured: "",
|
||||
winner: "",
|
||||
loser: "",
|
||||
},
|
||||
|
||||
getSettings() {
|
||||
return {
|
||||
messages: this.messages,
|
||||
zone: zoneManager.settings,
|
||||
sendPositionDelay: sendPositionTimeouts.delay,
|
||||
outOfZoneDelay: outOfZoneTimeouts.delay
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the game settings
|
||||
* @param {Object} newSettings settings to be updated, can be partial
|
||||
* @returns true if the settings are applied
|
||||
*/
|
||||
changeSettings(newSettings) {
|
||||
this.settings = { ...this.settings, ...newSettings };
|
||||
return true;
|
||||
if ("messages" in newSettings) this.messages = {...this.messages, ...newSettings.messages};
|
||||
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
|
||||
* @param {String} newState
|
||||
* @returns true if the state has been changed
|
||||
*/
|
||||
setState(newState) {
|
||||
// Checks is the newState is a Gamestate
|
||||
@@ -89,8 +97,8 @@ export default {
|
||||
case GameState.SETUP:
|
||||
trajectory.stop();
|
||||
zoneManager.stop();
|
||||
penaltyController.stop();
|
||||
timeoutHandler.endAllSendPositionTimeout();
|
||||
sendPositionTimeouts.clearAll();
|
||||
outOfZoneTimeouts.clearAll();
|
||||
for (let team of this.teams) {
|
||||
// Chasing
|
||||
team.captured = false;
|
||||
@@ -103,7 +111,6 @@ export default {
|
||||
// Placement
|
||||
team.ready = false;
|
||||
// Zone
|
||||
team.penalties = 0;
|
||||
team.outOfZone = false;
|
||||
team.outOfZoneDeadline = null;
|
||||
// Stats
|
||||
@@ -113,34 +120,33 @@ export default {
|
||||
team.nObserved = 0;
|
||||
team.finishDate = null;
|
||||
}
|
||||
this.startDate = null;
|
||||
this.stateDate = Date.now();
|
||||
this.updateTeamChasing();
|
||||
break;
|
||||
case GameState.PLACEMENT:
|
||||
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;
|
||||
}
|
||||
trajectory.stop();
|
||||
zoneManager.stop();
|
||||
penaltyController.stop();
|
||||
timeoutHandler.endAllSendPositionTimeout();
|
||||
this.startDate = null;
|
||||
sendPositionTimeouts.clearAll();
|
||||
outOfZoneTimeouts.clearAll();
|
||||
this.stateDate = Date.now();
|
||||
break;
|
||||
case GameState.PLAYING:
|
||||
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;
|
||||
}
|
||||
trajectory.start();
|
||||
zoneManager.start();
|
||||
penaltyController.start();
|
||||
this.initLastSentLocations();
|
||||
this.startDate = Date.now();
|
||||
this.stateDate = Date.now();
|
||||
break;
|
||||
case GameState.FINISHED:
|
||||
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;
|
||||
}
|
||||
for (const team of this.teams) {
|
||||
@@ -148,16 +154,15 @@ export default {
|
||||
}
|
||||
trajectory.stop();
|
||||
zoneManager.stop();
|
||||
penaltyController.stop();
|
||||
timeoutHandler.endAllSendPositionTimeout();
|
||||
sendPositionTimeouts.clearAll();
|
||||
outOfZoneTimeouts.clearAll();
|
||||
break;
|
||||
}
|
||||
// Update the state
|
||||
this.state = newState;
|
||||
secureAdminBroadcast("game_state", {state: newState, startDate: this.startDate});
|
||||
secureAdminBroadcast("game_state", {state: newState, stateDate: this.stateDate});
|
||||
playersBroadcast("game_state", newState);
|
||||
secureAdminBroadcast("teams", this.teams);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -183,7 +188,6 @@ export default {
|
||||
/**
|
||||
* Add a new team to the game
|
||||
* @param {String} teamName the name of the team
|
||||
* @returns true if the team has been added
|
||||
*/
|
||||
addTeam(teamName) {
|
||||
this.teams.push({
|
||||
@@ -206,7 +210,6 @@ export default {
|
||||
startingArea: null,
|
||||
ready: false,
|
||||
// Zone
|
||||
penalties: 0,
|
||||
outOfZone: false,
|
||||
outOfZoneDeadline: null,
|
||||
// Stats
|
||||
@@ -221,35 +224,18 @@ export default {
|
||||
battery: null,
|
||||
});
|
||||
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
|
||||
* If there are only 2 teams left, the game will end
|
||||
* This function will update the chasing and chased values of each teams
|
||||
* @returns true if successful
|
||||
*/
|
||||
updateTeamChasing() {
|
||||
if (this.playingTeamCount() <= 2) {
|
||||
if (this.state == GameState.PLAYING) {
|
||||
this.setState(GameState.FINISHED);
|
||||
}
|
||||
return false;
|
||||
const playingTeams = this.teams.reduce((count, team) => count + (!team.captured ? 1 : 0), 0);
|
||||
if (playingTeams <= 2) {
|
||||
if (this.state == GameState.PLAYING) this.setState(GameState.FINISHED);
|
||||
return;
|
||||
}
|
||||
let firstTeam = null;
|
||||
let previousTeam = null
|
||||
@@ -267,17 +253,15 @@ export default {
|
||||
this.getTeam(firstTeam).chased = previousTeam;
|
||||
this.getTeam(previousTeam).chasing = firstTeam;
|
||||
secureAdminBroadcast("teams", this.teams);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rearrange the order of the teams and update the chasing chain
|
||||
* @param {Array} newOrder An array of teams in the new order
|
||||
* @returns
|
||||
*/
|
||||
reorderTeams(newOrder) {
|
||||
this.teams = newOrder;
|
||||
return this.updateTeamChasing();
|
||||
this.updateTeamChasing();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -293,7 +277,6 @@ export default {
|
||||
* Update a team's values
|
||||
* @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
|
||||
* @returns true if the team has been updated
|
||||
*/
|
||||
updateTeam(teamId, newTeam) {
|
||||
this.teams = this.teams.map((t) => {
|
||||
@@ -304,20 +287,17 @@ export default {
|
||||
}
|
||||
})
|
||||
this.updateTeamChasing();
|
||||
penaltyController.checkPenalties();
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @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
|
||||
* @returns true if the location has been updated
|
||||
*/
|
||||
updateLocation(teamId, location) {
|
||||
const team = this.getTeam(teamId);
|
||||
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]}));
|
||||
// Update of events of the game
|
||||
@@ -328,9 +308,19 @@ export default {
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
sendUpdatedTeamInformations(team.id);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -340,8 +330,8 @@ export default {
|
||||
// Update of lastSentLocation
|
||||
for (const team of this.teams) {
|
||||
team.lastSentLocation = team.currentLocation;
|
||||
team.locationSendDeadline = Date.now() + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000;
|
||||
timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline);
|
||||
team.locationSendDeadline = Date.now() + sendPositionTimeouts.duration * 60 * 1000;
|
||||
sendPositionTimeouts.set(team.id);
|
||||
sendUpdatedTeamInformations(team.id);
|
||||
}
|
||||
// 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
|
||||
* @param {Number} teamId The ID of the team that will send its location
|
||||
* @returns true if the location has been sent
|
||||
*/
|
||||
sendLocation(teamId) {
|
||||
const team = this.getTeam(teamId);
|
||||
const enemyTeam = this.getTeam(team.chasing);
|
||||
if (!team || !team.currentLocation) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
const enemyTeam = this.getTeam(team.chasing);
|
||||
team.nSentLocation++;
|
||||
enemyTeam.nObserved++;
|
||||
const dateNow = Date.now();
|
||||
// Update of events of the game
|
||||
trajectory.writeSeePosition(dateNow, teamId, team.chasing);
|
||||
// Update of locationSendDeadline
|
||||
team.locationSendDeadline = dateNow + penaltyController.settings.allowedTimeBetweenPositionUpdate * 60 * 1000;
|
||||
timeoutHandler.setSendPositionTimeout(team.id, team.locationSendDeadline);
|
||||
team.locationSendDeadline = dateNow + sendPositionTimeouts.duration * 60 * 1000;
|
||||
sendPositionTimeouts.set(team.id);
|
||||
// Update of lastSentLocation
|
||||
team.lastSentLocation = team.currentLocation;
|
||||
// Update of enemyLocation
|
||||
@@ -378,22 +367,21 @@ export default {
|
||||
// Sending new infos to the team
|
||||
sendUpdatedTeamInformations(team.id);
|
||||
sendUpdatedTeamInformations(enemyTeam.id);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a team by its ID
|
||||
* @param {Number} teamId The id of the team to remove
|
||||
* @returns true if the team has been deleted
|
||||
*/
|
||||
removeTeam(teamId) {
|
||||
if (!this.getTeam(teamId)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
teamBroadcast("logout");
|
||||
this.teams = this.teams.filter(t => t.id !== teamId);
|
||||
this.updateTeamChasing();
|
||||
timeoutHandler.endSendPositionTimeout(teamId);
|
||||
return true;
|
||||
sendPositionTimeouts.clear(teamId);
|
||||
outOfZoneTimeouts.clear(teamId);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -402,13 +390,12 @@ export default {
|
||||
* And the chase chain will be updated
|
||||
* @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
|
||||
* @returns {Boolean} if the capture has been successfull or not
|
||||
*/
|
||||
requestCapture(teamId, captureCode) {
|
||||
const team = this.getTeam(teamId);
|
||||
const enemyTeam = this.getTeam(team.chasing);
|
||||
if (!enemyTeam || enemyTeam.captureCode != captureCode) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
team.nCaptures++;
|
||||
// Update of events of the game
|
||||
@@ -418,7 +405,6 @@ export default {
|
||||
// Sending new infos to the teams
|
||||
sendUpdatedTeamInformations(team.id);
|
||||
sendUpdatedTeamInformations(enemyTeam.id);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -429,7 +415,12 @@ export default {
|
||||
const team = this.getTeam(teamId);
|
||||
team.captured = true;
|
||||
team.finishDate = Date.now();
|
||||
timeoutHandler.endSendPositionTimeout(teamId);
|
||||
sendPositionTimeouts.clear(teamId);
|
||||
outOfZoneTimeouts.clear(teamId);
|
||||
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
|
||||
*/
|
||||
export function teamBroadcast(teamId, event, data) {
|
||||
for (const socketId of game.getTeam(teamId).sockets) {
|
||||
io.of("player").to(socketId).emit(event, data);
|
||||
}
|
||||
game.getTeam(teamId).sockets.forEach(socketId => io.of("player").to(socketId).emit(event, data));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,7 +50,6 @@ export function sendUpdatedTeamInformations(teamId) {
|
||||
startingArea: team.startingArea,
|
||||
ready: team.ready,
|
||||
// Constraints
|
||||
penalties: team.penalties,
|
||||
outOfZone: team.outOfZone,
|
||||
outOfZoneDeadline: team.outOfZoneDeadline,
|
||||
locationSendDeadline: team.locationSendDeadline,
|
||||
@@ -66,66 +63,60 @@ export function sendUpdatedTeamInformations(teamId) {
|
||||
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() {
|
||||
io.of("player").on("connection", (socket) => {
|
||||
console.log("Connection of a player");
|
||||
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", () => {
|
||||
console.log("Disconnection of a player");
|
||||
logoutPlayer(socket.id);
|
||||
logoutPlayer();
|
||||
});
|
||||
|
||||
socket.on("logout", () => {
|
||||
logoutPlayer();
|
||||
});
|
||||
|
||||
socket.on("login", (loginTeamId, callback) => {
|
||||
logoutPlayer();
|
||||
const team = game.getTeam(loginTeamId);
|
||||
if (!team) {
|
||||
callback({ isLoggedIn: false, message: "Login denied" });
|
||||
return;
|
||||
}
|
||||
logoutPlayer(socket.id);
|
||||
team.sockets.push(socket.id);
|
||||
teamId = loginTeamId;
|
||||
team.sockets.push(socket.id);
|
||||
sendUpdatedTeamInformations(loginTeamId);
|
||||
socket.emit("login_response", true);
|
||||
socket.emit("game_state", game.state);
|
||||
socket.emit("game_settings", game.settings);
|
||||
socket.emit("zone", {
|
||||
type: zoneManager.settings.type,
|
||||
begin: zoneManager.getCurrentZone(),
|
||||
end: zoneManager.getNextZone(),
|
||||
endDate: zoneManager.currentZoneEndDate,
|
||||
})
|
||||
});
|
||||
callback({ isLoggedIn : true, message: "Logged in"});
|
||||
});
|
||||
|
||||
socket.on("logout", () => {
|
||||
logoutPlayer(socket.id);
|
||||
teamId = null;
|
||||
})
|
||||
|
||||
socket.on("update_position", (position) => {
|
||||
// Only the first player to connect to the team socket can update the current position
|
||||
// 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;
|
||||
}
|
||||
if (!teamId) return;
|
||||
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) {
|
||||
game.updateLocation(teamId, position);
|
||||
team.lastCurrentLocationDate = Date.now();
|
||||
@@ -134,27 +125,18 @@ export function initTeamSocket() {
|
||||
});
|
||||
|
||||
socket.on("send_position", () => {
|
||||
if (!teamId) {
|
||||
return;
|
||||
}
|
||||
if (!teamId) return;
|
||||
game.sendLocation(teamId);
|
||||
});
|
||||
|
||||
socket.on("capture", (captureCode, callback) => {
|
||||
if (!teamId) {
|
||||
return;
|
||||
}
|
||||
if (!game.requestCapture(teamId, captureCode)) {
|
||||
callback({ hasCaptured : false, message: "Capture failed" });
|
||||
return;
|
||||
}
|
||||
if (!teamId) return;
|
||||
game.requestCapture(teamId, captureCode);
|
||||
callback({ hasCaptured : true, message: "Capture successful" });
|
||||
});
|
||||
|
||||
socket.on("deviceInfo", (infos) => {
|
||||
if (!teamId) {
|
||||
return;
|
||||
}
|
||||
socket.on("device_info", (infos) => {
|
||||
if (!teamId) return;
|
||||
const team = game.getTeam(teamId);
|
||||
// Only the first socket shares its infos since he is the one whose location is tracked
|
||||
if (team.sockets.indexOf(socket.id) == 0) {
|
||||
@@ -163,10 +145,8 @@ export function initTeamSocket() {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("batteryUpdate", (batteryLevel) => {
|
||||
if (!teamId) {
|
||||
return;
|
||||
}
|
||||
socket.on("battery_update", (batteryLevel) => {
|
||||
if (!teamId) return;
|
||||
const team = game.getTeam(teamId);
|
||||
// Only the first socket shares its infos since he is the one whose location is tracked
|
||||
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() {
|
||||
const zone = {
|
||||
type: this.settings.type,
|
||||
begin: this.getCurrentZone(),
|
||||
end: this.getNextZone(),
|
||||
endDate:this.currentZone.endDate,
|
||||
};
|
||||
playersBroadcast("current_zone", zone);
|
||||
playersBroadcast("zone", zone);
|
||||
secureAdminBroadcast("current_zone", zone);
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user