Initial commit
This commit is contained in:
+511
@@ -0,0 +1,511 @@
|
||||
const EventEmitter = require("events");
|
||||
|
||||
const Utility = require("./Utility");
|
||||
const Reward = require("./Reward");
|
||||
const EntityGroup = require("./EntityGroup");
|
||||
|
||||
const mapMatrix = require("../Data/mapMatrix");
|
||||
const constants = require("../Data/constants");
|
||||
|
||||
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
|
||||
|
||||
const grids = require("./Grids");
|
||||
|
||||
const { TYPES, EVENTS } = constants;
|
||||
const { ENTITY_TYPE_PLAYER } = TYPES;
|
||||
const { EVENT_SPAWN_ADMIRAL } = EVENTS;
|
||||
|
||||
const COORDINATE_REGIONS = ["A", "B", "C"];
|
||||
const COORDINATE_SECTIONS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
|
||||
|
||||
function getType(compositeID) {
|
||||
return (compositeID >> 24) & 0xFF;
|
||||
};
|
||||
|
||||
class GameMap extends EventEmitter {
|
||||
constructor(config) {
|
||||
super();
|
||||
|
||||
this.w = config.size.width;
|
||||
this.h = config.size.height;
|
||||
this.cw = config.size.chunk.width;
|
||||
this.ch = config.size.chunk.height;
|
||||
this.chunkRows = config.size.height / config.size.chunk.height;
|
||||
this.chunkColumns = config.size.width / config.size.chunk.width;
|
||||
this.name = config.name;
|
||||
this.type = config.type;
|
||||
this.id = config.id;
|
||||
this.neighbours = config.neighbours;
|
||||
this.collision = config.collision ?? {};
|
||||
this.level = config.level;
|
||||
this.spawnedEntitiesTotalCount = 0;
|
||||
this.availableIdentifiers = [];
|
||||
this.players = new Map();
|
||||
this.npcs = new Map();
|
||||
this.monsters = new Map();
|
||||
this.towers = new Map();
|
||||
this.collectables = new Map();
|
||||
this.entities = [this.players, this.npcs, this.monsters, this.towers, this.collectables];
|
||||
this.entityGroups = Array.isArray(config.entityGroups) ? config.entityGroups.map(e => new EntityGroup(e)) : [];
|
||||
this.chunksData = [];
|
||||
this.networkPacketsGlobal = [];
|
||||
this.networkPacketsLocal = [];
|
||||
this.networkPacketsInvidiual = {};
|
||||
this.testPackets = [];
|
||||
this.eventBuffer = {};
|
||||
this.eventAuthors = [];
|
||||
|
||||
this.lookingForTarget = [];
|
||||
|
||||
this.dirtyChunks = [];
|
||||
|
||||
this.grid = grids[this.id - 1];
|
||||
|
||||
for (let i = 0; i < this.chunkColumns; i++) {
|
||||
for (let j = 0; j < this.chunkRows; j++) {
|
||||
const m = new DoubleLinkedList();
|
||||
|
||||
this[`chunkTest_${i}-${j}`] = m;
|
||||
this[`chunk_${i}-${j}`] = new DoubleLinkedList();
|
||||
|
||||
this.dirtyChunks.push(m);
|
||||
|
||||
this.chunksData.push({
|
||||
x: i * config.size.chunk.width,
|
||||
y: j * config.size.chunk.height,
|
||||
name: `chunk_${i}-${j}`,
|
||||
nameTest: `chunkTest_${i}-${j}`
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
registerIndividualNetworkPacket(identifier, packets, metadata = []) {
|
||||
if (!(packets instanceof Array)) packets = [packets];
|
||||
if (!(metadata instanceof Array)) metadata = [metadata];
|
||||
|
||||
if (!this.networkPacketsInvidiual[identifier]) this.networkPacketsInvidiual[identifier] = [];
|
||||
|
||||
packets.forEach(packet => this.networkPacketsInvidiual[identifier].push({
|
||||
index: this.networkPacketsLocal.length,
|
||||
packet,
|
||||
metadata
|
||||
}));
|
||||
}
|
||||
|
||||
registerGlobalNetworkPacket(identifier, packets) {
|
||||
if (!(packets instanceof Array)) packets = [packets];
|
||||
const entity = this.getEntityFromSubgroup(identifier);
|
||||
|
||||
packets.forEach(p => this.networkPacketsGlobal.push({
|
||||
position: entity.position,
|
||||
authorID: entity.id,
|
||||
packet: p
|
||||
}));
|
||||
}
|
||||
|
||||
registerNetworkPacket(identifier, packets, headers = []) {
|
||||
if (!(packets instanceof Array)) packets = [packets];
|
||||
|
||||
const entity = this.getEntityFromSubgroup(identifier);
|
||||
|
||||
if (headers[0] === 59) packets.forEach((p, i) => this.testPackets.push({ position: entity.position, authorID: entity.id, packet: p, header: headers[i] ? headers[i] : 59 }));
|
||||
else packets.forEach((p, i) => this.networkPacketsLocal.push({ position: entity.position, authorID: entity.id, packet: p, header: headers[i] ? headers[i] : 0 }));
|
||||
}
|
||||
|
||||
getNeighbourMapsInRadius(radius) {
|
||||
const maps = [];
|
||||
|
||||
for (let i = 0; i < mapMatrix.length; i++) {
|
||||
const mapRow = mapMatrix[i];
|
||||
|
||||
for (let j = 0; j < mapRow.length; j++) {
|
||||
if (this.id === mapRow[j]) {
|
||||
const startFromColumn = Math.max(0, j - radius);
|
||||
const startFromRow = Math.max(0, i - radius);
|
||||
const rawColumnBoundary = (j + radius) + 1;
|
||||
const rawRowBoundary = (i + radius) + 1;
|
||||
|
||||
const columnBoundary = rawColumnBoundary > mapRow.length - 1 ? mapRow.length : rawColumnBoundary;
|
||||
const rowBoundary = rawRowBoundary > mapMatrix.length - 1 ? mapMatrix.length : rawRowBoundary;
|
||||
|
||||
for (let l = startFromRow; l < rowBoundary; l++)
|
||||
for (let k = startFromColumn; k < columnBoundary; k++)
|
||||
maps.push(mapMatrix[l][k]);
|
||||
break;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
return maps;
|
||||
}
|
||||
|
||||
spawnEntityGroups(cb, NPC, attackCallback, dieCallback) {
|
||||
for (let i = 0, length = this.entityGroups.length; i < length; i++) {
|
||||
const group = this.entityGroups[i];
|
||||
if (!group.isDefeated()) continue;
|
||||
|
||||
group.default();
|
||||
|
||||
const randomPosition = this.grid.randomNode(20);
|
||||
group.setAreaPosition(randomPosition);
|
||||
|
||||
for (let i = 0, length = group.structure.length; i < length; i++) {
|
||||
const { typeID, entityTypeID, groupMemberTypeID } = group.structure[i];
|
||||
|
||||
const entityTypeSetting = group.getEntitySetting(groupMemberTypeID);
|
||||
|
||||
const preSelectedDestinationNode = this.grid.randomNodeInArea(group.predefinedArea);
|
||||
const spawnData = {
|
||||
id: this.spawnedEntitiesTotalCount,
|
||||
speed: entityTypeSetting.speed,
|
||||
respawnTime: entityTypeSetting.respawnTime,
|
||||
size: entityTypeSetting.size,
|
||||
entityTypeID,
|
||||
parentEntityTypeID: 0,
|
||||
maximumHitpoint: 4500,
|
||||
currentHitpoint: 4500,
|
||||
map: this,
|
||||
group,
|
||||
position: preSelectedDestinationNode,
|
||||
attackCallback,
|
||||
dieCallback
|
||||
};
|
||||
|
||||
const spawn = this.registerNonPlayerCharacter(spawnData, NPC);
|
||||
|
||||
if (groupMemberTypeID === 1) group.addLeader(spawn.npc);
|
||||
else group.addMember(spawn.npc);
|
||||
|
||||
cb(this, spawn);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
setupNonPlayerCharacterSpawn(spawn, cb, globalDieCallback, globalRewardCallback, globalAttackCallback, NPC) {
|
||||
spawn.forEach(data => {
|
||||
const { id, parentEntityTypeId, speed, minHealth, maxHealth, amount, respawnTime, rewardEntries } = data;
|
||||
if (!parentEntityTypeId) return;
|
||||
|
||||
const admiralNPC = spawn.find(parent => parent.id === parentEntityTypeId);
|
||||
|
||||
const reward = new Reward({
|
||||
distributionType: 1,
|
||||
baseReward: rewardEntries.reduce((result, entry) => {
|
||||
if (result[entry.categoryId]) result[entry.categoryId][entry.itemId] = {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance,
|
||||
lastShot: entry.isLastShot
|
||||
};
|
||||
else result[entry.categoryId] = {
|
||||
[entry.itemId]: {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance,
|
||||
lastShot: entry.isLastShot
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}, {})
|
||||
});
|
||||
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const spareID = this.availableIdentifiers.pop();
|
||||
const hitpoint = Math.floor(Math.random() * (maxHealth - minHealth + 1) + minHealth);
|
||||
|
||||
const npcSpawnData = {
|
||||
id: spareID ? spareID : this.spawnedEntitiesTotalCount,
|
||||
speed,
|
||||
respawnTime,
|
||||
reward,
|
||||
entityTypeID: id,
|
||||
parentEntityTypeID: parentEntityTypeId,
|
||||
maximumHitpoint: hitpoint,
|
||||
currentHitpoint: hitpoint,
|
||||
map: this,
|
||||
behaviour: {
|
||||
stopIfAttacked: true,
|
||||
aggressive: false
|
||||
},
|
||||
attackCallback: globalAttackCallback,
|
||||
rewardCallback: globalRewardCallback
|
||||
};
|
||||
|
||||
const newSpawn = this.registerNonPlayerCharacter(npcSpawnData, NPC);
|
||||
newSpawn.npc.dieCallback = data => {
|
||||
const isAdmiralReady = globalDieCallback.call(newSpawn.npc, data);
|
||||
if (isAdmiralReady && admiralNPC) {
|
||||
const { id, speed, minHealth, maxHealth, rewardEntries } = admiralNPC;
|
||||
|
||||
const reward = new Reward({
|
||||
distributionType: 1,
|
||||
baseReward: rewardEntries.reduce((result, entry) => {
|
||||
if (result[entry.categoryId]) result[entry.categoryId][entry.itemId] = {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance,
|
||||
lastShot: entry.isLastShot
|
||||
};
|
||||
else result[entry.categoryId] = {
|
||||
[entry.itemId]: {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance,
|
||||
lastShot: entry.isLastShot
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}, {})
|
||||
});
|
||||
|
||||
const spareID = this.availableIdentifiers.pop();
|
||||
const hitpoint = Math.floor(Math.random() * (maxHealth - minHealth + 1) + minHealth);
|
||||
|
||||
const admiralSpawnData = {
|
||||
id: spareID ? spareID : this.spawnedEntitiesTotalCount,
|
||||
isAdmiral: true,
|
||||
speed,
|
||||
reward,
|
||||
size: 192,
|
||||
entityTypeID: id,
|
||||
position: newSpawn.npc.position,
|
||||
maximumHitpoint: hitpoint,
|
||||
currentHitpoint: hitpoint,
|
||||
map: this,
|
||||
behaviour: {
|
||||
stopIfAttacked: true,
|
||||
aggressive: false
|
||||
},
|
||||
dieCallback: globalDieCallback,
|
||||
attackCallback: globalAttackCallback,
|
||||
rewardCallback: globalRewardCallback
|
||||
};
|
||||
|
||||
const emittingTo = this.getNeighbourMapsInRadius(1);
|
||||
const notificationData = {
|
||||
mapID: this.id,
|
||||
typeID: newSpawn.npc.typeID,
|
||||
entityTypeID: newSpawn.npc.entityTypeID
|
||||
};
|
||||
|
||||
this.emit(EVENT_SPAWN_ADMIRAL, notificationData, emittingTo);
|
||||
|
||||
const admiral = this.registerNonPlayerCharacter(admiralSpawnData, NPC);
|
||||
cb(this, admiral);
|
||||
};
|
||||
};
|
||||
|
||||
cb(this, newSpawn);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
registerNonPlayerCharacter(data, NPC) {
|
||||
const npc = new NPC(data);
|
||||
|
||||
if (!this.availableIdentifiers.length) this.spawnedEntitiesTotalCount++;
|
||||
|
||||
const npcSpawnProperties = npc.getProperties();
|
||||
|
||||
return { npc, npcSpawnProperties };
|
||||
}
|
||||
|
||||
setupMonsterSpawn(spawn, cb, globalDieCallback, globalRewardCallback, globalAttackCallback, Monster) {
|
||||
spawn.forEach(data => {
|
||||
const { id, parentEntityTypeId, minHealth, maxHealth, amount, respawnTime, rewardEntries } = data;
|
||||
if (!parentEntityTypeId) return;
|
||||
|
||||
const bossMonster = spawn.find(b => b.id === parentEntityTypeId);
|
||||
|
||||
const reward = new Reward({
|
||||
distributionType: 1,
|
||||
baseReward: rewardEntries.reduce((result, entry) => {
|
||||
if (result[entry.categoryId]) result[entry.categoryId][entry.itemId] = {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance
|
||||
};
|
||||
else result[entry.categoryId] = {
|
||||
[entry.itemId]: {
|
||||
amountMinimum: entry.min,
|
||||
amountMaximum: entry.max,
|
||||
chance: entry.chance
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
}, {})
|
||||
});
|
||||
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const spareID = this.availableIdentifiers.pop();
|
||||
const hitpoint = Math.floor(Math.random() * (maxHealth - minHealth + 1) + minHealth);
|
||||
|
||||
const monsterData = {
|
||||
id: spareID ? spareID : this.spawnedEntitiesTotalCount,
|
||||
entityTypeID: id,
|
||||
reward,
|
||||
respawnTime,
|
||||
parentEntityTypeID: parentEntityTypeId,
|
||||
maximumHitpoint: hitpoint,
|
||||
currentHitpoint: hitpoint,
|
||||
map: this,
|
||||
rewardCallback: globalRewardCallback,
|
||||
attackCallback: globalAttackCallback
|
||||
};
|
||||
|
||||
const newSpawn = this.registerMonster(monsterData, Monster);
|
||||
newSpawn.monster.dieCallback = data => {
|
||||
const isAdmiralReady = globalDieCallback.call(newSpawn.monster, data);
|
||||
|
||||
if (isAdmiralReady && bossMonster) {
|
||||
const { id, minHealth, maxHealth, reward } = bossMonster;
|
||||
const spareID = this.availableIdentifiers.pop();
|
||||
const hitpoint = Math.floor(Math.random() * (maxHealth - minHealth + 1) + minHealth);
|
||||
|
||||
const emittingTo = this.getNeighbourMapsInRadius(1);
|
||||
const notificationData = { mapID: this.id, typeID: newSpawn.monster.typeID, entityTypeID: newSpawn.monster.entityTypeID };
|
||||
this.emit(EVENT_SPAWN_ADMIRAL, notificationData, emittingTo);
|
||||
|
||||
const bossSpawnData = {
|
||||
id: spareID ? spareID : this.spawnedEntitiesTotalCount,
|
||||
isAdmiral: true,
|
||||
reward,
|
||||
entityTypeID: id,
|
||||
position: newSpawn.monster.position,
|
||||
maximumHitpoint: hitpoint,
|
||||
currentHitpoint: hitpoint,
|
||||
map: this,
|
||||
dieCallback: globalDieCallback,
|
||||
attackCallback: globalAttackCallback,
|
||||
rewardCallback: globalRewardCallback
|
||||
};
|
||||
|
||||
const boss = this.registerMonster(bossSpawnData, Monster);
|
||||
cb(boss);
|
||||
};
|
||||
};
|
||||
|
||||
cb(newSpawn);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
registerMonster(data, Monster) {
|
||||
const monster = new Monster(data);
|
||||
|
||||
if (!this.availableIdentifiers.length) this.spawnedEntitiesTotalCount++;
|
||||
|
||||
const monsterSpawnProperties = monster.getProperties();
|
||||
|
||||
return { monster, monsterSpawnProperties };
|
||||
}
|
||||
|
||||
registerCollectable(spawn, cb, globalDieCallback, globalRewardCallback, Collectable) {
|
||||
spawn.forEach(data => {
|
||||
const { id, amount, respawnTime, reward } = data;
|
||||
const newCollectables = [];
|
||||
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const collectable = new Collectable({
|
||||
id: this.spawnedEntitiesTotalCount++,
|
||||
entityTypeID: id,
|
||||
respawnTime,
|
||||
map: this,
|
||||
rewardEntries: reward,
|
||||
dieCallback: globalDieCallback,
|
||||
rewardCallback: globalRewardCallback
|
||||
});
|
||||
|
||||
const collectableSpawnProperties = collectable.getProperties();
|
||||
newCollectables.push({ collectable, collectableSpawnProperties });
|
||||
};
|
||||
|
||||
cb(newCollectables);
|
||||
});
|
||||
}
|
||||
|
||||
removeNPC(id) {
|
||||
this.npcs.delete(id);
|
||||
this.availableIdentifiers.push(id);
|
||||
}
|
||||
|
||||
removeMonster(id) {
|
||||
this.monsters.delete(id);
|
||||
this.availableIdentifiers.push(id);
|
||||
}
|
||||
|
||||
getNearbyPlayersGameID(objectToCheck) {
|
||||
const nearbyPlayers = [];
|
||||
|
||||
this.players.forEach(player => {
|
||||
if (!player.hasUnexpectedlyLeft &&
|
||||
player.isAlive &&
|
||||
Math.pow(objectToCheck.position.x - player.position.x, 2) + Math.pow(objectToCheck.position.y - player.position.y, 2) < Math.pow(player.sight, 2))
|
||||
nearbyPlayers.push(player.id);
|
||||
});
|
||||
|
||||
return nearbyPlayers;
|
||||
}
|
||||
|
||||
getLivingEntities(types) {
|
||||
if (!types.length) return;
|
||||
|
||||
const aliveEntities = [];
|
||||
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
const type = types[i];
|
||||
const entityGroup = this.entities[type - 1];
|
||||
entityGroup.forEach(entity => entity.isAlive ? aliveEntities.push(entity) : null);
|
||||
};
|
||||
|
||||
return aliveEntities;
|
||||
}
|
||||
|
||||
getAliveEntityFromSubgroup(type, id) {
|
||||
const subgroup = this.entities[type - 1];
|
||||
if (!subgroup) return null;
|
||||
|
||||
const entity = subgroup.get(+id);
|
||||
return entity && entity.isAlive ? entity : null;
|
||||
}
|
||||
|
||||
getEntityFromSubgroup(id) {
|
||||
const type = getType(id);
|
||||
|
||||
return this.entities[type] ? this.entities[type].get(id) : null;
|
||||
}
|
||||
|
||||
removePlayer = id => this.players.delete(id);
|
||||
|
||||
addPlayer = player => this.players.set(player.id, player);
|
||||
|
||||
switchPlayer(player, map) {
|
||||
this.removePlayer(player.id);
|
||||
|
||||
map.players.set(player.id, player);
|
||||
player.map = map;
|
||||
}
|
||||
|
||||
getGameCoordinates(spiedEntity) {
|
||||
const vertical = Math.round(spiedEntity.position.y / 100) - 1;
|
||||
const verticalRest = vertical % COORDINATE_SECTIONS.length;
|
||||
const index = Math.floor(Math.max(vertical, 0) / COORDINATE_SECTIONS.length);
|
||||
|
||||
const letters = `${COORDINATE_REGIONS[index]}${COORDINATE_SECTIONS[verticalRest]}`;
|
||||
const numbers = Math.floor(spiedEntity.position.x / 100) < 10 ? `0${Math.floor(spiedEntity.position.x / 100)}` : Math.floor(spiedEntity.position.x / 100);
|
||||
|
||||
return {
|
||||
name: spiedEntity.name,
|
||||
id: spiedEntity.id,
|
||||
map: spiedEntity.map.name,
|
||||
x: numbers,
|
||||
y: letters
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = GameMap;
|
||||
Reference in New Issue
Block a user