alclient
Version:
A node client for interacting with Adventure Land - The Code MMORPG. This package extends the functionality of 'alclient' by managing a mongo database.
319 lines • 11 kB
JavaScript
import { Tools } from "./Tools.js";
export class Entity {
G;
// EntityData (required)
abs;
angle;
cid;
focus;
going_x;
going_y;
in;
map;
move_num;
moving = false;
x;
y;
target;
abilities = {};
charge;
cooperative = false;
damage_type;
// Stats (required)
attack;
frequency;
hp;
mp;
range;
speed;
// Stats (optional)
apiercing = 0;
armor = 0;
avoidance = 0;
blast = 0;
breaks = 0;
crit = 0;
critdamage = 0;
evasion = 0;
lifesteal = 0;
mcourage = 0;
reflection = 0;
resistance = 0;
rpiercing = 0;
// GMonster properties (required)
aggro;
name;
rage;
respawn;
skin;
// GMonster properties (optional)
"1hp" = false;
aa = 0;
achievements = [];
announce;
article;
balance;
cute = false;
difficulty;
drop_on_hit = false;
escapist = false;
explanation;
global = false;
goldsteal = 0;
hit;
humanoid = false;
immune = false;
lucrativeness;
operator = false;
orientation;
passive = false;
peaceful = false;
pet;
poisonous = false;
prefix = "";
projectile;
rbuff;
roam = false;
size;
slots;
spawns = [];
special = false;
stationary = false;
supporter = false;
trap = false;
unlist = false;
// Misc.
id;
level = 1;
max_hp;
max_mp;
s = {};
type;
xp;
width;
height;
constructor(data, map, instance, G) {
this.G = G;
// Set soft properties
// NOTE: If `data` contains different values, we will overwrite these in updateData()
this.max_hp = G.monsters[data.type].hp;
this.max_mp = G.monsters[data.type].mp;
this.map = map;
this.in = instance;
for (const gKey in G.monsters[data.type]) {
this[gKey] = G.monsters[data.type][gKey];
}
// Set width and height if it is known
this.width = (G.dimensions[data.type]?.[0] ?? 0) * (G.monsters[data.type].size ?? 1);
this.height = (G.dimensions[data.type]?.[1] ?? 0) * (G.monsters[data.type].size ?? 1);
// Set everything else
this.updateData(data);
}
updateData(data) {
if (this.id !== undefined && this.id !== data.id)
throw new Error("The entity's ID does not match");
// Set everything
for (const key in data)
this[key] = data[key];
}
calculateDamageRange(defender) {
if (defender["1hp"])
return [1, 1];
let baseDamage = this.attack;
// TODO: I asked Wizard to add something to G.conditions.cursed and .marked so we don't need these hardcoded.
if (defender.s.cursed)
baseDamage *= 1.2;
if (defender.s.marked)
baseDamage *= 1.1;
if (this.damage_type == "physical")
baseDamage *= Tools.damage_multiplier(defender.armor - this.apiercing);
else if (this.damage_type == "magical")
baseDamage *= Tools.damage_multiplier(defender.resistance - this.rpiercing);
if (this.crit) {
if (this.crit >= 100) {
// Guaranteed crit
return [baseDamage * 0.9 * (2 + this.critdamage / 100), baseDamage * 1.1 * (2 + this.critdamage / 100)];
}
else {
return [baseDamage * 0.9, baseDamage * 1.1 * (2 + this.critdamage / 100)];
}
}
else {
return [baseDamage * 0.9, baseDamage * 1.1];
}
}
/**
* Returns true if the entity has a >0% chance to die from projectiles already cast.
*
* TODO: Consider `blast` and `explosion`
*
* @param {Map<string, ActionData>} projectiles (e.g.: bot.projectiles)
* @param {Map<string, Player>} players (e.g.: bot.players)
* @param {Map<string, Player>} entities (e.g.: bot.entities)
* @return {*} {boolean}
* @memberof Entity
*/
couldDieToProjectiles(character, projectiles, players, entities) {
if (this.avoidance >= 100)
return false;
let incomingProjectileDamage = 0;
for (const projectile of projectiles.values()) {
if (!projectile.damage)
continue; // Won't do any damage
if (projectile.target !== this.id)
continue; // This projectile is heading towards another entity
// NOTE: Entities can attack themselves if the projectile gets reflected
let attacker;
if (!attacker && character.id == projectile.attacker)
attacker = character;
if (!attacker)
attacker = players.get(projectile.attacker);
if (!attacker)
attacker = entities.get(projectile.attacker);
if (!attacker) {
// Couldn't find attacker, assume it will crit with maximum damage
incomingProjectileDamage += projectile.damage * 2.2;
if (incomingProjectileDamage >= this.hp)
return true;
continue;
}
if (attacker.damage_type == "physical" && this.evasion >= 100)
continue; // It will avoid the attack
if (attacker.damage_type == "magical" && this.reflection >= 100)
continue; // It will reflect the attack
const maximumDamage = attacker.calculateDamageRange(this, projectile.type)[1];
incomingProjectileDamage += maximumDamage;
if (incomingProjectileDamage >= this.hp)
return true;
}
return false;
}
/**
* Returns true if killing this monster could drop gold/loot, or give us tracker credit.
*
* @param {Character} player
* @return {*} {boolean}
* @memberof Entity
*/
couldGiveCreditForKill(player) {
// It's not attacking anyone
if (this.target == undefined)
return true;
// Everyone gets credit if you attack a cooperative monster
if (this.cooperative)
return true;
// It's attacking one of our party members (includes us)
if (this.isAttackingPartyMember(player))
return true;
return false;
}
/**
* Returns true if the monster is attacking the player, or one of its party members
* @param player The player whose party to check if the monster is attacking
*/
isAttackingPartyMember(player) {
// Check if the entity is targeting anything
if (this.target == undefined)
return false;
// Check if the entity is attacking us
// NOTE: I don't want to get in to the semantics if we are actually in a party, I'm assuming if we aren't in a party, we're a party of "1".
if (this.isAttackingUs(player))
return true;
// Check if the entity is targeting a party member
if (player?.partyData?.list?.includes(this.target))
return true;
return false;
}
/**
* Returns true if the monster is attacking us specifically, false otherwise
* @param player The player to check if the monster is attacking
*/
isAttackingUs(player) {
return this.target == player.id;
}
/**
* If the entity is disabled, they cannot move or attack
* @returns If the entity is disabled
*/
isDisabled() {
return (this.s.stunned || this.s.fingered || this.s.sleeping) !== undefined;
}
// TODO: Check if we can taunt when the entity is attacking another player we control (i.e. same account), but we're not partied.
/**
* Returns whether or not the Warrior could taunt this monster
* @param by The player that will perform the taunt
*/
isTauntable(by) {
// If this entity has no target, it is tauntable
if (this.target == undefined)
return true;
// If this entity is attacking a party member, it is tauntable
if (this.isAttackingPartyMember(by))
return true;
// If the player it's targeting is another character of ours, it is tauntable
const targeting = by.players.get(this.target);
if (targeting && targeting.owner == by.owner)
return true;
return false;
}
/**
* Returns true if the entity will burn to death without taking any additional damage
* @return {*} {boolean}
* @memberof Entity
*/
willBurnToDeath() {
if (this.lifesteal)
return false; // Could heal itself
if (this.abilities?.self_healing)
return false; // Could heal itself
if (!this.s.burned)
return false; // Not burning
const numIntervals = Math.floor(this.s.burned.ms / this.G.conditions.burned.interval) - 1;
// It will proc for 1 damage every interval
if (this["1hp"]) {
return numIntervals >= this.hp;
}
const burnDamage = numIntervals * (this.s.burned.intensity / 5);
return burnDamage >= this.hp;
}
/**
* Returns true if the entity has a 100% chance to die from projectiles already cast.
*
* @param {Map<string, ActionData>} projectiles (e.g.: bot.projectiles)
* @param {Map<string, Player>} players (e.g.: bot.players)
* @param {Map<string, Player>} entities (e.g.: bot.entities)
* @return {*} {boolean}
* @memberof Entity
*/
willDieToProjectiles(character, projectiles, players, entities) {
if (this.avoidance)
return false;
let incomingProjectileDamage = 0;
for (const projectile of projectiles.values()) {
if (!projectile.damage)
continue; // This projectile won't do damage
if (projectile.target !== this.id)
continue; // This projectile is heading towards another entity
// NOTE: Entities can attack themselves if the projectile gets reflected
let attacker;
if (character.id == projectile.attacker)
attacker = character;
if (!attacker)
attacker = players.get(projectile.attacker);
if (!attacker)
attacker = entities.get(projectile.attacker);
if (!attacker)
continue; // Couldn't find attacker, assume it will do no damage
if (attacker.damage_type == "magical" && this.reflection)
continue; // Entity could reflect the damage
if (attacker.damage_type == "physical" && this.evasion)
continue; // Entity could avoid the damage
const minimumDamage = attacker.calculateDamageRange(this, projectile.type)[0];
incomingProjectileDamage += minimumDamage;
if (incomingProjectileDamage >= this.hp)
return true;
}
return false;
}
}
//# sourceMappingURL=Entity.js.map