Initial commit
This commit is contained in:
+552
@@ -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;
|
||||
Reference in New Issue
Block a user