UNPKG

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.

321 lines 11 kB
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; dreturn = 0; evasion = 0; explosion = 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