mirror of
https://github.com/araxiaonline/ets-module-collection.git
synced 2026-06-13 02:52:20 -04:00
369 lines
13 KiB
TypeScript
369 lines
13 KiB
TypeScript
import * as Common from '../../constants/idmaps';
|
|
import { BotData, Equipment, EquipmentList } from './botmgr.server';
|
|
|
|
type CharInfo = {
|
|
name: string,
|
|
level: number,
|
|
className: Common.CharacterClass,
|
|
classId: keyof typeof Common.ClassesMapping,
|
|
raceName: Common.CharacterRace,
|
|
raceId: keyof typeof Common.RacesMapping
|
|
}
|
|
|
|
type CharStats = Partial<Record<keyof typeof Common.BotStat, number>>;
|
|
|
|
function humanizeTalentName(input: string): string {
|
|
if (input.length === 0) {
|
|
return input; // Return unchanged if the input is an empty string
|
|
}
|
|
|
|
try {
|
|
const parts = input.split("_");
|
|
parts[0] = parts[0].toLowerCase();
|
|
parts[0] = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
|
|
parts[1] = parts[1].toLowerCase();
|
|
parts[1] = parts[1].charAt(0).toUpperCase() + parts[1].slice(1);
|
|
|
|
return `${parts[1]} ${parts[0]}`;
|
|
} catch (e) {
|
|
print(`failed to humanize talent name: ${input}` + e);
|
|
}
|
|
|
|
}
|
|
export class BotUnit {
|
|
|
|
protected myself: Creature;
|
|
protected myOwner: Player;
|
|
protected charinfo: CharInfo;
|
|
protected equipment: EquipmentList;
|
|
protected statsLeft: Record<string, string>[];
|
|
protected statsRight: Record<string, string>[];
|
|
protected talentSpecId: number;
|
|
protected roles: number;
|
|
protected allStats: Record<string, string> = {};
|
|
|
|
constructor(creature: Creature) {
|
|
if(!creature.IsNPCBot()) {
|
|
return;
|
|
}
|
|
|
|
this.myself = creature;
|
|
this.myOwner = creature.GetBotOwner();
|
|
this.charinfo = {
|
|
name: creature.GetName(),
|
|
level: creature.GetLevel(),
|
|
className: Common.ClassesMapping[creature.GetClass()],
|
|
classId: creature.GetClass(),
|
|
raceName: Common.RacesMapping[creature.GetRace()],
|
|
raceId: creature.GetRace()
|
|
};
|
|
this.equipment = this._lookupEquipment();
|
|
this.talentSpecId = creature.GetTalentSpec();
|
|
this.parseStats(creature.GetBotDump());
|
|
try {
|
|
this.statsLeft = this._lookupStats('left');
|
|
this.statsRight = this._lookupStats('right');
|
|
} catch (e) {
|
|
print("failed to get stats for bot:" + e);
|
|
}
|
|
|
|
this.roles = creature.GetBotRoles();
|
|
|
|
|
|
}
|
|
|
|
public toBotData(): BotData {
|
|
return {
|
|
name: this.charinfo.name,
|
|
entry: this.myself.GetEntry(),
|
|
owner: this.myOwner.GetName(),
|
|
level: this.charinfo.level,
|
|
class: this.charinfo.className,
|
|
classId: this.charinfo.classId,
|
|
race: this.charinfo.raceName,
|
|
raceId: this.charinfo.raceId,
|
|
talentSpec: this.talentSpecId,
|
|
talentSpecName: humanizeTalentName(this.talentSpecName()),
|
|
roles: this.roles,
|
|
equipment: this.equipment,
|
|
leftStats: this.statsLeft,
|
|
rightStats: this.statsRight,
|
|
allStats: this.allStats
|
|
}
|
|
}
|
|
|
|
public isHealer(): boolean {
|
|
if(this.talentSpecId == Common.TalentSpecs.SHAMAN_RESTORATION ||
|
|
this.talentSpecId == Common.TalentSpecs.PRIEST_DISCIPLINE ||
|
|
this.talentSpecId == Common.TalentSpecs.PRIEST_HOLY ||
|
|
this.talentSpecId == Common.TalentSpecs.PALADIN_HOLY ||
|
|
this.talentSpecId == Common.TalentSpecs.DRUID_RESTORATION) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public isDualWield(): boolean {
|
|
if(this.talentSpecId == Common.TalentSpecs.ROGUE_COMBAT ||
|
|
this.talentSpecId == Common.TalentSpecs.ROGUE_SUBTLETY ||
|
|
this.talentSpecId == Common.TalentSpecs.ROGUE_ASSASSINATION ||
|
|
this.talentSpecId == Common.TalentSpecs.SHAMAN_ENHANCEMENT ||
|
|
this.talentSpecId == Common.TalentSpecs.WARRIOR_FURY) {
|
|
if(this.equipment[Common.BotEquipSlot.MAINHAND] &&
|
|
this.equipment[Common.BotEquipSlot.OFFHAND]) {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public GetMeleeStats (): Record<string, Common.BotStatName[]>{
|
|
const botStatValues = Object.values(Common.BotStatLabel);
|
|
type BotStatValues = typeof botStatValues[number];
|
|
|
|
return {
|
|
left: [
|
|
"Strength",
|
|
"Agility",
|
|
"Damage",
|
|
"Power",
|
|
"Hit Rating",
|
|
"Crit %",
|
|
"Expertise",
|
|
"Armor Pen"
|
|
],
|
|
right: [
|
|
"Haste Rating",
|
|
"Armor",
|
|
"Stamina",
|
|
"Defense",
|
|
"Dodge",
|
|
"Parry",
|
|
"Block",
|
|
"Physical Res."
|
|
]
|
|
}
|
|
}
|
|
|
|
public GetRangedStats (): Record<string, Common.BotStatName[]>{
|
|
return {
|
|
left: [
|
|
"Strength",
|
|
"Agility",
|
|
"Damage Rng",
|
|
"Speed",
|
|
"Power",
|
|
"Hit Rating",
|
|
"Crit %",
|
|
"Armor Pen"
|
|
],
|
|
right: [
|
|
"Expertise",
|
|
"Haste Rating",
|
|
"Armor",
|
|
"Stamina",
|
|
"Defense",
|
|
"Dodge",
|
|
"Parry",
|
|
"Block",
|
|
]
|
|
}
|
|
}
|
|
|
|
public GetCasterStats (): Record<string, Common.BotStatName[]> {
|
|
return {
|
|
left: [
|
|
"Intellect",
|
|
"Spirit",
|
|
"Stamina",
|
|
"Bonus Dmg",
|
|
"Crit %",
|
|
"Hit Rating",
|
|
"Spell Pen"
|
|
],
|
|
right: [
|
|
"Haste Rating",
|
|
"MP5",
|
|
"Spell Res.",
|
|
"Dodge",
|
|
"Armor",
|
|
"Parry",
|
|
]
|
|
}
|
|
}
|
|
|
|
public GetStatMappings() {
|
|
|
|
switch(this.talentSpecId) {
|
|
case Common.TalentSpecs.WARRIOR_ARMS:
|
|
case Common.TalentSpecs.WARRIOR_FURY:
|
|
case Common.TalentSpecs.WARRIOR_PROTECTION:
|
|
case Common.TalentSpecs.PALADIN_PROTECTION:
|
|
case Common.TalentSpecs.PALADIN_RETRIBUTION:
|
|
case Common.TalentSpecs.DK_BLOOD:
|
|
case Common.TalentSpecs.DK_FROST:
|
|
case Common.TalentSpecs.DK_UNHOLY:
|
|
case Common.TalentSpecs.ROGUE_ASSASSINATION:
|
|
case Common.TalentSpecs.ROGUE_COMBAT:
|
|
case Common.TalentSpecs.ROGUE_SUBTLETY:
|
|
case Common.TalentSpecs.SHAMAN_ENHANCEMENT:
|
|
case Common.TalentSpecs.DRUID_FERAL:
|
|
return this.GetMeleeStats();
|
|
|
|
case Common.TalentSpecs.HUNTER_SURVIVAL:
|
|
case Common.TalentSpecs.HUNTER_MARKSMANSHIP:
|
|
case Common.TalentSpecs.HUNTER_BEASTMASTERY:
|
|
return this.GetRangedStats();
|
|
|
|
case Common.TalentSpecs.MAGE_ARCANE:
|
|
case Common.TalentSpecs.MAGE_FIRE:
|
|
case Common.TalentSpecs.MAGE_FROST:
|
|
case Common.TalentSpecs.WARLOCK_AFFLICTION:
|
|
case Common.TalentSpecs.WARLOCK_DEMONOLOGY:
|
|
case Common.TalentSpecs.WARLOCK_DESTRUCTION:
|
|
case Common.TalentSpecs.PRIEST_DISCIPLINE:
|
|
case Common.TalentSpecs.PRIEST_HOLY:
|
|
case Common.TalentSpecs.PRIEST_SHADOW:
|
|
case Common.TalentSpecs.SHAMAN_ELEMENTAL:
|
|
case Common.TalentSpecs.SHAMAN_RESTORATION:
|
|
case Common.TalentSpecs.DRUID_BALANCE:
|
|
case Common.TalentSpecs.DRUID_RESTORATION:
|
|
return this.GetCasterStats();
|
|
|
|
default:
|
|
print(`Unknown Talent Spec: ${this.talentSpecId}`);
|
|
}
|
|
}
|
|
|
|
public talentSpecName() {
|
|
// print(`Talent Spec: ${this.talentSpecId}`);
|
|
const keys = Object.keys(Common.TalentSpecs);
|
|
for(let i=0; i < keys.length; i++) {
|
|
if(Common.TalentSpecs[keys[i]] === this.talentSpecId) {
|
|
return keys[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
private _lookupEquipment(): EquipmentList {
|
|
const myEquipment = {} as EquipmentList;
|
|
for(let slot=0; slot <= Common.BotEquipLast; slot++) {
|
|
const equipment = this.myself.GetBotEquipment(<BotEquipmentSlotNum>slot);
|
|
|
|
if(equipment) {
|
|
myEquipment[slot] = {
|
|
entry: equipment.GetEntry(),
|
|
link: equipment.GetItemLink(),
|
|
quality: <Common.QualityType>equipment.GetQuality(),
|
|
itemLevel: equipment.GetItemLevel(),
|
|
enchantmentId: equipment.GetEnchantmentId(0), // Only the permenant enchantments
|
|
}
|
|
} else {
|
|
myEquipment[slot] = undefined;
|
|
}
|
|
}
|
|
|
|
return myEquipment;
|
|
}
|
|
|
|
private _lookupStats(panel: 'left' | 'right'): Record<string, string>[] {
|
|
const statMappings = this.GetStatMappings();
|
|
const classStats: Record<string, string>[] = []
|
|
|
|
for(let stat = 0; stat < statMappings[panel].length; stat++) {
|
|
const statName = statMappings[panel][stat];
|
|
let statValue = this.allStats[statName];
|
|
const statRecord= {};
|
|
|
|
// skip offhand stats will be handled with main hand
|
|
if(statName === 'Dmg Off') {
|
|
continue;
|
|
}
|
|
|
|
// handle some special cases for stats
|
|
if(statName === 'Damage') {
|
|
statRecord[statName] = statValue;
|
|
classStats.push(statRecord);
|
|
|
|
// Go ahead and add dual wield damage also
|
|
if(this.isDualWield()) {
|
|
//statRecord['Dmg Off'] = statValue;
|
|
//classStats.push(statRecord);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(this.isHealer() && statName === 'Bonus Dmg') {
|
|
statRecord['Bonus Heals'] = statValue;
|
|
classStats.push(statRecord);
|
|
// print(`Stat: Bonus Heals = ${statValue}`);
|
|
continue;
|
|
}
|
|
|
|
if(statName && statValue) {
|
|
statRecord[statName] = statValue;
|
|
classStats.push(statRecord);
|
|
// print(`Stat: ${statName} = ${statValue}`);
|
|
} else {
|
|
// print("failed to get stat: " + statName);
|
|
}
|
|
|
|
}
|
|
|
|
return classStats;
|
|
}
|
|
|
|
private parseStats(botdump: string) {
|
|
const stats = botdump.split('\n');
|
|
for(let i=0; i<stats.length; i++) {
|
|
const parts = stats[i].split(':');
|
|
|
|
if(parts[0] == "Resistance") {
|
|
parts[0] = parts[0] + ":" + parts[1];
|
|
this.allStats[parts[0]] = parts[2];
|
|
continue;
|
|
}
|
|
|
|
if(!Common.BotStatLabel[parts[0]]) {
|
|
continue;
|
|
}
|
|
|
|
parts[1] = parts[1].replace("(-0.00 pct)", "");
|
|
parts[1] = parts[1].replace("pct", "%").trim();
|
|
parts[1] = parts[1].replace("+", "");
|
|
|
|
const statName = Common.BotStatLabel[parts[0]];
|
|
if(statName == "Damage" || statName == "Dmg Off" || statName == "Damage Rng") {
|
|
parts[1] = parts[2].split(",")[0].trim() + "-" + parts[3].trim();
|
|
|
|
}
|
|
|
|
if(statName == "Physical Res." || statName == "Spell Res.") {
|
|
const value = parseFloat(parts[1]);
|
|
if(value === 1) {
|
|
parts[1] = "0.00%";
|
|
}
|
|
parts[1] = ((1 - value) * 100) + ".00%";
|
|
|
|
}
|
|
|
|
if(statName == "Expertise") {
|
|
parts[1] = parts[1].trim().split(" ")[0];
|
|
}
|
|
|
|
if(statName == "Dodge" || statName == "Parry" || statName == "Block" ||
|
|
statName == "Crit %" ) {
|
|
parts[1] = parts[1].trim() + "%";
|
|
}
|
|
|
|
if(statName == "Strength" || statName == "Agility" || statName == "Intellect" ||
|
|
statName == "Spirit" || statName == "Stamina") {
|
|
parts[1] = "" + Math.round(parseInt(parts[1]));
|
|
}
|
|
|
|
this.allStats[statName] = parts[1].trim().replace(" %", "%");
|
|
// print("Parsed Stat: " + statName + " = " + parts[1]);
|
|
}
|
|
}
|
|
} |