/* 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.`) } }) }, }