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