mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-02-09 10:20:16 +01:00
added live map
This commit is contained in:
@@ -2,6 +2,8 @@ import { io, game, penaltyController } from "./index.js";
|
|||||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
||||||
|
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
|
import { currentZone, initZone, removeZone } from "./zone_manager.js";
|
||||||
|
import { GameState } from "./game.js";
|
||||||
config()
|
config()
|
||||||
|
|
||||||
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
|
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD;
|
||||||
@@ -47,12 +49,7 @@ export function initAdminSocketHandler() {
|
|||||||
//Other settings that need initialization
|
//Other settings that need initialization
|
||||||
socket.emit("penalty_settings", penaltyController.settings)
|
socket.emit("penalty_settings", penaltyController.settings)
|
||||||
socket.emit("game_settings", game.settings)
|
socket.emit("game_settings", game.settings)
|
||||||
socket.emit("zone_settings", game.zone.zoneSettings)
|
socket.emit("zone", currentZone)
|
||||||
socket.emit("zone", game.zone.currentZone)
|
|
||||||
socket.emit("new_zone", {
|
|
||||||
begin: game.zone.currentStartZone,
|
|
||||||
end: game.zone.nextZone
|
|
||||||
})
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//Attempt unsuccessful
|
//Attempt unsuccessful
|
||||||
@@ -72,20 +69,26 @@ export function initAdminSocketHandler() {
|
|||||||
playersBroadcast("game_settings", game.settings);
|
playersBroadcast("game_settings", game.settings);
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on("set_zone_settings", (settings) => {
|
socket.on("set_zone", (zone) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
socket.emit("error", "Not logged in");
|
socket.emit("error", "Not logged in");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!game.setZoneSettings(settings)) {
|
if(game.state != GameState.PLAYING) {
|
||||||
socket.emit("error", "Error changing zone");
|
initZone(zone)
|
||||||
socket.emit("zone_settings", game.zone.zoneSettings) //Still broadcast the old config to the client who submited an incorrect config to keep the client up to date
|
}else {
|
||||||
} else {
|
socket.emit("error", "Game is not in setup state")
|
||||||
secureAdminBroadcast("zone_settings", game.zone.zoneSettings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on("remove_zone", (zone, time) => {
|
||||||
|
if (!loggedIn) {
|
||||||
|
socket.emit("error", "Not logged in");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeZone(zone, time)
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("set_penalty_settings", (settings) => {
|
socket.on("set_penalty_settings", (settings) => {
|
||||||
if (!loggedIn) {
|
if (!loggedIn) {
|
||||||
socket.emit("error", "Not logged in");
|
socket.emit("error", "Not logged in");
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { secureAdminBroadcast } from "./admin_socket.js";
|
|||||||
import { penaltyController } from "./index.js";
|
import { penaltyController } from "./index.js";
|
||||||
import { isInCircle } from "./map_utils.js";
|
import { isInCircle } from "./map_utils.js";
|
||||||
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
import { playersBroadcast, sendUpdatedTeamInformations } from "./team_socket.js";
|
||||||
import { ZoneManager } from "./zone_manager.js";
|
import { resetZone } from "./zone_manager.js";
|
||||||
|
|
||||||
export const GameState = {
|
export const GameState = {
|
||||||
SETUP: "setup",
|
SETUP: "setup",
|
||||||
@@ -12,10 +12,9 @@ export const GameState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class Game {
|
export default class Game {
|
||||||
constructor(onUpdateZone, onUpdateNewZone) {
|
constructor() {
|
||||||
this.teams = [];
|
this.teams = [];
|
||||||
this.state = GameState.SETUP;
|
this.state = GameState.SETUP;
|
||||||
this.zone = new ZoneManager(onUpdateZone, onUpdateNewZone)
|
|
||||||
this.settings = {
|
this.settings = {
|
||||||
loserEndGameMessage: "",
|
loserEndGameMessage: "",
|
||||||
winnerEndGameMessage: "",
|
winnerEndGameMessage: "",
|
||||||
@@ -36,19 +35,9 @@ export default class Game {
|
|||||||
//The game has started
|
//The game has started
|
||||||
if (newState == GameState.PLAYING) {
|
if (newState == GameState.PLAYING) {
|
||||||
penaltyController.start();
|
penaltyController.start();
|
||||||
if (!this.zone.ready()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.initLastSentLocations();
|
this.initLastSentLocations();
|
||||||
this.zone.reset()
|
|
||||||
//If the zone cannot be setup, reset everything
|
|
||||||
if (!this.zone.start()) {
|
|
||||||
this.setState(GameState.SETUP);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (newState != GameState.PLAYING) {
|
if (newState != GameState.PLAYING) {
|
||||||
this.zone.reset();
|
|
||||||
penaltyController.stop();
|
penaltyController.stop();
|
||||||
}
|
}
|
||||||
//Game reset
|
//Game reset
|
||||||
@@ -235,23 +224,8 @@ export default class Game {
|
|||||||
this.updateTeamChasing();
|
this.updateTeamChasing();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the settings of the Zone manager
|
|
||||||
* The game should not be in PLAYING or FINISHED state
|
|
||||||
* @param {Object} newSettings The object containing the settings to be changed
|
|
||||||
* @returns false if failed
|
|
||||||
*/
|
|
||||||
setZoneSettings(newSettings) {
|
|
||||||
//cannot change zones while playing
|
|
||||||
if (this.state == GameState.PLAYING || this.state == GameState.FINISHED) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.zone.udpateSettings(newSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
finishGame() {
|
finishGame() {
|
||||||
this.setState(GameState.FINISHED);
|
this.setState(GameState.FINISHED);
|
||||||
this.zone.reset();
|
|
||||||
playersBroadcast("game_state", this.state);
|
playersBroadcast("game_state", this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,19 +35,7 @@ export const io = new Server(httpsServer, {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Zone update broadcast function, called by the game object
|
export const game = new Game();
|
||||||
function onUpdateNewZone(newZone) {
|
|
||||||
playersBroadcast("new_zone", newZone)
|
|
||||||
secureAdminBroadcast("new_zone", newZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUpdateZone(zone) {
|
|
||||||
playersBroadcast("zone", zone)
|
|
||||||
secureAdminBroadcast("zone", zone)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const game = new Game(onUpdateZone, onUpdateNewZone);
|
|
||||||
export const penaltyController = new PenaltyController();
|
export const penaltyController = new PenaltyController();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { sendUpdatedTeamInformations, teamBroadcast } from "./team_socket.js";
|
|||||||
import { GameState } from "./game.js";
|
import { GameState } from "./game.js";
|
||||||
import { secureAdminBroadcast } from "./admin_socket.js";
|
import { secureAdminBroadcast } from "./admin_socket.js";
|
||||||
import { game } from "./index.js";
|
import { game } from "./index.js";
|
||||||
|
import { isInZone } from "./zone_manager.js";
|
||||||
|
|
||||||
export class PenaltyController {
|
export class PenaltyController {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -91,10 +92,10 @@ export class PenaltyController {
|
|||||||
this.game.teams.forEach((team) => {
|
this.game.teams.forEach((team) => {
|
||||||
if (team.captured) { return }
|
if (team.captured) { return }
|
||||||
//All the informations are not ready yet
|
//All the informations are not ready yet
|
||||||
if (team.currentLocation == null || this.game.zone.currentZone == null) {
|
if (team.currentLocation == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isInCircle({ lat: team.currentLocation[0], lng: team.currentLocation[1] }, this.game.zone.currentZone.center, this.game.zone.currentZone.radius)) {
|
if (!isInZone(team.currentLocation)) {
|
||||||
//The team was not previously out of the zone
|
//The team was not previously out of the zone
|
||||||
if (!this.outOfBoundsSince[team.id]) {
|
if (!this.outOfBoundsSince[team.id]) {
|
||||||
this.outOfBoundsSince[team.id] = new Date();
|
this.outOfBoundsSince[team.id] = new Date();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { secureAdminBroadcast } from "./admin_socket.js";
|
import { secureAdminBroadcast } from "./admin_socket.js";
|
||||||
import { io, game } from "./index.js";
|
import { io, game } from "./index.js";
|
||||||
|
import { currentZone } from "./zone_manager.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a socket message to all the players of a team
|
* Send a socket message to all the players of a team
|
||||||
@@ -8,9 +9,13 @@ import { io, game } from "./index.js";
|
|||||||
* @param {*} data The payload
|
* @param {*} data The payload
|
||||||
*/
|
*/
|
||||||
export function teamBroadcast(teamId, event, data) {
|
export function teamBroadcast(teamId, event, data) {
|
||||||
|
if(!game.getTeam(teamId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (let socketId of game.getTeam(teamId).sockets) {
|
for (let socketId of game.getTeam(teamId).sockets) {
|
||||||
io.of("player").to(socketId).emit(event, data)
|
io.of("player").to(socketId).emit(event, data)
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,11 +84,7 @@ export function initTeamSocket() {
|
|||||||
socket.emit("login_response", true);
|
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", game.zone.currentZone)
|
socket.emit("zone", currentZone)
|
||||||
socket.emit("new_zone", {
|
|
||||||
begin: game.zone.currentStartZone,
|
|
||||||
end: game.zone.nextZone
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("logout", () => {
|
socket.on("logout", () => {
|
||||||
|
|||||||
@@ -1,199 +1,108 @@
|
|||||||
import { randomCirclePoint } from 'random-location'
|
import { playersBroadcast, teamBroadcast } from './team_socket.js';
|
||||||
import { isInCircle } from './map_utils.js';
|
import { secureAdminBroadcast } from './admin_socket.js';
|
||||||
import { map } from './util.js';
|
|
||||||
|
|
||||||
export class ZoneManager {
|
export let currentZone = []
|
||||||
constructor(onZoneUpdate, onNextZoneUpdate) {
|
let tileSize = 16;
|
||||||
//Setings storing where the zone will start, end and how it should evolve
|
|
||||||
//The zone will start by staying at its mzx value for reductionInterval minutes
|
|
||||||
//and then reduce during reductionDuration minutes, then wait again...
|
|
||||||
//The reduction factor is such that after reductionCount the zone will be the min zone
|
|
||||||
//a call to onZoneUpdate will be made every updateIntervalSeconds when the zone is changing
|
|
||||||
//a call to onNextZoneUpdate will be made when the zone reduction ends and a new next zone is announced
|
|
||||||
this.zoneSettings = {
|
|
||||||
min: { center: null, radius: null },
|
|
||||||
max: { center: null, radius: null },
|
|
||||||
reductionCount: 2,
|
|
||||||
reductionDuration: 1,
|
|
||||||
reductionInterval: 1,
|
|
||||||
updateIntervalSeconds: 1,
|
|
||||||
}
|
|
||||||
this.nextZoneDecrement = null;
|
|
||||||
//Live location of the zone
|
|
||||||
this.currentZone = { center: null, radius: null };
|
|
||||||
|
|
||||||
//If the zone is shrinking, this is the target of the current shrinking
|
export class TileNumber {
|
||||||
//If the zone is not shrinking, this will be the target of the next shrinking
|
constructor(x, y) {
|
||||||
this.nextZone = { center: null, radius: null };
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
//Zone at the begining of the shrinking
|
this.removeDate = null;
|
||||||
this.currentStartZone = { center: null, radius: null };
|
}
|
||||||
|
equals(other) {
|
||||||
this.startDate = null;
|
return this.x === other.x && this.y === other.y;
|
||||||
this.started = false;
|
|
||||||
this.updateIntervalId = null;
|
|
||||||
this.nextZoneTimeoutId = null;
|
|
||||||
|
|
||||||
this.onZoneUpdate = onZoneUpdate;
|
|
||||||
this.onNextZoneUpdate = onNextZoneUpdate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
removeIn(minutes) {
|
||||||
* Test if a given configuration object is valid, i.e if all needed values are well defined
|
this.removeDate = Date.now() + 1000 * 60 * minutes
|
||||||
* @param {Object} settings Settings object describing a config of a zone manager
|
|
||||||
* @returns if the config is correct
|
|
||||||
*/
|
|
||||||
validateSettings(settings) {
|
|
||||||
if (settings.reductionCount && (typeof settings.reductionCount != "number" || settings.reductionCount <= 0)) { return false }
|
|
||||||
if (settings.reductionDuration && (typeof settings.reductionDuration != "number" || settings.reductionDuration < 0)) { return false }
|
|
||||||
if (settings.reductionInterval && (typeof settings.reductionInterval != "number" || settings.reductionInterval < 0)) { return false }
|
|
||||||
if (settings.updateIntervalSeconds && (typeof settings.updateIntervalSeconds != "number" || settings.updateIntervalSeconds <= 0)) { return false }
|
|
||||||
if (settings.max && (typeof settings.max.radius != "number" || typeof settings.max.center.lat != "number" || typeof settings.max.center.lng != "number")) { return false }
|
|
||||||
if (settings.min && (typeof settings.min.radius != "number" || typeof settings.min.center.lat != "number" || typeof settings.min.center.lng != "number")) { return false }
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Test if the zone manager is ready to start
|
|
||||||
* @returns true if the zone manager is ready to be started, false otherwise
|
|
||||||
*/
|
|
||||||
ready() {
|
|
||||||
return this.validateSettings(this.zoneSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the settings of the zone, this can be done by passing an object containing the settings to change.
|
|
||||||
* Unless specified, the durations are in minutes
|
|
||||||
* Default config :
|
|
||||||
* `this.zoneSettings = {
|
|
||||||
* min: {center: null, radius: null},
|
|
||||||
* max: {center: null, radius: null},
|
|
||||||
* reductionCount: 2,
|
|
||||||
* reductionDuration: 10,
|
|
||||||
* reductionInterval: 10,
|
|
||||||
* updateIntervalSeconds: 10,
|
|
||||||
* }`
|
|
||||||
* @param {Object} newSettings The fields of the settings to udpate
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
udpateSettings(newSettings) {
|
|
||||||
//validate settings
|
|
||||||
this.zoneSettings = { ...this.zoneSettings, ...newSettings };
|
|
||||||
this.nextZoneDecrement = (this.zoneSettings.max.radius - this.zoneSettings.min.radius) / this.zoneSettings.reductionCount;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reinitialize the object and stop all the tasks
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
this.currentZoneCount = 0;
|
|
||||||
this.started = false;
|
|
||||||
if (this.updateIntervalId != null) {
|
|
||||||
clearInterval(this.updateIntervalId);
|
|
||||||
this.updateIntervalId = null;
|
|
||||||
}
|
|
||||||
if (this.nextZoneTimeoutId != null) {
|
|
||||||
clearTimeout(this.nextZoneTimeoutId);
|
|
||||||
this.nextZoneTimeoutId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the zone reduction sequence
|
|
||||||
*/
|
|
||||||
start() {
|
|
||||||
this.started = true;
|
|
||||||
this.startDate = new Date();
|
|
||||||
//initialize the zone to its max value
|
|
||||||
this.nextZone = JSON.parse(JSON.stringify(this.zoneSettings.max));
|
|
||||||
this.currentStartZone = JSON.parse(JSON.stringify(this.zoneSettings.max));
|
|
||||||
this.currentZone = JSON.parse(JSON.stringify(this.zoneSettings.max));
|
|
||||||
return this.setNextZone();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the center of the next zone, this center need to satisfy two properties
|
|
||||||
* - it needs to be included in the current zone, this means that this new point should lie in the circle of center currentZone.center and radius currentZone.radius - newRadius
|
|
||||||
* - it needs to include the last zone, which means that the center must lie in the center of center min.center and of radius newRadius - min.radius
|
|
||||||
* @param newRadius the radius that the new zone will have
|
|
||||||
* @returns the coordinates of the new center as an object with lat and long fields
|
|
||||||
*/
|
|
||||||
getRandomNextCenter(newRadius) {
|
|
||||||
let ok = false;
|
|
||||||
let res = null
|
|
||||||
let tries = 0;
|
|
||||||
const MAX_TRIES = 100000
|
|
||||||
//take a random point satisfying both conditions
|
|
||||||
while (tries++ < MAX_TRIES && !ok) {
|
|
||||||
res = randomCirclePoint({ latitude: this.currentZone.center.lat, longitude: this.currentZone.center.lng }, this.currentZone.radius - newRadius);
|
|
||||||
ok = (isInCircle({ lat: res.latitude, lng: res.longitude }, this.zoneSettings.min.center, newRadius - this.zoneSettings.min.radius))
|
|
||||||
}
|
|
||||||
if(tries>=MAX_TRIES) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
lat: res.latitude,
|
|
||||||
lng: res.longitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the next zone satifying the given settings, update the nextZone and currentStartZone
|
|
||||||
* Wait for the appropriate duration before starting a new zone reduction if needed
|
|
||||||
*/
|
|
||||||
setNextZone() {
|
|
||||||
//At this point, nextZone == currentZone, we need to update the next zone, the raidus decrement, and start a timer before the next shrink
|
|
||||||
//last zone
|
|
||||||
if (this.currentZoneCount == this.zoneSettings.reductionCount) {
|
|
||||||
this.nextZone = JSON.parse(JSON.stringify(this.zoneSettings.min))
|
|
||||||
this.currentStartZone = JSON.parse(JSON.stringify(this.zoneSettings.min))
|
|
||||||
} else if (this.currentZoneCount == this.zoneSettings.reductionCount - 1) {
|
|
||||||
this.currentStartZone = JSON.parse(JSON.stringify(this.currentZone))
|
|
||||||
this.nextZone = JSON.parse(JSON.stringify(this.zoneSettings.min))
|
|
||||||
this.nextZoneTimeoutId = setTimeout(() => this.startShrinking(), 1000 * 60 * this.zoneSettings.reductionInterval)
|
|
||||||
this.currentZoneCount++;
|
|
||||||
} else if (this.currentZoneCount < this.zoneSettings.reductionCount) {
|
|
||||||
this.nextZone.center = this.getRandomNextCenter(this.nextZone.radius - this.nextZoneDecrement)
|
|
||||||
//Next center cannot be found
|
|
||||||
if(this.nextZone.center === false) {
|
|
||||||
console.log("no center")
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.nextZone.radius -= this.nextZoneDecrement;
|
|
||||||
this.currentStartZone = JSON.parse(JSON.stringify(this.currentZone))
|
|
||||||
this.nextZoneTimeoutId = setTimeout(() => this.startShrinking(), 1000 * 60 * this.zoneSettings.reductionInterval)
|
|
||||||
this.currentZoneCount++;
|
|
||||||
}
|
|
||||||
this.onZoneUpdate(JSON.parse(JSON.stringify(this.currentStartZone)))
|
|
||||||
this.onNextZoneUpdate({
|
|
||||||
begin: JSON.parse(JSON.stringify(this.currentStartZone)),
|
|
||||||
end: JSON.parse(JSON.stringify(this.nextZone))
|
|
||||||
})
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Start a task that will run periodically updatinng the zone size, and calling the onZoneUpdate callback
|
|
||||||
* This will also periodically check if the reduction is over or not
|
|
||||||
* If the reduction is over this function will call setNextZone
|
|
||||||
*/
|
|
||||||
startShrinking() {
|
|
||||||
const startTime = new Date();
|
|
||||||
this.updateIntervalId = setInterval(() => {
|
|
||||||
const completed = ((new Date() - startTime) / (1000 * 60)) / this.zoneSettings.reductionDuration;
|
|
||||||
this.currentZone.radius = map(completed, 0, 1, this.currentStartZone.radius, this.nextZone.radius)
|
|
||||||
this.currentZone.center.lat = map(completed,0,1, this.currentStartZone.center.lat, this.nextZone.center.lat)
|
|
||||||
this.currentZone.center.lng = map(completed,0,1, this.currentStartZone.center.lng, this.nextZone.center.lng)
|
|
||||||
this.onZoneUpdate(JSON.parse(JSON.stringify(this.currentZone)))
|
|
||||||
//Zone shrinking is over
|
|
||||||
if (completed >= 1) {
|
|
||||||
clearInterval(this.updateIntervalId);
|
|
||||||
this.updateIntervalId = null;
|
|
||||||
this.setNextZone();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, this.zoneSettings.updateIntervalSeconds * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function latLngToTileNumber(pos, tileSize) {
|
||||||
|
const numTilesX = 2 ** tileSize;
|
||||||
|
const numTilesY = 2 ** tileSize;
|
||||||
|
const lngDegrees = pos[1];
|
||||||
|
const latRadians = pos[0] * (Math.PI / 180);
|
||||||
|
return {
|
||||||
|
x:Math.round(numTilesX * ((lngDegrees + 180) / 360)),
|
||||||
|
y:Math.round(numTilesY * (1 - Math.log(Math.tan(latRadians) + 1 / Math.cos(latRadians)) / Math.PI) / 2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function broadcastZoneState() {
|
||||||
|
playersBroadcast("zone", currentZone);
|
||||||
|
secureAdminBroadcast("zone", currentZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all tiles from the zone
|
||||||
|
*/
|
||||||
|
export function resetZone() {
|
||||||
|
currentZone = [];
|
||||||
|
broadcastZoneState();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTileSize(size) {
|
||||||
|
resetZone();
|
||||||
|
tileSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a position is in the zone
|
||||||
|
* @param {Object} position The position to check
|
||||||
|
*/
|
||||||
|
export function isInZone(position) {
|
||||||
|
let tile = latLngToTileNumber(position, tileSize);
|
||||||
|
return currentZone.some(square => square.equals(tile))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a zone with a list of tiles
|
||||||
|
* @param {Array} zone Array of tiles to add
|
||||||
|
*/
|
||||||
|
export function initZone(zone) {
|
||||||
|
currentZone = [];
|
||||||
|
try {
|
||||||
|
for (let { x, y } of zone) {
|
||||||
|
currentZone.push(new TileNumber(x, y))
|
||||||
|
}
|
||||||
|
broadcastZoneState();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
secureAdminBroadcast("error", "Invalid zone format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a list of tiles in a warning state for a certain amount of time, before removing them
|
||||||
|
* @param {Array} zone Array of tiles to remove
|
||||||
|
* @param {Number} time Time before those tiles get removed in minutes
|
||||||
|
*/
|
||||||
|
export function removeZone(zone, time) {
|
||||||
|
for (let tile of zone) {
|
||||||
|
for (let currentTile of currentZone) {
|
||||||
|
if (currentTile.equals(tile)) {
|
||||||
|
currentTile.removeIn(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
broadcastZoneState();
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
let changed = false;
|
||||||
|
currentZone = currentZone.map(square => {
|
||||||
|
if (square.removeDate !== null && square.removeDate < Date.now()) {
|
||||||
|
changed = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return square;
|
||||||
|
}).filter(square => square !== null)
|
||||||
|
if (changed) {
|
||||||
|
broadcastZoneState();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user