eqemu-server/zone/client_mods.cpp
2019-09-01 23:54:24 -05:00

1780 lines
41 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../common/global_define.h"
#include "../common/eqemu_logsys.h"
#include "../common/rulesys.h"
#include "../common/spdat.h"
#include "../common/data_verification.h"
#include "client.h"
#include "mob.h"
#ifdef BOTS
#include "bot.h"
#endif
#include <algorithm>
int32 Client::GetMaxStat() const
{
if ((RuleI(Character, StatCap)) > 0) {
return (RuleI(Character, StatCap));
}
int level = GetLevel();
int32 base = 0;
if (level < 61) {
base = 255;
}
else if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF) {
base = 255 + 5 * (level - 60);
}
else if (level < 71) {
base = 255 + 5 * (level - 60);
}
else {
base = 330;
}
return (base);
}
int32 Client::GetMaxResist() const
{
int level = GetLevel();
int32 base = 500;
if (level > 65) {
base += ((level - 65) * 5);
}
return base;
}
int32 Client::GetMaxSTR() const
{
return GetMaxStat()
+ itembonuses.STRCapMod
+ spellbonuses.STRCapMod
+ aabonuses.STRCapMod;
}
int32 Client::GetMaxSTA() const
{
return GetMaxStat()
+ itembonuses.STACapMod
+ spellbonuses.STACapMod
+ aabonuses.STACapMod;
}
int32 Client::GetMaxDEX() const
{
return GetMaxStat()
+ itembonuses.DEXCapMod
+ spellbonuses.DEXCapMod
+ aabonuses.DEXCapMod;
}
int32 Client::GetMaxAGI() const
{
return GetMaxStat()
+ itembonuses.AGICapMod
+ spellbonuses.AGICapMod
+ aabonuses.AGICapMod;
}
int32 Client::GetMaxINT() const
{
return GetMaxStat()
+ itembonuses.INTCapMod
+ spellbonuses.INTCapMod
+ aabonuses.INTCapMod;
}
int32 Client::GetMaxWIS() const
{
return GetMaxStat()
+ itembonuses.WISCapMod
+ spellbonuses.WISCapMod
+ aabonuses.WISCapMod;
}
int32 Client::GetMaxCHA() const
{
return GetMaxStat()
+ itembonuses.CHACapMod
+ spellbonuses.CHACapMod
+ aabonuses.CHACapMod;
}
int32 Client::GetMaxMR() const
{
return GetMaxResist()
+ itembonuses.MRCapMod
+ spellbonuses.MRCapMod
+ aabonuses.MRCapMod;
}
int32 Client::GetMaxPR() const
{
return GetMaxResist()
+ itembonuses.PRCapMod
+ spellbonuses.PRCapMod
+ aabonuses.PRCapMod;
}
int32 Client::GetMaxDR() const
{
return GetMaxResist()
+ itembonuses.DRCapMod
+ spellbonuses.DRCapMod
+ aabonuses.DRCapMod;
}
int32 Client::GetMaxCR() const
{
return GetMaxResist()
+ itembonuses.CRCapMod
+ spellbonuses.CRCapMod
+ aabonuses.CRCapMod;
}
int32 Client::GetMaxFR() const
{
return GetMaxResist()
+ itembonuses.FRCapMod
+ spellbonuses.FRCapMod
+ aabonuses.FRCapMod;
}
int32 Client::GetMaxCorrup() const
{
return GetMaxResist()
+ itembonuses.CorrupCapMod
+ spellbonuses.CorrupCapMod
+ aabonuses.CorrupCapMod;
}
int32 Client::LevelRegen()
{
bool sitting = IsSitting();
bool feigned = GetFeigned();
int level = GetLevel();
bool bonus = GetPlayerRaceBit(GetBaseRace()) & RuleI(Character, BaseHPRegenBonusRaces);
uint8 multiplier1 = bonus ? 2 : 1;
int32 hp = 0;
//these calculations should match up with the info from Monkly Business, which was last updated ~05/2008: http://www.monkly-business.net/index.php?pageid=abilities
if (level < 51) {
if (sitting) {
if (level < 20) {
hp += 2 * multiplier1;
}
else if (level < 50) {
hp += 3 * multiplier1;
}
else { //level == 50
hp += 4 * multiplier1;
}
}
else { //feigned or standing
hp += 1 * multiplier1;
}
}
//there may be an easier way to calculate this next part, but I don't know what it is
else { //level >= 51
int32 tmp = 0;
float multiplier2 = 1;
if (level < 56) {
tmp = 2;
if (bonus) {
multiplier2 = 3;
}
}
else if (level < 60) {
tmp = 3;
if (bonus) {
multiplier2 = 3.34;
}
}
else if (level < 61) {
tmp = 4;
if (bonus) {
multiplier2 = 3;
}
}
else if (level < 63) {
tmp = 5;
if (bonus) {
multiplier2 = 2.8;
}
}
else if (level < 65) {
tmp = 6;
if (bonus) {
multiplier2 = 2.67;
}
}
else { //level >= 65
tmp = 7;
if (bonus) {
multiplier2 = 2.58;
}
}
hp += int32(float(tmp) * multiplier2);
if (sitting) {
hp += 3 * multiplier1;
}
else if (feigned) {
hp += 1 * multiplier1;
}
}
return hp;
}
int32 Client::CalcHPRegen(bool bCombat)
{
int item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped
item_regen += GetHeroicSTA() / 20;
item_regen += aabonuses.HPRegen;
int base = 0;
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data)
base = static_cast<int>(base_data->hp_regen);
auto level = GetLevel();
bool skip_innate = false;
if (IsSitting()) {
if (level >= 50) {
base++;
if (level >= 65)
base++;
}
if ((Timer::GetCurrentTime() - tmSitting) > 60000) {
if (!IsAffectedByBuffByGlobalGroup(GlobalGroup::Lich)) {
auto tic_diff = std::min((Timer::GetCurrentTime() - tmSitting) / 60000, static_cast<uint32>(9));
if (tic_diff != 1) { // starts at 2 mins
int tic_bonus = tic_diff * 1.5 * base;
if (m_pp.InnateSkills[InnateRegen] != InnateDisabled)
tic_bonus = tic_bonus * 1.2;
base = tic_bonus;
skip_innate = true;
} else if (m_pp.InnateSkills[InnateRegen] == InnateDisabled) { // no innate regen gets first tick
int tic_bonus = base * 1.5;
base = tic_bonus;
}
}
}
}
if (!skip_innate && m_pp.InnateSkills[InnateRegen] != InnateDisabled) {
if (level >= 50) {
++base;
if (level >= 55) {
++base;
}
}
base *= 2;
}
if (IsStarved())
base = 0;
base += GroupLeadershipAAHealthRegeneration();
// some IsKnockedOut that sets to -1
base = base * 100.0f * AreaHPRegen * 0.01f + 0.5f;
// another check for IsClient && !(base + item_regen) && Cur_HP <= 0 do --base; do later
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto max_hp = GetMaxHP();
int fast_regen = 6 * (max_hp / zone->newzone_data.FastRegenHP);
if (base < fast_regen) // weird, but what the client is doing
base = fast_regen;
}
int regen = base + item_regen + spellbonuses.HPRegen; // TODO: client does this in buff tick
return (regen * RuleI(Character, HPRegenMultiplier) / 100);
}
int32 Client::CalcHPRegenCap()
{
int cap = RuleI(Character, ItemHealthRegenCap);
if (GetLevel() > 60)
cap = std::max(cap, GetLevel() - 30); // if the rule is set greater than normal I guess
if (GetLevel() > 65)
cap += GetLevel() - 65;
cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap;
return (cap * RuleI(Character, HPRegenMultiplier) / 100);
}
int32 Client::CalcMaxHP()
{
float nd = 10000;
max_hp = (CalcBaseHP() + itembonuses.HP);
//The AA desc clearly says it only applies to base hp..
//but the actual effect sent on live causes the client
//to apply it to (basehp + itemhp).. I will oblige to the client's whims over
//the aa description
nd += aabonuses.MaxHP; //Natural Durability, Physical Enhancement, Planar Durability
max_hp = (float)max_hp * (float)nd / (float)10000; //this is to fix the HP-above-495k issue
max_hp += spellbonuses.HP + aabonuses.HP;
max_hp += GroupLeadershipAAHealthEnhancement();
max_hp += max_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f);
if (current_hp > max_hp) {
current_hp = max_hp;
}
int hp_perc_cap = spellbonuses.HPPercCap[0];
if (hp_perc_cap) {
int curHP_cap = (max_hp * hp_perc_cap) / 100;
if (current_hp > curHP_cap || (spellbonuses.HPPercCap[1] && current_hp > spellbonuses.HPPercCap[1])) {
current_hp = curHP_cap;
}
}
return max_hp;
}
uint32 Mob::GetClassLevelFactor()
{
uint32 multiplier = 0;
uint8 mlevel = GetLevel();
switch (GetClass()) {
case WARRIOR: {
if (mlevel < 20) {
multiplier = 220;
}
else if (mlevel < 30) {
multiplier = 230;
}
else if (mlevel < 40) {
multiplier = 250;
}
else if (mlevel < 53) {
multiplier = 270;
}
else if (mlevel < 57) {
multiplier = 280;
}
else if (mlevel < 60) {
multiplier = 290;
}
else if (mlevel < 70) {
multiplier = 300;
}
else {
multiplier = 311;
}
break;
}
case DRUID:
case CLERIC:
case SHAMAN: {
if (mlevel < 70) {
multiplier = 150;
}
else {
multiplier = 157;
}
break;
}
case BERSERKER:
case PALADIN:
case SHADOWKNIGHT: {
if (mlevel < 35) {
multiplier = 210;
}
else if (mlevel < 45) {
multiplier = 220;
}
else if (mlevel < 51) {
multiplier = 230;
}
else if (mlevel < 56) {
multiplier = 240;
}
else if (mlevel < 60) {
multiplier = 250;
}
else if (mlevel < 68) {
multiplier = 260;
}
else {
multiplier = 270;
}
break;
}
case MONK:
case BARD:
case ROGUE:
case BEASTLORD: {
if (mlevel < 51) {
multiplier = 180;
}
else if (mlevel < 58) {
multiplier = 190;
}
else if (mlevel < 70) {
multiplier = 200;
}
else {
multiplier = 210;
}
break;
}
case RANGER: {
if (mlevel < 58) {
multiplier = 200;
}
else if (mlevel < 70) {
multiplier = 210;
}
else {
multiplier = 220;
}
break;
}
case MAGICIAN:
case WIZARD:
case NECROMANCER:
case ENCHANTER: {
if (mlevel < 70) {
multiplier = 120;
}
else {
multiplier = 127;
}
break;
}
default: {
if (mlevel < 35) {
multiplier = 210;
}
else if (mlevel < 45) {
multiplier = 220;
}
else if (mlevel < 51) {
multiplier = 230;
}
else if (mlevel < 56) {
multiplier = 240;
}
else if (mlevel < 60) {
multiplier = 250;
}
else {
multiplier = 260;
}
break;
}
}
return multiplier;
}
int32 Client::CalcBaseHP()
{
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
int stats = GetSTA();
if (stats > 255) {
stats = (stats - 255) / 2;
stats += 255;
}
base_hp = 5;
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
base_hp += base_data->base_hp + (base_data->hp_factor * stats);
base_hp += (GetHeroicSTA() * 10);
}
}
else {
uint32 Post255;
uint32 lm = GetClassLevelFactor();
if ((GetSTA() - 255) / 2 > 0) {
Post255 = (GetSTA() - 255) / 2;
}
else {
Post255 = 0;
}
base_hp = (5) + (GetLevel() * lm / 10) + (((GetSTA() - Post255) * GetLevel() * lm / 3000)) + ((Post255 * GetLevel()) * lm / 6000);
}
return base_hp;
}
// This is for calculating Base HPs + STA bonus for SoD or later clients.
uint32 Client::GetClassHPFactor()
{
int factor;
// Note: Base HP factor under level 41 is equal to factor / 12, and from level 41 to 80 is factor / 6.
// Base HP over level 80 is factor / 10
// HP per STA point per level is factor / 30 for level 80+
// HP per STA under level 40 is the level 80 HP Per STA / 120, and for over 40 it is / 60.
switch (GetClass()) {
case DRUID:
case ENCHANTER:
case NECROMANCER:
case MAGICIAN:
case WIZARD:
factor = 240;
break;
case BEASTLORD:
case BERSERKER:
case MONK:
case ROGUE:
case SHAMAN:
factor = 255;
break;
case BARD:
case CLERIC:
factor = 264;
break;
case SHADOWKNIGHT:
case PALADIN:
factor = 288;
break;
case RANGER:
factor = 276;
break;
case WARRIOR:
factor = 300;
break;
default:
factor = 240;
break;
}
return factor;
}
// This should return the combined AC of all the items the player is wearing.
int32 Client::GetRawItemAC()
{
int32 Total = 0;
// this skips MainAmmo..add an '=' conditional if that slot is required (original behavior)
for (int16 slot_id = EQEmu::invslot::BONUS_BEGIN; slot_id <= EQEmu::invslot::BONUS_STAT_END; slot_id++) {
const EQEmu::ItemInstance* inst = m_inv[slot_id];
if (inst && inst->IsClassCommon()) {
Total += inst->GetItem()->AC;
}
}
return Total;
}
int32 Client::CalcMaxMana()
{
switch (GetCasterClass()) {
case 'I':
case 'W': {
max_mana = (CalcBaseMana() + itembonuses.Mana + spellbonuses.Mana + aabonuses.Mana + GroupLeadershipAAManaEnhancement());
break;
}
case 'N': {
max_mana = 0;
break;
}
default: {
Log(Logs::Detail, Logs::Spells, "Invalid Class '%c' in CalcMaxMana", GetCasterClass());
max_mana = 0;
break;
}
}
if (max_mana < 0) {
max_mana = 0;
}
if (current_mana > max_mana) {
current_mana = max_mana;
}
int mana_perc_cap = spellbonuses.ManaPercCap[0];
if (mana_perc_cap) {
int curMana_cap = (max_mana * mana_perc_cap) / 100;
if (current_mana > curMana_cap || (spellbonuses.ManaPercCap[1] && current_mana > spellbonuses.ManaPercCap[1])) {
current_mana = curMana_cap;
}
}
Log(Logs::Detail, Logs::Spells, "Client::CalcMaxMana() called for %s - returning %d", GetName(), max_mana);
return max_mana;
}
int32 Client::CalcBaseMana()
{
int ConvertedWisInt = 0;
int MindLesserFactor, MindFactor;
int WisInt = 0;
int base_mana = 0;
int wisint_mana = 0;
int32 max_m = 0;
switch (GetCasterClass()) {
case 'I':
WisInt = GetINT();
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201) {
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
}
}
else {
ConvertedWisInt = WisInt;
}
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
max_m = base_data->base_mana + (ConvertedWisInt * base_data->mana_factor) + (GetHeroicINT() * 10);
}
}
else {
if ((( WisInt - 199 ) / 2) > 0) {
MindLesserFactor = ( WisInt - 199 ) / 2;
}
else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
}
else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
}
break;
case 'W':
WisInt = GetWIS();
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
if (WisInt > 100) {
ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100);
if (WisInt > 201) {
ConvertedWisInt -= ((WisInt - 201) * 5 / 4);
}
}
else {
ConvertedWisInt = WisInt;
}
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
max_m = base_data->base_mana + (ConvertedWisInt * base_data->mana_factor) + (GetHeroicWIS() * 10);
}
}
else {
if ((( WisInt - 199 ) / 2) > 0) {
MindLesserFactor = ( WisInt - 199 ) / 2;
}
else {
MindLesserFactor = 0;
}
MindFactor = WisInt - MindLesserFactor;
if (WisInt > 100) {
max_m = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40);
}
else {
max_m = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100);
}
}
break;
case 'N': {
max_m = 0;
break;
}
default: {
LogDebug("Invalid Class [{}] in CalcMaxMana", GetCasterClass());
max_m = 0;
break;
}
}
#if EQDEBUG >= 11
LogDebug("Client::CalcBaseMana() called for [{}] - returning [{}]", GetName(), max_m);
#endif
return max_m;
}
int32 Client::CalcBaseManaRegen()
{
uint8 clevel = GetLevel();
int32 regen = 0;
if (IsSitting() || (GetHorseId() != 0)) {
if (HasSkill(EQEmu::skills::SkillMeditate)) {
regen = (((GetSkill(EQEmu::skills::SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4;
}
else {
regen = 2;
}
}
else {
regen = 2;
}
return regen;
}
int32 Client::CalcManaRegen(bool bCombat)
{
int regen = 0;
auto level = GetLevel();
// so the new formulas break down with older skill caps where you don't have the skill until 4 or 8
// so for servers that want to use the old skill progression they can set this rule so they
// will get at least 1 for standing and 2 for sitting.
bool old = RuleB(Character, OldMinMana);
if (!IsStarved()) {
// client does some base regen for shrouds here
if (IsSitting() || CanMedOnHorse()) {
// kind of weird to do it here w/e
// client does some base medding regen for shrouds here
if (GetClass() != BARD) {
auto skill = GetSkill(EQEmu::skills::SkillMeditate);
if (skill > 0) {
regen++;
if (skill > 1)
regen++;
if (skill >= 15)
regen += skill / 15;
}
}
if (old)
regen = std::max(regen, 2);
} else if (old) {
regen = std::max(regen, 1);
}
}
if (level > 61) {
regen++;
if (level > 63)
regen++;
}
regen += aabonuses.ManaRegen;
// add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet?
int item_bonus = itembonuses.ManaRegen; // this is capped already
int heroic_bonus = 0;
switch (GetCasterClass()) {
case 'W':
heroic_bonus = GetHeroicWIS();
break;
default:
heroic_bonus = GetHeroicINT();
break;
}
item_bonus += heroic_bonus / 25;
regen += item_bonus;
if (level <= 70 && regen > 65)
regen = 65;
regen = regen * 100.0f * AreaManaRegen * 0.01f + 0.5f;
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto max_mana = GetMaxMana();
int fast_regen = 6 * (max_mana / zone->newzone_data.FastRegenMana);
if (regen < fast_regen) // weird, but what the client is doing
regen = fast_regen;
}
regen += spellbonuses.ManaRegen; // TODO: live does this in buff tick
return (regen * RuleI(Character, ManaRegenMultiplier) / 100);
}
int32 Client::CalcManaRegenCap()
{
int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap;
return (cap * RuleI(Character, ManaRegenMultiplier) / 100);
}
uint32 Client::CalcCurrentWeight()
{
const EQEmu::ItemData* TempItem = nullptr;
EQEmu::ItemInstance* ins = nullptr;
uint32 Total = 0;
int x;
for (x = EQEmu::invslot::POSSESSIONS_BEGIN; x <= EQEmu::invslot::POSSESSIONS_END; x++) {
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins) {
TempItem = ins->GetItem();
}
if (TempItem) {
Total += TempItem->Weight;
}
}
for (x = EQEmu::invbag::GENERAL_BAGS_BEGIN; x <= EQEmu::invbag::CURSOR_BAG_END; x++) {
int TmpWeight = 0;
TempItem = 0;
ins = GetInv().GetItem(x);
if (ins) {
TempItem = ins->GetItem();
}
if (TempItem) {
TmpWeight = TempItem->Weight;
}
if (TmpWeight > 0) {
// this code indicates that weight redux bags can only be in the first general inventory slot to be effective...
// is this correct? or can we scan for the highest weight redux and use that? (need client verifications)
int bagslot = EQEmu::invslot::slotGeneral1;
int reduction = 0;
for (int m = EQEmu::invbag::GENERAL_BAGS_BEGIN + EQEmu::invbag::SLOT_COUNT; m <= EQEmu::invbag::CURSOR_BAG_END; m += EQEmu::invbag::SLOT_COUNT) {
if (x >= m) {
bagslot += 1;
}
}
EQEmu::ItemInstance* baginst = GetInv().GetItem(bagslot);
if (baginst && baginst->GetItem() && baginst->IsClassBag()) {
reduction = baginst->GetItem()->BagWR;
}
if (reduction > 0) {
TmpWeight -= TmpWeight * reduction / 100;
}
Total += TmpWeight;
}
}
//TODO: coin weight reduction (from purses, etc), since client already calculates it
/* From the Wiki http://www.eqemulator.net/wiki/wikka.php?wakka=EQEmuDBSchemaitems under bagwr (thanks Trevius):
Interestingly, you can also have bags that reduce coin weight. However, in order to set bags to reduce coin weight, you MUST set the Item ID somewhere between 17201 and 17230. This is hard coded into the client.
The client is set to have certain coin weight reduction on a per Item ID basis within this range. The best way to create an new item to reduce coin weight is to examine existing bags in this range.
Search for the words "coin purse" with the #finditem command in game and the Bag WR setting on those bags is the amount they will reduce coin weight. It is easiest to overwrite one of those bags if you wish to create one with the
same weight reduction amount for coins. You can use other Item IDs in this range for setting coin weight reduction, but by using an existing item, at least you will know the amount the client will reduce it by before you create it.
This is the ONLY instance I have seen where the client is hard coded to particular Item IDs to set a certain property for an item. It is very odd.
*/
// SoD+ client has no weight for coin
if (EQEmu::behavior::StaticLookup(EQEmu::versions::ConvertClientVersionToMobVersion(ClientVersion()))->CoinHasWeight) {
Total += (m_pp.platinum + m_pp.gold + m_pp.silver + m_pp.copper) / 4;
}
float Packrat = (float)spellbonuses.Packrat + (float)aabonuses.Packrat + (float)itembonuses.Packrat;
if (Packrat > 0) {
Total = (uint32)((float)Total * (1.0f - ((Packrat * 1.0f) / 100.0f))); //AndMetal: 1% per level, up to 5% (calculated from Titanium client). verified thru client that it reduces coin weight by the same %
}
//without casting to float & back to uint32, this didn't work right
return Total;
}
int32 Client::CalcAlcoholPhysicalEffect()
{
if (m_pp.intoxication <= 55) {
return 0;
}
return (m_pp.intoxication - 40) / 16;
}
int32 Client::CalcSTR()
{
int32 val = m_pp.STR + itembonuses.STR + spellbonuses.STR + CalcAlcoholPhysicalEffect();
int32 mod = aabonuses.STR;
STR = val + mod;
if (STR < 1) {
STR = 1;
}
int m = GetMaxSTR();
if (STR > m) {
STR = m;
}
return (STR);
}
int32 Client::CalcSTA()
{
int32 val = m_pp.STA + itembonuses.STA + spellbonuses.STA + CalcAlcoholPhysicalEffect();;
int32 mod = aabonuses.STA;
STA = val + mod;
if (STA < 1) {
STA = 1;
}
int m = GetMaxSTA();
if (STA > m) {
STA = m;
}
return (STA);
}
int32 Client::CalcAGI()
{
int32 val = m_pp.AGI + itembonuses.AGI + spellbonuses.AGI - CalcAlcoholPhysicalEffect();;
int32 mod = aabonuses.AGI;
int32 str = GetSTR();
//Encumbered penalty
if (weight > (str * 10)) {
//AGI is halved when we double our weight, zeroed (defaults to 1) when we triple it. this includes AGI from AAs
float total_agi = float(val + mod);
float str_float = float(str);
AGI = (int32)(((-total_agi) / (str_float * 2)) * (((float)weight / 10) - str_float) + total_agi); //casting to an int assumes this will be floor'd. without using floats & casting to int16, the calculation doesn't work right
}
else {
AGI = val + mod;
}
if (AGI < 1) {
AGI = 1;
}
int m = GetMaxAGI();
if (AGI > m) {
AGI = m;
}
return (AGI);
}
int32 Client::CalcDEX()
{
int32 val = m_pp.DEX + itembonuses.DEX + spellbonuses.DEX - CalcAlcoholPhysicalEffect();;
int32 mod = aabonuses.DEX;
DEX = val + mod;
if (DEX < 1) {
DEX = 1;
}
int m = GetMaxDEX();
if (DEX > m) {
DEX = m;
}
return (DEX);
}
int32 Client::CalcINT()
{
int32 val = m_pp.INT + itembonuses.INT + spellbonuses.INT;
int32 mod = aabonuses.INT;
INT = val + mod;
if (m_pp.intoxication) {
int32 AlcINT = INT - (int32)((float)m_pp.intoxication / 200.0f * (float)INT) - 1;
if ((AlcINT < (int)(0.2 * INT))) {
INT = (int)(0.2f * (float)INT);
}
else {
INT = AlcINT;
}
}
if (INT < 1) {
INT = 1;
}
int m = GetMaxINT();
if (INT > m) {
INT = m;
}
return (INT);
}
int32 Client::CalcWIS()
{
int32 val = m_pp.WIS + itembonuses.WIS + spellbonuses.WIS;
int32 mod = aabonuses.WIS;
WIS = val + mod;
if (m_pp.intoxication) {
int32 AlcWIS = WIS - (int32)((float)m_pp.intoxication / 200.0f * (float)WIS) - 1;
if ((AlcWIS < (int)(0.2 * WIS))) {
WIS = (int)(0.2f * (float)WIS);
}
else {
WIS = AlcWIS;
}
}
if (WIS < 1) {
WIS = 1;
}
int m = GetMaxWIS();
if (WIS > m) {
WIS = m;
}
return (WIS);
}
int32 Client::CalcCHA()
{
int32 val = m_pp.CHA + itembonuses.CHA + spellbonuses.CHA;
int32 mod = aabonuses.CHA;
CHA = val + mod;
if (CHA < 1) {
CHA = 1;
}
int m = GetMaxCHA();
if (CHA > m) {
CHA = m;
}
return (CHA);
}
int Client::CalcHaste()
{
/* Tests: (based on results in newer char window)
68 v1 + 46 item + 25 over + 35 inhib = 204%
46 item + 5 v2 + 25 over + 35 inhib = 65%
68 v1 + 46 item + 5 v2 + 25 over + 35 inhib = 209%
75% slow + 35 inhib = 25%
35 inhib = 65%
75% slow = 25%
Conclusions:
the bigger effect in slow v. inhib wins
slow negates all other hastes
inhib will only negate all other hastes if you don't have v1 (ex. VQ)
*/
// slow beats all! Besides a better inhibit
if (spellbonuses.haste < 0) {
if (-spellbonuses.haste <= spellbonuses.inhibitmelee) {
Haste = 100 - spellbonuses.inhibitmelee;
}
else {
Haste = 100 + spellbonuses.haste;
}
return Haste;
}
// No haste and inhibit, kills all other hastes
if (spellbonuses.haste == 0 && spellbonuses.inhibitmelee) {
Haste = 100 - spellbonuses.inhibitmelee;
return Haste;
}
int h = 0;
int cap = 0;
int level = GetLevel();
// we know we have a haste spell and not slowed, no extra inhibit melee checks needed
if (spellbonuses.haste) {
h += spellbonuses.haste - spellbonuses.inhibitmelee;
}
if (spellbonuses.hastetype2 && level > 49) { // type 2 is capped at 10% and only available to 50+
h += spellbonuses.hastetype2 > 10 ? 10 : spellbonuses.hastetype2;
}
// 26+ no cap, 1-25 10
if (level > 25) { // 26+
h += itembonuses.haste;
}
else { // 1-25
h += itembonuses.haste > 10 ? 10 : itembonuses.haste;
}
// 60+ 100, 51-59 85, 1-50 level+25
if (level > 59) { // 60+
cap = RuleI(Character, HasteCap);
}
else if (level > 50) { // 51-59
cap = 85;
}
else { // 1-50
cap = level + 25;
}
cap = mod_client_haste_cap(cap);
if (h > cap) {
h = cap;
}
// 51+ 25 (despite there being higher spells...), 1-50 10
if (level > 50) { // 51+
h += spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3;
}
else { // 1-50
h += spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3;
}
h += ExtraHaste; //GM granted haste.
h = mod_client_haste(h);
Haste = 100 + h;
return Haste;
}
//The AA multipliers are set to be 5, but were 2 on WR
//The resistant discipline which I think should be here is implemented
//in Mob::ResistSpell
int32 Client::CalcMR()
{
//racial bases
switch (GetBaseRace()) {
case HUMAN:
MR = 25;
break;
case BARBARIAN:
MR = 25;
break;
case ERUDITE:
MR = 30;
break;
case WOOD_ELF:
MR = 25;
break;
case HIGH_ELF:
MR = 25;
break;
case DARK_ELF:
MR = 25;
break;
case HALF_ELF:
MR = 25;
break;
case DWARF:
MR = 30;
break;
case TROLL:
MR = 25;
break;
case OGRE:
MR = 25;
break;
case HALFLING:
MR = 25;
break;
case GNOME:
MR = 25;
break;
case IKSAR:
MR = 25;
break;
case VAHSHIR:
MR = 25;
break;
case FROGLOK:
MR = 30;
break;
case DRAKKIN:
{
MR = 25;
if (GetDrakkinHeritage() == 2)
MR += 10;
else if (GetDrakkinHeritage() == 5)
MR += 2;
break;
}
default:
MR = 20;
}
MR += itembonuses.MR + spellbonuses.MR + aabonuses.MR;
if (GetClass() == WARRIOR || GetClass() == BERSERKER) {
MR += GetLevel() / 2;
}
if (MR < 1) {
MR = 1;
}
if (MR > GetMaxMR()) {
MR = GetMaxMR();
}
return (MR);
}
int32 Client::CalcFR()
{
//racial bases
switch (GetBaseRace()) {
case HUMAN:
FR = 25;
break;
case BARBARIAN:
FR = 25;
break;
case ERUDITE:
FR = 25;
break;
case WOOD_ELF:
FR = 25;
break;
case HIGH_ELF:
FR = 25;
break;
case DARK_ELF:
FR = 25;
break;
case HALF_ELF:
FR = 25;
break;
case DWARF:
FR = 25;
break;
case TROLL:
FR = 5;
break;
case OGRE:
FR = 25;
break;
case HALFLING:
FR = 25;
break;
case GNOME:
FR = 25;
break;
case IKSAR:
FR = 30;
break;
case VAHSHIR:
FR = 25;
break;
case FROGLOK:
FR = 25;
break;
case DRAKKIN:
{
FR = 25;
if (GetDrakkinHeritage() == 0)
FR += 10;
else if (GetDrakkinHeritage() == 5)
FR += 2;
break;
}
default:
FR = 20;
}
int c = GetClass();
if (c == RANGER) {
FR += 4;
int l = GetLevel();
if (l > 49) {
FR += l - 49;
}
}
if (c == MONK) {
FR += 8;
int l = GetLevel();
if (l > 49) {
FR += l - 49;
}
}
FR += itembonuses.FR + spellbonuses.FR + aabonuses.FR;
if (FR < 1) {
FR = 1;
}
if (FR > GetMaxFR()) {
FR = GetMaxFR();
}
return (FR);
}
int32 Client::CalcDR()
{
//racial bases
switch (GetBaseRace()) {
case HUMAN:
DR = 15;
break;
case BARBARIAN:
DR = 15;
break;
case ERUDITE:
DR = 10;
break;
case WOOD_ELF:
DR = 15;
break;
case HIGH_ELF:
DR = 15;
break;
case DARK_ELF:
DR = 15;
break;
case HALF_ELF:
DR = 15;
break;
case DWARF:
DR = 15;
break;
case TROLL:
DR = 15;
break;
case OGRE:
DR = 15;
break;
case HALFLING:
DR = 20;
break;
case GNOME:
DR = 15;
break;
case IKSAR:
DR = 15;
break;
case VAHSHIR:
DR = 15;
break;
case FROGLOK:
DR = 15;
break;
case DRAKKIN:
{
DR = 15;
if (GetDrakkinHeritage() == 1)
DR += 10;
else if (GetDrakkinHeritage() == 5)
DR += 2;
break;
}
default:
DR = 15;
}
int c = GetClass();
// the monk one is part of base resist
if (c == MONK) {
int l = GetLevel();
if (l > 50)
DR += l - 50;
}
if (c == PALADIN) {
DR += 8;
int l = GetLevel();
if (l > 49) {
DR += l - 49;
}
}
else if (c == SHADOWKNIGHT || c == BEASTLORD) {
DR += 4;
int l = GetLevel();
if (l > 49) {
DR += l - 49;
}
}
DR += itembonuses.DR + spellbonuses.DR + aabonuses.DR;
if (DR < 1) {
DR = 1;
}
if (DR > GetMaxDR()) {
DR = GetMaxDR();
}
return (DR);
}
int32 Client::CalcPR()
{
//racial bases
switch (GetBaseRace()) {
case HUMAN:
PR = 15;
break;
case BARBARIAN:
PR = 15;
break;
case ERUDITE:
PR = 15;
break;
case WOOD_ELF:
PR = 15;
break;
case HIGH_ELF:
PR = 15;
break;
case DARK_ELF:
PR = 15;
break;
case HALF_ELF:
PR = 15;
break;
case DWARF:
PR = 20;
break;
case TROLL:
PR = 15;
break;
case OGRE:
PR = 15;
break;
case HALFLING:
PR = 20;
break;
case GNOME:
PR = 15;
break;
case IKSAR:
PR = 15;
break;
case VAHSHIR:
PR = 15;
break;
case FROGLOK:
PR = 30;
break;
case DRAKKIN:
{
PR = 15;
if (GetDrakkinHeritage() == 3)
PR += 10;
else if (GetDrakkinHeritage() == 5)
PR += 2;
break;
}
default:
PR = 15;
}
int c = GetClass();
// this monk bonus is part of the base
if (c == MONK) {
int l = GetLevel();
if (l > 50)
PR += l - 50;
}
if (c == ROGUE) {
PR += 8;
int l = GetLevel();
if (l > 49) {
PR += l - 49;
}
}
else if (c == SHADOWKNIGHT) {
PR += 4;
int l = GetLevel();
if (l > 49) {
PR += l - 49;
}
}
PR += itembonuses.PR + spellbonuses.PR + aabonuses.PR;
if (PR < 1) {
PR = 1;
}
if (PR > GetMaxPR()) {
PR = GetMaxPR();
}
return (PR);
}
int32 Client::CalcCR()
{
//racial bases
switch (GetBaseRace()) {
case HUMAN:
CR = 25;
break;
case BARBARIAN:
CR = 35;
break;
case ERUDITE:
CR = 25;
break;
case WOOD_ELF:
CR = 25;
break;
case HIGH_ELF:
CR = 25;
break;
case DARK_ELF:
CR = 25;
break;
case HALF_ELF:
CR = 25;
break;
case DWARF:
CR = 25;
break;
case TROLL:
CR = 25;
break;
case OGRE:
CR = 25;
break;
case HALFLING:
CR = 25;
break;
case GNOME:
CR = 25;
break;
case IKSAR:
CR = 15;
break;
case VAHSHIR:
CR = 25;
break;
case FROGLOK:
CR = 25;
break;
case DRAKKIN:
{
CR = 25;
if (GetDrakkinHeritage() == 4)
CR += 10;
else if (GetDrakkinHeritage() == 5)
CR += 2;
break;
}
default:
CR = 25;
}
int c = GetClass();
if (c == RANGER || c == BEASTLORD) {
CR += 4;
int l = GetLevel();
if (l > 49) {
CR += l - 49;
}
}
CR += itembonuses.CR + spellbonuses.CR + aabonuses.CR;
if (CR < 1) {
CR = 1;
}
if (CR > GetMaxCR()) {
CR = GetMaxCR();
}
return (CR);
}
int32 Client::CalcCorrup()
{
Corrup = GetBaseCorrup() + itembonuses.Corrup + spellbonuses.Corrup + aabonuses.Corrup;
if (Corrup > GetMaxCorrup()) {
Corrup = GetMaxCorrup();
}
return (Corrup);
}
int32 Client::CalcATK()
{
ATK = itembonuses.ATK + spellbonuses.ATK + aabonuses.ATK + GroupLeadershipAAOffenseEnhancement();
return (ATK);
}
uint32 Mob::GetInstrumentMod(uint16 spell_id) const
{
if (GetClass() != BARD || spells[spell_id].IsDisciplineBuff) // Puretone is Singing but doesn't get any mod
return 10;
uint32 effectmod = 10;
int effectmodcap = 0;
bool nocap = false;
if (RuleB(Character, UseSpellFileSongCap)) {
effectmodcap = spells[spell_id].songcap / 10;
// this looks a bit weird, but easiest way I could think to keep both systems working
if (effectmodcap == 0)
nocap = true;
else
effectmodcap += 10;
} else {
effectmodcap = RuleI(Character, BaseInstrumentSoftCap);
}
// this should never use spell modifiers...
// if a spell grants better modifers, they are copied into the item mods
// because the spells are supposed to act just like having the intrument.
// item mods are in 10ths of percent increases
// clickies (Symphony of Battle) that have a song skill don't get AA bonus for some reason
// but clickies that are songs (selo's on Composers Greaves) do get AA mod as well
switch (spells[spell_id].skill) {
case EQEmu::skills::SkillPercussionInstruments:
if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0)
effectmod = 10;
else if (GetSkill(EQEmu::skills::SkillPercussionInstruments) == 0)
effectmod = 10;
else if (itembonuses.percussionMod > spellbonuses.percussionMod)
effectmod = itembonuses.percussionMod;
else
effectmod = spellbonuses.percussionMod;
if (IsBardSong(spell_id))
effectmod += aabonuses.percussionMod;
break;
case EQEmu::skills::SkillStringedInstruments:
if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0)
effectmod = 10;
else if (GetSkill(EQEmu::skills::SkillStringedInstruments) == 0)
effectmod = 10;
else if (itembonuses.stringedMod > spellbonuses.stringedMod)
effectmod = itembonuses.stringedMod;
else
effectmod = spellbonuses.stringedMod;
if (IsBardSong(spell_id))
effectmod += aabonuses.stringedMod;
break;
case EQEmu::skills::SkillWindInstruments:
if (itembonuses.windMod == 0 && spellbonuses.windMod == 0)
effectmod = 10;
else if (GetSkill(EQEmu::skills::SkillWindInstruments) == 0)
effectmod = 10;
else if (itembonuses.windMod > spellbonuses.windMod)
effectmod = itembonuses.windMod;
else
effectmod = spellbonuses.windMod;
if (IsBardSong(spell_id))
effectmod += aabonuses.windMod;
break;
case EQEmu::skills::SkillBrassInstruments:
if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0)
effectmod = 10;
else if (GetSkill(EQEmu::skills::SkillBrassInstruments) == 0)
effectmod = 10;
else if (itembonuses.brassMod > spellbonuses.brassMod)
effectmod = itembonuses.brassMod;
else
effectmod = spellbonuses.brassMod;
if (IsBardSong(spell_id))
effectmod += aabonuses.brassMod;
break;
case EQEmu::skills::SkillSinging:
if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0)
effectmod = 10;
else if (itembonuses.singingMod > spellbonuses.singingMod)
effectmod = itembonuses.singingMod;
else
effectmod = spellbonuses.singingMod;
if (IsBardSong(spell_id))
effectmod += aabonuses.singingMod + spellbonuses.Amplification;
break;
default:
effectmod = 10;
return effectmod;
}
if (!RuleB(Character, UseSpellFileSongCap))
effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap;
if (effectmod < 10)
effectmod = 10;
if (!nocap && effectmod > effectmodcap) // if the cap is calculated to be 0 using new rules, no cap.
effectmod = effectmodcap;
Log(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", GetName(), spell_id,
effectmod, effectmodcap);
return effectmod;
}
void Client::CalcMaxEndurance()
{
max_end = CalcBaseEndurance() + spellbonuses.Endurance + itembonuses.Endurance + aabonuses.Endurance;
if (max_end < 0) {
max_end = 0;
}
if (current_endurance > max_end) {
current_endurance = max_end;
}
int end_perc_cap = spellbonuses.EndPercCap[0];
if (end_perc_cap) {
int curEnd_cap = (max_end * end_perc_cap) / 100;
if (current_endurance > curEnd_cap || (spellbonuses.EndPercCap[1] && current_endurance > spellbonuses.EndPercCap[1])) {
current_endurance = curEnd_cap;
}
}
}
int32 Client::CalcBaseEndurance()
{
int32 base_end = 0;
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF && RuleB(Character, SoDClientUseSoDHPManaEnd)) {
double heroic_stats = (GetHeroicSTR() + GetHeroicSTA() + GetHeroicDEX() + GetHeroicAGI()) / 4.0f;
double stats = (GetSTR() + GetSTA() + GetDEX() + GetAGI()) / 4.0f;
if (stats > 201.0f) {
stats = 1.25f * (stats - 201.0f) + 352.5f;
}
else if (stats > 100.0f) {
stats = 2.5f * (stats - 100.0f) + 100.0f;
}
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
base_end = base_data->base_end + (heroic_stats * 10.0f) + (base_data->endurance_factor * static_cast<int>(stats));
}
}
else {
int Stats = GetSTR() + GetSTA() + GetDEX() + GetAGI();
int LevelBase = GetLevel() * 15;
int at_most_800 = Stats;
if (at_most_800 > 800) {
at_most_800 = 800;
}
int Bonus400to800 = 0;
int HalfBonus400to800 = 0;
int Bonus800plus = 0;
int HalfBonus800plus = 0;
int BonusUpto800 = int( at_most_800 / 4 ) ;
if (Stats > 400) {
Bonus400to800 = int( (at_most_800 - 400) / 4 );
HalfBonus400to800 = int( std::max( ( at_most_800 - 400 ), 0 ) / 8 );
if (Stats > 800) {
Bonus800plus = int( (Stats - 800) / 8 ) * 2;
HalfBonus800plus = int( (Stats - 800) / 16 );
}
}
int bonus_sum = BonusUpto800 + Bonus400to800 + HalfBonus400to800 + Bonus800plus + HalfBonus800plus;
base_end = LevelBase;
//take all of the sums from above, then multiply by level*0.075
base_end += ( bonus_sum * 3 * GetLevel() ) / 40;
}
return base_end;
}
int32 Client::CalcEnduranceRegen(bool bCombat)
{
int base = 0;
if (!IsStarved()) {
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
base = static_cast<int>(base_data->end_regen);
if (!auto_attack && base > 0)
base += base / 2;
}
}
// so when we are mounted, our local client SpeedRun is always 0, so this is always false, but the packets we process it to our own shit :P
bool is_running = runmode && animation != 0 && GetHorseId() == 0; // TODO: animation is really what MQ2 calls SpeedRun
int weight_limit = GetSTR();
auto level = GetLevel();
if (GetClass() == MONK) {
if (level > 99)
weight_limit = 58;
else if (level > 94)
weight_limit = 57;
else if (level > 89)
weight_limit = 56;
else if (level > 84)
weight_limit = 55;
else if (level > 79)
weight_limit = 54;
else if (level > 64)
weight_limit = 53;
else if (level > 63)
weight_limit = 50;
else if (level > 61)
weight_limit = 47;
else if (level > 59)
weight_limit = 45;
else if (level > 54)
weight_limit = 40;
else if (level > 50)
weight_limit = 38;
else if (level > 44)
weight_limit = 36;
else if (level > 29)
weight_limit = 34;
else if (level > 14)
weight_limit = 32;
else
weight_limit = 30;
}
bool encumbered = (CalcCurrentWeight() / 10) >= weight_limit;
if (is_running)
base += level / -15;
if (encumbered)
base += level / -15;
auto item_bonus = GetHeroicAGI() + GetHeroicDEX() + GetHeroicSTA() + GetHeroicSTR();
item_bonus = item_bonus / 4 / 50;
item_bonus += itembonuses.EnduranceRegen; // this is capped already
base += item_bonus;
base = base * AreaEndRegen + 0.5f;
auto aa_regen = aabonuses.EnduranceRegen;
int regen = base;
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto max_end = GetMaxEndurance();
int fast_regen = 6 * (max_end / zone->newzone_data.FastRegenEndurance);
if (aa_regen < fast_regen) // weird, but what the client is doing
aa_regen = fast_regen;
}
regen += aa_regen;
regen += spellbonuses.EnduranceRegen; // TODO: client does this in buff tick
return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100);
}
int32 Client::CalcEnduranceRegenCap()
{
int cap = RuleI(Character, ItemEnduranceRegenCap);
return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100);
}
int32 Client::CalcItemATKCap()
{
int cap = RuleI(Character, ItemATKCap) + itembonuses.ItemATKCap + spellbonuses.ItemATKCap + aabonuses.ItemATKCap;
return cap;
}
int Client::GetRawACNoShield(int &shield_ac) const
{
int ac = itembonuses.AC + spellbonuses.AC + aabonuses.AC;
shield_ac = 0;
const EQEmu::ItemInstance *inst = m_inv.GetItem(EQEmu::invslot::slotSecondary);
if (inst) {
if (inst->GetItem()->ItemType == EQEmu::item::ItemTypeShield) {
ac -= inst->GetItem()->AC;
shield_ac = inst->GetItem()->AC;
for (uint8 i = EQEmu::invaug::SOCKET_BEGIN; i <= EQEmu::invaug::SOCKET_END; i++) {
if (inst->GetAugment(i)) {
ac -= inst->GetAugment(i)->GetItem()->AC;
shield_ac += inst->GetAugment(i)->GetItem()->AC;
}
}
}
}
return ac;
}