Initial commit

This commit is contained in:
2026-08-24 22:20:36 +02:00
commit 62f7b1278a
251 changed files with 16670 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules/
.env
.env.*
*.log
Logs/
+23
View File
@@ -0,0 +1,23 @@
const { TASK_TYPES } = require("../Data/constants");
const { TASK_TYPE_DEAL_DAMAGE } = TASK_TYPES;
const Game = require("../Game/Game");
const { players } = Game;
function globalAttackCallback(data) {
const { typeID, entityTypeID, authorID, damage } = data;
const player = players[authorID];
if (!player) return;
/*
* only quest is updated here
* if player is unavailable, it means that they have logged out: damage without player being online???
* conclusion: only overtime damage can cause this issue
*/
const toBeAdjusted = [{ taskCategoryID: TASK_TYPE_DEAL_DAMAGE, typeID, entityTypeID }];
Object.values(player.quests.list).forEach(quest => quest.adjustTask(toBeAdjusted, damage));
};
module.exports = globalAttackCallback;
+253
View File
@@ -0,0 +1,253 @@
const Reward = require("../Game/Reward");
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
const admiralTable = require("../Data/admiralTable");
const leaderboards = require("../Data/leaderboard");
const { TASK_TYPES, TYPES } = require("../Data/constants");
const rewardCallback = require("./reward");
const { TASK_TYPE_DESTROY } = TASK_TYPES;
const { ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_MONSTER } = TYPES;
const Game = require("../Game/Game");
const { players } = Game;
const percentages = [0.40, 0.25, 0.15, 0.10, 0.07, 0.03];
const amounts = [1, 3, 5, 10, 20, 25, 35];
const totalAmount = amounts.reduce((acc, cur) => acc + cur, 0);
let aa = new Array(amounts.length);
setTimeout(async () => {
const q = await execute("SELECT * FROM leaderboard_1 ORDER BY amount DESC LIMIT 1099");
const batches = [];
const values = q.data;
let totalSize = 0;
for (let i = 0; i < percentages.length; i++) { // this loop is for slicing based on percentages
if (values.length - totalAmount < 0) {
batches.push([]);
continue;
};
const batchSize = (values.length - totalAmount) * percentages[i];
const batch = values.slice(totalSize, totalSize + batchSize);
batches.push(batch);
totalSize += batchSize;
};
let rest = values.slice(totalSize, totalSize + totalAmount);
//require("../Miscellaneous/winston").error(`Total length: ${values.length} (these are from DB, raw)`)
// require("../Miscellaneous/winston").error(`Rest length: ${rest.length} (these are based on fixed amounts instead of percentages)`)
let x = 0;
for (let i = 0; i < amounts.length; i++) {
const batchSize = amounts[i];
const batch = rest.slice(totalAmount - (totalAmount - x), totalAmount - (totalAmount - x - batchSize));
// require("../Miscellaneous/winston").error(`amounts array index: ${i} | slicing between: ${totalAmount - (totalAmount - x)} - ${totalAmount - (totalAmount - x - batchSize)}`)
aa[amounts.length - 1 - i] = batch;
x += batchSize;
};
batches.push(...aa);
const thresholds = new Array(batches.length);
for (let j = 0; j < batches.length; j++) {
const batch = batches[j];
const lastElemWithValue = batch.filter(e => e.id);
// require("../Miscellaneous/winston").error(`Batch ${j}: length is ${batch.length}, last elem with value: ${lastElemWithValue[lastElemWithValue.length - 1]?.id} ${lastElemWithValue[lastElemWithValue.length - 1]?.amount}`)
thresholds[j] = lastElemWithValue[lastElemWithValue.length - 1] ? lastElemWithValue[lastElemWithValue.length - 1].amount : 0;
for (let k = 0; k < batch.length; k++)
db.execute("UPDATE playerdata SET globalrank = ? WHERE playerID = ?", [j, batch[k].id]);
};
global.thresholds = thresholds;
// require("../Miscellaneous/winston").error(thresholds);
}, 2000)
const tracker = new DoubleLinkedList();
function globalDieCallback(source) {
const { typeID, entityTypeID, parentEntityTypeID, isAdmiral, authorID, authorTypeID } = source;
const id = `${typeID}_${isAdmiral ? entityTypeID : parentEntityTypeID}`;
const ids = {
typeID,
parentEntityTypeID
};
console.trace("death", source);
const player = players[authorID];
switch (typeID) {
case 1:
leaderboards[4].updatePosition(authorID, 1);
break;
case 2:
leaderboards[5].updatePosition(authorID, 1);
break;
case 3:
leaderboards[6].updatePosition(authorID, 1);
break;
};
if (authorTypeID === ENTITY_TYPE_PLAYER && typeID === ENTITY_TYPE_PLAYER) {
const targetPlayer = players[source.id];
const keyAuthor = `${player.id}-${targetPlayer.id}`;
const killCountAuthor = tracker.getElement(keyAuthor);
if (killCountAuthor === 2) return console.info("reached threshold");
const keyTarget = `${targetPlayer.id}-${player.id}`;
const killCountTarget = tracker.getElement(keyTarget);
if (killCountTarget) tracker.accumulateItem(keyTarget, -1);
tracker.accumulateItem(keyAuthor, 1);
const amount = 10;
const reward = new Reward({
distributionType: 3,
baseReward: [
{
categoryID: 6,
id: 4,
amountMinimum: amount
}
]
});
reward.dynamic(rewards => rewardCallback({ isRegular: true, authorID, source, rewards }));
const battlePointsTarget = targetPlayer.statusPoints[3] && targetPlayer.statusPoints[3].amount;
console.info(battlePointsTarget, amount, "sp")
if (battlePointsTarget >= amount) {
const reward_2 = new Reward({
distributionType: 3,
baseReward: [
{
categoryID: 6,
id: 4,
amountMinimum: amount * -1
}
]
});
reward_2.dynamic(rewards => rewardCallback({ isRegular: true, authorID: source.id, source, rewards }));
};
};
if (typeID === ENTITY_TYPE_NPC) {
} else if (typeID === ENTITY_TYPE_MONSTER) {
};
const toBeAdjusted = [{
taskCategoryID: TASK_TYPE_DESTROY,
typeID,
entityTypeID
}];
for (questID in player.quests.list) player.quests.list[questID].adjustTask(toBeAdjusted, 1);
if (this.reward) {
const playerInventory = player.inventory.getPosessions();
switch (this.reward.distributionType) {
case 1:
this.reward.multiple({
playerInventory,
damage: this.damageReceived,
maximum: this.totalDamageReceived,
lastShotAuthorID: authorID
}, (ids, rewards) => this.rewardCallback({ isRegular: true, authorID: ids.id, source, rewards }));
break;
case 2:
this.reward.single(1, playerInventory, rewards => this.rewardCallback({ isRegular: true, authorID, source, rewards }));
break;
};
};
const playerInventory = player.inventory.getPosessions();
if (isAdmiral) return admiralTable.descreaseAdmiralCount(id);
const isAdmiralSpawnReady = parentEntityTypeID ? admiralTable.increaseCounter(ids) : false;
return isAdmiralSpawnReady;
};
module.exports = globalDieCallback;
//const globalPoints = Math.floor(player.statusPoints[0] / 10000 + player.statusPoints[1] / 2000 + player.statusPoints[2] / 2000);
//if (globalPoints) leaderboards[0].updatePosition(authorID, globalPoints, true);
/*
let aa = new Array(amounts.length);
setTimeout(async () => {
const q = await execute("SELECT * FROM leaderboard_1 ORDER BY amount DESC");
const batches = [];
const values = q.data;
let totalSize = 0;
for (let i = 0; i < percentages.length; i++) { // this loop is for slicing based on percentages
if (values.length - totalAmount < 0) {
batches.push([]);
continue;
};
const batchSize = (values.length - totalAmount) * percentages[i];
const batch = values.slice(totalSize, totalSize + batchSize);
batches.push(batch);
totalSize += batchSize;
};
let rest = values.slice(totalSize, totalSize + totalAmount);
require("../Miscellaneous/winston").error(`Total length: ${values.length} (these are from DB, raw)`)
require("../Miscellaneous/winston").error(`Rest length: ${rest.length} (these are based on fixed amounts instead of percentages)`)
let x = 0;
for (let i = 0; i < amounts.length; i++) {
const batchSize = amounts[i];
const batch = rest.slice(totalAmount - (totalAmount - x), totalAmount - (totalAmount - x - batchSize));
require("../Miscellaneous/winston").error(`amounts array index: ${i} | slicing between: ${totalAmount - (totalAmount - x)} - ${totalAmount - (totalAmount - x - batchSize)}`)
aa[amounts.length - 1 - i] = batch;
x += batchSize;
};
batches.push(...aa);
const thresholds = new Array(batches.length);
for (let j = 0; j < batches.length; j++) {
const batch = batches[j];
const lastElemWithValue = batch.filter(e => e.id);
require("../Miscellaneous/winston").error(`Batch ${j}: length is ${batch.length}, last elem with value: ${lastElemWithValue[lastElemWithValue.length - 1]?.id} ${lastElemWithValue[lastElemWithValue.length - 1]?.amount}`)
thresholds[j] = lastElemWithValue[lastElemWithValue.length - 1] ? lastElemWithValue[lastElemWithValue.length - 1].amount : 0;
for (let k = 0; k < batch.length; k++)
db.execute("UPDATE playerdata SET globalrank = ? WHERE playerID = ?", [j, batch[k].id]);
};
global.thresholds = thresholds;
require("../Miscellaneous/winston").error(thresholds);
}, 2000)
*/
+183
View File
@@ -0,0 +1,183 @@
const RewardPacket = require("../Packets/Reward");
const DesignChangedPacket = require("../Packets/DesignChanged");
const LevelUpPacket = require("../Packets/LevelUp");
const GuildDataEconomyDonatedPacket = require("../Packets/GuildDataEconomyDonated");
const StatisticsWorker = global.StatisticsWorker;
const Game = require("../Game/Game");
const { players } = Game;
const constants = require("../Data/constants");
const leaderboards = require("../Data/leaderboard");
const eliteRewards = require("../Data/eliteRewards");
const [playerEconomy, guildEconomy] = require("../Data/batchSave");
const { CATEGORIES } = constants;
const { CATEGORY_ECONOMY, CATEGORY_DESIGN, CATEGORY_STATUS, CATEGORY_RANKING_POINTS } = CATEGORIES;
const statusMappedToLeaderboard = {
1: 1,
2: 2,
3: 3
};
const EXP_GUILD_ALLOWED_SOURCES = {
2: true,
3: true
};
async function rewardCallback(reward) {
if (!reward) return;
const player = players[reward.authorID];
if (!player) return;
const initializedPacket = RewardPacket.init(reward.isRegular, reward.rewards.length);
const initializedGuildPacket = RewardPacket.init(true, 1);
let designRewardID = 0;
reward.rewards.forEach(r => {
if (r.categoryID === CATEGORY_STATUS) {
if (player.guild.id && r.id === 1 && EXP_GUILD_ALLOWED_SOURCES[reward.source.typeID]) {
const newLevel = player.guild.levelUpdate(r.amount);
guildEconomy.accumulateItem(`${player.guild.id}-${r.categoryID}-${r.id}`, r.amount);
const guildExperienceReward = {
categoryID: 9,
id: r.id,
amount: r.amount
};
initializedGuildPacket.update(guildExperienceReward);
const guildRewardPacket = initializedGuildPacket.get();
const topic = Game.getTopic(player.guild.topic);
topic.push(guildRewardPacket);
if (newLevel !== -1) {
const levelUp = {
level: newLevel,
statusLevelID: 4,
id: player.id
};
const levelUpPacket = LevelUpPacket(levelUp);
topic.push(levelUpPacket);
/*player.emit("levelUp", {
level: newLevel,
statusLevelID: 4,
id: player.id
});*/
};
};
const leaderboardID = statusMappedToLeaderboard[r.id];
const leaderboard = leaderboards[leaderboardID];
if (leaderboard) leaderboard.updatePosition(player.id, r.amount);
player.updateLevels(r.id, r.amount);
}// else if (r.categoryID === CATEGORY_RANKING_POINTS) player.updateRankingPoints(r.id, r.amount);
else player.inventory.refreshSingle(r);
if (r.categoryID === CATEGORY_ECONOMY) {
if (player.guild.id) {
let guildTax = 0;
switch (r.id) {
case 1:
guildTax = player.guild.taxRates[0];
break;
case 2:
guildTax = player.guild.taxRates[1];
break;
};
const guildTaxAmount = Math.floor(r.amount * guildTax);
const playerRewardAmount = r.amount - guildTaxAmount;
console.info("guild tax reward", guildTaxAmount, r.categoryID, r.id);
player.guild.bank.depositTax({
categoryID: r.categoryID,
id: r.id,
amount: guildTaxAmount
});
const guildEconomyDonated = {
id: r.id,
amount: guildTaxAmount
};
const guildEconomyDonatedPacket = GuildDataEconomyDonatedPacket(guildEconomyDonated);
const topic = Game.getTopic(player.guild.topic);
topic.push(guildEconomyDonatedPacket);
if (guildTax) guildEconomy.accumulateItem(`${player.guild.id}-${r.categoryID}-${r.id}`, guildTaxAmount);
r.amount -= guildTaxAmount;
};
};
if (r.categoryID === CATEGORY_DESIGN && !designRewardID) designRewardID = r.id;
console.info("reward amount", r.amount)
playerEconomy.accumulateItem(`${player.id}-${r.categoryID}-${r.id}`, r.amount);
initializedPacket.update(r);
const now = new Date();
StatisticsWorker.postMessage({
id: 1,
data: {
timestamp: now,
playerID: player.id,
categoryID: r.categoryID,
id: r.id,
amount: r.amount,
source: {
typeID: reward.source.typeID,
entityTypeID: reward.source.entityTypeID,
}
}
});
});
const packet = initializedPacket.get();
player.map.registerIndividualNetworkPacket(player.id, packet);
if (designRewardID) {
player.textureID = designRewardID;
const designReward = {
typeID: player.typeID,
id: player.id,
designID: designRewardID
};
const packet = DesignChangedPacket(designReward);
player.map.registerNetworkPacket(player.id, packet);
if (designRewardID === 5) {
for (let i = 1, length = player.eliteLevel; i < length; i++) {
const currentEliteLevelRewards = eliteRewards[i];
if (currentEliteLevelRewards) {
currentEliteLevelRewards.single(0, player.inventory.getPosessions(), rewards => player.rewardCallback({ isRegular: true, authorID: player.id, source: {}, rewards }));
player.inventory.resourceSharedLimiter.cannon.quantity.maximum += 2;
};
};
const ePropertyChanged = {
typeID: 5,
value: player.inventory.resourceSharedLimiter.cannon.quantity.maximum
};
player.emit("propertyChanged", ePropertyChanged, player.id);
};
};
};
module.exports = rewardCallback;
+191
View File
@@ -0,0 +1,191 @@
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;
+35
View File
@@ -0,0 +1,35 @@
const AdmiralCounter = require("../Game/AdmiralCounter");
const admiralTable = new AdmiralCounter({
2: {
2: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
4: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
6: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
8: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
10: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
12: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
14: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
16: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
18: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
20: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
22: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
24: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
26: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
28: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
30: { childToBeKilledCount: 2, maximumAdmiralCount: 2 }
},
3: {
2: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
4: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
6: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
8: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
10: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
12: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
14: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
16: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
18: { childToBeKilledCount: 2, maximumAdmiralCount: 2 },
20: { childToBeKilledCount: 2, maximumAdmiralCount: 2 }
}
});
module.exports = admiralTable;
+15
View File
@@ -0,0 +1,15 @@
const ammunitions = {
1: { damage: 1 },
2: { damage: 2 },
3: { damage: 3 },
4: { damage: 4 },
5: { damage: 5 },
6: { damage: 6 },
7: { damage: 7, overtimeHealthEffectID: 1 },
8: { damage: 8, chanceToTerminateMovement: 20 },
9: { damage: -9 },
10: { damage: -10 },
11: { damage: 11 }
};
module.exports = ammunitions;
+5
View File
@@ -0,0 +1,5 @@
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
const saveLists = [new DoubleLinkedList(), new DoubleLinkedList()];
module.exports = saveLists;
+34
View File
@@ -0,0 +1,34 @@
const cannons = {
1: {
range: 150, damage: 1, reload: 6500, scatter: 0
},
2: {
range: 200, damage: 3, reload: 5750, scatter: 0
},
3: {
range: 250, damage: 5, reload: 5000, scatter: 0
},
4: {
range: 300, damage: 8, reload: 4500, scatter: 0
},
5: {
range: 350, damage: 11, reload: 4000, scatter: 0
},
6: {
range: 400, damage: 10, reload: 4000, scatter: 0
},
7: {
range: 400, damage: 11, reload: 3500, scatter: 0
},
8: {
range: 450, damage: 11, reload: 3500, scatter: 0
},
9: {
range: 350, damage: 4, reload: 1000, scatter: 0
},
10: {
range: 500, damage: 15, reload: 3000, scatter: 0
}
};
module.exports = cannons;
+70
View File
@@ -0,0 +1,70 @@
const cannonsSchemeNPC = {
1: [
[1, 20]
],
2: [
[1, 1],
[4, 2],
[8, 3],
[10, 400]
],
3: [
[10, 30]
],
4: [
[1, 10],
[4, 5],
[8, 15],
[10, 300]
],
5: [
[1, 10],
[4, 5],
[8, 15],
[10, 3]
],
6: [
[1, 10],
[4, 5],
[8, 15],
[10, 300]
],
7: [
[1, 10],
[4, 5],
[8, 15],
[10, 3]
],
8: [
[1, 10],
[4, 5],
[8, 15],
[10, 300]
],
9: [
[1, 10],
[4, 5],
[8, 15],
[10, 30]
],
10: [
[1, 10],
[4, 5],
[8, 15],
[10, 30]
],
11: [
[1, 10],
[4, 5],
[8, 15],
[10, 3]
],
12: [
[1, 10],
[4, 5],
[8, 15],
[10, 30]
]
};
module.exports = cannonsSchemeNPC;
+316
View File
@@ -0,0 +1,316 @@
/*
MAXIMUM VALUES, DO NOT CHANGE AT ALL
*/
const MAX_SAFE_INTEGER = Math.pow(2, 32) - 1;
const MAX_LEVEL_ELITE = 18;
const MAX_GUILD_TAX_RATE_GOLD = 30;
const MAX_GUILD_TAX_RATE_EMERALD = 10;
/*
TYPE OF ENTITES
*/
const ENTITY_TYPE_PLAYER = 1;
const ENTITY_TYPE_NPC = 2;
const ENTITY_TYPE_MONSTER = 3;
const ENTITY_TYPE_TOWER = 4;
const ENTITY_TYPE_COLLECTABLE = 5;
/*
ECONOMY
*/
const ECONOMY_GOLD = 1;
const ECONOMY_EMERALD = 2;
/*
UNATTACKABLE ENTITIES
*/
const UNATTACKABLE_ENTITY_TYPES = [ENTITY_TYPE_COLLECTABLE];
/*
EVENTS
*/
const EVENT_DISCONNECT = "disconnect";
const EVENT_DISCONNECT_CANCEL = "disconnectCancel"
const EVENT_DISCONNECT_IDLE = "disconnectIdle"
const EVENT_RESPAWN = "respawn";
const EVENT_MAP_CHANGED = "mapChanged";
const EVENT_PROPERTY_CHANGED = "propertyChanged";
const EVENT_ATTACK_STARTED = "attackStarted";
const EVENT_ATTACKING = "attacking";
const EVENT_DIE = "die";
const EVENT_LEVEL_UP = "levelUp";
const EVENT_SPEED_UPDATE = "speedUpdate";
const EVENT_ATTACK_ABORTED_BY_USER = "attackAbortedByUser";
const EVENT_STOP_MOVEMENT = "stopMovement";
const EVENT_CANCEL_STATE_CHANGE_TIMEOUT = "cancelStateChangeTimeout";
const EVENT_ABLE_TO_JUMP = "ableToJump";
const EVENT_UNABLE_TO_JUMP = "unableToJump";
const EVENT_LEVEL_IS_LOW = "levelIsLow";
const EVENT_UPDATE_VISIBILITY = "updateVisibility";
const EVENT_DESTINATION_REACHED = "destinationReached";
const EVENT_DESTINATION_NEW = "newDestination";
const EVENT_SEEK_TARGET = "seekTarget";
const EVENT_ADD_EVENTS = "addEvents";
const EVENT_READY = "ready";
const EVENT_DISTRIBUTE_REWARD = "distributeReward";
const EVENT_DAMAGE = "damage";
const EVENT_ITEM_USED = "itemUsed";
const EVENT_CANNONS_USED = "cannonsUsed";
const EVENT_HARPOONERS_USED = "harpoonersUsed";
const EVENT_QUEST_DONE = "questDone";
const EVENT_EFFECT_ADD = "effectAdd";
const EVENT_DESINATION_NEW = "newDestination";
const EVENT_ATTACK_BAR = "attackBar";
const EVENT_PATH_READY = "scheduledPathReady"
const EVENT_SPAWN_ADMIRAL = "admiralSpawnNotification";
const EVENT_HEALTH_CHANGED = "healthChanged";
const EVENT_HEALTH_CHANGED_AUTHORLESS = "healthChangedAuthorless";
const EVENT_GUILD_ISLAND_CAPTURE = "guildIslandCapture";
const EVENT_GUILD_ISLAND_TOWER_BUILT = "guildDataIslandTowerBuilt";
const EVENT_GUILD_ISLAND_TOWER_HEALTH_CHANGED = "guildDataIslandTowerHealthChanged";
/*
BEHAVIOUR
*/
const BEHAVIOUR_AGGRESSIVE = "aggressive";
const BEHAVIOUR_STOP_IF_ATTACKED = "stopIfAttacked";
/*
MODE OF MAPS
*/
const MAP_MODE_PVE = "PvE";
const MAP_MODE_PVP = "PvP";
/*
LOGOUT TIMEOUTS
*/
const LOGOUT_TIME_BASIC = 30 * 1000;
const LOGOUT_TIME_PREMIUM = 10 * 1000;
const LOGOUT_TIME_IDLE = 10 * 60 * 10000;
/*
BASE VALUES FOR CALCULATIONS
*/
const BASE_REPAIR_AMOUNT_MINIMUM = 1000;
const BASE_REPAIR_AMOUNT_MAXIMUM = 2000;
const BASE_SPEED_PLAYER_REPAIR = 1500;
const BASE_SPEED_PLAYER = 100;
/*
TASKS
*/
const TASK_TYPE_DEAL_DAMAGE = 1;
const TASK_TYPE_DESTROY = 2;
/*
TIMING EVENTS
*/
const TIMING_EVENT_CANNON_ATTACK = 1;
const TIMING_EVENT_HARPOONER_ATTACK = 2;
const TIMING_EVENT_REPAIR = 3;
const TIMING_EVENT_DISCONNECT = 4;
const TIMING_EVENT_DISCONNECT_IDLE = 5;
const TIMING_EVENT_MAP_CHANGE = 6;
const TIMING_EVENT_MOVE = 7;
const TIMING_EVENT_RESPAWN = 8;
const TIMING_EVENT_OVERTIME_DECREASE_HITPOINT = 9;
const TIMING_EVENT_OVERTIME_INCREASE_HITPOINT = 10;
const TIMING_EVENT_DIE = 11;
const TIMING_EVENT_OVERTIME_DECREASE_HITPOINT_2 = 12;
/*
ACQUIRABLE ITEM CATEGORIES
*/
const CATEGORY_ECONOMY = 1;
const CATEGORY_ITEM = 2;
const CATEGORY_AMMUNITION = 3;
const CATEGORY_HARPOON = 4;
const CATEGORY_DESIGN = 5;
const CATEGORY_STATUS = 6;
const CATEGORY_CANNON = 7;
const CATEGORY_HARPOONER = 8;
const CATEGORY_SAIL = 9;
const CATEGORY_PIRATE = 10;
const CATEGORY_RANKING_POINTS = 11;
/*
QUEST STATUS
*/
const QUEST_STATE_ACTIVE = 0;
const QUEST_STATE_COMPLETED = 0;
/*
PROPERTY ALTERING CATEGORIES
*/
const AFFECT_CANNON_DAMAGE = 1;
const AFFECT_HARPOONER_DAMAGE = 2;
const AFFECT_PROTECTION = 3;
const AFFECT_SAILING_SPEED = 4;
const AFFECT_HITPOINT = 5;
/*
GUILD PERMISSION BITS
*/
const GUILD_PERMISSION_KICK = 1;
const GUILD_PERMISSION_ACCEPT_JOIN_REQUEST = 2;
const GUILD_PERMISSION_EDIT_IDENTITY = 4;
const GUILD_PERMISSION_EDIT_TAX = 8;
const GUILD_PERMISSION_EDIT_ISLAND = 16;
/*
LEADERBOARD TYPES
*/
const LEADERBOARD_EXPERIENCE_POINTS = 1;
const LEADERBOARD_NPC_SINK = 2;
const LEADERBOARD_MONSTER_SINK = 3;
const LEADERBOARD_PLAYER_SINK = 4;
const LEADERBOARD_ELITE_POINTS = 5;
const LEADERBOARD_FISHING_POINTS = 6;
/*
GUILD COOLDOWN TIMES
*/
const GUILD_COOLDOWN_EDIT_TAX_RATES = 60000;
const GUILD_COOLDOWN_EDIT_TAG = 60000;
const GUILD_COOLDOWN_EDIT_NAME = 60000;
const GUILD_COOLDOWN_EDIT_DESCRIPTION = 60000;
const COLLECTIBLE_CURRENCIES_BY_ID = {
1: "gold",
2: "emerald",
3: "draconicScales",
4: "corsaireCoins"
};
const COLLECTIBLE_CURRENCIES_BY_NAME = {
gold: 1,
emerald: 2,
draconicScales: 3,
corsaireCoins: 4
};
const ENTITY_TYPES_BY_ID = {
1: ENTITY_TYPE_PLAYER,
2: ENTITY_TYPE_NPC,
3: ENTITY_TYPE_MONSTER,
4: ENTITY_TYPE_TOWER,
5: ENTITY_TYPE_COLLECTABLE
};
const TIMING_EVENTS_BY_ID = {
1: "attackWithCannon",
2: "attackWithHarpooner",
3: "repair",
4: "disconnect",
5: "disconnect",
6: "isChangeMapAvailable",
7: "move",
8: "respawn",
9: "overtimeDecreaseCurrentHitpoint",
10: "overtimeIncreaseCurrentHitpoint",
11: "die",
12: "overtimeDecreaseCurrentHitpoint",
13: "overtimeDecreaseCurrentHitpoint",
14: "respawn",
15: "changeMap",
16: "abortAttack",
17: "effectRemove",
97: "attackWithCannon",
98: "attackWithCannon",
99: "attackWithCannon",
100: "attackWithCannon",
101: "attackWithCannon",
102: "attackWithCannon",
103: "attackWithCannon",
104: "attackWithCannon",
105: "attackWithCannon",
106: "attackWithCannon",
197: "attackWithHarpooner",
198: "attackWithHarpooner",
199: "attackWithHarpooner",
200: "attackWithHarpooner",
201: "attackWithHarpooner",
202: "attackWithHarpooner",
299: "changeSpeed",
300: "changeSpeed",
999: "refreshVisibilities"
};
const EVENTS_BY_TIMING_EVENTS_ID = {
1: EVENT_ATTACKING,
2: EVENT_ATTACKING
};
const EFFECTS_TERMINATE_MOVEMENT = [1];
module.exports.MAXIMUM = {
MAX_SAFE_INTEGER, MAX_LEVEL_ELITE, MAX_GUILD_TAX_RATE_GOLD, MAX_GUILD_TAX_RATE_EMERALD
};
module.exports.TYPES = {
ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_MONSTER, ENTITY_TYPE_TOWER, ENTITY_TYPE_COLLECTABLE
};
module.exports.UNATTACKABLE_ENTITY_TYPES = UNATTACKABLE_ENTITY_TYPES;
module.exports.EVENTS = {
EVENT_DISCONNECT, EVENT_DISCONNECT_CANCEL, EVENT_DISCONNECT_IDLE, EVENT_RESPAWN, EVENT_MAP_CHANGED, EVENT_PROPERTY_CHANGED, EVENT_ATTACK_STARTED, EVENT_ATTACKING, EVENT_DIE, EVENT_SPEED_UPDATE,
EVENT_ATTACK_ABORTED_BY_USER, EVENT_STOP_MOVEMENT, EVENT_CANCEL_STATE_CHANGE_TIMEOUT,
EVENT_ABLE_TO_JUMP, EVENT_UNABLE_TO_JUMP, EVENT_LEVEL_IS_LOW, EVENT_UPDATE_VISIBILITY, EVENT_DESTINATION_REACHED,
EVENT_DESTINATION_NEW, EVENT_SEEK_TARGET, EVENT_LEVEL_UP, EVENT_ADD_EVENTS, EVENT_READY, EVENT_DISTRIBUTE_REWARD,
EVENT_DAMAGE, EVENT_ITEM_USED, EVENT_CANNONS_USED, EVENT_HARPOONERS_USED, EVENT_QUEST_DONE, EVENT_EFFECT_ADD, EVENT_DESINATION_NEW, EVENT_ATTACK_BAR, EVENT_PATH_READY, EVENT_SPAWN_ADMIRAL,
EVENT_HEALTH_CHANGED, EVENT_HEALTH_CHANGED_AUTHORLESS, EVENT_GUILD_ISLAND_CAPTURE, EVENT_GUILD_ISLAND_TOWER_BUILT, EVENT_GUILD_ISLAND_TOWER_HEALTH_CHANGED
};
module.exports.BEHAVIOURS = {
BEHAVIOUR_AGGRESSIVE, BEHAVIOUR_STOP_IF_ATTACKED
};
module.exports.MAP_MODES = {
MAP_MODE_PVE, MAP_MODE_PVP
};
module.exports.LOGOUT_TIMES = {
LOGOUT_TIME_BASIC, LOGOUT_TIME_PREMIUM, LOGOUT_TIME_IDLE
};
module.exports.BASE = {
BASE_REPAIR_AMOUNT_MINIMUM, BASE_REPAIR_AMOUNT_MAXIMUM, BASE_SPEED_PLAYER_REPAIR, BASE_SPEED_PLAYER
};
module.exports.TASK_TYPES = {
TASK_TYPE_DEAL_DAMAGE, TASK_TYPE_DESTROY
};
module.exports.TIMING_EVENTS = {
TIMING_EVENT_CANNON_ATTACK, TIMING_EVENT_HARPOONER_ATTACK, TIMING_EVENT_REPAIR, TIMING_EVENT_DISCONNECT, TIMING_EVENT_DISCONNECT_IDLE, TIMING_EVENT_MAP_CHANGE, TIMING_EVENT_MOVE, TIMING_EVENT_RESPAWN,
TIMING_EVENT_OVERTIME_DECREASE_HITPOINT, TIMING_EVENT_OVERTIME_INCREASE_HITPOINT, TIMING_EVENT_OVERTIME_DECREASE_HITPOINT_2
};
module.exports.CATEGORIES = {
CATEGORY_ECONOMY, CATEGORY_ITEM, CATEGORY_AMMUNITION, CATEGORY_HARPOON, CATEGORY_DESIGN, CATEGORY_STATUS, CATEGORY_CANNON, CATEGORY_HARPOONER, CATEGORY_SAIL, CATEGORY_PIRATE, CATEGORY_RANKING_POINTS
};
module.exports.AFFECTS = {
AFFECT_CANNON_DAMAGE, AFFECT_HARPOONER_DAMAGE, AFFECT_PROTECTION, AFFECT_SAILING_SPEED, AFFECT_HITPOINT
};
module.exports.GUILD_PERMISSIONS = {
GUILD_PERMISSION_KICK, GUILD_PERMISSION_ACCEPT_JOIN_REQUEST, GUILD_PERMISSION_EDIT_IDENTITY, GUILD_PERMISSION_EDIT_TAX, GUILD_PERMISSION_EDIT_ISLAND
};
module.exports.GUILD_COOLDOWNS = {
GUILD_COOLDOWN_EDIT_TAX_RATES, GUILD_COOLDOWN_EDIT_TAG, GUILD_COOLDOWN_EDIT_NAME, GUILD_COOLDOWN_EDIT_DESCRIPTION
};
module.exports.COLLECTIBLE_CURRENCIES_BY_ID = COLLECTIBLE_CURRENCIES_BY_ID;
module.exports.COLLECTIBLE_CURRENCIES_BY_NAME = COLLECTIBLE_CURRENCIES_BY_NAME;
module.exports.ENTITY_TYPES_BY_ID = ENTITY_TYPES_BY_ID;
module.exports.TIMING_EVENTS_BY_ID = TIMING_EVENTS_BY_ID;
module.exports.EVENTS_BY_TIMING_EVENTS_ID = EVENTS_BY_TIMING_EVENTS_ID;
module.exports.EFFECTS_TERMINATE_MOVEMENT = EFFECTS_TERMINATE_MOVEMENT;
+62
View File
@@ -0,0 +1,62 @@
const DESIGNS = {
1: true,
2: true,
3: true,
4: true,
5: true,
6: true,
7: true,
8: true,
9: true,
10: true,
11: true,
12: true,
13: true,
14: true,
15: true,
16: true,
17: true,
18: true,
19: true,
20: true,
21: true,
22: true,
23: true,
24: true,
25: true,
26: true,
27: true,
28: true,
29: true,
30: true,
31: true,
32: true,
33: true,
34: true,
35: true,
36: true,
37: true,
38: true,
39: true,
40: true,
41: true,
42: true,
43: true,
44: true,
45: true,
46: true,
47: true,
48: true,
49: true,
50: true,
51: true,
52: true,
53: true,
54: true,
55: true,
56: true,
57: true,
58: true
};
module.exports = DESIGNS;
+18
View File
@@ -0,0 +1,18 @@
const effects = {
1: [1],
2: [2],
3: [3],
4: [4],
5: [5],
6: [6],
7: [7],
8: [8]
};
/*
Keys are the effectID's. Arrays bounded to them contain itemID's.
This is used to activate the items bounded to the effects.
It looks redundant: arrays are used so multiple items can be bounded to one effect.
*/
module.exports = effects;
+151
View File
@@ -0,0 +1,151 @@
const Reward = require("../Game/Reward");
const eliteRewards = [
0,
new Reward({
distributionType: 2,
baseReward: {
5: {
6: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
7: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
8: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
9: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
10: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
11: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
12: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
13: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
14: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
15: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
16: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
17: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
18: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
19: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
20: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
21: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
22: { amountMinimum: 1, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
5: {
23: { amountMinimum: 1, chance: 100 }
}
}
})
];
module.exports = eliteRewards;
+77
View File
@@ -0,0 +1,77 @@
const entityGroup = {
id: 1,
mapIDs: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
entitySettings: {
1: {
speed: 600,
respawnTime: 0,
size: 128
},
2: {
speed: 400,
respawnTime: 60,
size: 96
},
3: {
speed: 120,
respawnTime: 30,
size: 64
}
},
structure: [
{
typeID: 2,
entityTypeID: 113,
groupMemberTypeID: 1
},
{
typeID: 2,
entityTypeID: 113,
groupMemberTypeID: 1
},
{
typeID: 2,
entityTypeID: 114,
groupMemberTypeID: 2
},
{
typeID: 2,
entityTypeID: 114,
groupMemberTypeID: 2
},
{
typeID: 2,
entityTypeID: 114,
groupMemberTypeID: 2
},
{
typeID: 2,
entityTypeID: 115,
groupMemberTypeID: 3
},
{
typeID: 2,
entityTypeID: 115,
groupMemberTypeID: 3
},
{
typeID: 2,
entityTypeID: 115,
groupMemberTypeID: 3
},
{
typeID: 2,
entityTypeID: 115,
groupMemberTypeID: 3
},
{
typeID: 2,
entityTypeID: 115,
groupMemberTypeID: 3
}
]
};
const entityGroups = [entityGroup];
module.exports = entityGroups;
+22
View File
@@ -0,0 +1,22 @@
const harpooners = {
1: {
range: 150 / 4, damage: 1 / 4, reload: 6500 / 4
},
2: {
range: 200 / 4, damage: 3 / 4, reload: 5750 / 4
},
3: {
range: 250 / 4, damage: 5 / 4, reload: 5000 / 4
},
4: {
range: 300 / 4, damage: 8 / 4, reload: 4500 / 4
},
5: {
range: 350 / 4, damage: 11 / 4, reload: 4000 / 4
},
6: {
range: 400 / 4, damage: 10 / 4, reload: 4000 / 4
}
};
module.exports = harpooners;
+12
View File
@@ -0,0 +1,12 @@
const harpoons = {
1: { damage: 50 },
2: { damage: 250 },
3: { damage: 500 },
4: { damage: 750 },
5: { damage: 1250 },
6: { damage: 1500 },
7: { damage: 2000 },
8: { damage: 2500 }
};
module.exports = harpoons;
+33
View File
@@ -0,0 +1,33 @@
const constants = require("../Data/constants");
const { AFFECTS, MAP_MODES, TYPES } = constants;
const { AFFECT_CANNON_DAMAGE, AFFECT_HARPOONER_DAMAGE, AFFECT_PROTECTION, AFFECT_SAILING_SPEED, AFFECT_HITPOINT } = AFFECTS;
const { MAP_MODE_PVE, MAP_MODE_PVP } = MAP_MODES;
const { ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_TOWER } = TYPES;
const itemTable = {
1: { scale: 1.2, isSelf: true, isDurable: true, affects: [2], range: 0, cooldown: 0, statusID: 1, useOnImpact: false },
2: { scale: 1.2, isSelf: true, isDurable: true, affects: [2], range: 0, cooldown: 0, statusID: 2, useOnImpact: false },
3: { scale: 1.2, isSelf: true, isDurable: true, affects: [2], range: 0, cooldown: 0, statusID: 3, useOnImpact: false },
4: { scale: 1.2, isSelf: true, isDurable: true, affects: [2], range: 0, cooldown: 0, statusID: 4, useOnImpact: false },
5: { scale: 0.2, isSelf: true, isDurable: true, affects: [AFFECT_PROTECTION], range: 0, cooldown: 0, statusID: 5, useOnImpact: false },
6: { scale: 0.2, isSelf: true, isDurable: true, affects: [AFFECT_PROTECTION], range: 0, cooldown: 0, statusID: 6, useOnImpact: false },
7: { scale: 0.2, isSelf: true, isDurable: true, affects: [AFFECT_PROTECTION], range: 0, cooldown: 0, statusID: 7, useOnImpact: false },
8: { scale: 0.2, isSelf: true, isDurable: true, affects: [AFFECT_PROTECTION], range: 0, cooldown: 0, statusID: 8, useOnImpact: false },
9: { scale: -1000, isSelf: false, isDurable: false, affects: [AFFECT_HITPOINT], range: 700, cooldown: 2000, statusID: 0, useOnImpact: true, allowedTargetTypes: {
"PvE": [ENTITY_TYPE_NPC, ENTITY_TYPE_TOWER],
"PvP": [ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_TOWER]
}},
10: { scale: -25000, isSelf: false, isDurable: false, affects: [AFFECT_HITPOINT], range: 700, cooldown: 2000, statusID: 0, useOnImpact: true, allowedTargetTypes: {
"PvE": [ENTITY_TYPE_NPC, ENTITY_TYPE_TOWER],
"PvP": [ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_TOWER]
}},
11: { scale: 10000, isSelf: true, isDurable: false, affects: [AFFECT_HITPOINT], range: 0, cooldown: 10000, statusID: 0, useOnImpact: true },
12: { scale: 15000, isSelf: true, isDurable: false, affects: [AFFECT_HITPOINT], range: 0, cooldown: 10000, statusID: 0, useOnImpact: true },
13: { scale: 16000, isSelf: true, isDurable: false, affects: [AFFECT_HITPOINT], range: 0, cooldown: 10000, statusID: 0, useOnImpact: true },
14: { scale: 17000, isSelf: true, isDurable: false, affects: [AFFECT_HITPOINT], range: 0, cooldown: 10000, statusID: 0, useOnImpact: true },
15: { scale: 35000, isSelf: true, isDurable: false, affects: [AFFECT_HITPOINT], range: 0, cooldown: 10000, statusID: 0, useOnImpact: true },
16: { scale: -12, isSelf: true, isDurable: false, affects: [AFFECT_SAILING_SPEED], range: 0, cooldown: 15000, time: 5000, statusID: 16, iid: 299, useOnImpact: true },
17: { scale: -25, isSelf: true, isDurable: false, affects: [AFFECT_SAILING_SPEED], range: 0, cooldown: 30000, time: 10000, statusID: 17, iid: 300, useOnImpact: true }
};
module.exports = itemTable;
+20
View File
@@ -0,0 +1,20 @@
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
const leaderboards = [];
for (let i = 0; i < 7; i++) {
const leaderboard = {
cache: {},
list: new DoubleLinkedList(),
size: 0,
entriesNEW: {},
updatePosition(id, amount, isAbsolute) {
if (isAbsolute) this.list.addItem(id, amount);
else this.list.accumulateItem(id, amount);
}
};
leaderboards.push(leaderboard);
};
module.exports = leaderboards;
+162
View File
@@ -0,0 +1,162 @@
const Reward = require("../Game/Reward");
const levelRewards = [
0,
0,
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
3: { amountMinimum: 42, chance: 100 }
},
8: {
3: { amountMinimum: 42, chance: 100 }
}
}
}),
new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 42, chance: 100 }
},
2: {
5: { amountMinimum: 42, chance: 100 }
},
7: {
1: { amountMinimum: 42, chance: 100 }
},
8: {
1: { amountMinimum: 42, chance: 100 }
}
}
})
];
module.exports = levelRewards;
+45
View File
@@ -0,0 +1,45 @@
const { MAXIMUM } = require("./constants");
const { MAX_LEVEL_ELITE } = MAXIMUM;
const a = 1;
const b = -1;
const levels = {
pirateLevel(points) {
const c = points * 2 / 250 * -1;
const discriminant = Math.pow(b, 2) - 4 * a * c;
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
const currentLevel = Math.floor(x1);
return currentLevel;
},
eliteLevel(points) {
const c = points * 2 / 750 * -1;
const discriminant = Math.pow(b, 2) - 4 * a * c;
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
const currentLevel = Math.floor(x1);
return Math.min(currentLevel, MAX_LEVEL_ELITE);
},
fishingLevel(points) {
const c = points * 2 / 525 * -1;
const discriminant = Math.pow(b, 2) - 4 * a * c;
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
const currentLevel = Math.floor(x1);
return currentLevel;
},
guildLevel(points) {
const c = points * 2 / 20 * -1;
const discriminant = Math.pow(b, 2) - 4 * a * c;
const x1 = (-b + Math.sqrt(discriminant)) / (2 * a);
const currentLevel = Math.floor(x1);
return currentLevel;
}
};
module.exports = levels;
+31
View File
@@ -0,0 +1,31 @@
const loginRewards = {
7: {
1: {
3: {
5: { amountMinimum: 1500, chance: 100 }
},
7: {
3: { amountMinimum: 2, chance: 100 }
},
8: {
3: { amountMinimum: 2, chance: 100 }
}
},
2: {
3: {
5: { amountMinimum: 1500, chance: 100 }
},
7: {
3: { amountMinimum: 2, chance: 100 }
},
8: {
3: { amountMinimum: 2, chance: 100 }
}
},
3: {
}
}
};
module.exports = loginRewards;
+9
View File
@@ -0,0 +1,9 @@
const maps = [
[1, 6, 11, 16],
[2, 7, 12, 17],
[3, 8, 13, 18],
[4, 9, 14, 19],
[5, 10, 15, 20]
];
module.exports = maps;
+436
View File
@@ -0,0 +1,436 @@
const constans = require("./constants");
const entityGroups = require("./entityGroups");
const { MAP_MODES } = constans;
const { MAP_MODE_PVE, MAP_MODE_PVP } = MAP_MODES;
const MAP_NEIGHBOUR_DIRECTION_LEFT = 1;
const MAP_NEIGHBOUR_DIRECTION_RIGHT = 2;
const MAP_NEIGHBOUR_DIRECTION_TOP = 3;
const MAP_NEIGHBOUR_DIRECTION_BOTTOM = 4;
const map_1 = {
name: "1/1",
level: 1,
id: 1,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(1)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 5, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 2, align: "y", threshold: 96 }
],
collision: {}
};
const map_2 = {
name: "1/2",
level: 1,
id: 2,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(2)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 1, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 6, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 3, align: "y", threshold: 96 }
]
}
const map_3 = {
name: "2/1",
level: 10,
id: 3,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(3)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 7, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 2, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 4, align: "y", threshold: 96 }
]
}
const map_4 = {
name: "2/2",
level: 10,
id: 4,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(4)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 8, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 3, align: "y", threshold: 96 }
]
}
const map_5 = {
name: "3/1",
level: 25,
id: 5,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(5)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 1, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 9, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 6, align: "y", threshold: 96 }
]
}
const map_6 = {
name: "3/2",
level: 25,
id: 6,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(6)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 2, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 10, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 5, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 7, align: "y", threshold: 96 }
]
}
const map_7 = {
name: "4/1",
level: 50,
id: 7,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(7)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 3, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 11, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 6, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 8, align: "y", threshold: 96 }
]
}
const map_8 = {
name: "4/2",
level: 50,
id: 8,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(8)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 4, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 12, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 7, align: "y", threshold: 96 },
]
}
const map_9 = {
name: "5/1",
level: 75,
id: 9,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(9)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 5, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 13, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 10, align: "y", threshold: 96 }
]
}
const map_10 = {
name: "5/2",
level: 75,
id: 10,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(10)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 6, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 14, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 9, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 11, align: "y", threshold: 96 }
]
}
const map_11 = {
name: "6/1",
level: 100,
id: 11,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(11)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 7, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 15, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 10, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 12, align: "y", threshold: 96 }
]
}
const map_12 = {
name: "6/2",
level: 1,
id: 12,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(12)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 8, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 16, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 11, align: "y", threshold: 96 }
]
}
const map_13 = {
name: "7/1",
level: 1,
id: 13,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(13)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 9, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 17, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 14, align: "y", threshold: 96 }
]
}
const map_14 = {
name: "7/2",
level: 1,
id: 14,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(14)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 10, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 18, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 13, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 15, align: "y", threshold: 96 }
]
}
const map_15 = {
name: "8/1",
level: 1,
id: 15,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(15)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 11, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 19, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 14, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 16, align: "y", threshold: 96 }
]
}
const map_16 = {
name: "8/2",
level: 1,
id: 16,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(16)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 12, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_RIGHT, id: 20, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 15, align: "y", threshold: 96 }
]
}
const map_17 = {
name: "9/1",
level: 1,
id: 17,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(17)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 13, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 18, align: "y", threshold: 96 }
]
}
const map_18 = {
name: "9/2",
level: 1,
id: 18,
type: MAP_MODE_PVE,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(18)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 14, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 17, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 19, align: "y", threshold: 96 }
]
}
const map_19 = {
name: "10/1",
level: 1,
id: 19,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(19)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 15, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 18, align: "y", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_BOTTOM, id: 20, align: "y", threshold: 96 }
]
}
const map_20 = {
name: "10/2",
level: 1,
id: 20,
type: MAP_MODE_PVP,
size: {
width: 6000,
height: 4500,
chunk: {
width: 1200,
height: 900
}
},
entityGroups: entityGroups.filter(e => e.mapIDs.includes(20)),
neighbours: [
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_LEFT, id: 16, align: "x", threshold: 96 },
{ directionIndex: MAP_NEIGHBOUR_DIRECTION_TOP, id: 19, align: "y", threshold: 96 }
]
}
module.exports = [map_1, map_2, map_3, map_4, map_5, map_6, map_7, map_8, map_9, map_10, map_11, map_12, map_13, map_14, map_15, map_16, map_17, map_18, map_19, map_20];
+7
View File
@@ -0,0 +1,7 @@
const OVERTIME_HEALTH_EFFECTS = {
1: { amount: 0.15, ticks: 10, time: 1000 },
2: { amount: 0.15, ticks: 5, time: 500 },
3: { amount: 0.15, ticks: 10, time: 100 }
};
module.exports = OVERTIME_HEALTH_EFFECTS;
+18
View File
@@ -0,0 +1,18 @@
const Utility = require("../Game/Utility");
const packetRelevancies = {
0: function (player, packet) {
return player.inDistance[packet.authorID] || player.id === packet.authorID;
},
1: function (player, packet) {
return Utility.IsInDistance(packet.position, player.position, player.sight + 60);
},
19: function (player) {
return true;
},
59: function (player, packet) {
return player.isAlive && (player.inDistance[packet.authorID] || player.id === packet.authorID);
}
};
module.exports = packetRelevancies;
+71
View File
@@ -0,0 +1,71 @@
const ReadyMessage = require("../Messages/ReadyMessage");
const QuestMessage = require("../Messages/QuestMessage");
const SpyMessage = require("../Messages/SpyMessage");
const CauldronMessage = require("../Messages/CauldronMessage");
const MovementMessage = require("../Messages/MovementMessage");
const CloseMessage = require("../Messages/CloseMessage");
const RepairMessage = require("../Messages/RepairMessage");
const AttackStartMessage = require("../Messages/AttackStartMessage");
const AttackAbortMessage = require("../Messages/AttackAbortMessage");
const RespawnMessage = require("../Messages/RespawnMessage");
const EquipmentChangeMessage = require("../Messages/EquipmentChangeMessage");
const MapChangeMessage = require("../Messages/MapChangeMessage");
const AmmunitionChangeMessage = require("../Messages/AmmunitionChangeMessage");
const DesignChangeMessage = require("../Messages/DesignChangeMessage");
const ItemUseMessage = require("../Messages/ItemUseMessage");
const ItemBuyMessage = require("../Messages/ItemBuyMessage");
const LeaderboardPageMessage = require("../Messages/LeaderboardPageMessage");
const GuildMemberLeaveMessage = require("../Messages/GuildMemberLeaveMessage");
const GuildMemberJoinMessage = require("../Messages/GuildMemberJoinMessage");
const GuildJoinRequestSubmitMessage = require("../Messages/GuildJoinRequestSubmitMessage");
const GuildDataMessage = require("../Messages/GuildDataMessage");
const GuildDonateMessage = require("../Messages/GuildDonateMessage");
const GuildDetailsTaxEditMessage = require("../Messages/GuildDetailsTaxEditMessage");
const GuildDetailsIdentityEditMessage = require("../Messages/GuildDetailsIdentityEditMessage");
const GuildIslandTowerRepairMessage = require("../Messages/GuildIslandTowerRepairMessage");
const GuildIslandTowerBuildMessage = require("../Messages/GuildIslandTowerBuildMessage");
const LeaderboardDataMessage = require("../Messages/LeaderboardDataMessage");
const GuildSearchMessage = require("../Messages/GuildSearchMessage");
const GuildJoinRequestRevokeMessage = require("../Messages/GuildJoinRequestRevokeMessage");
const GuildCreateMessage = require("../Messages/GuildCreateMessage");
const packetTable = {
1: { handler: ReadyMessage, expectedSize: 1, isAvailable: true },
2: { handler: QuestMessage, expectedSize: 3, isAvailable: true },
3: { handler: SpyMessage, expectedSize: 5, isAvailable: true },
4: { handler: CauldronMessage, expectedSize: 3, isAvailable: true },
5: { handler: MovementMessage, expectedSize: 5, isAvailable: true },
6: { handler: CloseMessage, expectedSize: 1, isAvailable: true },
7: { handler: RepairMessage, expectedSize: 1, isAvailable: true },
8: { handler: AttackStartMessage, expectedSize: 6, isAvailable: true },
9: { handler: AttackAbortMessage, expectedSize: 6, isAvailable: true },
10: { handler: RespawnMessage, expectedSize: 1, isAvailable: true },
11: { handler: EquipmentChangeMessage, expectedSize: 5, isAvailable: true },
12: { handler: MapChangeMessage, expectedSize: 1, isAvailable: true },
13: { handler: AmmunitionChangeMessage, expectedSize: 3, isAvailable: true },
14: { handler: DesignChangeMessage, expectedSize: 3, isAvailable: true },
15: { handler: ItemUseMessage, expectedSize: 7, isAvailable: true },
16: { handler: ItemBuyMessage, expectedSize: 7, isAvailable: true },
17: { handler: LeaderboardPageMessage, expectedSize: 4, isAvailable: true },
18: { handler: GuildMemberLeaveMessage, expectedSize: 5, isAvailable: true },
19: { handler: GuildMemberJoinMessage, expectedSize: 5, isAvailable: true },
20: { handler: GuildJoinRequestSubmitMessage, expectedSize: 0, isAvailable: true },
21: { handler: GuildDataMessage, expectedSize: 1, isAvailable: true },
22: { handler: GuildDonateMessage, expectedSize: 6, isAvailable: true },
23: { handler: GuildDetailsTaxEditMessage, expectedSize: 3, isAvailable: true },
24: { handler: GuildDetailsIdentityEditMessage, expectedSize: 0, isAvailable: true },
25: { handler: GuildIslandTowerRepairMessage, expectedSize: 6, isAvailable: true },
26: { handler: GuildIslandTowerBuildMessage, expectedSize: 7, isAvailable: true },
27: { handler: LeaderboardDataMessage, expectedSize: 1, isAvailable: true },
28: { handler: GuildSearchMessage, expectedSize: 0, isAvailable: true },
29: { handler: GuildJoinRequestRevokeMessage, expectedSize: 7, isAvailable: true },
30: { handler: GuildCreateMessage, expectedSize: 0, isAvailable: true }
};
module.exports = packetTable;
/*
handler: the function that runs when packet is received
expectedSize: size of the packet
isAvailable: whether the server accepts this packet
*/
+8
View File
@@ -0,0 +1,8 @@
const entries = {
1: { name: "Corsaire Coins (200)", unit_amount: 1000, amount: 200 },
2: { name: "Corsaire Coins (450)", unit_amount: 2000, amount: 450 },
3: { name: "Corsaire Coins (1000)", unit_amount: 3000, amount: 1000 },
4: { name: "Corsaire Coins (2000)", unit_amount: 4000, amount: 2000 }
};
module.exports = entries;
+30
View File
@@ -0,0 +1,30 @@
module.exports.QUERY_FETCH_RESOURCES = "SELECT categoryID, itemID AS id, amount FROM resources WHERE playerID = ?";
module.exports.QUERY_FETCH_PLAYER_DATA = "SELECT * FROM playerdata WHERE playerID = ?";
module.exports.QUERY_FETCH_PLAYER_INFORMATION = "SELECT extensions.cannonSlots FROM extensions WHERE playerID = ?";
module.exports.QUERY_FETCH_GUILD_INFORMATION = "SELECT guilds.id, guilds.tag FROM guilds JOIN guildmembers ON guilds.id = guildmembers.guildID WHERE guildmembers.playerID = ?";
module.exports.QUERY_INSERT_DEFAULT_PLAYER = "INSERT INTO playerdata (playerID, x, y, mapID) VALUES (?, ?, ?, ?)";
module.exports.QUERY_FETCH_EQUIPMENTS = "SELECT itemID AS id, categoryID, amountEquipped FROM equipments WHERE playerID = ?";
module.exports.QUERY_FETCH_NPC_SPAWN = "SELECT npc.id, npc.parentEntityTypeId, npc.name, npc.speed, npc.minHealth, npc.maxHealth, spawn.amount, spawn.respawnTime, reward.categoryId, reward.itemId, reward.min, reward.max, reward.chance, reward.isLastShot FROM spawn INNER JOIN npc ON spawn.entityTypeId = npc.id LEFT JOIN reward ON spawn.id = reward.rewardId WHERE mapId IN (?, 0) AND typeId = ?";
module.exports.QUERY_FETCH_MONSTER_SPAWN = "SELECT monster.id, monster.parentEntityTypeId, monster.name, monster.minHealth, monster.maxHealth, spawn.amount, spawn.respawnTime, reward.categoryId, reward.itemId, reward.min, reward.max, reward.chance, reward.isLastShot FROM spawn INNER JOIN monster ON spawn.entityTypeId = monster.id LEFT JOIN reward ON spawn.id = reward.rewardId WHERE mapId IN (?, 0) AND typeId = ?";
module.exports.QUERY_FETCH_COLLECTABLE_SPAWN = "SELECT collectable.id, collectable.name, spawn.amount, spawn.respawnTime, reward.categoryId, reward.itemId, reward.min, reward.max, reward.chance FROM spawn INNER JOIN collectable ON spawn.entityTypeId = collectable.id LEFT JOIN reward ON spawn.id = reward.rewardId WHERE mapId = ? AND typeId = ?";
module.exports.QUERY_FETCH_ACTIVE_QUESTS = "SELECT quests.questID, completedAmount, CAST(state AS UNSIGNED) AS state, questprogression.amount, questprogression.taskIndex, questprogression.taskType FROM quests LEFT JOIN questprogression ON quests.questID = questprogression.questID WHERE quests.playerID = ?";
module.exports.QUERY_FETCH_STATUS = "SELECT statusID FROM status WHERE playerID = ? AND isActive = ?";
module.exports.QUERY_FETCH_GUILD_REQUESTS = "SELECT guilds.id, guildrequests.playerID, guildrequests.message FROM guildrequests INNER JOIN guilds ON guildrequests.guildID = guilds.id";
module.exports.QUERY_FETCH_GUILD_MEMBERS = "SELECT guilds.id, BINARY(guilds.name) AS name, BINARY(guilds.tag) AS tag, BINARY(guilds.description) AS description, guilds.createdBy, guilds.taxRateGold / 100 AS taxRateGold, guilds.taxRateEmerald / 100 AS taxRateEmerald, guilds.taxRatesEditedAt, guilds.tagEditedAt, guilds.nameEditedAt, guilds.descriptionEditedAt, guilds.descriptionEditedAt, guildmembers.playerID, guildmembers.permission FROM guildmembers INNER JOIN guilds ON guildmembers.guildID = guilds.id";
module.exports.QUERY_FETCH_GUILD_ISLANDS = "SELECT * FROM guildislands";
module.exports.QUERY_FETCH_GUILD_ISLAND_DAMAGES = "SELECT * FROM guildislanddamages";
module.exports.QUERY_FETCH_GUILD_DIPLOMACIES = "SELECT guildID, targetGuildID, CONV(HEX(color), 16, 10) + 0 AS color FROM guilddiplomacy JOIN guilds ON guilddiplomacy.guildID = guilds.id";
module.exports.QUERY_FETCH_GUILD_RESOURCES = "SELECT guildID, categoryID, itemID AS id, amount FROM guildresources";
module.exports.QUERY_FETCH_GUILD_TOWERS = "SELECT gi.mapID, gt.id, gt.guildIslandID, gt.entityTypeID, gt.currentHitpoints, gt.positionX, gt.positionY FROM guildtowers gt INNER JOIN guildislands gi ON gt.guildIslandID = gi.id WHERE gi.mapID = ?";
module.exports.QUERY_UPDATE_GUILD_ISLAND_TOWER_DEAD = "UPDATE guildtowers SET currentHitpoints = ? WHERE id = ?";
module.exports.QUERY_UPDATE_GUILD_ISLAND_TOWER_UPGRADE = "UPDATE guildtowers SET entityTypeID = ?, currentHitpoints = ? WHERE id = ?";
module.exports.QUERY_UPDATE_GUILD_ISLAND_TOWER_DEFAULT = "UPDATE guildtowers SET currentHitpoints = ? WHERE guildIslandID = ?";
module.exports.QUERY_DELETE_GUILD_ISLAND_DAMAGES = "DELETE FROM guildislanddamages WHERE guildIslandID = ?";
module.exports.QUERY_UPDATE_GUILD_ISLAND_AUTHOR = "UPDATE guildislands SET guildID = ? WHERE id = ?";
module.exports.QUERY_UPDATE_GUILD_ISLAND_DAMAGES = "INSERT INTO guildislanddamages (damage, guildIslandID, guildID) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE damage = damage + ?";
module.exports.QUERY_UPDATE_GUILD_RESOURCE_SINGLE = "INSERT INTO guildresources (guildID, categoryID, itemID, amount) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?";
module.exports.QUERY_UPDATE_PLAYER_RESOURCE_SINGLE = "INSERT INTO resources (playerID, categoryID, itemID, amount) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?";
module.exports.QUERY_UPDATE_GUILD_TAX_RATES = "UPDATE guilds SET taxRateGold = ?, taxRateEmerald = ? WHERE id = ?";
module.exports.QUERY_DELETE_GUILD_MEMBER = "DELETE FROM guildmembers WHERE playerID = ? AND isLeader = ?";
module.exports.QUERY_FETCH_GUILD_OFFLINE_MEMBERS = "SELECT accounts.id, username, itemID, amount FROM resources INNER JOIN accounts on resources.playerID = accounts.id WHERE categoryID = 6 AND itemID = 1 AND playerID IN (?)";
module.exports.QUERY_FETCH_SHOP_ENTRIES = "SELECT * FROM shop";
+97
View File
@@ -0,0 +1,97 @@
const quest = {
id: 1,
maximumComplete: 1,
minimumLevel: 1,
maximumLevel: 99,
task: {
1: [
{ typeID: 2, entityTypeID: 1, requiredAmount: 200, amount: 0 },
{ typeID: 3, entityTypeID: 1, requiredAmount: 5000, amount: 0 },
],
2: [
{ typeID: 5, entityTypeID: 1, requiredAmount: 3, amount: 0 },
{ typeID: 3, entityTypeID: 3, requiredAmount: 3, amount: 0 }
]
},
reward: {
3: {
5: { amountMinimum: 69, chance: 100 }
},
5: {
18: { amountMinimum: 1, chance: 100 }
},
6: {
1: { amountMinimum: 100, chance: 100 }
},
7: {
1: { amountMinimum: 3, chance: 100 }
},
8: {
1: { amountMinimum: 3, chance: 100 }
}
}
};
const quest2 = {
id: 2,
maximumComplete: 3,
minimumLevel: 1,
maximumLevel: 2,
task: {
1: [
{ typeID: 2, entityTypeID: 3, requiredAmount: 2000, amount: 0 },
{ typeID: 2, entityTypeID: 5, requiredAmount: 1500, amount: 0 },
],
2: [
{ typeID: 5, entityTypeID: 1, requiredAmount: 10, amount: 0 },
{ typeID: 3, entityTypeID: 3, requiredAmount: 3, amount: 0 }
]
},
reward: {
5: {
6: { amountMinimum: 1, chance: 100 }
}
}
};
const quest3 = {
id: 3,
maximumComplete: 2,
minimumLevel: 1,
maximumLevel: 99,
task: {
2: [
{ typeID: 5, entityTypeID: 1, requiredAmount: 10, amount: 0 }
]
},
reward: {
3: {
1: { amountMinimum: 1000, chance: 100 },
2: { amountMinimum: 2000, chance: 100 },
5: { amountMinimum: 2700, chance: 100 },
},
5: {
35: { amountMinimum: 1, chance: 100 }
}
}
};
const quest4 = {
id: 4,
maximumComplete: 1,
minimumLevel: 4,
maximumLevel: 4,
task: {
2: [
{ typeID: 5, entityTypeID: 1, requiredAmount: 3, amount: 0 }
]
},
reward: {
5: {
34: { amountMinimum: 1, chance: 100 }
}
}
};
module.exports = [quest, quest2, quest3, quest4];
+49
View File
@@ -0,0 +1,49 @@
const schemeTower = {
1: {
maximumHitpoints: 5000,
},
2: {
maximumHitpoints: 15000,
},
3: {
maximumHitpoints: 25000,
},
4: {
maximumHitpoints: 35000,
},
5: {
maximumHitpoints: 45000,
},
6: {
maximumHitpoints: 55000,
},
7: {
maximumHitpoints: 65000,
},
8: {
maximumHitpoints: 75000,
},
9: {
maximumHitpoints: 85000,
},
10: {
maximumHitpoints: 95000,
},
11: {
maximumHitpoints: 105000,
},
12: {
maximumHitpoints: 115000,
},
13: {
maximumHitpoints: 125000,
},
14: {
maximumHitpoints: 135000,
},
15: {
maximumHitpoints: 145000,
}
};
module.exports = schemeTower;
+123
View File
@@ -0,0 +1,123 @@
const constants = require("./constants");
const { CATEGORIES } = constants;
const { CATEGORY_ITEM, CATEGORY_AMMUNITION, CATEGORY_HARPOON, CATEGORY_DESIGN, CATEGORY_CANNON, CATEGORY_HARPOONER } = CATEGORIES;
const SHOP = [];
const SHOP_ENTRIES_BUY = {
};
module.exports = {
SHOP,
SHOP_ENTRIES_BUY
};
/*
const SHOP = {
[CATEGORY_ITEM]: {
},
[CATEGORY_AMMUNITION]: {
1: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 1500,
maximumGlobalAmount: 2500,
shopCategoryID: 4,
isAvailable: () => true
},
2: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 3000,
maximumGlobalAmount: 1000,
shopCategoryID: 4,
isAvailable: () => true
},
},
[CATEGORY_HARPOON]: {
1: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 2000,
shopCategoryID: 6,
isAvailable: () => true
},
2: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 2000,
shopCategoryID: 6,
isAvailable: () => true
},
3: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 2000,
shopCategoryID: 6,
isAvailable: () => true
},
4: {
currency: 1,
unitPrice: 21,
maximumPlayerAmount: 2000,
shopCategoryID: 6,
isAvailable: () => true
}
},
[CATEGORY_DESIGN]: {
2: {
currency: 1,
unitPrice: 200_000,
maximumPlayerAmount: 1,
shopCategoryID: 2,
isAvailable: (player) => !player.inventory.hasDesign(2)
},
3: {
currency: 1,
unitPrice: 400_000,
maximumPlayerAmount: 1,
shopCategoryID: 2,
isAvailable: (player) => !player.inventory.hasDesign(3)
},
4: {
currency: 1,
unitPrice: 600_000,
maximumPlayerAmount: 1,
shopCategoryID: 2,
isAvailable: (player) => !player.inventory.hasDesign(4)
},
5: {
currency: 2,
unitPrice: 60_000,
maximumPlayerAmount: 1,
shopCategoryID: 2,
isAvailable: (player) => !player.inventory.hasDesign(5)
}
},
[CATEGORY_CANNON]: {
1: {
currency: 1,
unitPrice: 1_000_000,
maximumPlayerAmount: 10,
shopCategoryID: 3,
isAvailable: () => true
},
2: {
currency: 1,
unitPrice: 1_000_000,
maximumPlayerAmount: 10,
shopCategoryID: 3,
isAvailable: () => true
},
},
[CATEGORY_HARPOONER]: {
1: {
currency: 1,
unitPrice: 1_000_000,
maximumPlayerAmount: 10,
shopCategoryID: 5,
isAvailable: () => true
}
}
};*/
+12
View File
@@ -0,0 +1,12 @@
const startRewards = [
{ categoryID: 1, id: 1, amount: 100_000 },
{ categoryID: 1, id: 2, amount: 1_000 },
{ categoryID: 7, id: 1, amount: 20 },
{ categoryID: 8, id: 1, amount: 10 },
{ categoryID: 3, id: 1, amount: 2_000 },
{ categoryID: 4, id: 1, amount: 1_000 },
// { categoryID: 7, id: 10, amount: 50 },
// { categoryID: 3, id: 3, amount: 99999999 },
];
module.exports = startRewards;
+16
View File
@@ -0,0 +1,16 @@
const STRIPE_IP_COLLECTION = {
"3.18.12.63": true,
"3.130.192.231": true,
"13.235.14.237": true,
"13.235.122.149": true,
"18.211.135.69": true,
"35.154.171.200": true,
"52.15.183.38": true,
"54.88.130.119": true,
"54.88.130.237": true,
"54.187.174.169": true,
"54.187.205.235": true,
"54.187.216.72": true
};
module.exports = STRIPE_IP_COLLECTION;
+42
View File
@@ -0,0 +1,42 @@
const Game = require("../Game/Game");
const { players } = Game;
const extractBehaviourFeatures = require("../Utility/extractBehaviourFeatures");
// const NORMAL_CLOSE_CODES = [0, 1, 2];
module.exports = function (ws, code, message) {
if (code === 1337) return;
const player = players[ws.accountID];
if (!player) {
ws.isInterrupted = true;
delete socket.sockets[ws.id];
return;
};
const playerActions = global.actions[player.id];
const features = extractBehaviourFeatures(playerActions);
delete global.actions[player.id];
if (features) {
features.playerID = player.id;
features.timestampStart = new Date(ws.connectedAt);
global.StatisticsWorker.postMessage({
id: 2,
data: features
});
};
player.inDistance = {};
player.isAvailable = false;
player.hasUnexpectedlyLeft = true;
const individualPackets = player.map.networkPacketsInvidiual[player.id];
if (individualPackets) individualPackets.length = 0;
console.info("close event", performance.now());
delete socket.sockets[ws.id];
};
+3
View File
@@ -0,0 +1,3 @@
module.exports = function (ws) {
console.info("Drain?");
};
+94
View File
@@ -0,0 +1,94 @@
const packetTable = require("../Data/packets");
const { players } = require("../Game/Game");
const actions = [];
global.actions = actions;
const { MongoClient } = require("mongodb");
let db = null;
async function connect() {
const client = new MongoClient("mongodb://localhost:27017");
await client.connect({
maxPoolSize: 100,
minPoolSize: 20,
maxConnecting: 20,
waitQueueTimeoutMS: 0,
socketTimeoutMS: 30000,
connectTimeoutMS: 10000,
retryWrites: false,
directConnection: true
});
return client.db('test');
};
(async function() {
db = await connect();
global.mongodb = db;
return;
setInterval(async () => {
const t = [];
for (let i = 0; i < 32; i++) {
t.push({
playerID: 1,
timestamp: new Date(),
opcode: Math.floor(Math.random() * 120)
});
};
const bulkOps = t.map(doc => ({
insertOne: doc
}));
const n = performance.now();
await db.collection("Actions").insertMany(t, {
ordered: false,
writeConcern: { w: 0 },
bypassDocumentValidation: true
});
/* await db.collection('Actions').bulkWrite(bulkOps, {
ordered: false,
writeConcern: { w: 0 },
bypassDocumentValidation: true
});*/
}, 500);
})();
module.exports = async function (ws, message, isBinary) {
const player = players[ws.accountID];
if (!player) return;
const isLimitReached = ws.limiter.update();
if (isLimitReached) return ws.close();
if (player.isInactive) return; // this shouldn't happen, though
const view = new DataView(message);
const packetID = view.getUint8(0);
const packet = packetTable[packetID];
if (packetID !== 1 && !player.isAvailable) return;
if (!packet) return;
if (packet.expectedSize && packet.expectedSize !== view.byteLength) return console.info("expectedSize", view.byteLength, packet.expectedSize);
if (!packet.isAvailable) return;
player.lastInteractionTimestamp = performance.now();
const metadata = packet.handler(ws, view, player) ?? [];
if (packetID > 1) {
const action = {
ts: new Date(),
pid: player.id,
opcode: packetID,
metadata
};
const relevantContext = global.actionContext.getElement(player.id).save(2000);
relevantContext.push(action);
console.info("asd", relevantContext.length)
};
};
+239
View File
@@ -0,0 +1,239 @@
const Limiter = require("../Game/PacketLimiter");
const EquipmentLimiter = require("../Game/EquipmentLimiter");
const Inventory = require("../Game/Inventory");
const Player = require("../Game/Player");
const Game = require("../Game/Game");
const playerHandlers = require("../Handlers/Player");
const EnvironmentSetupPacket = require("../Packets/EnvironmentSetup");
const rewardCallback = require("../Callbacks/reward");
const attackCallback = require("../Callbacks/attack");
const baseStarterRewards = require("../Data/startRewards");
const { maps, players, guilds } = Game;
const queries = require("../Data/queries");
const { QUERY_FETCH_PLAYER_DATA, QUERY_FETCH_PLAYER_INFORMATION, QUERY_FETCH_GUILD_INFORMATION, QUERY_INSERT_DEFAULT_PLAYER, QUERY_FETCH_ACTIVE_QUESTS, QUERY_FETCH_STATUS, QUERY_FETCH_RESOURCES, QUERY_FETCH_EQUIPMENTS } = queries;
const playerEvents = Object.keys(playerHandlers);
const PlayerBehaviour = require("../Game/PlayerBehaviour");
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
global.actionContext = new DoubleLinkedList();
module.exports = async function (ws) {
console.info("Before open for", ws.username, Game.state, ws.isAdmin);
if (Game.state === 0 && !ws.isAdmin) return ws.end(1337);
ws.connectedAt = Date.now();
let abruptDisconnectTest = performance.now();
const ID = ws.accountID;
const sessionID = Date.now().toString(36) + Math.random().toString(36).substring(2) + "!" + ID;
socket.sockets[sessionID] = ws;
ws.id = sessionID;
ws.limiter = new Limiter({
countThreshold: 10,
timeThreshold: 1000
});
const alreadyConnectedPlayer = players[ID];
if (alreadyConnectedPlayer) {
const player = alreadyConnectedPlayer;
const alreadyConnectedSoket = socket.sockets[player.sessionID];
if (alreadyConnectedSoket) alreadyConnectedSoket.end(1);
player.sessionID = sessionID;
ws.subscribe(player.map.id.toString());
player.map.removePlayer(player.id);
player.map.addPlayer(player);
player.isUnderDisconnect = false;
player.cancelDisconnect();
const packet = EnvironmentSetupPacket(player.map.id);
return ws.send(packet, true, true);
};
const playerBehaviour = new PlayerBehaviour({
size: 16,
buffer: global.playerBehaviourContext,
id: ID
});
global.actionContext.addItem(ID, playerBehaviour);
const generalQuery = execute(QUERY_FETCH_PLAYER_DATA, [ID]);
const informationQuery = execute(QUERY_FETCH_PLAYER_INFORMATION, [ID]);
const guildQuery = execute(QUERY_FETCH_GUILD_INFORMATION, [ID]);
const questQuery = execute(QUERY_FETCH_ACTIVE_QUESTS, [ID]);
const statusQuery = execute(QUERY_FETCH_STATUS, [ID, true]);
const resourcesQuery = execute(QUERY_FETCH_RESOURCES, [ID]);
const equipmentsQuery = execute(QUERY_FETCH_EQUIPMENTS, [ID]);
const res = await Promise.all([generalQuery, informationQuery, guildQuery, questQuery, statusQuery, resourcesQuery, equipmentsQuery]);
const hasError = res.some(q => q.error !== undefined);
if (hasError) return;
if (ws.isInterrupted) return;
let isAlive = false;
let map = null;
let hitpoints = {
current: 0,
maximum: 0
};
let position = {};
let attemptToDoubleLog = false;
let maximumCannonRange = 0;
let maximumHarpoonerRange = 0;
let textureID = 0;
let sight = 0;
let speed = 0;
let globalRank = 0;
let levelRank = 0;
const playerFromDB = res[0].data[0];
const guildFromDB = res[2].data[0];
const questFromDB = res[3].data;
const statusFromDB = res[4].data;
const resourcesFromDB = res[5].data;
const equipmentsFromDB = res[6].data;
const statusPoints = [];
const guildID = guildFromDB ? guildFromDB.id : 0;
const guild = guilds[guildID];
const selectedAmmunition = playerFromDB ? playerFromDB.selectedAmmunitionID : 0;
const selectedHarpoon = playerFromDB ? playerFromDB.selectedHarpoonID : 0;
const resourceSharedLimiter = {
cannon: new EquipmentLimiter({
maximum: 50
}),
harpooner: new EquipmentLimiter({
maximum: 25
}),
sail: new EquipmentLimiter({
maximum: 5
}),
pirate: new EquipmentLimiter({
maximum: 250
})
};
const inventory = new Inventory({
resourceSharedLimiter
});
resourcesFromDB.forEach(r => {
switch (r.categoryID) {
case 6:
statusPoints.push(r);
break;
case 7:
case 8:
const equipment = equipmentsFromDB.find(e => e.id === r.id && e.categoryID === r.categoryID);
r.amountEquipped = equipment ? equipment.amountEquipped : 0;
break;
};
inventory.refreshSingle(r);
});
inventory.setSelectedAmmunition(selectedAmmunition);
inventory.setSelectedHarpoon(selectedHarpoon);
if (!attemptToDoubleLog) {
const playerHasData = playerFromDB ? true : false;
if (!playerHasData) {
const startingMap = maps.find(m => m.id === 1);
const { x, y } = startingMap.grid.randomNode(10);
textureID = 1;
sight = 450;
speed = 250;
globalRank = 1;
levelRank = 1;
isAlive = true;
map = startingMap;
position = {
x,
y
};
hitpoints = {
current: 10000,
maximum: 50000
};
baseStarterRewards.forEach(r => inventory.refreshSingle(r));
await execute(QUERY_INSERT_DEFAULT_PLAYER, [ID, x, y, 1]);
} else {
textureID = playerFromDB.designID;
sight = playerFromDB.sight;
speed = playerFromDB.speed;
globalRank = playerFromDB.globalRank;
levelRank = playerFromDB.levelRank;
isAlive = !!hitpoints;
map = maps.find(m => m.id === playerFromDB.mapID);
position = {
x: playerFromDB.x,
y: playerFromDB.y
};
hitpoints = {
current: playerFromDB.currentHitpoints,
maximum: playerFromDB.maximumHitpoints
};
};
};
const player = new Player({
id: ID,
sessionID: ws.id,
name: ws.username,
textureID,
map,
guild,
positionX: position.x,
positionY: position.y,
isAlive,
hitpoints,
maximumCannonRange,
maximumHarpoonerRange,
sight,
speed,
globalRank,
levelRank,
inventory,
statusPoints,
activeStatusEffects: statusFromDB,
quests: questFromDB,
rewardCallback,
attackCallback
});
player.setDesign(textureID);
ws.subscribe(player.map.id.toString());
playerEvents.forEach(e => player.on(e, playerHandlers[e]));
players[ID] = player;
const packet = EnvironmentSetupPacket(player.map.id);
ws.send(packet, true, true);
};
+64
View File
@@ -0,0 +1,64 @@
const crypto = require("crypto");
const redis = require("../Miscellaneous/redis");
const Game = require("../Game/Game");
const HTTP_BAD_REQUEST = "403";
function badRequest(res) {
res.cork(() => res.isAborted ? null : res
.writeStatus(HTTP_BAD_REQUEST)
.end());
};
module.exports = async function (res, req, context) {
const cookieHeader = req.getHeader("cookie");
const cookie = cookieHeader.split("ps=")[1];
res.onAborted(() => {
res.isAborted = true;
console.info("ABORTED!!!");
});
if (!cookie) {
console.info("upgrade 1")
return badRequest(res);
};
const secWebSocketKey = req.getHeader("sec-websocket-key");
const secWebSocketProtocol = req.getHeader("sec-websocket-protocol");
const secWebSocketExtensions = req.getHeader("sec-websocket-extensions");
const userAgent = req.getHeader("user-agent");
const ip = req.getHeader("x-forwarded-for");
const formattedCookie = cookie.split(".")[0].slice(4);
const cookieInDB = await redis.get(`sess:${formattedCookie}`);
if (!cookieInDB) {
console.info("upgrade 2")
return badRequest(res);
};
const parsedCookie = JSON.parse(cookieInDB);
if (!parsedCookie.accountID) {
console.info("upgrade 3")
return badRequest(res);
};
const hash = crypto.createHash("md5")
.update(userAgent)
.update(ip)
.digest("hex");
// if (hash !== parsedCookie.fingerprint) return badRequest(res);
const { accountID, username, isAdmin } = parsedCookie;
console.info("Before upgrading connection for", username);
res.cork(() => res.upgrade({ accountID, username, cookie, isAdmin },
secWebSocketKey,
secWebSocketProtocol,
secWebSocketExtensions,
context));
};
+39
View File
@@ -0,0 +1,39 @@
class AdmiralCounter {
constructor(configuration) {
this.entities = Object.entries(configuration);
this.admiralCounters = {};
}
increaseCounter(ids) {
const { typeID, parentEntityTypeID } = ids;
const id = `${typeID}_${parentEntityTypeID}`;
if (!this.admiralCounters[id]) this.admiralCounters[id] = { childKilledCount: 1, admiralAliveCount: 0 };
else this.admiralCounters[id].childKilledCount++;
const thresholds = this.entities.find(e => parseInt(e[0]) === typeID);
if (!thresholds) return;
const threshold = thresholds[1][parentEntityTypeID];
if (!threshold) return;
const counter = this.admiralCounters[id];
if (counter.childKilledCount === threshold.childToBeKilledCount) {
counter.childKilledCount = 0;
if (counter.admiralAliveCount < threshold.maximumAdmiralCount) {
counter.admiralAliveCount++;
return true;
};
};
return false;
}
descreaseAdmiralCount(id) {
const counter = this.admiralCounters[id];
if (counter) counter.admiralAliveCount--;
}
};
module.exports = AdmiralCounter;
+71
View File
@@ -0,0 +1,71 @@
const EventEmitter = require("events");
const constants = require("../Data/constants");
const { CATEGORIES } = constants;
const { CATEGORY_CANNON } = CATEGORIES;
const eventsMapped = {
1: 97,
2: 98,
3: 99,
4: 100,
5: 101,
6: 102,
7: 103,
8: 104,
9: 105,
10: 106
};
class Cannon extends EventEmitter {
constructor(info) {
super();
this.id = info.id;
this.categoryID = CATEGORY_CANNON;
this.eventID = eventsMapped[this.id];
this.damage = info.damage;
this.range = info.range;
this.reload = info.reload;
this.scatter = info.scatter;
this.amount = info.amount;
this.amountEquipped = info.amountEquipped || 0;
this.lastShotTickTimestamp = 0;
this.resourceSharedLimiter = info.resourceSharedLimiter;;
};
equip(quantity) {
if (this.amountEquipped + quantity > this.amount) return
const isTypeQunatityExhausted = this.resourceSharedLimiter.count(quantity);
if (!isTypeQunatityExhausted) return
this.amountEquipped += quantity;
return true;
}
unequip(quantity) {
if (this.amountEquipped - quantity < 0) return
const isTypeQunatityExhausted = this.resourceSharedLimiter.count(-quantity);
if (!isTypeQunatityExhausted) return
this.amountEquipped -= quantity;
return true;
}
getProperties() {
return {
typeID: 1,
id: this.id,
amount: this.amount,
amountEquipped: this.amountEquipped
};
}
};
module.exports = Cannon;
+70
View File
@@ -0,0 +1,70 @@
const Entity = require("./Entity");
const Reward = require("./Reward");
const constants = require("../Data/constants");
const { TYPES, TIMING_EVENTS } = constants;
const { ENTITY_TYPE_COLLECTABLE } = TYPES;
const { TIMING_EVENT_RESPAWN } = TIMING_EVENTS;
class Collectable extends Entity {
constructor(info) {
super({
type: ENTITY_TYPE_COLLECTABLE,
id: info.id,
map: info.map,
isAlive: true,
position: info.position
});
this.respawnTime = 10000;
this.entityTypeID = info.entityTypeID;
this.dieCallback = info.dieCallback;
this.rewardCallback = info.rewardCallback;
this.reward = new Reward({
distributionType: 2,
baseReward: info.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;
}, {})
});
this.map.collectables.set(this.id, this);
}
die(deathEvent) {
super.die();
const die = {
typeID: this.typeID,
entityTypeID: this.entityTypeID,
id: this.id,
authorID: deathEvent.authorID,
targetEntityTypeID: this.entityTypeID
};
this.dieCallback(die);
this.setSingleEvent(TIMING_EVENT_RESPAWN, this.respawnTime);
}
getProperties() {
const { position, type, typeID, entityTypeID, id } = this;
return { position, type, typeID, entityTypeID, id };
}
};
module.exports = Collectable;
+106
View File
@@ -0,0 +1,106 @@
const EventScheduler = require("./EventScheduler");
const constants = require("../Data/constants");
const { TYPES, TIMING_EVENTS } = constants;
const { ENTITY_TYPE_PLAYER } = TYPES;
const { TIMING_EVENT_RESPAWN } = TIMING_EVENTS;
class Entity extends EventScheduler {
constructor(info) {
const id = info.type === ENTITY_TYPE_PLAYER ? info.id : Entity.composeID(info.type - 1, info.id);
super(info.type, id, info.map.id);
this.isAlive = info.isAlive ?? true;
this.chunk = null;
this.type = null;
this.map = info.map;
this.typeID = info.type;
this.id = id;
const { map } = this;
if (info.position) {
const cell = info.map.grid.getCell(info.position.x, info.position.y);
const coordinate = info.map.grid.getCoords(cell.xw, cell.xh)
this.position = coordinate;
} else this.position = map.grid.randomNode(10);
if (info.type !== ENTITY_TYPE_PLAYER) {
const currentChunk = this.getCurrentChunk();
map[currentChunk].addItem(id, this);
this.chunk = currentChunk;
};
};
static composeID(entityType, entityID) {
return ((entityType & 0xFF) << 24) | (entityID & 0xFFFFFF);
}
static getType(compositeID) {
return (compositeID >> 24) & 0xFF;
}
static getID(compositeID) {
return compositeID & 0xFFFFFF;
}
getCurrentChunk() {
return `chunk_${Math.floor(this.position.x / (10 * 120))}-${Math.floor(this.position.y / (10 * 90))}`;
}
removeFromChunk() {
if (!this.chunk) return;
this.map[this.chunk].removeItem(this.id);
this.chunk = null;
}
updateChunk() {
const { chunk, map, id } = this;
const currentChunk = this.getCurrentChunk();
if (chunk !== currentChunk) {
this.removeFromChunk();
map[currentChunk].addItem(id, this);
this.chunk = currentChunk;
this.chunkIndex = [Math.floor(this.position.x / (10 * 120)), Math.floor(this.position.y / (10 * 90))];
return true;
};
return false;
}
die() {
if (!this.isAlive) return;
this.isAlive = false;
this.removeFromChunk();
const players = this.map.getLivingEntities([ENTITY_TYPE_PLAYER]);
players.forEach(p => delete p.inDistance[this.id]); // this is not efficient, should update
}
respawn(respawn = {}) {
if (this.isAlive) return;
this.isAlive = true;
this.position = respawn.position ? respawn.position : this.map.grid.randomNode(10);
this.updateChunk();
this.removeEvent(TIMING_EVENT_RESPAWN);
}
markActive() {
this.map[`chunkTest_${Math.floor(this.position.x / (10 * 120))}-${Math.floor(this.position.y / (10 * 90))}`].addItem(this.id, this);
}
};
module.exports = Entity;
+96
View File
@@ -0,0 +1,96 @@
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
class EntityGroup {
constructor(info) {
this.id = info.id;
this.mapIDs = Array.isArray(info.mapIDs) ? info.mapIDs : [];
this.structure = info.structure;
this.entitySettings = info.entitySettings;
this.members = new DoubleLinkedList();
this.leaders = new DoubleLinkedList();
this.isSummonPossible = true;
this.predefinedArea = info.predefinedArea ? info.predefinedArea : {
x: 1000,
y: 1000,
width: 750,
height: 750
};
};
setAreaPosition(position) {
this.predefinedArea.x = position.x;
this.predefinedArea.y = position.y;
}
getEntitySetting(groupMemberTypeID) {
const entitySetting = this.entitySettings[groupMemberTypeID];
return entitySetting ? entitySetting : {
speed: 600,
respawnTime: 0
};
}
cancelRespawn() {
if (!this.isSummonPossible) return;
this.members.each((key, value) => {
value.cancelEvent(8);
return true;
});
this.isSummonPossible = false;
}
default() {
this.members.clearEach(() => null);
this.leaders.clearEach(() => null);
this.isSummonPossible = true;
}
isDefeated() {
let deadEntitiesCount = 0;
this.members.each((key, value) => {
if (!value.isAlive) deadEntitiesCount++;
return true;
});
this.leaders.each((key, value) => {
if (!value.isAlive) deadEntitiesCount++;
return true;
});
return this.structure.length === deadEntitiesCount || (this.members.isEmpty() && this.leaders.isEmpty());
}
addMember(entity) {
this.members.addItem(entity.id, entity);
}
addLeader(entity) {
this.leaders.addItem(entity.id, entity);
}
isLeader(id) {
return this.leaders.hasElement(id);
}
isLeaderAlive() {
let isAlive = false;
this.leaders.each(function (key, value) {
isAlive = value.isAlive;
return !value.isAlive;
});
return isAlive;
}
};
module.exports = EntityGroup;
+20
View File
@@ -0,0 +1,20 @@
class EquipmentLimiter {
constructor(info) {
this.quantity = {
current: 0,
maximum: info.maximum
};
};
count(quantity) {
if (!quantity) return false;
if (this.quantity.current + quantity > this.quantity.maximum || this.quantity.current + quantity < 0) return false;
this.quantity.current += quantity;
return true;
}
};
module.exports = EquipmentLimiter;
+154
View File
@@ -0,0 +1,154 @@
const EventEmitter = require("events");
const Game = require("./Game");
class EventScheduler extends EventEmitter {
constructor(typeID, id, mapID) {
super();
this.entity = {
typeID,
id
};
this.events = {};
this.mapID = mapID;
};
setLocalization(mapID) {
this.mapID = mapID;
}
getLocalization = () => this.mapID;
setReusableSingleEvent(id, time, data) {
if (!this.events[id] || this.events[id][0].isOver) {
const previousTick = this.getEventTick(id);
const relativeTo = previousTick ? previousTick : performance.now();
const tickAt = relativeTo + (time > 0 ? time : 0);
const event = {
id,
entity: this.entity,
isCancelled: false,
index: 0,
tickAt,
data,
getLocalization: this.getLocalization,
isSingle: true,
isOver: false
};
Game.events.add(tickAt, event);
this.events[id] = [event];
} else {
this.events[id][0].isCancelled = false;
};
}
setSingleEvent(id, time, data) {
const tickAt = performance.now() + time;
const event = {
id,
entity: this.entity,
isCancelled: false,
index: 0,
tickAt,
data,
getLocalization: this.getLocalization
};
if (!this.events[id]) {
Game.events.add(tickAt, event);
this.events[id] = [event];
};
}
setEventTest2(id, time, data) {
const previousTick = this.getEventTick(id);
const relativeTo = previousTick ? previousTick : performance.now();
const tickAt = relativeTo + time;
const event = {
id,
entity: this.entity,
isCancelled: false,
index: this.events[id] ? this.events[id].length - 1 : 0,
tickAt,
data,
getLocalization: this.getLocalization
};
Game.events.add(tickAt, event);
this.events[id] = [event];
}
setEvent(id, time, data) {
const previousTick = this.getEventTick(id);
const relativeTo = previousTick ? previousTick : performance.now();
const tickAt = relativeTo + time;
const event = {
id,
entity: this.entity,
isCancelled: false,
index: this.events[id] ? this.events[id].length - 1 : 0,
tickAt,
data,
getLocalization: this.getLocalization
};
Game.events.add(tickAt, event);
if (this.events[id]) this.events[id].push(event);
else this.events[id] = [event];
}
setIndependentEvent(id, time, data) {
const tickAt = performance.now() + time;
const event = {
id,
entity: this.entity,
isCancelled: false,
index: this.events[id] ? this.events[id].length : 0,
tickAt,
data,
getLocalization: this.getLocalization
};
Game.events.add(tickAt, event);
if (this.events[id]) this.events[id].push(event);
else this.events[id] = [event];
}
getEventTick(id, index) {
const event = this.events[id];
if (!event) return 0;
if (!index) index = event.length - 1;
const element = event[index];
return element && !element.isOver && element.tickAt;
}
removeEvent(id) {
delete this.events[id];
}
cancelEvent(id, index) {
if (this.events[id]) {
if (index == null) {
for (let i = 0; i < this.events[id].length; i++) this.events[id][i].isCancelled = true;
} else if (index === -1) {
for (let i = 0; i < this.events[id].length - 1; i++) this.events[id][i].isCancelled = true;
} else {
this.events[id][index].isCancelled = true;
};
};
}
};
module.exports = EventScheduler;
+43
View File
@@ -0,0 +1,43 @@
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
const PQ = require("../Utility/Q");
const maps = [];
for (let i = 0; i < 20; i++) maps.push(require(`../Maps/Map_${i + 1}`));
const game = {
players: {},
packets: new DoubleLinkedList(),
guilds: {},
guildIslands: [],
events: new PQ(),
eventsFixed: new PQ(),
maps,
getTopic: function (topicName) {
const topic = this.packets.getElement(topicName);
if (topic) return topic;
return this.packets.addItem(topicName, []);
},
addPlayer: function (player) {
this.players[player.id] = player;
},
/* testDestroyAll: function() {
this.maps.forEach(map => map.npcs.forEach(npc => npc))
},*/
disconnectAll: function() {
this.state = 0;
this.maps.forEach(map => map.players.forEach(player => player.disconnect(() => {
socket.sockets[player.sessionID].end(2);
delete this.players[player.id];
delete socket.sockets[player.sessionID];
}, true)))
// socket.us_listen_socket_close(this.listeningServer);
// this.listeningServer = null;
},
listeningServer: null,
state: 1
};
module.exports = game;
+125
View File
@@ -0,0 +1,125 @@
class Grid {
constructor(config) {
this.width = config.width;
this.height = config.height;
this.collision = config.collision ?? {};
}
getCoords(tileX, tileY) {
const x = 0 + tileX * 24 + !(tileY % 2) * (24 / 2)
const y = 0 + tileY * (18 / 2);
return {
x,
y
};
}
getCell(roundedX, roundedY) {
const x = roundedX - 0;
const y = roundedY - 0;
const isEven = ((Math.floor(y / 9) * 9 - 9) / 18) % 1 !== 0;
const xw = isEven ? Math.floor(x / 24) : Math.floor((x + 12) / 24);
const xh = Math.floor(y / (18 / 2));
return {
xw,
xh
};
}
isValidNode(node) {
const blockedTile = this.collision[`${node.xw}-${node.xh}`];
return (!blockedTile && node.xw > 0 && node.xh > 0 && node.xw < this.width / 24 && node.xh < (this.height * 2) / 18);
}
randomNodeNear(nearToX, nearToY, radius) {
let r = radius * Math.sqrt(Math.random());
let a = 2 * Math.PI * Math.random();
let nearX = Math.round(r * Math.cos(a) + nearToX);
let nearY = Math.round(r * Math.sin(a) + nearToY);
let triedNode = this.getCell(nearX, nearY);
const { xw, xh } = triedNode;
let { x, y } = this.getCoords(xw, xh);
while (!this.isValidNode(triedNode) || (x === nearToX && y === nearToY) || Math.pow(x - nearToX, 2) + Math.pow(y - nearToY, 2) < Math.pow(500, 2)) {
r = radius * Math.sqrt(Math.random());
a = 2 * Math.PI * Math.random();
nearX = Math.round(r * Math.cos(a) + nearToX);
nearY = Math.round(r * Math.sin(a) + nearToY);
triedNode = this.getCell(nearX, nearY);
const { xw, xh } = triedNode;
const coords = this.getCoords(xw, xh);
x = coords.x;
y = coords.y;
};
return {
x,
y
};
}
randomNodeInArea(area) {
let xw = 0;
let xh = 0;
const minimumWidth = area.x - area.width / 2;
const maximumWidth = area.x + area.width / 2;
const minimumHeight = area.y - area.height / 2;
const maximumHeight = area.y + area.height / 2;
do {
xw = Math.floor((Math.random() * (maximumWidth - minimumWidth + 1) + minimumWidth) / 24);
xh = Math.floor((Math.random() * (maximumHeight - minimumHeight + 1) + minimumHeight) / (18 / 2));
} while (!this.isValidNode({ xw, xh }));
return this.getCoords(xw, xh);
}
randomNode(percentage) {
let xw = 0;
let xh = 0;
if (!percentage) percentage = 0;
percentage /= 200;
const minimumWidth = 0 + this.width * percentage;
const maximumWidth = this.width * (1 - percentage);
const minimumHeight = 0 + this.height * percentage;
const maximumHeight = this.height * (1 - percentage);
do {
xw = Math.floor((Math.random() * (maximumWidth - minimumWidth + 1) + minimumWidth) / 24);
xh = Math.floor((Math.random() * (maximumHeight - minimumHeight + 1) + minimumHeight) / (18 / 2));
} while (!this.isValidNode({ xw, xh }));
return this.getCoords(xw, xh);
}
getNodeNeighbours(node) {
const { xw, xh } = node;
let rightdown, leftdown, rightup, leftup;
if (node.xh % 2 !== 0) {
rightdown = { xw, xh: xh + 1 };
leftdown = { xw: xw - 1, xh: xh + 1 };
rightup = { xw, xh: xh - 1 };
leftup = { xw: xw - 1, xh: xh - 1 };
} else {
rightdown = { xw: xw + 1, xh: xh + 1 };
leftdown = { xw: xw, xh: xh + 1 };
rightup = { xw: xw + 1, xh: xh - 1 };
leftup = { xw: xw, xh: xh - 1 };
};
return [rightup, leftup, leftdown, rightdown];
}
};
module.exports = Grid;
+15
View File
@@ -0,0 +1,15 @@
const Grid = require("./Grid");
const data = require("../Data/maps");
const grids = [];
data.forEach(d => {
const grid = new Grid({
width: d.size.width,
height: d.size.height,
collision: d.collision
});
grids.push(grid);
})
module.exports = grids;
+143
View File
@@ -0,0 +1,143 @@
const InventoryList = require("./InventoryList");
const LEVELS = require("../Data/levels");
const GUILD_MAXIMUM_REQUESTS_SIZE = 100;
const ECONOMY = [1, 2];
const globalRequests = {};
class Guild {
constructor(info) {
this.id = info.id;
this.topic = `g${info.id}`;
this.name = info.name;
this.tag = info.tag;
this.description = info.description;
this.ownerID = info.ownerID;
this.bank = new GuildBank();
this.taxRates = info.taxRates;
this.experiencePoints = info.experiencePoints ?? 0;
this.level = LEVELS.guildLevel(this.experiencePoints) ?? 1;
this.size = 0;
this.sizeMaximum = 30;
this.members = [];
this.requests = [];
this.diplomacy = {};
this.timestamps = info.timestamps;
this.bank.depositTax({
categoryID: 1,
id: 1,
amount: 0
});
this.bank.depositTax({
categoryID: 1,
id: 2,
amount: 0
});
};
levelUpdate(amount) {
this.experiencePoints += amount;
if (this.level >= 25) return -1;
const newLevel = LEVELS.guildLevel(this.experiencePoints);
if (newLevel !== this.level) {
if (newLevel > 25) this.level = 25;
else this.level = newLevel;
return this.level;
};
return -1;
}
isLeader(playerID) {
return this.ownerID === playerID;
}
hasRequested(playerID) {
const isRequestPresent = this.requests.some(r => r.playerID === playerID);
return isRequestPresent;
}
setPermission(playerID, permission) {
const member = this.members.find(m => m.id === playerID);
if (!member) return;
member.permission = permission;
}
addRequest(playerID, message) {
//if (this.requestSize >= GUILD_MAXIMUM_REQUESTS_SIZE) return;
if (globalRequests[playerID] >= 8) return;
console.info(globalRequests[playerID], "request count for player", playerID);
if (globalRequests[playerID]) globalRequests[playerID]++;
else globalRequests[playerID] = 1;
const request = {
playerID,
message
};
this.requests.push(request);
return true;
}
removeRequest(playerID) {
if (globalRequests[playerID]) globalRequests[playerID]--;
this.requests = this.requests.filter(r => r.playerID !== playerID);
}
isMember(playerID) {
return this.members.find(member => member.id === playerID) ? true : false;
}
getMember(playerID) {
return this.members.find(member => member.id === playerID);
}
addMember(member) {
if (this.size >= this.sizeMaximum) return;
if (this.isMember(member.id)) return;
this.removeRequest(member.id);
this.members.push(member);
delete globalRequests[member.id];
this.size++;
return true;
}
removeMember(playerID) {
this.size--;
this.members = this.members.filter(member => member.id !== playerID);
}
};
class GuildBank {
constructor(info) {
this.list = new InventoryList();
this.deposits = [];
};
depositTax(resource) {
if (!ECONOMY.includes(resource.id)) return;
this.list.accumulateItem(resource.id, resource);
}
};
module.exports = Guild;
+69
View File
@@ -0,0 +1,69 @@
const DoubleLinkedList = require("../Utility/DoubleLinkedList");
const constants = require("../Data/constants");
const { TIMING_EVENTS } = constants;
const { TIMING_EVENT_RESPAWN } = TIMING_EVENTS;
const Game = require("./Game");
const { guilds } = Game;
class GuildIsland {
constructor(info) {
this.id = info.id;
this.locationMapID = info.locationMapID;
this.index = info.index;
this.position = info.position;
this.guild = info.guild;
this.towers = [];
this.totalDamageReceived = new DoubleLinkedList();
};
updateReceivedDamage(guildID, damage) {
this.totalDamageReceived.accumulateItem(guildID, damage);
}
getTower(id) {
const tower = this.towers.find(t => t.id === id);
return tower;
}
addTower(tower) {
this.towers.push(tower);
}
updateOwnership() {
const towersAlive = this.towers.filter(t => t.hp.current);
if (towersAlive.length) return null;
let maximumDamage = 0;
let newGuildID = 0;
this.totalDamageReceived.clearEach((guildID, damage) => {
if (damage > maximumDamage) {
maximumDamage = damage;
newGuildID = guildID;
};
});
this.towers.forEach(tower => tower.setEvent(TIMING_EVENT_RESPAWN, 10000));
const topicOld = this.guild.topic;
const guildOld = guilds[this.guild.id];
const guildNew = newGuildID ? guilds[newGuildID] : { tag: new ArrayBuffer(0), id: 0 };
const topicNew = guildNew.topic;
this.guild = guildNew;
return {
guildOld,
guildNew,
topicOld,
topicNew
};
}
};
module.exports = GuildIsland;
+63
View File
@@ -0,0 +1,63 @@
const EventEmitter = require("events");
const constants = require("../Data/constants");
const { CATEGORIES } = constants;
const { CATEGORY_HARPOONER } = CATEGORIES;
const eventsMapped = {
1: 197,
2: 198,
3: 199,
4: 200,
5: 201,
6: 202
};
class Harpooner extends EventEmitter {
constructor(info) {
super();
this.id = info.id;
this.categoryID = CATEGORY_HARPOONER;
this.eventID = eventsMapped[this.id];
this.damage = info.damage;
this.range = info.range;
this.reload = info.reload;
this.amount = info.amount;
this.amountEquipped = info.amountEquipped || 0;
this.resourceSharedLimiter = info.resourceSharedLimiter;
}
equip(quantity) {
if (this.amountEquipped + quantity > this.amount) return
const isTypeQunatityExhausted = this.resourceSharedLimiter.count(quantity);
if (!isTypeQunatityExhausted) return
this.amountEquipped += quantity;
return true;
}
unequip(quantity) {
if (this.amountEquipped - quantity < 0) return
const isTypeQunatityExhausted = this.resourceSharedLimiter.count(-quantity);
if (!isTypeQunatityExhausted) return
this.amountEquipped -= quantity;
return true;
}
getProperties() {
return {
typeID: 2,
id: this.id,
amount: this.amount,
amountEquipped: this.amountEquipped
};
}
};
module.exports = Harpooner;
+303
View File
@@ -0,0 +1,303 @@
const Item = require("./Item");
const Cannon = require("./Cannon");
const Harpooner = require("./Harpooner");
const InventoryList = require("../Game/InventoryList");
const schemeCannons = require("../Data/cannons");
const schemeHarpooners = require("../Data/harpooners");
const ammunitions = require("../Data/ammunitions");
const harpoons = require("../Data/harpoons");
const itemTable = require("../Data/items");
const constants = require("../Data/constants");
const { CATEGORIES, AFFECTS } = constants;
const { CATEGORY_ITEM, CATEGORY_AMMUNITION, CATEGORY_HARPOON, CATEGORY_DESIGN, CATEGORY_ECONOMY, CATEGORY_STATUS, CATEGORY_CANNON, CATEGORY_HARPOONER } = CATEGORIES;
const { AFFECT_CANNON_DAMAGE, AFFECT_HARPOONER_DAMAGE, AFFECT_PROTECTION, AFFECT_SAILING_SPEED, AFFECT_HITPOINT } = AFFECTS;
const AFFECT_TABLE = {
1: [1, 2, 3, 4],
[AFFECT_PROTECTION]: [5, 6, 7, 8]
};
class Inventory {
constructor(info) {
this.selectedAmmunition = 0;
this.selectedHarpoon = 0;
this.resourceSharedLimiter = info.resourceSharedLimiter;
this[CATEGORY_AMMUNITION] = new InventoryList();
this[CATEGORY_HARPOON] = new InventoryList();
this[CATEGORY_ITEM] = new InventoryList();
this[CATEGORY_STATUS] = new InventoryList();
this[CATEGORY_ECONOMY] = new InventoryList();
this[CATEGORY_DESIGN] = new InventoryList();
this[CATEGORY_CANNON] = new InventoryList();
this[CATEGORY_HARPOONER] = new InventoryList();
this[CATEGORY_DESIGN].addItem(1, {
categoryID: CATEGORY_DESIGN,
id: 1,
amount: 1
}); // default design
}
refreshSingle(element) {
switch (element.categoryID) {
case CATEGORY_ECONOMY:
this[CATEGORY_ECONOMY].accumulateItem(element.id, element);
break;
case CATEGORY_ITEM:
const inventoryItem = this[CATEGORY_ITEM].getElement(element.id);
if (!inventoryItem) return this.addItem(element);
inventoryItem.amount += element.amount;
break;
case CATEGORY_AMMUNITION:
if (!element.amount) return;
this[CATEGORY_AMMUNITION].accumulateItem(element.id, element);
if (!this.selectedAmmunition) this.setSelectedAmmunition(element.id);
break;
case CATEGORY_HARPOON:
if (!element.amount) return;
this[CATEGORY_HARPOON].accumulateItem(element.id, element);
if (!this.selectedHarpoon) this.setSelectedHarpoon(element.id);
break;
case CATEGORY_DESIGN:
this[CATEGORY_DESIGN].accumulateItem(element.id, element);
break;
case CATEGORY_STATUS:
this[CATEGORY_STATUS].accumulateItem(element.id, element);
break;
case CATEGORY_CANNON:
let cannon = this[CATEGORY_CANNON].getElement(element.id);
if (!cannon) {
const schemeCannon = schemeCannons[element.id];
if (!schemeCannon) return;
cannon = new Cannon({
id: element.id,
damage: schemeCannon.damage,
range: schemeCannon.range,
reload: schemeCannon.reload,
scatter: schemeCannon.scatter,
amount: element.amount,
amountEquipped: element.amountEquipped,
resourceSharedLimiter: this.resourceSharedLimiter.cannon
});
this[CATEGORY_CANNON].accumulateItem(cannon.id, cannon);
} else this[CATEGORY_CANNON].accumulateItem(cannon.id, element);
break;
case CATEGORY_HARPOONER:
let harpooner = this[CATEGORY_HARPOONER].getElement(element.id);
if (!harpooner) {
const schemeHarpooner = schemeHarpooners[element.id];
if (!schemeHarpooner) return;
harpooner = new Harpooner({
id: element.id,
damage: schemeHarpooner.damage,
range: schemeHarpooner.range,
reload: schemeHarpooner.reload,
scatter: schemeHarpooner.scatter,
amount: element.amount,
amountEquipped: element.amountEquipped,
resourceSharedLimiter: this.resourceSharedLimiter.harpooner
});
this[CATEGORY_HARPOONER].accumulateItem(harpooner.id, harpooner);
} else this[CATEGORY_HARPOONER].accumulateItem(element.id, element);
break;
};
}
use(categoryID, id, amount) {
const category = this[categoryID];
if (!category) return
const item = category[id];
if (!item || !item.amount) return
const itemScheme = itemTable[id];
if (!itemScheme && categoryID === CATEGORY_ITEM) return
let removedCount = -1;
if (itemScheme.isDurable) item.isActive = true;
else {
if (categoryID === CATEGORY_ITEM) item.nextUseAt = performance.now() + itemScheme.cooldown;
removedCount = itemScheme.useOnImpact || categoryID !== CATEGORY_ITEM ? Math.min(amount, item.amount) : 0;
item.amount -= removedCount;
item.usedAmount = removedCount;
};
return item;
}
disable(id) {
const item = this[CATEGORY_ITEM].getElement(id);
if (!item) return;
item.isActive = false;
}
alterAmount(categoryID, id, amount) {
const category = this[categoryID];
if (!category) return
const element = category.getElement(id);
if (!element || !element.amount) return
const usedAmount = Math.min(amount, element.amount);
element.amount -= usedAmount;
return usedAmount;
}
setSelectedAmmunition(id) {
if (!id) return this.selectedAmmunition = 0;
if (!ammunitions[id]) return;
const ammunition = this[CATEGORY_AMMUNITION].hasElement(id);
if (!ammunition) return this.selectedAmmunition = 0;
console.info(ammunition)
return this.selectedAmmunition = id;
}
getSelectedAmmunition() {
const selectedAmmunition = this[CATEGORY_AMMUNITION].getElement(this.selectedAmmunition);
if (!selectedAmmunition) return;
return selectedAmmunition;
}
setSelectedHarpoon(id) {
if (!id) return this.selectedHarpoon = 0;
if (!harpoons[id]) return;
const harpoon = this[CATEGORY_HARPOON].hasElement(id);
if (!harpoon) return this.selectedHarpoon = 0;
return this.selectedHarpoon = id;
}
getSeletedHarpoon() {
const selectedHarpoon = this[CATEGORY_HARPOON].getElement(this.selectedHarpoon);
if (!selectedHarpoon) return;
return selectedHarpoon;
}
addItem(entry) {
const itemScheme = itemTable[entry.id];
if (!itemScheme) return;
if (!entry.amount) return;
const { time, scale, affects } = itemScheme;
const item = new Item({
id: entry.id,
amount: entry.amount,
time,
scale,
affects
});
this[CATEGORY_ITEM].addItem(item.id, item);
}
getItemsByAffect(affect) {
const items = Object.values(this[CATEGORY_ITEM]);
return items.filter(item => item.affects.includes(affect));
}
useAvailableItemsByAffect(affect, cb) {
AFFECT_TABLE[affect]
.forEach(itemID => {
const item = this[CATEGORY_ITEM].getElement(itemID);
if (item && item.isActive && item.amount) {
const use = item.use();
cb(use.scale, itemID, use.statusID, use.isStatusActive);
};
});
}
getPosessions() {
return {
[CATEGORY_AMMUNITION]: this[CATEGORY_AMMUNITION],
[CATEGORY_HARPOON]: this[CATEGORY_HARPOON],
[CATEGORY_DESIGN]: this[CATEGORY_DESIGN]
};
}
getDesignIDs() {
const designIDs = [];
this[CATEGORY_DESIGN].each(key => designIDs.push(key));
return designIDs;
}
getEconomy() {
const economy = [];
this[CATEGORY_ECONOMY].each((key, value) => economy.push(value));
return economy;
}
getAmmunitions() {
const ammunitions = [];
this[CATEGORY_AMMUNITION].each((key, value) => ammunitions.push(value));
return ammunitions;
}
getHarpoons() {
const harpoons = [];
this[CATEGORY_HARPOON].each((key, value) => harpoons.push(value));
return harpoons;
}
getItems() {
const items = [];
this[CATEGORY_ITEM].each((key, value) => items.push(value));
return items;
}
getCannons() {
const cannons = [];
this[CATEGORY_CANNON].each((key, value) => cannons.push(value));
return cannons;
}
getHarpooners() {
const harpooners = [];
this[CATEGORY_HARPOONER].each((key, value) => harpooners.push(value));
return harpooners;
}
hasDesign(id) {
return this[CATEGORY_DESIGN].hasElement(id);
}
getItem(id) {
return this[CATEGORY_ITEM].getElement(id);
}
};
module.exports = Inventory;
+41
View File
@@ -0,0 +1,41 @@
const LinkedList = require("../Utility/LinkedList");
class LinkedListNode {
constructor(key, value) {
this.key = key;
this.value = value;
this.next = null;
};
};
class InventoryList extends LinkedList {
constructor() {
super();
};
accumulateItem(key, value) {
const entry = this.map.get(key);
const updatedValue = entry ? entry.value.amount + value.amount : value;
if (entry) entry.value.amount = updatedValue;
else this.addItem(key, updatedValue); /// eeeh
}
addItem(key, value) {
if (!key || !value || !value.categoryID || !value.id || isNaN(value.amount)) return console.info("addItem", key, value);
const temp = new LinkedListNode(key, value);
if (!this.head) {
this.head = temp;
this.tail = temp;
} else {
this.tail.next = temp;
this.tail = temp;
};
this.map.set(key, temp);
}
};
module.exports = InventoryList;
+61
View File
@@ -0,0 +1,61 @@
const itemTable = require("../Data/items");
const constants = require("../Data/constants");
const { CATEGORIES } = constants;
const { CATEGORY_ITEM } = CATEGORIES;
class Item {
constructor(config) {
this.categoryID = CATEGORY_ITEM;
this.id = config.id;
this.amount = config.amount;
this.affects = config.affects;
this.scale = config.scale;
this.isDurable = config.isDurable;
this.time = config.time;
this.isActive = false;
}
use(amount) {
const itemScheme = itemTable[this.id];
if (!itemScheme) return;
if (!this.amount) return global.logger.send({ event: "itemUsage", reason: "item amount is 0" });
if (!amount) amount = 1;
const removedAmount = Math.min(amount, this.amount);
this.amount -= removedAmount;
if (!this.amount && itemScheme.isDurable) this.isActive = false;
return {
scale: this.scale,
statusID: itemScheme.statusID,
isStatusActive: this.amount && itemScheme.isDurable
};
}
activate(amount) {
const itemScheme = itemTable[this.id];
if (!itemScheme) return;
if (!this.amount) return;
if (!amount) amount = 1;
const removedAmount = itemScheme.useOnImpact ? Math.min(amount, this.amount) : 0;
if (itemScheme.isDurable) this.isActive = true;
else {
this.nextUseAt = performance.now() + itemScheme.cooldown;
this.amount -= removedAmount;
};
return {
...this,
removedAmount
};
}
};
module.exports = Item;
+511
View File
@@ -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;
+7
View File
@@ -0,0 +1,7 @@
const maps = [];
for (let i = 0; i < 19; i++) {
const map = require(`../Maps/Map_${i + 1}`);
maps.push(map);
};
module.exports = maps;
+147
View File
@@ -0,0 +1,147 @@
const Entity = require("./Entity");
const constants = require("../Data/constants");
const { TYPES, EVENTS, TIMING_EVENTS, MAXIMUM } = constants;
const { ENTITY_TYPE_PLAYER, ENTITY_TYPE_MONSTER } = TYPES;
const { TIMING_EVENT_RESPAWN } = TIMING_EVENTS;
const { EVENT_DIE } = EVENTS;
const { MAX_SAFE_INTEGER } = MAXIMUM;
class Monster extends Entity {
constructor(info) {
super({
type: ENTITY_TYPE_MONSTER,
id: info.id,
map: info.map,
isAlive: true,
position: info.position
});
this.hp = {
maximum: info.maximumHitpoint,
current: info.currentHitpoint
};
this.entityTypeID = info.entityTypeID;
this.parentEntityTypeID = info.parentEntityTypeID ?? 0;
this.respawnTime = info.respawnTime * 1000;
this.isAdmiral = info.isAdmiral ?? false;
this.attackers = [];
this.totalDamageReceived = 0;
this.damageReceived = {};
this.dieCallback = info.dieCallback;
this.attackCallback = info.attackCallback;
this.rewardCallback = info.rewardCallback;
this.reward = info.reward;
const { map, id } = this;
map.monsters.set(id, this);
const nearbyPlayersID = map.getNearbyPlayersGameID(this);
for (let i = 0; i < nearbyPlayersID.length; i++) {
const player = map.getEntityFromSubgroup(nearbyPlayersID[i]);
player.inDistance[id] = 0x01;
};
}
increaseCurrentHitpoint(amount) {
this.hp.current = Math.min(this.hp.maximum, this.hp.current + amount);
return this.hp.current;
}
decreaseCurrentHitpoint(amount, author) {
const { authorID } = author;
amount = Math.abs(amount);
const damage = Math.min(this.hp.current, amount);
if (this.damageReceived[authorID]) this.damageReceived[authorID] += damage;
else this.damageReceived[authorID] = damage;
this.totalDamageReceived += damage;
this.hp.current -= damage;
this.attackCallback({
typeID: this.typeID,
entityTypeID: this.entityTypeID,
authorID,
damage
});
const isOverSafeLimit = this.totalDamageReceived >= MAX_SAFE_INTEGER;
if (isOverSafeLimit) this.totalDamageReceived = MAX_SAFE_INTEGER;
if (this.hp.current === 0 || isOverSafeLimit) {
const die = {
typeID: this.typeID,
lastShotTypeID: 1,
lastShotID: author.authorID,
targetID: this.id,
targetEntityTypeID: this.entityTypeID
};
this.die(die);
// this.setSingleEvent(11, 0, die);
};
return {
normalizedAmount: damage,
currentHitpoint: this.hp.current
};
}
isAtMaximumHitpoints() {
return this.hp.current === this.hp.maximum;
}
die(die) {
super.die();
this.hp.current = 0;
this.attackers.forEach(data => {
const attackerEntity = this.map.getEntityFromSubgroup(data.attackerEntry);
attackerEntity.wipeAttack();
});
this.attackers.length = 0;
this.emit(EVENT_DIE, die, this.id);
const admiralProperties = {
typeID: this.typeID,
entityTypeID: this.entityTypeID,
parentEntityTypeID: this.parentEntityTypeID,
id: this.id,
isAdmiral: this.isAdmiral,
authorID: die.lastShotID
};
this.dieCallback(admiralProperties);
this.removeEvent(11);
this.damageReceived = {};
if (!this.isAdmiral) this.setEvent(TIMING_EVENT_RESPAWN, this.respawnTime);
}
respawn() {
if (this.isAlive) return;
this.hp.current = this.hp.maximum;
super.respawn();
this.removeEvent(TIMING_EVENT_RESPAWN);
}
getProperties() {
const { position, id, hp, type, typeID, isAdmiral, entityTypeID } = this;
return { position, id, hp, type, typeID, isAdmiral, entityTypeID };
}
};
module.exports = Monster;
+763
View File
@@ -0,0 +1,763 @@
const Entity = require("./Entity");
const Utility = require("./Utility");
const Cannon = require("./Cannon");
const ammunitions = require("../Data/ammunitions");
const cannonsToEquip = require("../Data/cannonsSchemeNPC");
const cannons = require("../Data/cannons");
const OVERTIME_HEALTH_EFFECTS = require("../Data/overtime");
const constants = require("../Data/constants");
const { EVENTS, TIMING_EVENTS, TYPES, EFFECTS_TERMINATE_MOVEMENT, EVENTS_BY_TIMING_EVENTS_ID, AFFECTS, MAXIMUM } = constants;
const { EVENT_DESTINATION_REACHED, EVENT_DESTINATION_NEW, EVENT_SPEED_UPDATE, EVENT_ATTACK_STARTED, EVENT_DIE, EVENT_ATTACKING, EVENT_EFFECT_ADD, EVENT_ATTACK_BAR, EVENT_HEALTH_CHANGED } = EVENTS;
const { ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC } = TYPES;
const { TIMING_EVENT_MOVE, TIMING_EVENT_RESPAWN } = TIMING_EVENTS;
const { AFFECT_CANNON_DAMAGE } = AFFECTS;
const { MAX_SAFE_INTEGER } = MAXIMUM;
const EVENTS_REMOVE_DEATH_NPC = [9, 11, 17];
class NPC extends Entity {
constructor(info) {
super({
type: ENTITY_TYPE_NPC,
id: info.id,
map: info.map,
isAlive: true,
position: info.position
});
this.hp = {
maximum: info.maximumHitpoint,
current: info.currentHitpoint
};
this.entityTypeID = info.entityTypeID ?? 0;
this.parentEntityTypeID = info.parentEntityTypeID ?? 0;
this.respawnTime = info.respawnTime * 1000;
this.isAdmiral = info.isAdmiral ?? false;
this.speed = info.speed;
this.size = info.size ?? 128;
this.cannonRange = 0;
this.i = 0;
this.path = [];
this.attackers = [];
this.isSpeedLowered = false;
this.damageReceived = {};
// this.behaviour = info.behaviour;
this.totalDamageReceived = 0;
this.lastShotTickTimestamp = 0;
this.activeOvertimeHealthEffects = {};
this.rewardCallback = info.rewardCallback;
this.attackCallback = info.attackCallback;
this.dieCallback = info.dieCallback;
this.reward = info.reward;
this.activeEffects = {};
this.cannonTypesOutOfRange = [];
this.searchedTarget = null;
this.group = info.group;
this.inventory = {
cannons: [],
selectedAmmunition: 1,
equipCannons() {
const cannonsEquippedScheme = cannonsToEquip[info.entityTypeID];
if (!cannonsEquippedScheme) return;
for (let i = 0, length = cannonsEquippedScheme.length; i < length; i++) {
const [id, amount] = cannonsEquippedScheme[i];
const cannonScheme = cannons[id];
if (this.cannonRange < cannonScheme.range) this.cannonRange = cannonScheme.range;
const cannon = new Cannon({
id,
damage: cannonScheme.damage,
range: cannonScheme.range,
reload: cannonScheme.reload,
scatter: 0,
amount,
amountEquipped: amount
});
this.cannons.push(cannon);
};
},
getCannons() {
return this.cannons;
}
};
this.inventory.equipCannons();
const cannonsStatistics = this.getCannonsStatistics();
this.cannonRange = cannonsStatistics.maximumCannonRange;
this.cannonTypesInUse = cannonsStatistics.distinctCannonTypes;
this.behaviour = {
stopIfAttacked: !!this.parentEntityTypeID,
attackIfAttacked: true,
isAggressive: !this.parentEntityTypeID,
isChaser: false,
stopAttackIfAbandoned: true,
selectTargetType: 1
};
this.behaviourBits = Object.values(this.behaviour).reduce((prev, cur, i) => cur ? prev + Math.pow(2, i) : prev + 0, 0);
if (this.behaviour.isChaser) {
this.chasingBehaviour = {
range: 750,
recalculateAccumulator: 0,
position: {
x: 0,
y: 0
},
target: null
};
};
const { map, id } = this;
map.npcs.set(id, this);
const nearbyPlayersID = map.getNearbyPlayersGameID(this);
for (let i = 0; i < nearbyPlayersID.length; i++) {
const player = map.getEntityFromSubgroup(nearbyPlayersID[i]);
player.inDistance[id] = true;
};
}
trackOvertimeHealthEffect(id) {
const effect = this.activeOvertimeHealthEffects[id];
if (effect) effect.count++;
else this.activeOvertimeHealthEffects[id] = { count: 0 };
return effect ? effect.count : 0;
}
overtimeDecreaseCurrentHitpoint(event) {
if (!event) return;
const { id, authorTypeID, authorID, damage, iid } = event;
const { amount, ticks, time } = OVERTIME_HEALTH_EFFECTS[id];
const decrease = this.decreaseCurrentHitpoint(damage * amount, {
authorTypeID,
authorID
});
if (!decrease) return;
const currentCount = this.trackOvertimeHealthEffect(id);
if (currentCount < ticks && decrease.currentHitpoint) this.setIndependentEvent(iid, time, event);
else delete this.activeOvertimeHealthEffects[id];
const data = {
targetTypeID: this.typeID,
targetID: this.id,
amount: decrease.normalizedAmount,
type: 0,
isCritical: false
};
this.emit(EVENT_HEALTH_CHANGED, data, this.id);
}
effectAdd(effectID) {
this.activeEffects[effectID] = true;
this.emit(EVENT_EFFECT_ADD, {
typeID: this.typeID,
id: this.id,
effectID
});
}
effectRemove(effectID) {
this.activeEffects[effectID] = false;
this.emit(EVENT_EFFECT_ADD, {
typeID: this.typeID,
id: this.id,
effectID
});
this.removeEvent(17);
switch (effectID) {
case 1:
this.determineMovementState();
break;
};
}
isAtMaximumHitpoints() {
return this.hp.current === this.hp.maximum;
}
increaseCurrentHitpoint(amount) {
amount = Math.abs(amount);
const normalizedAmount = this.hp.current + amount > this.hp.maximum ? amount - (this.hp.current + amount - this.hp.maximum) : amount;
this.hp.current += normalizedAmount;
if (this.isSpeedLowered && !this.isHPBelowPercentageThreshold(35)) {
this.changeSpeed(-this.speed / 2);
this.isSpeedLowered = false;
};
return {
normalizedAmount,
currentHitpoint: this.hp.current
};
}
decreaseCurrentHitpoint(amount, author) {
if (this.hp.current === 0) return;
const { authorID } = author;
amount = Math.abs(amount);
const damage = Math.min(this.hp.current, amount);
if (this.damageReceived[authorID]) this.damageReceived[authorID] += damage;
else this.damageReceived[authorID] = damage
this.totalDamageReceived += damage;
this.hp.current -= damage;
if (!this.isSpeedLowered && this.isHPBelowPercentageThreshold(35)) {
this.changeSpeed(this.speed);
this.isSpeedLowered = true;
};
this.attackCallback({
typeID: this.typeID,
entityTypeID: this.entityTypeID,
authorID,
damage
});
const isOverSafeLimit = this.totalDamageReceived >= MAX_SAFE_INTEGER;
if (isOverSafeLimit) this.totalDamageReceived = MAX_SAFE_INTEGER;
if (this.hp.current === 0 || isOverSafeLimit) {
const die = {
typeID: this.typeID,
lastShotTypeID: 1,
lastShotID: author.authorID,
targetID: this.id,
targetEntityTypeID: this.entityTypeID
};
this.die(die);
//this.setSingleEvent(11, 0, die);
};
return {
normalizedAmount: damage,
currentHitpoint: this.hp.current
};
}
isHPBelowPercentageThreshold(percentageThreshold) {
return this.hp.maximum / 100 * percentageThreshold > this.hp.current;
}
changeSpeed(speed) {
this.speed += speed;
const eSpeed = {
typeID: this.typeID,
id: this.id,
speed: this.speed
};
this.emit(EVENT_SPEED_UPDATE, eSpeed, this.id);
}
die(eventDeath) {
if (!this.isAlive) return;
this.emit(EVENT_DIE, eventDeath, this.id);
super.die();
this.hp.current = 0;
this.attackers.forEach(data => {
const attackerEntity = this.map.getEntityFromSubgroup(data.attackerEntry);
attackerEntity.wipeAttack();
});
this.attackers.length = 0;
if (this.group)
this.emit("miniMapDotRemoveTest", {
id: this.id
});
const admiralProperties = {
typeID: this.typeID,
entityTypeID: this.entityTypeID,
parentEntityTypeID: this.parentEntityTypeID,
id: this.id,
isAdmiral: this.isAdmiral,
authorID: eventDeath.lastShotID
};
this.stopMovement();
this.abortAttack();
this.untrackChasedTarget();
this.dieCallback(admiralProperties);
this.damageReceived = {};
this.activeEffects = {};
EVENTS_REMOVE_DEATH_NPC.forEach(e => this.removeEvent(e));
if (this.group && !this.group.isLeaderAlive()) this.group.cancelRespawn();
if ((!this.isAdmiral && !this.group) || (this.group && !this.group.isLeader(this.id) && this.group.isLeaderAlive())) this.setEvent(TIMING_EVENT_RESPAWN, this.respawnTime);
}
respawn() {
if (this.isAlive) return;
this.hp.current = this.hp.maximum;
this.isSpeedLowered = false;
this.speed /= 2;
super.respawn({
position: this.group ? this.map.grid.randomNodeInArea(this.group.predefinedArea) : null
});
this.determineMovementState();
this.removeEvent(TIMING_EVENT_RESPAWN);
}
haltMovement() {
this.isDirty = this.isDirty || this.isMoving && this.behaviour.stopIfAttacked;
}
stopMovement() {
this.isDirty = false;
this.isMoving = false;
this.path = [];
this.i = 0;
this.removeEvent(TIMING_EVENT_MOVE);
}
startMovement(path) {
if (!Array.isArray(path)) return;
if (!path.length) return;
this.path = path;
this.isMoving = true;
const [x, y] = this.path[this.i];
this.emit(EVENT_DESTINATION_NEW, {
id: this.id,
typeID: this.typeID,
mapID: this.map.id,
x,
y
});
this.setEvent(TIMING_EVENT_MOVE, this.speed);
}
getCannonsStatistics() {
const cannons = this.inventory.getCannons();
let maximumCannonRange = 0;
const distinctCannonTypes = cannons.reduce((acc, cur) => {
const alreadyAddedType = acc.find(c => c.id === cur.id);
if (!alreadyAddedType && cur.amountEquipped) {
if (cur.range > maximumCannonRange) maximumCannonRange = cur.range;
acc.push({
id: cur.id,
reload: cur.reload,
range: cur.range,
eventID: cur.eventID
});
};
return acc;
}, []);
return {
distinctCannonTypes,
maximumCannonRange
};
}
calculateCannonDamage(id) {
const { inventory } = this;
const ammunitionScheme = ammunitions[inventory.selectedAmmunition];
let scaleSum = 1;
let damage = 0;
let k = 0;
/*inventory.useAvailableItemsByAffect(AFFECT_CANNON_DAMAGE, (scale, itemID, statusID, isStatusActive) => {
scaleSum += scale;
if (!isStatusActive) this.disableStatusEffect(statusID);
});*/
const cannons = this.inventory.getCannons();
for (let i = 0, length = cannons.length; i < length; i++) {
const cannonType = cannons[i];
for (let j = 0, length = cannonType.amountEquipped; j < length; j++) {
const minimum = ((cannonType.damage * ((100 + cannonType.scatter) / 100) + 1) * scaleSum) - 1;
const maximum = cannonType.damage * scaleSum;
if (id === cannonType.id) {
damage += Math.ceil((Math.random() * (maximum - minimum) + minimum) * ammunitionScheme.damage);
k += cannonType.amountEquipped;
};
};
};
return {
usedCannonCount: k,
value: damage,
critical: false,
usedAmmunition: ammunitionScheme
};
}
initializeAttackWithCannon(target) {
if (!target) return;
if (this.isAttacking()) return false;
const isInShootingRange = Utility.IsInDistance(this.position, target.position, this.cannonRange);
if (!isInShootingRange) return false;
if (target.id === this.id) return false;
if (!target.isAlive) return false;
if (this.hp.current <= 0) return false;
if (!this.behaviour.attackIfAttacked) return false;
if (!this.inventory.getCannons().some(c => c.amountEquipped)) return false;
if (target.typeID === ENTITY_TYPE_PLAYER) target.cancelDisconnect();
this.haltMovement();
this.target = target;
this.searchedTarget = null;
const attackerInformation = {
attackerEntry: this.id
};
target.attackers.push(attackerInformation);
const eventAttackBar = {
authorID: this.id,
authorTypeID: this.typeID,
isTarget: false,
source: 1
};
target.emit(EVENT_ATTACK_BAR, eventAttackBar);
this.cannonTypesInUse.forEach(c => this.setReusableSingleEvent(c.eventID, 0, { id: c.id }));
}
flush(timingEvent) {
const currentFlush = this.map.eventBuffer[this.id];
if (!currentFlush) return
this.emit("attacking", currentFlush.data, currentFlush.author);
delete this.map.eventBuffer[this.id];
}
attackWithCannon(event) {
if (!this.isAlive) return;
const { target } = this;
const cannonType = this.cannonTypesInUse.find(c => c.id === event.id);
const recheckShootingRange = Utility.IsInDistance(this.position, target.position, cannonType.range);
if (!recheckShootingRange) {
this.cannonTypesOutOfRange.push({
cannonID: cannonType.id,
lastFiredAtTimestamp: performance.now(),
event
});
if (this.cannonRange === cannonType.range) {
this.abortAttack();
} else {
this.setReusableSingleEvent(cannonType.eventID, cannonType.reload, event);
};
return true;
};
if (this.behaviour.stopAttackIfAbandoned && !this.behaviour.isAggressive && !this.attackers.length) return this.abortAttack();
if (target.isRepairing()) target.cancelRepair();
const damage = this.calculateCannonDamage(cannonType.id);
this.lastShotTickTimestamp = performance.now();
const healthUpdate = target.decreaseCurrentHitpoint(damage.value, {
authorTypeID: this.typeID,
authorID: this.id
});
// this.emit("attacking", attacking, this.id);
const flushBuffer = this.map.eventBuffer[this.id];
if (flushBuffer) flushBuffer.data.amount += healthUpdate.normalizedAmount;
else {
const attacking = {
authorID: this.id,
authorTypeID: this.typeID,
targetID: target.id,
targetTypeID: target.typeID,
amount: healthUpdate.normalizedAmount,
isCritical: damage.critical
};
this.map.eventBuffer[this.id] = {
data: attacking,
author: target.id,
target: this.id
};
this.map.eventAuthors.push(this.id);
/*this.flushBuffer[EVENT_ATTACKING] = {
data: attacking,
author: target.id
};*/
};
//this.flush(1);
if (healthUpdate.currentHitpoint <= 0) return;
this.setReusableSingleEvent(cannonType.eventID, cannonType.reload, event);
return true;
}
wipeAttack() {
const { target } = this;
if (!target) return
this.target = null;
this.untrackChasedTarget();
this.stopMovement();
this.determineMovementState();
this.cannonTypesInUse.forEach(c => this.cancelEvent(c.eventID));
}
abortAttack() {
const { target } = this;
if (!target) return;
if (this.behaviour.isChaser) {
this.chasingBehaviour.target = this.target;
this.chasingBehaviour.position = Object.assign({}, this.target.position);
};
this.target.attackers = target.attackers.filter(data => data.attackerEntry !== this.id);
this.target = null;
this.cannonTypesInUse.forEach(c => this.cancelEvent(c.eventID));
this.determineMovementState();
this.markActive();
this.map.lookingForTarget.push(this);
}
determineMovementState(preSelectedDestinationNode) {
if (!this.isAlive) return;
if (this.isDirty) return;
if (this.isMoving) return;
if (this.behaviour.stopIfAttacked && this.isAttacking()) return;
if (!this.behaviour.stopIfAttacked && this.isMoving) return;
if (this.behaviour.stopIfAttacked && this.attackers.length) return;
if (EFFECTS_TERMINATE_MOVEMENT.some(effect => this.activeEffects[effect])) return;
if (!this.isDirty && this.chasingBehaviour && this.chasingBehaviour.target
&& this.chasingBehaviour.position.x === this.position.x && this.chasingBehaviour.position.y === this.position.y) {
this.chasingBehaviour.position = {
x: this.chasingBehaviour.target.position.x,
y: this.chasingBehaviour.target.position.y
};
};
let destination = null;
if (this.group) destination = this.map.grid.randomNodeInArea(this.group.predefinedArea);
else if (preSelectedDestinationNode) destination = preSelectedDestinationNode;
else if (this.chasingBehaviour && this.chasingBehaviour.target) destination = this.chasingBehaviour.position;
else destination = null;
const predefinedArea = this.group ? this.group.predefinedArea : null;
this.emit(EVENT_DESTINATION_REACHED, {
destination,
predefinedArea
});
}
seekEnemy(searchedTarget) {
if (!searchedTarget) return;
if (!this.isAlive) return;
if (this.isAttacking()) return;
if (!this.behaviour.isAggressive) return;
if (!this.searchedTarget) return this.searchedTarget = searchedTarget;
let isBehaviourSatisfied = false;
switch (this.behaviour.selectTargetType) {
case 1:
isBehaviourSatisfied = searchedTarget.hp.current - this.searchedTarget.hp.current <= 0;
break;
case 2:
isBehaviourSatisfied = this.searchedTarget.hp.current - searchedTarget.hp.current <= 0;
break;
case 3:
isBehaviourSatisfied = searchedTarget.hp.maximum - this.searchedTarget.hp.maximum < 0;
break;
case 4:
isBehaviourSatisfied = this.searchedTarget.hp.maximum - searchedTarget.hp.maximum < 0;
break;
case 5:
isBehaviourSatisfied = searchedTarget.speed - this.searchedTarget.speed < 0;
break;
case 6:
isBehaviourSatisfied = this.searchedTarget.speed - searchedTarget.speed < 0;
break;
case 7:
isBehaviourSatisfied = searchedTarget.sight - this.searchedTarget.sight < 0;
break;
case 8:
isBehaviourSatisfied = this.searchedTarget.sight - searchedTarget.sight < 0;
break;
};
const isInShootingRange = Utility.IsInDistance(this.position, searchedTarget.position, this.cannonRange);
const isSelectedInShootingRange = Utility.IsInDistance(this.position, this.searchedTarget.position, this.cannonRange);
if ((isBehaviourSatisfied && isInShootingRange) || !isSelectedInShootingRange) return this.searchedTarget = searchedTarget;
}
trackChasedTarget() {
if (!this.chasingBehaviour || !this.chasingBehaviour.target) return;
const isInRange = Utility.IsInDistance(this.position, this.chasingBehaviour.target.position, this.chasingBehaviour.range);
if (!isInRange) {
this.untrackChasedTarget();
return this.isDirty = true;
};
const chasedPosition = this.chasingBehaviour.target.position;
if (chasedPosition.x === this.chasingBehaviour.position.x && chasedPosition.y === this.chasingBehaviour.position.y) return;
const threshold = Math.ceil((1000 - this.speed) / 150);
if (++this.chasingBehaviour.recalculateAccumulator === threshold) {
const isChasedTargetInShootingRange = Utility.IsInDistance(this.chasingBehaviour.position, chasedPosition, this.range);
if (!isChasedTargetInShootingRange) {
this.chasingBehaviour.position = {
x: chasedPosition.x,
y: chasedPosition.y
};
this.isDirty = true;
};
this.chasingBehaviour.recalculateAccumulator = 0;
};
}
untrackChasedTarget() {
if (!this.chasingBehaviour || !this.chasingBehaviour.target) return;
this.chasingBehaviour.target = null;
this.chasingBehaviour.position.x = 0;
this.chasingBehaviour.position.y = 0;
this.chasingBehaviour.recalculateAccumulator = 0;
}
move() {
const { position } = this;
const [currentX, currentY] = this.path[this.i];
const dx = currentX - position.x;
const dy = currentY - position.y;
position.x += dx;
position.y += dy;
if (this.group) {
this.emit("miniMapMove", {
id: this.id,
position: this.position
});
};
this.updateChunk();
this.markActive();
if (this.searchedTarget) {
const target = this.map.getEntityFromSubgroup(this.searchedTarget.id);
if (target) this.initializeAttackWithCannon(target);
else this.searchedTarget = null;
};
if (this.i === this.path.length - 1 || this.isDirty) {
this.untrackChasedTarget();
this.stopMovement();
this.determineMovementState();
} else {
const nextNode = this.path[++this.i];
const [x, y] = nextNode;
this.trackChasedTarget();
this.setEvent(TIMING_EVENT_MOVE, this.speed);
this.emit(EVENT_DESTINATION_NEW, {
id: this.id,
typeID: this.typeID,
x,
y
});
};
}
isAttacking = () => this.target ? true : false;
getProperties() {
const { hp, position, id, speed, size, typeID, entityTypeID, path, behaviourBits, isMoving, isAdmiral, isSpeedLowered } = this;
const activeEffectsBits = Object.values(this.activeEffects).reduce((prev, cur, i) => cur ? prev + Math.pow(2, i) : prev + 0, 0);
return { hp, position, id, speed, size, typeID, entityTypeID, path: path.length ? path[this.i] : [], behaviourBits, activeEffectsBits, isMoving, isAdmiral, isSpeedLowered };
}
};
module.exports = NPC;
+21
View File
@@ -0,0 +1,21 @@
class Limiter {
constructor(info) {
this.countThreshold = info.countThreshold;
this.timeThreshold = info.timeThreshold;
this.nextTime = 0;
this.time = 0;
this.count = 0;
};
update() {
if (performance.now() - this.time < this.timeThreshold) this.count++;
else {
this.time = performance.now();
this.count = 1;
};
return this.count > this.countThreshold;
}
};
module.exports = Limiter;
+1536
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
class PlayerBehaviour {
constructor(info) {
this.id = info.id;
this.size = info.size ?? 32;
this.entries = Array.from({ length: this.size }, () => ({ isProcessed: false, timestamp: Infinity, entry: {}, id: 0 }));
this.accumulator = 0;
this.buffer = info.buffer;
};
add(entry) {
const index = this.accumulator++ % this.size;
this.entries[index] = {
timestamp: Date.now(),
entry,
id: this.id,
isProcessed: false
};
}
save(threshold) {
const now = Date.now();
for (let i = 0; i < this.size; i++) {
const entry = this.entries[i];
if (entry.id === 0) continue;
if (entry.isProcessed) continue;
if (entry.timestamp + threshold < now) continue;
entry.isProcessed = true;
// require("../Miscellaneous/winston").error(entry)
this.buffer.push(entry.entry); // entry.entry
};
return this.buffer;
}
};
module.exports = PlayerBehaviour;
+68
View File
@@ -0,0 +1,68 @@
const EventEmitter = require("events");
const Reward = require("./Reward");
class Quest extends EventEmitter {
constructor(configuration, doneCallback) {
super();
this.id = configuration.id;
this.currentComplete = configuration.completedAmount;
this.maximumComplete = configuration.maximumComplete;
this.task = JSON.parse(JSON.stringify(configuration.task));
this.reward = new Reward({
distributionType: 2,
baseReward: configuration.reward
});
this.name = configuration.name || "Quest without a name";
this.finishedCount = 0;
this.totalTasksCount = Object.values(this.task).reduce((pre, cur) => pre + cur.length, 0);
this.doneCallback = doneCallback;
}
adjustTask(events, amount) {
events.forEach(type => {
const tasksInCategory = this.task[type.taskCategoryID];
if (!tasksInCategory) return
tasksInCategory.forEach(task => {
if (task.isFinished) return
if (type.typeID === task.typeID && type.entityTypeID === task.entityTypeID) {
task.amount += amount;
if (task.requiredAmount <= task.amount) {
task.amount = task.requiredAmount;
task.isFinished = true;
this.finishedCount++;
};
};
});
});
if (this.finishedCount === this.totalTasksCount) {
this.doneCallback(this.id);
};
}
getTasks() {
const taskTypes = Object.keys(this.task);
return taskTypes.map(t => this.task[t]);
}
getProgression() {
const taskTypes = Object.keys(this.task);
const progression = [];
taskTypes.forEach(t => this.task[t].forEach((c, i) => {
progression.push({ amount: c.amount, index: i, type: t });
}));
return progression;
}
};
module.exports = Quest;
+86
View File
@@ -0,0 +1,86 @@
const Quest = require("./Quest");
const quests = require("../Data/quests");
class Quests {
constructor(quests, doneCallback) {
let currentQuest = quests[0];
this.completedQuests = [];
this.finishedQuests = {};
this.completedQuestsAmount = {};
this.doneCallback = doneCallback;
this.list = {};
this.list = quests.reduce((result, entry) => {
this.completedQuestsAmount[entry.questID] = entry.completedAmount;
if (entry.state === 1 || entry.state === 2) return result;
const quest = this.add(entry.questID);
if (!quest) return result;
if (currentQuest.id !== entry.questID) {
result[entry.questID] = quest;
currentQuest = quest;
};
if (!entry.amount) return result;
const { typeID, entityTypeID } = result[entry.questID].task[entry.taskType][entry.taskIndex];
currentQuest.adjustTask([{
taskCategoryID: entry.taskType,
typeID,
entityTypeID
}], entry.amount);
return result;
}, {});
};
add(id) {
const questScheme = quests[id - 1];
if (!questScheme) return;
const isAcceptable = !questScheme.maximumComplete || questScheme.maximumComplete && questScheme.maximumComplete > this.completedQuestsAmount[id] || !this.completedQuestsAmount[id] && questScheme.maximumComplete;
if (!isAcceptable) return;
const isQuestAlreadyAdded = this.list[id];
if (isQuestAlreadyAdded) return true;
const quest = new Quest(questScheme, completedQuestID => {
if (questScheme.maximumComplete !== -1) this.completedQuests.push(completedQuestID);
if (!this.completedQuestsAmount[completedQuestID]) this.completedQuestsAmount[completedQuestID] = 1;
else this.completedQuestsAmount[completedQuestID]++;
const completedQuest = this.list[completedQuestID];
this.doneCallback(completedQuest);
this.remove(completedQuestID);
});
delete this.finishedQuests[id];
this.list[id] = quest;
return quest;
}
remove(id) {
const questScheme = quests[id - 1];
if (!questScheme) return;
this.finishedQuests[id] = true;
delete this.list[id];
}
has(id) {
return this.list[id] ? true : false;
}
refresh(pirateLevel) {
for (const id in this.list) {
const questScheme = quests[+id - 1];
if (questScheme.maximumLevel < pirateLevel || questScheme.minimumLevel > pirateLevel) this.remove(+id);
};
}
};
module.exports = Quests;
+134
View File
@@ -0,0 +1,134 @@
const Utility = require("./Utility");
const chooseWeightedRandom = require("../Utility/chooseWeightedRandom");
const uniqueCategories = {
5: true
};
const uniqueItems = {};
class Reward {
constructor(information) {
this.distributionType = information.distributionType;
this.baseReward = information.baseReward;
this.damage = information.damage;
};
single(amountOfItems, inventory, cb) {
if (this.distributionType !== 2) return;
const { baseReward } = this;
const allRewardCategories = Object.entries(baseReward);
const rewardChances = [];
const rewardEntries = [];
const finalRewards = [];
allRewardCategories.forEach(category => {
const [categoryName, categoryEntries] = category;
Object.entries(categoryEntries).forEach(subEntry => {
const [id, entry] = subEntry;
const isUniqueReward = uniqueCategories[categoryName] || uniqueItems[categoryName] && uniqueItems[categoryName][id];
const isAlreadyAcquired = inventory[categoryName] && inventory[categoryName].hasElement(+id);
if (isUniqueReward && isAlreadyAcquired) return;
const rewardEntry = {
categoryID: +categoryName,
id: +id,
amount: entry.amountMaximum ? Utility.RandomIntegerBetween(entry.amountMinimum, entry.amountMaximum) : entry.amountMinimum,
index: rewardEntries.length
};
if (entry.chance === 100) finalRewards.push(rewardEntry);
else {
rewardChances.push(entry.chance);
rewardEntries.push(rewardEntry);
};
});
});
const batchedRewards = {};
if (rewardEntries.length) {
for (let i = 0; i < amountOfItems; i++) {
const chosenItem = chooseWeightedRandom(rewardEntries, rewardChances);
const batchedEntryIndex = batchedRewards[`${chosenItem.categoryID}-${chosenItem.id}`];
if (batchedEntryIndex !== undefined) {
finalRewards[batchedEntryIndex].amount += chosenItem.amount;
} else {
batchedRewards[`${chosenItem.categoryID}-${chosenItem.id}`] = finalRewards.length;
finalRewards.push(Object.assign({}, chosenItem));
const isUniqueReward = uniqueCategories[chosenItem.categoryID] || uniqueItems[chosenItem.categoryID] && uniqueItems[chosenItem.categoryID][chosenItem.id];
if (isUniqueReward) rewardChances[chosenItem.index] = 0;
};
};
};
cb(finalRewards);
}
multiple(data, cb) {
if (this.distributionType !== 1) return;
const { baseReward } = this;
const { playerInventory, damage, maximum, lastShotAuthorID } = data;
const allRewardCategories = Object.keys(baseReward);
const totalParticipants = Object.keys(damage);
totalParticipants.forEach(playerID => {
playerID = parseInt(playerID);
const rewards = [];
allRewardCategories.forEach(category => {
category = parseInt(category);
const rew = baseReward[category];
Object.keys(rew).forEach(id => {
id = parseInt(id);
const isUniqueReward = uniqueCategories[category] || uniqueItems[category] && uniqueItems[category][id];
const isAlreadyAcquired = playerInventory[category] && playerInventory[category].hasElement(id);
if (isUniqueReward && isAlreadyAcquired) return;
if (rew[id].lastShot && playerID !== lastShotAuthorID) return;
if (rew[id].chance < Math.random() * 100) return;
const baseRewardAmount = rew[id].amountMaximum ? Utility.RandomIntegerBetween(rew[id].amountMinimum, rew[id].amountMaximum) : rew[id].amountMinimum;
const amountOfReward = rew[id].lastShot ? baseRewardAmount : Math.round(baseRewardAmount / 100 * (damage[playerID] / maximum * 100));
if (amountOfReward <= 0) return;
rewards.push({
categoryID: category,
id,
amount: amountOfReward
});
});
});
cb({ id: playerID }, rewards);
});
}
dynamic(cb) {
if (this.distributionType !== 3) return;
for (let i = 0, length = this.baseReward.length; i < length; i++) {
const reward = this.baseReward[i];
const baseRewardAmount = reward.amountMaximum ? Utility.RandomIntegerBetween(reward.amountMinimum, reward.amountMaximum) : reward.amountMinimum;
cb([{
categoryID: reward.categoryID,
id: reward.id,
amount: baseRewardAmount
}]);
};
}
};
module.exports = Reward;
+15
View File
@@ -0,0 +1,15 @@
class SessionManager {
constructor() {
this.startAt = Date.now();
this.endedAt = null;
this.currency = [0, 0, 0, 0]
this.kills = [0, 0, 0, 0, 0];
this.experience = 0;
this.sunkCount = 0;
this.packets = {};
this.ammunition = {};
this.harpoon = {};
};
};
module.exports = SessionManager;
+552
View File
@@ -0,0 +1,552 @@
const Entity = require("./Entity");
const Utility = require("./Utility");
const OVERTIME_HEALTH_EFFECTS = require("../Data/overtime");
const GUILD_TOWER_SCHEME = require("../Data/schemeTower");
const constants = require("../Data/constants");
const { EVENTS, TIMING_EVENTS, TYPES, MAXIMUM } = constants;
const { ENTITY_TYPE_TOWER, ENTITY_TYPE_PLAYER } = TYPES;
const { EVENT_ATTACK_STARTED, EVENT_DIE, EVENT_CANNONS_USED, EVENT_ATTACK_BAR, EVENT_HEALTH_CHANGED, EVENT_HEALTH_CHANGED_AUTHORLESS, EVENT_GUILD_ISLAND_CAPTURE } = EVENTS;
const { TIMING_EVENT_RESPAWN, TIMING_EVENT_REPAIR } = TIMING_EVENTS;
const { MAX_SAFE_INTEGER } = MAXIMUM;
const EVENTS_REMOVE_DEATH_TOWER = [9, 11, 12, 13, 17];
class Tower extends Entity {
constructor(info) {
super({
type: ENTITY_TYPE_TOWER,
id: info.id,
map: info.map,
isAlive: info.currentHitpoint !== 0,
position: info.position
});
if (!info.island) return;
this.order = info.order;
this.behaviour = {
attackIfAttacked: true,
isAggressive: true,
stopAttackIfAbandoned: false,
selectTargetType: 1
};
this.hp = {
maximum: info.maximumHitpoint,
current: info.currentHitpoint
};
this.island = info.island;
this.island.addTower(this);
this.entityTypeID = info.entityTypeID;
this.attackers = [];
this.totalDamageReceived = 0;
this.damageReceived = {};
this.activeEffects = {};
this.activeOvertimeHealthEffects = {};
this.dieCallback = info.dieCallback;
this.attackCallback = info.attackCallback;
this.map.towers.set(this.id, this);
setTimeout(() => {
// this.repair();
}, 15000);
};
trackOvertimeHealthEffect(id) {
const effect = this.activeOvertimeHealthEffects[id];
if (effect) effect.count++;
else this.activeOvertimeHealthEffects[id] = { count: 0 };
return effect ? effect.count : 0;
}
overtimeDecreaseCurrentHitpoint(event) {
if (!event) return;
const { id, authorTypeID, authorID, authorGuildID, damage, iid } = event;
const { amount, ticks, time } = OVERTIME_HEALTH_EFFECTS[id];
const decrease = this.decreaseCurrentHitpoint(damage * amount, {
authorTypeID,
authorID,
authorGuildID
});
if (!decrease) return;
const currentCount = this.trackOvertimeHealthEffect(id);
if (currentCount < ticks && decrease.currentHitpoint) this.setIndependentEvent(iid, time, event);
else delete this.activeOvertimeHealthEffects[id];
const data = {
targetTypeID: this.typeID,
targetID: this.id,
amount: decrease.normalizedAmount,
type: 0,
isCritical: false
};
this.emit(EVENT_HEALTH_CHANGED_AUTHORLESS, data, this.id);
}
initializeRepair() {
if (!this.isAlive) return;
if (this.isAtMaximumHitpoints()) return;
this.repair();
}
repair(doneIndex) {
const { id } = this;
this.lastRepairTickTimestamp = performance.now();
const healthUpdate = this.increaseCurrentHitpoint(Math.floor(Math.random() * 2000));
if (!healthUpdate.normalizedAmount) return console.info("aaa", healthUpdate);
const eventRepair = {
targetTypeID: ENTITY_TYPE_TOWER,
targetID: id,
amount: healthUpdate.normalizedAmount,
type: 1,
isCritical: false,
};
this.emit(EVENT_HEALTH_CHANGED_AUTHORLESS, eventRepair, this.id);
if (healthUpdate.currentHitpoint >= this.hp.maximum) return this.cancelRepair();
this.setReusableSingleEvent(TIMING_EVENT_REPAIR, 1000, doneIndex);
return true;
}
cancelRepair() {
if (!this.isRepairing()) return;
this.cancelEvent(TIMING_EVENT_REPAIR);
}
increaseCurrentHitpoint(amount) {
amount = Math.abs(amount);
const normalizedAmount = this.hp.current + amount <= this.hp.maximum ? amount : this.hp.maximum - this.hp.current;
this.hp.current += normalizedAmount;
const eventGuildIslandTowerHealthChanged = {
id: this.id,
entityTypeID: this.entityTypeID,
guildIslandID: this.island.id,
currentHitpoint: this.hp.current
};
this.emit("guildDataIslandTowerHealthChanged", eventGuildIslandTowerHealthChanged, this.id);
return {
normalizedAmount,
currentHitpoint: this.hp.current
};
}
decreaseCurrentHitpoint(amount, author) {
if (this.hp.current === 0) return console.info("couldn't decrease tower");
const { authorID, authorGuildID } = author;
amount = Math.abs(amount);
const damage = Math.min(this.hp.current, amount);
this.island.updateReceivedDamage(authorGuildID, damage);
if (this.damageReceived[authorID]) this.damageReceived[authorID] += damage;
else this.damageReceived[authorID] = damage;
this.totalDamageReceived += damage;
this.hp.current -= damage;
this.attackCallback({
typeID: this.typeID,
entityTypeID: this.entityTypeID,
authorID,
damage
});
const eventGuildIslandDamage = {
guildIslandID: this.island.id,
guildID: authorGuildID,
damage
};
const eventGuildIslandTowerHealthChanged = {
id: this.id,
entityTypeID: this.entityTypeID,
guildIslandID: this.island.id,
currentHitpoint: this.hp.current
};
this.emit("guildDataIslandTowerHealthChanged", eventGuildIslandTowerHealthChanged, this.id);
this.emit("_", eventGuildIslandDamage);
const isOverSafeLimit = this.totalDamageReceived >= MAX_SAFE_INTEGER;
if (isOverSafeLimit) this.totalDamageReceived = MAX_SAFE_INTEGER;
if (this.hp.current === 0 || isOverSafeLimit) {
const death = {
typeID: this.typeID,
lastShotTypeID: 1,
lastShotID: author.authorID,
targetID: this.id
};
this.die(death);
// this.setSingleEvent(11, 0, die);
};
return {
normalizedAmount: damage,
currentHitpoint: this.hp.current
};
}
initializeAttackWithCannon(selectedTarget) {
//const cannonsStatistics = this.getCannonsStatistics();
const isInShootingRange = Utility.IsInDistance(this.position, selectedTarget.position, 600);
//if (UNATTACKABLE_ENTITY_TYPES.includes(selectedTarget.typeID)) return
if (!isInShootingRange) return
//if (this.isRepairing()) return
// if (selectedTarget.typeID === ENTITY_TYPE_PLAYER && this.map.type === MAP_PVE) return
//const selected = this.inventory.getSelectedAmmunition();
//if (!selected || selected.amount <= 0) return
//if (ammunitions[this.inventory.selectedAmmunition].damage < 0 && selectedTarget.isAtMaximumHitpoints()) return
if (this.target && this.target.id === selectedTarget.id) return
if (selectedTarget.id === this.id) return
if (!selectedTarget.isAlive) return
if (!this.isAlive) return
//if (selectedTarget.typeID === ENTITY_TYPE_PLAYER && selectedTarget.guild.id === this.island.guild.id) return
//if (!this.inventory.getCannons().some(c => c.amountEquipped)) return
if (this.target && this.target.id !== selectedTarget.id) this.abortAttack();
const attackInformation = {
attackerEntry: this.id
};
selectedTarget.attackers.push(attackInformation);
this.target = selectedTarget;
this.searchedTarget = null;
/*if (selectedTarget.typeID === ENTITY_TYPE_PLAYER) {
const attacking = {
authorID: this.id,
authorTypeID: this.typeID,
};
selectedTarget.emit(EVENT_ATTACK_STARTED, attacking, this.id);
};*/
const attacking = {
authorID: this.id,
authorTypeID: this.typeID,
isTarget: false,
source: 1
};
this.emit(EVENT_ATTACK_STARTED, attacking, this.id);
selectedTarget.emit(EVENT_ATTACK_BAR, attacking, selectedTarget.id);
this.setReusableSingleEvent(97, 0, { id: 1 });
//this.cannonTypesInUse = cannonsStatistics.distinctCannonTypes;
//this.maximumCannonRange = cannonsStatistics.maximumCannonRange;
//cannonsStatistics.distinctCannonTypes.forEach(c => this.setReusableSingleEvent(c.eventID, 0, { id: c.id }));
}
attackWithCannon(event) {
const { target } = this;
if (!target) return;
const recheckShootingRange = Utility.IsInDistance(this.position, target.position, 600);
if (!recheckShootingRange) {
this.abortAttack();
return true;
};
this.lastShotTickTimestamp = performance.now();
const attackingAnimation = {
authorSessionID: this.sessionID,
authorID: this.id,
authorTypeID: this.typeID,
targetID: target.id,
targetTypeID: target.typeID,
entityTypeID: target.entityTypeID
};
const damage = //this.calculateCannonDamage(1);
{ value: 2000 }
const attacking = {
authorID: this.id,
authorTypeID: this.typeID,
targetID: target.id,
targetTypeID: target.typeID,
amount: 0,
isCritical: false,
type: damage.value <= 0
};
const healthUpdate = damage.value >= 0 ?
target.decreaseCurrentHitpoint(damage.value, {
authorTypeID: this.typeID,
authorID: this.id,
authorGuildID: this.guild ? this.guild.id : 0
}) : target.increaseCurrentHitpoint(damage.value);
attacking.amount = healthUpdate.normalizedAmount;
attacking.isCritical = damage.critical;
const flushBuffer = this.map.eventBuffer[this.id];
if (flushBuffer) flushBuffer.data[0].amount += attacking.amount;
else {
const data = [attacking, attackingAnimation];
this.map.eventBuffer[this.id] = {
data,
author: target.id,
target: this.id
};
this.map.eventAuthors.push(this.id);
};
this.emit(EVENT_CANNONS_USED, {
amount: damage.usedCannonCount,
type: 0
}, this.id);
if (healthUpdate.currentHitpoint <= 0) return;
this.setReusableSingleEvent(97, 2000, event);
return true;
}
removeTarget() {
if (!this.target) return;
this.target.attackers = this.target.attackers.filter(data => data.attackerEntry !== this.id);
this.target = null;
}
wipeAttack() {
const { target } = this;
if (!target) return;
this.cancelEvent(97);
this.target = null;
}
abortAttack() {
if (!this.isAttacking()) return;
if (!this.target) return;
this.cancelEvent(97);
const target = {
id: this.target.id,
typeID: this.target.typeID
};
this.removeTarget();
return target;
}
die(eventDeath) {
super.die();
console.trace("tower death")
this.hp.current = 0;
this.attackers.forEach(data => {
const attackerEntity = this.map.getEntityFromSubgroup(data.attackerEntry);
attackerEntity.wipeAttack();
});
this.attackers.length = 0;
const eventGuildIslandTowerDestroyed = {
id: this.id,
entityTypeID: this.entityTypeID,
guildIslandID: this.island.id
};
this.emit(EVENT_DIE, [eventDeath, eventGuildIslandTowerDestroyed], this.id);
const admiralProperties = {
typeID: this.typeID,
entityTypeID: this.entityTypeID,
parentEntityTypeID: this.parentEntityTypeID,
id: this.id,
isAdmiral: this.isAdmiral,
authorID: eventDeath.lastShotID
};
this.abortAttack();
this.dieCallback(admiralProperties);
EVENTS_REMOVE_DEATH_TOWER.forEach(e => this.removeEvent(e));
const updatedOwnership = this.island.updateOwnership();
if (updatedOwnership) {
const { guildOld, guildNew, topicOld, topicNew } = updatedOwnership;
const eventGuildIslandCaptured = {
topicOld: topicOld,
topicNew: topicNew,
packet: {
tag: guildNew.tag
},
database: {
newID: guildNew.id,
oldID: guildOld.id
}
};
this.emit(EVENT_GUILD_ISLAND_CAPTURE, eventGuildIslandCaptured, this.id);
};
this.damageReceived = {};
}
seekEnemy(searchedTarget) {
if (!searchedTarget) return;
if (!this.isAlive) return;
if (this.isAttacking()) return;
if (!this.behaviour.isAggressive) return;
if (this.island.guild.id && this.island.guild.id === searchedTarget.guild.id) return;
if (!this.searchedTarget) return this.searchedTarget = searchedTarget;
let isBehaviourSatisfied = false;
switch (this.behaviour.selectTargetType) {
case 1:
isBehaviourSatisfied = searchedTarget.hp.current - this.searchedTarget.hp.current <= 0;
break;
case 2:
isBehaviourSatisfied = this.searchedTarget.hp.current - searchedTarget.hp.current <= 0;
break;
case 3:
isBehaviourSatisfied = searchedTarget.hp.maximum - this.searchedTarget.hp.maximum < 0;
break;
case 4:
isBehaviourSatisfied = this.searchedTarget.hp.maximum - searchedTarget.hp.maximum < 0;
break;
case 5:
isBehaviourSatisfied = searchedTarget.speed - this.searchedTarget.speed < 0;
break;
case 6:
isBehaviourSatisfied = this.searchedTarget.speed - searchedTarget.speed < 0;
break;
case 7:
isBehaviourSatisfied = searchedTarget.sight - this.searchedTarget.sight < 0;
break;
case 8:
isBehaviourSatisfied = this.searchedTarget.sight - searchedTarget.sight < 0;
break;
};
const isInShootingRange = Utility.IsInDistance(this.position, searchedTarget.position, 600);
const isSelectedInShootingRange = Utility.IsInDistance(this.position, this.searchedTarget.position, 600);
if ((isBehaviourSatisfied && isInShootingRange) || !isSelectedInShootingRange) return this.searchedTarget = searchedTarget;
}
respawn() {
if (this.isAlive) return;
this.entityTypeID = 1;
const baseScheme = GUILD_TOWER_SCHEME[this.entityTypeID];
if (!baseScheme) return;
this.hp.maximum = baseScheme.maximumHitpoints;
this.hp.current = this.hp.maximum;
super.respawn({
position: this.position
});
const eventTowerBuild = {
id: this.id,
entityTypeID: this.entityTypeID,
guildIslandID: this.island.id
};
this.emit("guildDataIslandTowerBuilt", eventTowerBuild, this.id);
console.info("guildDataIslandTowerBuilt")
this.markActive();
}
upgrade(entityTypeID) {
if (!this.isAlive) {
this.respawn();
return true;
};
const baseScheme = GUILD_TOWER_SCHEME[entityTypeID];
if (!baseScheme) return false;
const isNextLevelTarget = entityTypeID - this.entityTypeID === 1;
if (isNextLevelTarget) {
this.hp.maximum = baseScheme.maximumHitpoints;
this.hp.current = baseScheme.maximumHitpoints;
this.entityTypeID = entityTypeID;
} else return false;
const eventTowerBuild = {
id: this.id,
entityTypeID: this.entityTypeID,
guildIslandID: this.island.id
};
this.emit("guildDataIslandTowerBuilt", eventTowerBuild, this.id);
return true;
}
getProperties() {
const { position, id, hp, type, typeID, isAdmiral, entityTypeID, island, order } = this;
return { position, id, hp, type, typeID, isAdmiral, entityTypeID, guildTag: island.guild ? island.guild.tag : null, order };
}
isAtMaximumHitpoints() {
return this.hp.current === this.hp.maximum;
}
isRepairing() {
const event = this.events[TIMING_EVENT_REPAIR];
return event && !event[event.length - 1].isCancelled ? true : false;
}
isAttacking = () => this.target ? true : false;
};
module.exports = Tower;
+11
View File
@@ -0,0 +1,11 @@
class Utility {
static RandomIntegerBetween(minimum, maximum) {
return Math.floor(Math.random() * (maximum - minimum + 1) + minimum);
}
static IsInDistance(basePoint, targetPoint, range) {
return (basePoint.x - targetPoint.x) ** 2 + (basePoint.y - targetPoint.y) ** 2 <= range ** 2;
}
};
module.exports = Utility;
+54
View File
@@ -0,0 +1,54 @@
const EntityNewPacket = require("../Packets/EntityNew");
const EntityDiePacket = require("../Packets/EntityDie");
const constants = require("../Data/constants");
const { TYPES, EVENTS } = constants;
const { ENTITY_TYPE_COLLECTABLE } = TYPES;
const { EVENT_DIE } = EVENTS;
const queries = require("../Data/queries");
const { QUERY_FETCH_COLLECTABLE_SPAWN } = queries;
const Collectable = require("../Game/Collectable");
module.exports = async function (map, globalDieCallback, globalRewardCallback) {
const databaseSpawn = await execute(QUERY_FETCH_COLLECTABLE_SPAWN, [map.id, ENTITY_TYPE_COLLECTABLE]);
if (databaseSpawn.error) return process.exit();
const spawns = databaseSpawn.data.reduce((spawns, spawn) => {
if (!spawns[spawn.id]) {
spawns[spawn.id] = {
id: spawn.id,
amount: spawn.amount,
respawnTime: spawn.respawnTime,
reward: []
};
};
if (spawn.categoryId) spawns[spawn.id].reward.push({
categoryId: spawn.categoryId,
itemId: spawn.itemId,
min: spawn.min,
max: spawn.max,
chance: spawn.chance,
isLastShot: spawn.isLastShot
});
return spawns;
}, {});
const convertedSpawn = Object.values(spawns);
map.registerCollectable(convertedSpawn, collectablesData => {
collectablesData.forEach(data => {
const { collectable, collectableSpawnProperties } = data;
const packet = EntityNewPacket.collectable(collectableSpawnProperties);
map.registerNetworkPacket(collectable.id, packet);
collectable.on(EVENT_DIE, function (data, author) {
const packet = EntityDiePacket(data);
map.registerNetworkPacket(author, packet, [1]);
});
});
}, globalDieCallback, globalRewardCallback, Collectable);
};
+26
View File
@@ -0,0 +1,26 @@
const AdmiralSpawnedPacket = require("../Packets/AdmiralSpawned");
const Game = require("../Game/Game");
const { maps } = Game;
const nonPlayerCharacterHandler = require("./NPC");
const monsterHandler = require("./Monster");
const collectableHandler = require("./Collectable");
const towerHandler = require("./Tower");
const die = require("../Callbacks/die");
const reward = require("../Callbacks/reward");
const attack = require("../Callbacks/attack");
maps.forEach(map => {
nonPlayerCharacterHandler(map, die, reward, attack);
collectableHandler(map, die, reward);
monsterHandler(map, die, reward, attack);
towerHandler(map, die, reward, attack);
map.on("admiralSpawnNotification", function (data, emittingTo) {
const packet = AdmiralSpawnedPacket(data);
emittingTo.forEach(mapID => app.publish(mapID.toString(), packet, true, true));
});
});
+72
View File
@@ -0,0 +1,72 @@
const EntityNewPacket = require("../Packets/EntityNew");
const AttackInformationPacket = require("../Packets/AttackInformation");
const AttackerInformationPacket = require("../Packets/AttackerInformation");
const EntityDiePacket = require("../Packets/EntityDie");
const constants = require("../Data/constants");
const { TYPES, EVENTS } = constants;
const { ENTITY_TYPE_MONSTER } = TYPES;
const { EVENT_ATTACK_STARTED, EVENT_DIE, EVENT_ATTACK_BAR } = EVENTS;
const queries = require("../Data/queries");
const { QUERY_FETCH_MONSTER_SPAWN } = queries;
const Monster = require("../Game/Monster");
module.exports = async function (map, globalDieCallback, globalRewardCallback, globalAttackCallback) {
const databaseSpawn = await execute(QUERY_FETCH_MONSTER_SPAWN, [map.id, ENTITY_TYPE_MONSTER]);
if (databaseSpawn.error) return process.exit();
const spawns = databaseSpawn.data.reduce((spawns, spawn) => {
if (!spawns[spawn.id]) {
spawns[spawn.id] = {
id: spawn.id,
parentEntityTypeId: spawn.parentEntityTypeId,
name: spawn.name,
minHealth: spawn.minHealth,
maxHealth: spawn.maxHealth,
amount: spawn.amount,
respawnTime: spawn.respawnTime,
rewardEntries: []
};
};
if (spawn.categoryId) spawns[spawn.id].rewardEntries.push({
categoryId: spawn.categoryId,
itemId: spawn.itemId,
min: spawn.min,
max: spawn.max,
chance: spawn.chance,
isLastShot: spawn.isLastShot
});
return spawns;
}, {});
const convertedSpawn = Object.values(spawns);
map.setupMonsterSpawn(convertedSpawn,
function monsterNetworkListeners (data) {
const { monster, monsterSpawnProperties } = data;
const packet = EntityNewPacket.monster(monsterSpawnProperties);
map.registerNetworkPacket(monster.id, packet);
monster.on(EVENT_ATTACK_STARTED, function (data, author) {
const packet = AttackInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
monster.on(EVENT_ATTACK_BAR, function (data, author) {
const packet = AttackerInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
monster.on(EVENT_DIE, function (data, author) {
const packet = EntityDiePacket(data);
this.map.registerNetworkPacket(author, packet, [1]);
});
}, globalDieCallback, globalRewardCallback, globalAttackCallback, Monster);
};
+78
View File
@@ -0,0 +1,78 @@
const getFixedDate = require("../Utility/getFixedDate");
const Game = require("../Game/Game");
const { maps } = Game;
const constants = require("../Data/constants");
const { TYPES } = constants;
const { ENTITY_TYPE_NPC } = TYPES;
const queries = require("../Data/queries");
const { QUERY_FETCH_NPC_SPAWN } = queries;
const NPCWorker = global.NPCWorker;
const NPC = require("../Game/NPC");
const nonPlayerCharacterNetworkListeners = require("./NPCTest");
const messages = {
1: function (data) {
const { path, id, mapID } = data;
const map = maps[mapID - 1];
const npc = map.npcs.get(id);
if (!npc) return;
npc.startMovement(path);
}
};
NPCWorker.on("message", data => {
const { messageId } = data;
const event = messages[messageId];
if (event) event(data);
});
module.exports = async function (map, globalDieCallback, globalRewardCallback, globalAttackCallback) {
const databaseSpawn = await execute(QUERY_FETCH_NPC_SPAWN, [map.id, ENTITY_TYPE_NPC]);
if (databaseSpawn.error) return process.exit();
const spawns = databaseSpawn.data.reduce((spawns, spawn) => {
if (!spawns[spawn.id]) {
spawns[spawn.id] = {
id: spawn.id,
parentEntityTypeId: spawn.parentEntityTypeId,
name: spawn.name,
speed: spawn.speed,
minHealth: spawn.minHealth,
maxHealth: spawn.maxHealth,
amount: spawn.amount,
respawnTime: spawn.respawnTime,
rewardEntries: []
};
};
if (spawn.categoryId) spawns[spawn.id].rewardEntries.push({
categoryId: spawn.categoryId,
itemId: spawn.itemId,
min: spawn.min,
max: spawn.max,
chance: spawn.chance,
isLastShot: spawn.isLastShot
});
return spawns;
}, {});
const convertedSpawn = Object.values(spawns);
map.setupNonPlayerCharacterSpawn(convertedSpawn, nonPlayerCharacterNetworkListeners, globalDieCallback, globalRewardCallback, globalAttackCallback, NPC);
const event = {
fn: map.spawnEntityGroups.bind(map),
arguments: [nonPlayerCharacterNetworkListeners, NPC, globalAttackCallback, globalDieCallback],
next: Game.eventsFixed.add.bind(Game.eventsFixed)
};
const nextRunDate = getFixedDate(30);
Game.eventsFixed.add(nextRunDate, event);
};
+93
View File
@@ -0,0 +1,93 @@
const EntityNewPacket = require("../Packets/EntityNew");
const AttackInformationPacket = require("../Packets/AttackInformation");
const AttackerInformationPacket = require("../Packets/AttackerInformation");
const AttackingPacket = require("../Packets/Attacking");
const AttackAbortedPacket = require("../Packets/AttackAborted");
const EntityDiePacket = require("../Packets/EntityDie");
const EffectAddPacket = require("../Packets/EffectAdd");
const EntitySpeedChangedPacket = require("../Packets/EntitySpeedChanged");
const HealthChangedPacket = require("../Packets/HealthChanged");
const HealthChangedAuthorlessPacket = require("../Packets/HealthChangedAuthorless");
const MiniMapEntryMovePacket = require("../Packets/MiniMapEntryMove");
const MiniMapEntryRemovePacket = require("../Packets/MiniMapEntryRemove");
const NewDestinationNodePacket = require("../Packets/NewDestinationNode");
const constants = require("../Data/constants");
const { EVENTS } = constants;
const { EVENT_DESTINATION_REACHED, EVENT_ATTACK_STARTED, EVENT_HEALTH_CHANGED, EVENT_ATTACKING, EVENT_ATTACK_ABORTED_BY_USER, EVENT_DIE, EVENT_SPEED_UPDATE, EVENT_DESTINATION_NEW, EVENT_EFFECT_ADD, EVENT_ATTACK_BAR } = EVENTS;
module.exports = function nonPlayerCharacterNetworkListeners(map, data) {
const { npc, npcSpawnProperties } = data;
const packet = EntityNewPacket.npc(npcSpawnProperties);
map.registerNetworkPacket(npc.id, packet.buffer ? packet.buffer : packet);
npc.on(EVENT_DESTINATION_REACHED, function (data) {
NPCWorker.postMessage({
position: npc.position,
mapID: map.id,
id: npc.id,
preSelectedDestinationNode: data.destination,
predefinedArea: data.predefinedArea
});
});
npc.on(EVENT_ATTACK_STARTED, function (data, author) {
const packet = AttackInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
npc.on(EVENT_ATTACK_BAR, function (data, author) {
const packet = AttackerInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
npc.on(EVENT_HEALTH_CHANGED, function (data, author) {
const packet = HealthChangedAuthorlessPacket(data);
this.map.registerNetworkPacket(this.id, packet);
});
npc.on(EVENT_ATTACKING, function (data, author) {
const _packet = AttackingPacket(data);
const packet = HealthChangedPacket(data);
this.map.registerNetworkPacket(this.id, [_packet, packet], [1, 1]);
});
npc.on(EVENT_ATTACK_ABORTED_BY_USER, function (data, author) {
const packet = AttackAbortedPacket(data);
this.map.registerNetworkPacket(this.id, packet);
});
npc.on(EVENT_DIE, function (data, author) {
const packet = EntityDiePacket(data);
this.map.registerNetworkPacket(this.id, packet, [1]);
});
npc.on(EVENT_SPEED_UPDATE, function (data, author) {
const packet = EntitySpeedChangedPacket(data);
this.map.registerNetworkPacket(this.id, packet);
});
npc.on(EVENT_DESTINATION_NEW, function (data, author) {
const packet = NewDestinationNodePacket(data);
this.map.registerNetworkPacket(this.id, packet, [59]);
});
npc.on(EVENT_EFFECT_ADD, function (data, author) {
const packet = EffectAddPacket(data);
this.map.registerNetworkPacket(this.id, packet);
});
npc.on("miniMapDotRemoveTest", function (data, author) {
const packet = MiniMapEntryRemovePacket(data);
this.map.registerGlobalNetworkPacket(this.id, packet);
});
npc.on("miniMapMove", function (data, author) {
const packet = MiniMapEntryMovePacket(data);
this.map.registerGlobalNetworkPacket(this.id, packet);
});
npc.determineMovementState();
};
+364
View File
@@ -0,0 +1,364 @@
const MainPlayerPacket = require("../Packets/MainPlayer");
const QuestRemovalPacket = require("../Packets/QuestRemoval");
const EntityOldPacket = require("../Packets/EntityOld");
const EntityNewPacket = require("../Packets/EntityNew");
const AmmunitionUsedPacket = require("../Packets/AmmunitionUsed");
const LogoutCancelledPacket = require("../Packets/LogoutCancelled");
const PropertyChangedPacket = require("../Packets/PropertyChanged")
const AttackInformationPacket = require("../Packets/AttackInformation");
const AttackingPacket = require("../Packets/Attacking");
const AttackAbortedPacket = require("../Packets/AttackAborted");
const AttackerInformationPacket = require("../Packets/AttackerInformation");
const EntityDiePacket = require("../Packets/EntityDie");
const RespawnConfirmationPacket = require("../Packets/RespawnConfirmation");
const EffectAddPacket = require("../Packets/EffectAdd");
const EntitySpeedChangedPacket = require("../Packets/EntitySpeedChanged");
const MapChangeStatePacket = require("../Packets/MapChangeState");
const MapChangedPacket = require("../Packets/MapChanged");
const LevelUpPacket = require("../Packets/LevelUp");
const LevelIsLowPacket = require("../Packets/LevelIsLow");
const HealthChangedAuthorlessPacket = require("../Packets/HealthChangedAuthorless");
const HealthChangedPacket = require("../Packets/HealthChanged");
const ItemUsedPacket = require("../Packets/ItemUsed");
const NewDestinationNodePacket = require("../Packets/NewDestinationNode");
const GuildMemberNewPacket = require("../Packets/GuildMemberNew");
const GuildDataMemberChangedPacket = require("../Packets/GuildDataMemberChanged");
const quests = require("../Data/quests");
const constants = require("../Data/constants");
const Game = require("../Game/Game");
const { players } = Game;
const { TYPES } = constants;
const { ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_MONSTER, ENTITY_TYPE_TOWER, ENTITY_TYPE_COLLECTABLE } = TYPES;
const [playerEconomy] = require("../Data/batchSave");
// just to add "lag"
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
};
global.PlayerWorker.on("message", data => {
const player = players[data.id];
if (!player) return;
if (player.map.id !== data.mapId) return;
if (player.movementRequestTracker !== data.movementRequestTracker) return;
player.startingPosition = data.startingPosition;
const { path } = data;
if (!player.isMoving) player.initializeMovement(path, data);
else player.emit("scheduledPathReady", path, data);
});
module.exports.scheduledPathReady = function (path, data) {
if (path.length) {
this.data = data;
this.scheduledPath = path;
this.isNewPathReady = true;
} else this.isLastNodeFinished = true;
if (this.isNewPathReady && this.isLastNodeFinished) {
this.isNewPathReady = false;
this.isLastNodeFinished = false;
this.initializeMovement(this.scheduledPath, this.data);
this.scheduledPath = [];
};
};
module.exports.updateVisibility = function (entityProps) {
const { type, data } = entityProps;
let packet = null;
if (type) {
switch (data.typeID) {
case ENTITY_TYPE_PLAYER: packet = EntityNewPacket.player(data); break;
case ENTITY_TYPE_NPC: packet = EntityNewPacket.npc(data); break;
case ENTITY_TYPE_MONSTER: packet = EntityNewPacket.monster(data); break;
case ENTITY_TYPE_TOWER: packet = EntityNewPacket.tower(data); break;
case ENTITY_TYPE_COLLECTABLE: packet = EntityNewPacket.collectable(data); break;
};
} else packet = EntityOldPacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet.buffer ? packet.buffer : packet, [packet.metadata]);
};
module.exports.disconnect = async function (data) {
const { map, position, hp, inventory, textureID } = this;
const playerID = this.id;
const oldEntityID = {
typeID: this.typeID,
id: playerID
};
const packet = EntityOldPacket(oldEntityID);
this.map.registerNetworkPacket(this.id, packet, [1]);
if (this.guild.id) {
const guildDataMemberChanged = {
id: playerID,
isOnline: false
};
const packet = GuildDataMemberChangedPacket(guildDataMemberChanged);
const topic = Game.getTopic(this.guild.topic);
topic.push(packet);
};
this.isUnderDisconnect = true;
if (!this.hasUnexpectedlyLeft) {
socket.sockets[this.sessionID].end();
delete socket.sockets[this.sessionID];
console.info("player handler end socket", performance.now());
};
const ammunition = inventory.getAmmunitions();
const harpoon = inventory.getHarpoons();
const economy = inventory.getEconomy();
const items = inventory.getItems();
const designs = inventory.getDesignIDs();
const cannons = inventory.getCannons();
const harpooners = inventory.getHarpooners();
const resources = [];
const equipments = [];
economy.forEach(e => {
playerEconomy.removeItem(`${playerID}-1-${e.id}`);
resources.push([playerID, 1, e.id, e.amount])
});
items.forEach(i => {
playerEconomy.removeItem(`${playerID}-2-${i.id}`);
resources.push([playerID, 2, i.id, i.amount])
});
ammunition.forEach(a => {
playerEconomy.removeItem(`${playerID}-3-${a.id}`);
resources.push([playerID, 3, a.id, a.amount])
});
harpoon.forEach(h => {
playerEconomy.removeItem(`${playerID}-4-${h.id}`);
resources.push([playerID, 4, h.id, h.amount])
});
designs.forEach(id => {
playerEconomy.removeItem(`${playerID}-5-${id}`);
resources.push([playerID, 5, id, 1])
});
this.statusPoints.forEach(s => {
playerEconomy.removeItem(`${playerID}-6-${s.id}`);
resources.push([playerID, 6, s.id, s.amount])
});
cannons.forEach(c => {
playerEconomy.removeItem(`${playerID}-7-${c.id}`);
resources.push([playerID, 7, c.id, c.amount]);
equipments.push([playerID, 7, c.id, c.amountEquipped]);
});
harpooners.forEach(h => {
playerEconomy.removeItem(`${playerID}-8-${h.id}`);
resources.push([playerID, 8, h.id, h.amount]);
equipments.push([playerID, 8, h.id, h.amountEquipped])
});
// await delay(20000)
Object.entries(this.quests.completedQuestsAmount).forEach(async q => {
const [questID, amount] = q;
const questScheme = quests[questID - 1];
const isQuestAccepted = this.quests.has(questID);
let questState = 0;
if (questScheme.maximumComplete && amount >= questScheme.maximumComplete) questState = 2;
else if (!isQuestAccepted) questState = 1;
else questState = 0;
await execute("INSERT INTO quests (playerID, questID, state, completedAmount) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE state = ?, completedAmount = ?", [playerID, questID, questState, amount, questState, amount]);
if (isQuestAccepted) {
const quest = this.quests.list[questID];
const questProgression = quest.getProgression();
questProgression.forEach(async progression => {
const { amount, index, type } = progression;
await execute("INSERT INTO questprogression (playerID, questID, amount, taskIndex, taskType) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?", [playerID, quest.id, amount, index, type, amount]);
});
};
});
const finishedQuestsID = Object.keys(this.quests.finishedQuests);
if (finishedQuestsID.length) await execute("UPDATE questprogression SET amount = 0 WHERE questID IN (?)", finishedQuestsID);
await execute("UPDATE playerdata SET mapID = ?, x = ?, y = ?, currentHitpoints = ?, maximumHitpoints = ?, selectedAmmunitionID = ?, selectedHarpoonID = ?, designID = ? WHERE playerID = ?", [map.id, position.x, position.y, hp.current, hp.maximum, inventory.selectedAmmunition, inventory.selectedHarpoon, textureID, playerID]);
if (resources.length) await query(`INSERT INTO resources (playerID, categoryID, itemID, amount) VALUES ? ON DUPLICATE KEY UPDATE amount = VALUES(amount)`, [resources]);
const activeStatusEffects = this.activeStatusEffects.length ? this.activeStatusEffects : [0];
const activeStatusEffectsFormatted = this.activeStatusEffects.map(e => [playerID, e, 1]);
if (this.activeStatusEffects.length) await query(`INSERT INTO status (playerID, statusID, isActive) VALUES ? ON DUPLICATE KEY UPDATE isActive = ?`, [activeStatusEffectsFormatted, 1]);
await query(`UPDATE status SET isActive = ? WHERE statusID NOT IN (?) AND playerID = ?`, [0, activeStatusEffects, playerID]);
if (equipments.length) await query("INSERT INTO equipments (playerID, categoryID, itemID, amountEquipped) VALUES ? ON DUPLICATE KEY UPDATE amountEquipped = VALUES(amountEquipped)", [equipments]);
if (this.isUnderDisconnect) {
console.info("player handler disconnect finished", performance.now());
delete players[playerID];
}
};
module.exports.disconnectCancel = function() {
const packet = LogoutCancelledPacket();
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.healthChangedAuthorless = function (data, author) {
const packet = HealthChangedAuthorlessPacket(data);
this.map.registerNetworkPacket(author, packet);
};
module.exports.attackBar = function (data, author) {
const packet = AttackerInformationPacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.attackStarted = function (data, author) {
const packet = AttackInformationPacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.attacking = function (data, author) {
const packet = AttackingPacket(data[1]);
const packett = HealthChangedPacket(data[0]);
this.map.registerNetworkPacket(author, [packet, packett], [1, 1]);
};
module.exports.attackAbortedByUser = function (data, author) {
const packet = AttackAbortedPacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.die = function (data, author) {
const packet = EntityDiePacket(data);
this.map.registerNetworkPacket(author, packet, [19]);
};
module.exports.propertyChanged = function (data, author) {
const packet = PropertyChangedPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
};
module.exports.ableToJump = function (data) {
const packet = MapChangeStatePacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.unableToJump = function (data) {
const packet = MapChangeStatePacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.mapChanged = function (data, author) {
const selfSwitchPacket = MapChangedPacket(data.newMap);
this.map.registerIndividualNetworkPacket(this.id, selfSwitchPacket);
const socketInstance = socket.sockets[this.sessionID];
socketInstance.unsubscribe(data.oldMap.id.toString());
socketInstance.subscribe(data.newMap.mapID.toString());
};
module.exports.levelUp = function (data) {
if (this.guild.id) {
const guildDataMemberChanged = {
id: this.id,
level: data.level
};
const guildDataMemberChangedPacket = GuildDataMemberChangedPacket(guildDataMemberChanged);
const topic = Game.getTopic(this.guild.topic);
topic.push(guildDataMemberChangedPacket);
}
const packet = LevelUpPacket(data);
this.map.registerNetworkPacket(this.id, packet);
};
module.exports.levelIsLow = function() {
const packet = LevelIsLowPacket();
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.questDone = async function (data) {
await execute("INSERT INTO quests (playerID, questID, state, completedAmount) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE state = ?, completedAmount = completedAmount + 1", [this.id, data.questID, 1, 1, 1]);
const packet = QuestRemovalPacket(data);
this.map.registerIndividualNetworkPacket(this.id, packet);
};
module.exports.testOvertimeDamage = function (data, author) {
const packet = HealthChangedPacket(data);
this.map.registerNetworkPacket(author, packet);
};
module.exports.itemUsed = function (data, author) {
const packet = ItemUsedPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
};
module.exports.cannonsUsed = function (data, author) {
const packet = AmmunitionUsedPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
};
module.exports.harpoonersUsed = function (data, author) {
const packet = AmmunitionUsedPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
};
module.exports.newDestination = function (data) {
const packet = NewDestinationNodePacket(data);
this.map.registerNetworkPacket(this.id, packet);
};
module.exports.effectAdd = function (data) {
const packet = EffectAddPacket(data);
this.map.registerNetworkPacket(this.id, packet);
};
module.exports.speedUpdate = function (data) {
const packet = EntitySpeedChangedPacket(data);
this.map.registerNetworkPacket(this.id, packet);
};
module.exports.respawn = async function() {
const respawnPacket = RespawnConfirmationPacket();
this.map.registerIndividualNetworkPacket(this.id, respawnPacket);
const respawningPlayerProperties = this.getProperties();
const respawningPlayerPacket = MainPlayerPacket(respawningPlayerProperties);
const packetsToSend = [respawningPlayerPacket];
if (this.guild && this.guild.id) {
const guildProperties = {
playerID: this.id,
tag: this.guild.tag
};
const guildPacket = GuildMemberNewPacket(guildProperties);
packetsToSend.push(guildPacket);
};
this.map.registerIndividualNetworkPacket(this.id, packetsToSend);
};
+184
View File
@@ -0,0 +1,184 @@
const EntityNewPacket = require("../Packets/EntityNew");
const EntityDiePacket = require("../Packets/EntityDie");
const GuildIslandCapturedPacket = require("../Packets/GuildIslandCaptured");
const AttackInformationPacket = require("../Packets/AttackInformation");
const AttackerInformationPacket = require("../Packets/AttackerInformation");
const AttackingPacket = require("../Packets/Attacking");
const HealthChangedPacket = require("../Packets/HealthChanged");
const HealthChangedAuthorlessPacket = require("../Packets/HealthChangedAuthorless");
const GuildDataIslandTowerDestroyedPacket = require("../Packets/GuildDataIslandTowerDestroyed");
const GuildDataIslandTowerHealthChangedPacket = require("../Packets/GuildDataIslandTowerHealthChanged");
const GuildDataIslandCapturedPacket = require("../Packets/GuildDataIslandCaptured");
const GuildDataIslandTowerBuiltPacket = require("../Packets/GuildDataIslandTowerBuilt");
const WorldMapGuildEntryPacket = require("../Packets/WorldMapGuildEntry");
const GuildDataIslandPacket = require("../Packets/GuildDataIsland");
const GuildDataIslandTowerPacket = require("../Packets/GuildDataIslandTower");
const schemeTower = require("../Data/schemeTower");
const queries = require("../Data/queries");
const { QUERY_FETCH_GUILD_TOWERS, QUERY_UPDATE_GUILD_ISLAND_TOWER_DEAD, QUERY_UPDATE_GUILD_ISLAND_TOWER_UPGRADE, QUERY_UPDATE_GUILD_ISLAND_TOWER_DEFAULT, QUERY_DELETE_GUILD_ISLAND_DAMAGES, QUERY_UPDATE_GUILD_ISLAND_AUTHOR, QUERY_UPDATE_GUILD_ISLAND_DAMAGES } = queries;
const constants = require("../Data/constants");
const { EVENTS } = constants;
const { EVENT_ATTACK_STARTED, EVENT_HEALTH_CHANGED_AUTHORLESS, EVENT_ATTACKING, EVENT_ATTACK_BAR, EVENT_DIE, EVENT_GUILD_ISLAND_CAPTURE, EVENT_GUILD_ISLAND_TOWER_BUILT, EVENT_GUILD_ISLAND_TOWER_HEALTH_CHANGED } = EVENTS;
const Tower = require("../Game/Tower");
const Game = require("../Game/Game");
const { guildIslands } = Game;
module.exports = async function (map, globalDieCallback, globalRewardCallback, globalAttackCallback) {
const towers = await execute(QUERY_FETCH_GUILD_TOWERS, [map.id]);
if (towers.error) return process.exit();
const allTowersMap = towers.data;
if (!allTowersMap) return;
let order = 0;
let previousIslandID = 0;
for (let i = 0, length = allTowersMap.length; i < length; i++) {
const tower = allTowersMap[i];
const scheme = schemeTower[tower.entityTypeID];
if (!scheme) continue;
const island = guildIslands.find(island => island.id === tower.guildIslandID);
if (previousIslandID !== island.id) order = 0;
previousIslandID = island.id;
const towerInstance = new Tower({
id: tower.id,
order: ++order,
entityTypeID: tower.entityTypeID,
island,
map,
maximumHitpoint: scheme.maximumHitpoints,
currentHitpoint: tower.currentHitpoints,
position: {
x: tower.positionX,
y: tower.positionY
},
attackCallback: globalAttackCallback,
dieCallback: globalDieCallback
});
const packet = EntityNewPacket.tower(towerInstance.getProperties());
map.registerNetworkPacket(towerInstance.id, packet);
towerInstance.on(EVENT_DIE, function (data, id) {
const packet = EntityDiePacket(data[0]);
this.map.registerNetworkPacket(id, [packet], [1]);
const packet_2 = GuildDataIslandTowerDestroyedPacket(data[1]);
const topic = Game.getTopic(this.island.guild.topic);
topic.push(packet_2);
execute(QUERY_UPDATE_GUILD_ISLAND_TOWER_DEAD, [0, Tower.getID(this.id)]);
});
towerInstance.on(EVENT_ATTACK_STARTED, function (data, author) {
const packet = AttackInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
towerInstance.on(EVENT_HEALTH_CHANGED_AUTHORLESS, function (data, author) {
const packet = HealthChangedAuthorlessPacket(data);
this.map.registerNetworkPacket(this.id, packet, [1]);
});
towerInstance.on(EVENT_ATTACK_BAR, function (data, author) {
const packet = AttackerInformationPacket(data);
this.map.registerIndividualNetworkPacket(author, packet);
});
towerInstance.on(EVENT_ATTACKING, function (data, author) {
const packet = AttackingPacket(data[1]);
const packet_2 = HealthChangedPacket(data[0]);
this.map.registerNetworkPacket(author, [packet, packet_2], [1, 1]);
});
towerInstance.on("_", function (data) {
execute(QUERY_UPDATE_GUILD_ISLAND_DAMAGES, [data.damage, data.guildIslandID, data.guildID, data.damage]);
});
towerInstance.on(EVENT_GUILD_ISLAND_TOWER_HEALTH_CHANGED, function (data, author) {
const topic = Game.getTopic(this.island.guild.topic);
const packet = GuildDataIslandTowerHealthChangedPacket(data);
topic.push(packet);
});
towerInstance.on(EVENT_GUILD_ISLAND_TOWER_BUILT, async function (data, author) {
await execute(QUERY_UPDATE_GUILD_ISLAND_TOWER_UPGRADE, [this.entityTypeID, this.hp.maximum, Tower.getID(this.id)]);
const topic = Game.getTopic(this.island.guild.topic);
const packet = GuildDataIslandTowerBuiltPacket(data);
topic.push(packet);
});
towerInstance.on(EVENT_GUILD_ISLAND_CAPTURE, async function (data, author) {
const packet = GuildIslandCapturedPacket(data.packet);
app.publish(this.map.id.toString(), packet, true, true);
if (data.database.oldID !== data.database.newID) {
const packet_2 = GuildDataIslandCapturedPacket({
guildIslandID: this.island.id
});
const topicOld = Game.getTopic(data.topicOld);
topicOld.push(packet_2);
const islandProperties = {
id: this.island.id,
mapID: this.map.id,
position: this.island.position,
};
const guildDataIslandPacket = GuildDataIslandPacket(islandProperties);
const topicNew = Game.getTopic(data.topicNew);
topicNew.push(guildDataIslandPacket);
this.island.towers.forEach(tower => {
const towerProperties = {
entityTypeID: tower.entityTypeID,
id: tower.id,
guildIslandID: island.id,
order: tower.order,
hp: {
current: tower.hp.current
},
isRepairing: tower.isRepairing()
};
const guildDataIslandTowerPacket = GuildDataIslandTowerPacket(towerProperties);
topicNew.push(guildDataIslandTowerPacket);
});
};
const packet_3 = WorldMapGuildEntryPacket({
locationMapID: this.map.id,
index: this.island.index,
bufferTag: this.island.guild.tag
});
Game.maps.forEach(map => app.publish(map.id.toString(), packet_3, true, true));
let dbConnection = null;
try {
dbConnection = await db.getConnection();
await dbConnection.beginTransaction();
await execute(QUERY_UPDATE_GUILD_ISLAND_TOWER_DEFAULT, [5000, this.island.id]);
await execute(QUERY_DELETE_GUILD_ISLAND_DAMAGES, [this.island.id]);
await execute(QUERY_UPDATE_GUILD_ISLAND_AUTHOR, [data.database.newID, this.island.id]);
await dbConnection.commit();
} catch {
await dbConnection.rollback();
} finally {
dbConnection.release();
};
});
};
};
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[0]);
module.exports = map;
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[9]);
module.exports = map
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[10]);
module.exports = map
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[11]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[12]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[13]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[14]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[15]);
module.exports = map
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[16]);
module.exports = map
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require("../Game/Map");
const data = require("../Data/maps");
const map = new GameMap(data[17]);
module.exports = map;
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require("../Game/Map");
const data = require("../Data/maps");
const map = new GameMap(data[18]);
module.exports = map;
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require("../Game/Map");
const data = require("../Data/maps");
const map = new GameMap(data[1]);
module.exports = map;
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require("../Game/Map");
const data = require("../Data/maps");
const map = new GameMap(data[19]);
module.exports = map;
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[2]);
module.exports = map;
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[3]);
module.exports = map;
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[4]);
module.exports = map
+5
View File
@@ -0,0 +1,5 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[5]);
module.exports = map;
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[6]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[7]);
module.exports = map
+6
View File
@@ -0,0 +1,6 @@
const GameMap = require('../Game/Map');
const data = require("../Data/maps");
const map = new GameMap(data[8]);
module.exports = map
+34
View File
@@ -0,0 +1,34 @@
const AmmunitionChangeConfirmationPacket = require("../Packets/AmmunitionChangeConfirmation");
module.exports = function (ws, message, player) {
const type = message.getUint8(1);
const id = message.getUint8(2);
let selectedAmmunitionConfirmedID = 0;
let selectedAmmunitionTypeConfirmedID = 0;
switch (type) {
case 1:
selectedAmmunitionConfirmedID = player.inventory.setSelectedAmmunition(id);
selectedAmmunitionTypeConfirmedID = 1;
break;
case 2:
selectedAmmunitionConfirmedID = player.inventory.setSelectedHarpoon(id);
selectedAmmunitionTypeConfirmedID = 2;
break;
};
const ammunitionChangeConfirmation = {
typeID: selectedAmmunitionTypeConfirmedID,
id: selectedAmmunitionConfirmedID
};
console.info(ammunitionChangeConfirmation);
const ammunitionChangeConfirmationPacket = AmmunitionChangeConfirmationPacket(ammunitionChangeConfirmation);
player.map.registerIndividualNetworkPacket(player.id, ammunitionChangeConfirmationPacket);
const metadata = [player.hp.current / player.hp.maximum, player.isAttacking(), 0, player.inventory.selectedAmmunition];
return metadata;
};
+26
View File
@@ -0,0 +1,26 @@
const AttackAbortedPacket = require("../Packets/AttackAborted");
const constants = require("../Data/constants");
const { TYPES } = constants;
const { ENTITY_TYPE_NPC } = TYPES;
module.exports = function (ws, message, player) {
const targetIdentifier = player.abortAttack();
if (!targetIdentifier) return;
const packet = AttackAbortedPacket({
authorID: targetIdentifier.id,
authorTypeID: targetIdentifier.typeID,
targetID: player.id,
targetTypeID: player.typeID
});
player.map.registerIndividualNetworkPacket(player.id, packet);
if (targetIdentifier.typeID === ENTITY_TYPE_NPC) {
const target = player.map.getEntityFromSubgroup(targetIdentifier.id);
if (!target) return;
target.determineMovementState();
};
};
+18
View File
@@ -0,0 +1,18 @@
const constants = require("../Data/constants");
const { TYPES } = constants;
const { ENTITY_TYPE_MONSTER } = TYPES;
module.exports = function (ws, message, player) {
const typeID = message.getUint8(1);
const id = message.getUint32(2);
const wasAttacking = player.isAttacking();
const target = player.map.getEntityFromSubgroup(id);
if (target) {
if (typeID === ENTITY_TYPE_MONSTER) player.initializeAttackWithHarpooner(target);
else player.initializeAttackWithCannon(target);
};
return [player.inventory.selectedAmmunition, player.hp.current / player.hp.maximum, player.position.x, player.position.y, wasAttacking, 0 - 0];
};
+44
View File
@@ -0,0 +1,44 @@
const Reward = require("../Game/Reward");
const rewardCallback = require("../Callbacks/reward");
const MojoRemovedPacket = require("../Packets/MojoRemoved");
const MAXIMUM_BATCH_COUNT_MOJO = 1000;
const cauldronReward = new Reward({
distributionType: 2,
baseReward: {
3: {
5: { amountMinimum: 1500, chance: 16.5 },
6: { amountMinimum: 1500, chance: 16.5 },
7: { amountMinimum: 1500, chance: 16.5 },
8: { amountMinimum: 1500, chance: 16.5 },
9: { amountMinimum: 1500, chance: 16.5 }
},
7: {
10: { amountMinimum: 1, amountMaximum: 5, chance: 16.5 }
},
5: {
17: { amountMinimum: 1, chance: 20 }
}
}
});
module.exports = function (ws, message, player) {
let amount = message.getUint16(1);
if (!amount) return
if (amount > MAXIMUM_BATCH_COUNT_MOJO) amount = MAXIMUM_BATCH_COUNT_MOJO;
const usedAmount = player.inventory.alterAmount(1, 3, amount);
if (!usedAmount) return
const playerInventory = player.inventory.getPosessions();
cauldronReward.single(usedAmount, playerInventory, rewards => rewardCallback({ isRegular: false, authorID: player.id, rewards, source: {} }));
const packet = MojoRemovedPacket(usedAmount);
player.map.registerIndividualNetworkPacket(player.id, packet);
// global.test.accumulateItem(`${player.id}-1-3`, -amount);
};
+13
View File
@@ -0,0 +1,13 @@
const packet_255 = require("../Packets/packet_255");
module.exports = function (ws, message, player) {
if (player.isBeingAttacked()) return;
const shouldCancelDisconnect = player.isDisconnecting() && !player.hasUnexpectedlyLeft;
const packet = packet_255({ packetId: 12, isSuccessful: shouldCancelDisconnect });
player.map.registerIndividualNetworkPacket(player.id, packet);
if (shouldCancelDisconnect) return player.cancelDisconnect();
player.regularDisconnect();
};

Some files were not shown because too many files have changed in this diff Show More