I have rewritten how Bot AC is calculated to emulate Client AC. I've wondered for a while why my Warrior Bot MT had only ~100 more AC than my Magician, and have found the reason. Basically, Bots were getting negligible base AC from level and AGI, and 0 from defense skill. Almost all AC was from equipment and spells.
I did a test last night leveling up a new toon from 1 to 65, both by using #level 65 and #setxp going up to 65, then updating my Bot. Using #level 65, the Bot was left with a base AC of 40 (40 AC at level 65 for a Warrior!). If I used #setxp, leveling up, then updating my Bot, the AC went up with the levels, but maxed at around 200. When the Bot was camped and respawned, it respawned with only 40 AC. Obviously this is not correct.
Using the below code, my Warrior Bot went from ~1200 AC buffed to 1450 AC unbuffed, which matches what magelo.com reports is should be (I created a few toons there by hand a while ago to be able to check these things). Note - Bot stats suffer from double adding of stats from items and spells, including AC, which throws many calculations off. The above AC has been adjusted, taking this into account. My Warrior shows a STR in game of 850 with buffs, wearing a mix of Ornate, VT, pre-EP gear. This is approximately double what it should be (Magelo says it should be 326 unbuffed). I believe I have found the source of this issue, but will need to test it out the next couple of days, so I will go head and post this now. This code will be unchanged by the fix for double stats, except for the actual ingame calculations done with this code.
bot.h
Code:
--- EQEmuServer\zone\bot.h Tue Nov 9 23:56:21 2010
+++ C:\EqEmuSource\EQEmuServer\zone\bot.h Mon Nov 8 22:01:27 2010
@@ -263,6 +263,7 @@
bool GetRangerAutoWeaponSelect() { return _rangerAutoWeaponSelect; }
BotRoleType GetBotRole() { return _botRole; }
bool IsBotCaster() { return (GetClass() == CLERIC || GetClass() == DRUID || GetClass() == SHAMAN || GetClass() == NECROMANCER || GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == ENCHANTER); }
+ int16 MaxSkill(SkillType skillid, int16 class_, int16 level) const;
// "SET" Class Methods
void SetBotSpellID(uint32 newSpellID);
@@ -323,6 +324,7 @@
sint16 _baseFR;
sint16 _basePR;
int _baseAC;
+ int16 _baseDefense;
sint16 _baseSTR;
sint16 _baseSTA;
sint16 _baseDEX;
@@ -335,8 +337,10 @@
int8 _baseGender; // Bots gender. Necessary to preserve the original value otherwise it can be changed by illusions.
// Class Methods
+ sint16 acmod();
void GenerateBaseStats();
void GenerateAppearance();
+ void GenerateBaseDefense();
void GenerateArmorClass();
void GenerateBaseHitPoints();
void GenerateAABonuses();
bot.cpp
Code:
--- EQEmuServer\zone\bot.cpp Tue Nov 9 23:56:35 2010
+++ C:\EqEmuSource\EQEmuServer\zone\bot.cpp Tue Nov 9 23:55:49 2010
@@ -53,6 +53,7 @@
GenerateAppearance();
GenerateBaseStats();
+ GenerateBaseDefense();
GenerateArmorClass();
// Calculate HitPoints Last As It Uses Base Stats
@@ -118,6 +119,7 @@
}
GenerateBaseStats();
+ GenerateBaseDefense();
GenerateArmorClass();
// Calculate HitPoints Last As It Uses Base Stats
@@ -757,17 +759,422 @@
}
+int16 Bot::MaxSkill(SkillType skillid, int16 class_, int16 level) const {
+ return(database.GetSkillCap(class_, skillid, level));
+}
+
+sint16 Bot::acmod() {
+ int agility = GetAGI();
+ int level = GetLevel();
+ if(agility < 1 || level < 1)
+ return(0);
+
+ if (agility <=74){
+ if (agility == 1)
+ return -24;
+ else if (agility <=3)
+ return -23;
+ else if (agility == 4)
+ return -22;
+ else if (agility <=6)
+ return -21;
+ else if (agility <=8)
+ return -20;
+ else if (agility == 9)
+ return -19;
+ else if (agility <=11)
+ return -18;
+ else if (agility == 12)
+ return -17;
+ else if (agility <=14)
+ return -16;
+ else if (agility <=16)
+ return -15;
+ else if (agility == 17)
+ return -14;
+ else if (agility <=19)
+ return -13;
+ else if (agility == 20)
+ return -12;
+ else if (agility <=22)
+ return -11;
+ else if (agility <=24)
+ return -10;
+ else if (agility == 25)
+ return -9;
+ else if (agility <=27)
+ return -8;
+ else if (agility == 28)
+ return -7;
+ else if (agility <=30)
+ return -6;
+ else if (agility <=32)
+ return -5;
+ else if (agility == 33)
+ return -4;
+ else if (agility <=35)
+ return -3;
+ else if (agility == 36)
+ return -2;
+ else if (agility <=38)
+ return -1;
+ else if (agility <=65)
+ return 0;
+ else if (agility <=70)
+ return 1;
+ else if (agility <=74)
+ return 5;
+ }
+ else if(agility <= 137) {
+ if (agility == 75){
+ if (level <= 6)
+ return 9;
+ else if (level <= 19)
+ return 23;
+ else if (level <= 39)
+ return 33;
+ else
+ return 39;
+ }
+ else if (agility >= 76 && agility <= 79){
+ if (level <= 6)
+ return 10;
+ else if (level <= 19)
+ return 23;
+ else if (level <= 39)
+ return 33;
+ else
+ return 40;
+ }
+ else if (agility == 80){
+ if (level <= 6)
+ return 11;
+ else if (level <= 19)
+ return 24;
+ else if (level <= 39)
+ return 34;
+ else
+ return 41;
+ }
+ else if (agility >= 81 && agility <= 85){
+ if (level <= 6)
+ return 12;
+ else if (level <= 19)
+ return 25;
+ else if (level <= 39)
+ return 35;
+ else
+ return 42;
+ }
+ else if (agility >= 86 && agility <= 90){
+ if (level <= 6)
+ return 12;
+ else if (level <= 19)
+ return 26;
+ else if (level <= 39)
+ return 36;
+ else
+ return 42;
+ }
+ else if (agility >= 91 && agility <= 95){
+ if (level <= 6)
+ return 13;
+ else if (level <= 19)
+ return 26;
+ else if (level <= 39)
+ return 36;
+ else
+ return 43;
+ }
+ else if (agility >= 96 && agility <= 99){
+ if (level <= 6)
+ return 14;
+ else if (level <= 19)
+ return 27;
+ else if (level <= 39)
+ return 37;
+ else
+ return 44;
+ }
+ else if (agility == 100 && level >= 7){
+ if (level <= 19)
+ return 28;
+ else if (level <= 39)
+ return 38;
+ else
+ return 45;
+ }
+ else if (level <= 6) {
+ return 15;
+ }
+ //level is >6
+ else if (agility >= 101 && agility <= 105){
+ if (level <= 19)
+ return 29;
+ else if (level <= 39)
+ return 39;// not verified
+ else
+ return 45;
+ }
+ else if (agility >= 106 && agility <= 110){
+ if (level <= 19)
+ return 29;
+ else if (level <= 39)
+ return 39;// not verified
+ else
+ return 46;
+ }
+ else if (agility >= 111 && agility <= 115){
+ if (level <= 19)
+ return 30;
+ else if (level <= 39)
+ return 40;// not verified
+ else
+ return 47;
+ }
+ else if (agility >= 116 && agility <= 119){
+ if (level <= 19)
+ return 31;
+ else if (level <= 39)
+ return 41;
+ else
+ return 47;
+ }
+ else if (level <= 19) {
+ return 32;
+ }
+ //level is > 19
+ else if (agility == 120){
+ if (level <= 39)
+ return 42;
+ else
+ return 48;
+ }
+ else if (agility <= 125){
+ if (level <= 39)
+ return 42;
+ else
+ return 49;
+ }
+ else if (agility <= 135){
+ if (level <= 39)
+ return 42;
+ else
+ return 50;
+ }
+ else {
+ if (level <= 39)
+ return 42;
+ else
+ return 51;
+ }
+ } else if(agility <= 300) {
+ if(level <= 6) {
+ if(agility <= 139)
+ return(21);
+ else if(agility == 140)
+ return(22);
+ else if(agility <= 145)
+ return(23);
+ else if(agility <= 150)
+ return(23);
+ else if(agility <= 155)
+ return(24);
+ else if(agility <= 159)
+ return(25);
+ else if(agility == 160)
+ return(26);
+ else if(agility <= 165)
+ return(26);
+ else if(agility <= 170)
+ return(27);
+ else if(agility <= 175)
+ return(28);
+ else if(agility <= 179)
+ return(28);
+ else if(agility == 180)
+ return(29);
+ else if(agility <= 185)
+ return(30);
+ else if(agility <= 190)
+ return(31);
+ else if(agility <= 195)
+ return(31);
+ else if(agility <= 199)
+ return(32);
+ else if(agility <= 219)
+ return(33);
+ else if(agility <= 239)
+ return(34);
+ else
+ return(35);
+ } else if(level <= 19) {
+ if(agility <= 139)
+ return(34);
+ else if(agility == 140)
+ return(35);
+ else if(agility <= 145)
+ return(36);
+ else if(agility <= 150)
+ return(37);
+ else if(agility <= 155)
+ return(37);
+ else if(agility <= 159)
+ return(38);
+ else if(agility == 160)
+ return(39);
+ else if(agility <= 165)
+ return(40);
+ else if(agility <= 170)
+ return(40);
+ else if(agility <= 175)
+ return(41);
+ else if(agility <= 179)
+ return(42);
+ else if(agility == 180)
+ return(43);
+ else if(agility <= 185)
+ return(43);
+ else if(agility <= 190)
+ return(44);
+ else if(agility <= 195)
+ return(45);
+ else if(agility <= 199)
+ return(45);
+ else if(agility <= 219)
+ return(46);
+ else if(agility <= 239)
+ return(47);
+ else
+ return(48);
+ } else if(level <= 39) {
+ if(agility <= 139)
+ return(44);
+ else if(agility == 140)
+ return(45);
+ else if(agility <= 145)
+ return(46);
+ else if(agility <= 150)
+ return(47);
+ else if(agility <= 155)
+ return(47);
+ else if(agility <= 159)
+ return(48);
+ else if(agility == 160)
+ return(49);
+ else if(agility <= 165)
+ return(50);
+ else if(agility <= 170)
+ return(50);
+ else if(agility <= 175)
+ return(51);
+ else if(agility <= 179)
+ return(52);
+ else if(agility == 180)
+ return(53);
+ else if(agility <= 185)
+ return(53);
+ else if(agility <= 190)
+ return(54);
+ else if(agility <= 195)
+ return(55);
+ else if(agility <= 199)
+ return(55);
+ else if(agility <= 219)
+ return(56);
+ else if(agility <= 239)
+ return(57);
+ else
+ return(58);
+ } else { //lvl >= 40
+ if(agility <= 139)
+ return(51);
+ else if(agility == 140)
+ return(52);
+ else if(agility <= 145)
+ return(53);
+ else if(agility <= 150)
+ return(53);
+ else if(agility <= 155)
+ return(54);
+ else if(agility <= 159)
+ return(55);
+ else if(agility == 160)
+ return(56);
+ else if(agility <= 165)
+ return(56);
+ else if(agility <= 170)
+ return(57);
+ else if(agility <= 175)
+ return(58);
+ else if(agility <= 179)
+ return(58);
+ else if(agility == 180)
+ return(59);
+ else if(agility <= 185)
+ return(60);
+ else if(agility <= 190)
+ return(61);
+ else if(agility <= 195)
+ return(61);
+ else if(agility <= 199)
+ return(62);
+ else if(agility <= 219)
+ return(63);
+ else if(agility <= 239)
+ return(64);
+ else
+ return(65);
+ }
+ }
+ else{
+ //seems about 21 agil per extra AC pt over 300...
+ return (65 + ((agility-300) / 21));
+ }
+#if EQDEBUG >= 11
+ LogFile->write(EQEMuLog::Error, "Error in Bot::acmod(): Agility: %i, Level: %i",agility,level);
+#endif
+ return 0;
+};
+
+void Bot::GenerateBaseDefense() {
+ _baseDefense = MaxSkill(DEFENSE, GetClass(), GetLevel());
+}
+
void Bot::GenerateArmorClass() {
- // Base AC
- int bac = GetAC();
- switch(this->GetClass()) {
- case WARRIOR:
- case SHADOWKNIGHT:
- case PALADIN:
- bac = bac*1.5;
+ /// new formula
+ int avoidance = 0;
+ avoidance = (acmod() + ((_baseDefense*16)/9));
+ if (avoidance < 0)
+ avoidance = 0;
+
+ int mitigation = 0;
+ if (GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) {
+ mitigation = _baseDefense/4 + (itembonuses.AC+1);
+ mitigation -= 4;
+ } else {
+ mitigation = _baseDefense/3 + ((itembonuses.AC*4)/3);
+ if(GetClass() == MONK)
+ mitigation += GetLevel() * 13/10; //the 13/10 might be wrong, but it is close...
}
+ int displayed = 0;
+ displayed += ((avoidance+mitigation)*1000)/847; //natural AC
+
+ //Iksar AC, untested
+ if (GetRace() == IKSAR) {
+ displayed += 12;
+ int iksarlevel = GetLevel();
+ iksarlevel -= 10;
+ if (iksarlevel > 25)
+ iksarlevel = 25;
+ if (iksarlevel > 0)
+ displayed += iksarlevel * 12 / 10;
+ }
+
+ //spell AC bonuses are added directly to natural total
+ displayed += spellbonuses.AC;
- this->AC = bac;
+ this->AC = displayed;
}
void Bot::GenerateBaseHitPoints() {
@@ -8626,8 +9033,6 @@
GenerateAABonuses();
- GenerateArmorClass();
-
//// Calc Base Hit Points
//int16 lm = GetClassLevelFactor();
//int16 Post255;
@@ -8798,7 +9203,7 @@
DR += itembonuses.DR;
FR += itembonuses.FR;
PR += itembonuses.PR;
- AC += itembonuses.AC;
+ //AC += itembonuses.AC;
STR += itembonuses.STR;
STA += itembonuses.STA;
DEX += itembonuses.DEX;
@@ -8813,7 +9218,7 @@
DR += spellbonuses.DR;
FR += spellbonuses.FR;
PR += spellbonuses.PR;
- AC += spellbonuses.AC;
+ //AC += spellbonuses.AC;
STR += spellbonuses.STR;
STA += spellbonuses.STA;
DEX += spellbonuses.DEX;
@@ -8823,6 +9228,8 @@
CHA += spellbonuses.CHA;
ATK += spellbonuses.ATK;
+ GenerateBaseDefense();
+ GenerateArmorClass();
cur_hp = CalcMaxHP();
GenerateBaseManaPoints();
@@ -9582,6 +9989,7 @@
//bot->SetLevel(c->GetLevel());
bot->SetPetChooser(false);
bot->CalcBotStats();
+ bot->GenerateBaseDefense();
}
else {
if(c->GetFeigned()) {