const GAME_TICKRATE = 1000 / 30; const INACTIVITY_THRESHOLD_MS = 5 * 60000; let looseAccumulator = 0; let accumulator = 0; let difference = 0; let n = performance.now(); let nn = Date.now(); let batch = {}; let isLooseIteration = false; const packet = require("./Packets/packet"); const Game = require("./Game/Game"); const getFixedDate = require("./Utility/getFixedDate"); const packetRelevancies = require("./Data/packetRelevancies"); const constants = require("./Data/constants"); const { TIMING_EVENTS_BY_ID } = constants; function gameLoop() { difference = performance.now() - n; accumulator += difference; n = performance.now(); nn = Date.now(); if (accumulator > GAME_TICKRATE) { Game.maps.forEach(map => { map.eventAuthors.forEach(id => { const currentFlush = map.eventBuffer[id]; const entity = map.getEntityFromSubgroup(currentFlush.target); if (entity) entity.emit("attacking", currentFlush.data, currentFlush.author); }); if (looseAccumulator === 15) { looseAccumulator = 0; isLooseIteration = true; }; map.players.forEach(player => { if (isLooseIteration) { player.isInactive = player.lastInteractionTimestamp + INACTIVITY_THRESHOLD_MS < n; player.refreshVisibilities(false); if (player.isInactive) player.setIndependentEvent(4, 0); }; if (player.hasUnexpectedlyLeft) return; player.flush(1); batch.packet = null; batch.offset = 0; const localPacketsLength = map.networkPacketsLocal.length; const individual = map.networkPacketsInvidiual[player.id]; if (!localPacketsLength && individual) for (let i = 0, length = individual.length; i < length; i++) batch = packet(individual[i].packet, batch, player.id, individual[i].metadata); if (localPacketsLength) for (let i = 0, j = 0; i < localPacketsLength; i++) { const p = map.networkPacketsLocal[i]; const relevancyRequirement = packetRelevancies[p.header]; if (relevancyRequirement(player, p)) batch = packet(p.packet, batch, player.id, p.metadata); if (individual) while (j < individual.length && (i === individual[j].index || i === localPacketsLength - 1)) { batch = packet(individual[j].packet, batch, player.id, individual[j].metadata); j++; }; }; for (let i = 0, length = map.testPackets.length; i < length; i++) { const p = map.testPackets[i]; const relevancyRequirement = packetRelevancies[p.header]; if (relevancyRequirement(player, p)) batch = packet(p.packet, batch, player.id, p.metadata); }; for (let i = 0, length = map.networkPacketsGlobal.length; i < length; i++) { const p = map.networkPacketsGlobal[i]; batch = packet(p.packet, batch, player.id, p.metadata); }; if (player.topics) { player.topics.each((key, value) => { for (let i = 0, length = value.length; i < length; i++) { const p = value[i]; batch = packet(p, batch, player.id); }; }); }; if (batch.offset) { const packet = batch.packet.slice(0, batch.offset); const session = socket.sockets[player.sessionID]; session.send(packet, true, true); }; if (individual) individual.length = 0; }); map.testPackets.length = 0; map.networkPacketsGlobal.length = 0; map.eventAuthors.length = 0; map.eventBuffer = {}; map.packetCount = map.networkPacketsLocal.length; map.networkPacketsLocal.length = 0; if (isLooseIteration) { map.towers.forEach(tower => { if (!tower.searchedTarget) return; tower.initializeAttackWithCannon(tower.searchedTarget); tower.searchedTarget = null; }); map.lookingForTarget.forEach(e => e.initializeAttackWithCannon(e.searchedTarget)); map.lookingForTarget.length = 0; map.dirtyChunks.forEach(c => { c.map.clear(); c.head = null; c.tail = null; }); }; }); Game.packets.each((_, value) => { if (value.length > 0) console.info("Topic length", value.length, "All topics length", Game.packets.map.size) value.length = 0 }); const closest = Game.events.peek(); if (closest && n >= closest.value) Game.events.forEach(entry => { if (n < entry.value) return false; const event = Game.events.poll(); const { id, entity, index, getLocalization, data, isSingle } = event.obj; const mapID = getLocalization(); const map = Game.maps[mapID - 1]; const entityInstance = map.getEntityFromSubgroup(entity.id); const eventExists = entityInstance && entityInstance.events[id] && entityInstance.events[id][index]; const isCancelled = eventExists && entityInstance.events[id][index].isCancelled; if (isSingle && eventExists) entityInstance.events[id][index].isOver = true; if (!isCancelled && eventExists) { const methodName = TIMING_EVENTS_BY_ID[id]; entityInstance[methodName](data); }; return true; }); const closestFixed = Game.eventsFixed.peek(); if (closestFixed && nn >= closestFixed.value) Game.eventsFixed.forEach(entry => { if (nn < entry.value) return false; const event = Game.eventsFixed.poll(); const { fn, arguments, next } = event.obj; fn(...arguments); if (next) { const nextRunDate = getFixedDate(30); next(nextRunDate, event.obj); }; return true; }); isLooseIteration = false; looseAccumulator++; accumulator -= GAME_TICKRATE; }; if (difference < GAME_TICKRATE - 32) setTimeout(() => gameLoop()); else setImmediate(() => gameLoop()); }; module.exports = gameLoop;