Compare commits
32 commits
master
...
spell_cach
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3607f2102c | ||
|
|
a9b3b02819 | ||
|
|
fc93429358 | ||
|
|
66b1a9d531 | ||
|
|
62f2c346c3 | ||
|
|
aded4250a5 | ||
|
|
c49d1aa6d6 | ||
|
|
37d7ab2de8 | ||
|
|
a92a7e97e1 | ||
|
|
7d7fa32d6c | ||
|
|
98c9f9b835 | ||
|
|
cfb270a1dd | ||
|
|
4b53fd775c | ||
|
|
eb7d6669ef | ||
|
|
44dfbc4e0b | ||
|
|
69fbe24766 | ||
|
|
ef50f54462 | ||
|
|
76b956debf | ||
|
|
be97d2ac9e | ||
|
|
e0b2867e8b | ||
|
|
406cb825c3 | ||
|
|
b424fc9815 | ||
|
|
b609f9363b | ||
|
|
7d79d2fc4a | ||
|
|
85feb4f653 | ||
|
|
5bca6cedef | ||
|
|
ec9c7baef7 | ||
|
|
86f1e48596 | ||
|
|
61241e3245 | ||
|
|
2176eb5e8e | ||
|
|
4b44461c70 | ||
|
|
a60221393e |
22 changed files with 1259 additions and 562 deletions
|
|
@ -27,7 +27,7 @@ std::string EQEmuConfig::ConfigFile = "eqemu_config.json";
|
|||
EQEmuConfig *EQEmuConfig::_config = nullptr;
|
||||
|
||||
void EQEmuConfig::parse_config() {
|
||||
|
||||
|
||||
ShortName = _root["server"]["world"].get("shortname", "").asString();
|
||||
LongName = _root["server"]["world"].get("longname", "").asString();
|
||||
WorldAddress = _root["server"]["world"].get("address", "").asString();
|
||||
|
|
@ -63,15 +63,15 @@ void EQEmuConfig::parse_config() {
|
|||
loginlist.Insert(loginconfig);
|
||||
} while (LoginCount < 100);
|
||||
}
|
||||
|
||||
|
||||
//<locked> from xml converts to json as locked: "", so i default to "false".
|
||||
|
||||
|
||||
//<locked> from xml converts to json as locked: "", so i default to "false".
|
||||
//The only way to enable locked is by switching to true, meaning this value is always false until manually set true
|
||||
Locked = false;
|
||||
if (_root["server"]["world"].get("locked", "false").asString() == "true") Locked = true;
|
||||
WorldIP = _root["server"]["world"]["tcp"].get("host", "127.0.0.1").asString();
|
||||
WorldTCPPort = atoi(_root["server"]["world"]["tcp"].get("port", "9000").asString().c_str());
|
||||
|
||||
|
||||
TelnetIP = _root["server"]["world"]["telnet"].get("ip", "127.0.0.1").asString();
|
||||
TelnetTCPPort = atoi(_root["server"]["world"]["telnet"].get("port", "9001").asString().c_str());
|
||||
TelnetEnabled = false;
|
||||
|
|
@ -92,7 +92,7 @@ void EQEmuConfig::parse_config() {
|
|||
DatabasePassword = _root["server"]["database"].get("password", "eq").asString();
|
||||
DatabaseHost = _root["server"]["database"].get("host", "localhost").asString();
|
||||
DatabasePort = atoi(_root["server"]["database"].get("port", "3306").asString().c_str());
|
||||
DatabaseDB = _root["server"]["database"].get("db", "eq").asString();
|
||||
DatabaseDB = _root["server"]["database"].get("db", "eq").asString();
|
||||
|
||||
QSDatabaseHost = _root["server"]["qsdatabase"].get("host", "localhost").asString();
|
||||
QSDatabasePort = atoi(_root["server"]["qsdatabase"].get("port", "3306").asString().c_str());
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ class EQEmuConfig
|
|||
void parse_config();
|
||||
|
||||
EQEmuConfig()
|
||||
{
|
||||
{
|
||||
|
||||
}
|
||||
virtual ~EQEmuConfig() {}
|
||||
|
|
@ -166,7 +166,7 @@ class EQEmuConfig
|
|||
}
|
||||
catch (std::exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -400,6 +400,7 @@ RULE_BOOL(Spells, IgnoreSpellDmgLvlRestriction, false) // ignore the 5 level spr
|
|||
RULE_BOOL(Spells, AllowItemTGB, false) // TGB doesn't work with items on live, custom servers want it though
|
||||
RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the target type to single target.
|
||||
RULE_BOOL(Spells, OldRainTargets, false) // use old incorrectly implemented max targets for rains
|
||||
RULE_BOOL(Spells, Oct182017AAResistOtherSPAFix, false) // they were not saving the base2 of AAs before this patch
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Combat)
|
||||
|
|
|
|||
|
|
@ -215,6 +215,13 @@ typedef enum {
|
|||
DS_THORNS = 249
|
||||
} DmgShieldType;
|
||||
|
||||
enum SuppressionMask {
|
||||
SM_Buffs = 1,
|
||||
SM_Items = 2,
|
||||
SM_AAs = 4,
|
||||
SM_All = 255
|
||||
};
|
||||
|
||||
//Spell Effect IDs
|
||||
// https://forums.daybreakgames.com/eq/index.php?threads/enumerated-spa-list.206288/
|
||||
// mirror: http://pastebin.com/MYeQqGwe
|
||||
|
|
@ -534,7 +541,7 @@ typedef enum {
|
|||
#define SE_ForageAdditionalItems 313 // implemented[AA] - chance to forage additional items
|
||||
#define SE_Invisibility2 314 // implemented - fixed duration invisible
|
||||
#define SE_InvisVsUndead2 315 // implemented - fixed duration ITU
|
||||
//#define SE_ImprovedInvisAnimals 316 // not used
|
||||
#define SE_ImprovedInvisAnimals 316 // not used
|
||||
#define SE_ItemHPRegenCapIncrease 317 // implemented[AA] - increases amount of health regen gained via items
|
||||
#define SE_ItemManaRegenCapIncrease 318 // implemented - increases amount of mana regen you can gain via items
|
||||
#define SE_CriticalHealOverTime 319 // implemented
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -112,6 +112,7 @@ SET(zone_sources
|
|||
spawn2.h
|
||||
spawngroup.cpp
|
||||
special_attacks.cpp
|
||||
spell_cache.cpp
|
||||
spell_effects.cpp
|
||||
spells.cpp
|
||||
tasks.cpp
|
||||
|
|
@ -217,6 +218,7 @@ SET(zone_headers
|
|||
spawn2.cpp
|
||||
spawn2.h
|
||||
spawngroup.h
|
||||
spell_cache.h
|
||||
string_ids.h
|
||||
tasks.h
|
||||
titles.h
|
||||
|
|
|
|||
|
|
@ -486,6 +486,8 @@ void Client::ResetAA() {
|
|||
m_pp.group_leadership_exp = 0;
|
||||
m_pp.raid_leadership_exp = 0;
|
||||
|
||||
m_spell_cache.SetAltCached(false);
|
||||
|
||||
database.DeleteCharacterLeadershipAAs(CharacterID());
|
||||
// undefined for these clients
|
||||
if (ClientVersionBit() & EQEmu::versions::bit_TitaniumAndEarlier)
|
||||
|
|
@ -801,6 +803,7 @@ void Client::RefundAA() {
|
|||
Save();
|
||||
}
|
||||
|
||||
m_spell_cache.SetAltCached(false);
|
||||
SendAlternateAdvancementTable();
|
||||
SendAlternateAdvancementPoints();
|
||||
SendAlternateAdvancementStats();
|
||||
|
|
@ -1115,6 +1118,7 @@ void Client::FinishAlternateAdvancementPurchase(AA::Rank *rank, bool ignore_cost
|
|||
}
|
||||
}
|
||||
|
||||
m_spell_cache.SetAltCached(false);
|
||||
CalcBonuses();
|
||||
|
||||
if(cost > 0) {
|
||||
|
|
|
|||
222
zone/attack.cpp
222
zone/attack.cpp
|
|
@ -1517,12 +1517,14 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
|
|||
///////////////////////////////////////////////////////////
|
||||
////// Send Attack Damage
|
||||
///////////////////////////////////////////////////////////
|
||||
if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == my_hit.skill &&
|
||||
IsValidSpell(aabonuses.SkillAttackProc[2])) {
|
||||
float chance = aabonuses.SkillAttackProc[0] / 1000.0f;
|
||||
if (zone->random.Roll(chance))
|
||||
SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1,
|
||||
spells[aabonuses.SkillAttackProc[2]].ResistDiff);
|
||||
if (my_hit.damage_done > 0 && m_spell_cache.HasSkillAttackProcs()) {
|
||||
auto end = m_spell_cache.skill_attack_proc_end();
|
||||
for (auto it = m_spell_cache.skill_attack_proc_begin(); it != end; ++it) {
|
||||
if (it->skill == my_hit.skill && IsValidSpell(it->spell)) {
|
||||
if (zone->random.Roll(it->chance / 1000.0f))
|
||||
SpellFinished(it->spell, other, EQEmu::CastingSlot::Item, 0, -1, spells[it->spell].ResistDiff);
|
||||
}
|
||||
}
|
||||
}
|
||||
other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks);
|
||||
|
||||
|
|
@ -4226,12 +4228,11 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
|
|||
// 1: Try Slay Undead
|
||||
if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead ||
|
||||
defender->GetBodyType() == BT_Vampire) {
|
||||
int SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0];
|
||||
int SlayRateBonus = TotalEffect(SE_SlayUndead, 0);
|
||||
if (SlayRateBonus) {
|
||||
float slayChance = static_cast<float>(SlayRateBonus) / 10000.0f;
|
||||
if (zone->random.Roll(slayChance)) {
|
||||
int SlayDmgBonus = std::max(
|
||||
{ aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1] });
|
||||
int SlayDmgBonus = BestEffect(SE_SlayUndead, 1);
|
||||
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
|
||||
hit.damage_done = (hit.damage_done * SlayDmgBonus) / 100;
|
||||
|
||||
|
|
@ -4402,20 +4403,12 @@ bool Mob::TryFinishingBlow(Mob *defender, int &damage)
|
|||
{
|
||||
// base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100
|
||||
if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) {
|
||||
int FB_Dmg = TotalEffect(SE_FinishingBlow, 1);
|
||||
|
||||
uint32 FB_Dmg =
|
||||
aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1];
|
||||
|
||||
uint32 FB_Level = 0;
|
||||
FB_Level = aabonuses.FinishingBlowLvl[0];
|
||||
if (FB_Level < spellbonuses.FinishingBlowLvl[0])
|
||||
FB_Level = spellbonuses.FinishingBlowLvl[0];
|
||||
else if (FB_Level < itembonuses.FinishingBlowLvl[0])
|
||||
FB_Level = itembonuses.FinishingBlowLvl[0];
|
||||
int FB_Level = BestEffect(SE_FinishingBlowLvl, 0);
|
||||
|
||||
// modern AA description says rank 1 (500) is 50% chance
|
||||
int ProcChance =
|
||||
aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0];
|
||||
int ProcChance = TotalEffect(SE_FinishingBlow, 0);
|
||||
|
||||
if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) &&
|
||||
(ProcChance >= zone->random.Int(1, 1000))) {
|
||||
|
|
@ -4699,177 +4692,64 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit)
|
|||
|
||||
void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive)
|
||||
{
|
||||
|
||||
if (!on) {
|
||||
SetTarget(nullptr);
|
||||
Log(Logs::General, Logs::Error, "A null Mob object was passed to Mob::TrySkillProc for evaluation!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!spellbonuses.LimitToSkill[skill] && !itembonuses.LimitToSkill[skill] && !aabonuses.LimitToSkill[skill])
|
||||
return;
|
||||
|
||||
/*Allow one proc from each (Spell/Item/AA)
|
||||
Kayen: Due to limited avialability of effects on live it is too difficult
|
||||
to confirm how they stack at this time, will adjust formula when more data is avialablle to test.*/
|
||||
bool CanProc = true;
|
||||
Kayen: Due to limited availability of effects on live it is too difficult
|
||||
to confirm how they stack at this time, will adjust formula when more data is available to test.*/
|
||||
|
||||
uint16 base_spell_id = 0;
|
||||
uint16 proc_spell_id = 0;
|
||||
float ProcMod = 0;
|
||||
float chance = 0;
|
||||
float ProcMod = 0.0f;
|
||||
float chance = 0.0f;
|
||||
|
||||
if (IsDefensive)
|
||||
chance = on->GetSkillProcChances(ReuseTime, hand);
|
||||
else
|
||||
chance = GetSkillProcChances(ReuseTime, hand);
|
||||
|
||||
if (spellbonuses.LimitToSkill[skill]) {
|
||||
std::vector<SpellCache::sSkillProc>::const_iterator it, end;
|
||||
auto type = SpellCache::eSkillProc::Buff;
|
||||
int i = 0;
|
||||
|
||||
for (int e = 0; e < MAX_SKILL_PROCS; e++) {
|
||||
if (CanProc &&
|
||||
((!Success && spellbonuses.SkillProc[e] && IsValidSpell(spellbonuses.SkillProc[e]))
|
||||
|| (Success && spellbonuses.SkillProcSuccess[e] && IsValidSpell(spellbonuses.SkillProcSuccess[e])))) {
|
||||
while (i < 3) {
|
||||
if (Success) {
|
||||
it = m_spell_cache.skill_proc_success_begin(type);
|
||||
end = m_spell_cache.skill_proc_success_end(type);
|
||||
} else {
|
||||
it = m_spell_cache.skill_proc_attempt_begin(type);
|
||||
end = m_spell_cache.skill_proc_attempt_end(type);
|
||||
}
|
||||
|
||||
if (Success)
|
||||
base_spell_id = spellbonuses.SkillProcSuccess[e];
|
||||
else
|
||||
base_spell_id = spellbonuses.SkillProc[e];
|
||||
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
|
||||
if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) {
|
||||
proc_spell_id = spells[base_spell_id].base[i];
|
||||
ProcMod = static_cast<float>(spells[base_spell_id].base2[i]);
|
||||
}
|
||||
|
||||
else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQEmu::skills::HIGHEST_SKILL) {
|
||||
|
||||
if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) {
|
||||
float final_chance = chance * (ProcMod / 100.0f);
|
||||
if (zone->random.Roll(final_chance)) {
|
||||
ExecWeaponProc(nullptr, proc_spell_id, on);
|
||||
CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0,
|
||||
base_spell_id);
|
||||
CanProc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Reset and check for proc in sequence
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
}
|
||||
for (; it != end; ++it) {
|
||||
// abilities that have no skill limits can always proc? (see Lingering Death, Shrouding Speed Discipline)
|
||||
if ((it->skills.empty() ||
|
||||
it->skills.count(static_cast<EQEmu::skills::SkillType>(skill)) != 0) &&
|
||||
IsValidSpell(it->spell)) {
|
||||
ProcMod = static_cast<float>(it->chance);
|
||||
float final_chance = chance * (ProcMod / 100.0f);
|
||||
if (zone->random.Roll(final_chance)) {
|
||||
ExecWeaponProc(nullptr, it->spell, on);
|
||||
if (type == SpellCache::eSkillProc::Buff)
|
||||
CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0,
|
||||
buffs[it->slot].spellid);
|
||||
return; // do we want to allow others to have a chance?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (itembonuses.LimitToSkill[skill]) {
|
||||
CanProc = true;
|
||||
for (int e = 0; e < MAX_SKILL_PROCS; e++) {
|
||||
if (CanProc &&
|
||||
((!Success && itembonuses.SkillProc[e] && IsValidSpell(itembonuses.SkillProc[e]))
|
||||
|| (Success && itembonuses.SkillProcSuccess[e] && IsValidSpell(itembonuses.SkillProcSuccess[e])))) {
|
||||
|
||||
if (Success)
|
||||
base_spell_id = itembonuses.SkillProcSuccess[e];
|
||||
else
|
||||
base_spell_id = itembonuses.SkillProc[e];
|
||||
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
|
||||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (spells[base_spell_id].effectid[i] == SE_SkillProc || spells[base_spell_id].effectid[i] == SE_SkillProcSuccess) {
|
||||
proc_spell_id = spells[base_spell_id].base[i];
|
||||
ProcMod = static_cast<float>(spells[base_spell_id].base2[i]);
|
||||
}
|
||||
|
||||
else if (spells[base_spell_id].effectid[i] == SE_LimitToSkill && spells[base_spell_id].base[i] <= EQEmu::skills::HIGHEST_SKILL) {
|
||||
|
||||
if (CanProc && spells[base_spell_id].base[i] == skill && IsValidSpell(proc_spell_id)) {
|
||||
float final_chance = chance * (ProcMod / 100.0f);
|
||||
if (zone->random.Roll(final_chance)) {
|
||||
ExecWeaponProc(nullptr, proc_spell_id, on);
|
||||
CanProc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsClient() && aabonuses.LimitToSkill[skill]) {
|
||||
|
||||
CanProc = true;
|
||||
uint32 effect_id = 0;
|
||||
int32 base1 = 0;
|
||||
int32 base2 = 0;
|
||||
uint32 slot = 0;
|
||||
|
||||
for (int e = 0; e < MAX_SKILL_PROCS; e++) {
|
||||
if (CanProc &&
|
||||
((!Success && aabonuses.SkillProc[e])
|
||||
|| (Success && aabonuses.SkillProcSuccess[e]))) {
|
||||
int aaid = 0;
|
||||
|
||||
if (Success)
|
||||
base_spell_id = aabonuses.SkillProcSuccess[e];
|
||||
else
|
||||
base_spell_id = aabonuses.SkillProc[e];
|
||||
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
|
||||
for (auto &rank_info : aa_ranks) {
|
||||
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(rank_info.first, rank_info.second.first);
|
||||
auto ability = ability_rank.first;
|
||||
auto rank = ability_rank.second;
|
||||
|
||||
if (!ability) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &effect : rank->effects) {
|
||||
effect_id = effect.effect_id;
|
||||
base1 = effect.base1;
|
||||
base2 = effect.base2;
|
||||
slot = effect.slot;
|
||||
|
||||
if (effect_id == SE_SkillProc || effect_id == SE_SkillProcSuccess) {
|
||||
proc_spell_id = base1;
|
||||
ProcMod = static_cast<float>(base2);
|
||||
}
|
||||
else if (effect_id == SE_LimitToSkill && base1 <= EQEmu::skills::HIGHEST_SKILL) {
|
||||
|
||||
if (CanProc && base1 == skill && IsValidSpell(proc_spell_id)) {
|
||||
float final_chance = chance * (ProcMod / 100.0f);
|
||||
|
||||
if (zone->random.Roll(final_chance)) {
|
||||
ExecWeaponProc(nullptr, proc_spell_id, on);
|
||||
CanProc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
proc_spell_id = 0;
|
||||
ProcMod = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
++i;
|
||||
switch (i) {
|
||||
case 1:
|
||||
type = SpellCache::eSkillProc::Worn;
|
||||
break;
|
||||
case 2:
|
||||
type = SpellCache::eSkillProc::AA;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
zone/bonuses.cpp
116
zone/bonuses.cpp
|
|
@ -628,7 +628,9 @@ void Client::CalcEdibleBonuses(StatBonuses* newbon) {
|
|||
|
||||
void Mob::CalcAABonuses(StatBonuses *newbon)
|
||||
{
|
||||
memset(newbon, 0, sizeof(StatBonuses)); // start fresh
|
||||
m_spell_cache.ClearAltEffect();
|
||||
|
||||
RecacheSuppressionSpells(); // we gotta do this :P
|
||||
|
||||
for (const auto &aa : aa_ranks) {
|
||||
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
||||
|
|
@ -645,6 +647,8 @@ void Mob::CalcAABonuses(StatBonuses *newbon)
|
|||
|
||||
ApplyAABonuses(*rank, newbon);
|
||||
}
|
||||
|
||||
m_spell_cache.SetAltCached(true);
|
||||
}
|
||||
|
||||
//A lot of the normal spell functions (IsBlankSpellEffect, etc) are set for just spells (in common/spdat.h).
|
||||
|
|
@ -660,17 +664,19 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
|||
int32 base2 = 0; // only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table
|
||||
uint32 slot = 0;
|
||||
|
||||
int last_skill_proc = 0; // some book keeping for SE_SkillProc and SE_SkillProcSuccess for SE_LimitToSkill
|
||||
|
||||
for (const auto &e : rank.effects) {
|
||||
effect = e.effect_id;
|
||||
base1 = e.base1;
|
||||
base2 = e.base2;
|
||||
base2 = 0; // only some AAs use the base2
|
||||
slot = e.slot;
|
||||
|
||||
// we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it
|
||||
if (effect == 0 && base1 == 0 && base2 == 0)
|
||||
continue;
|
||||
|
||||
// IsBlankSpellEffect()
|
||||
// IsBlankSpellEffect() -- these shouldn't be in AAs
|
||||
if (effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block ||
|
||||
effect == SE_StackingCommand_Overwrite)
|
||||
continue;
|
||||
|
|
@ -678,12 +684,116 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
|||
Log(Logs::Detail, Logs::AA, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s",
|
||||
effect, rank.id, slot, base1, base2, GetCleanName());
|
||||
|
||||
// this should be removed when rewritten
|
||||
uint8 focus = IsFocusEffect(0, 0, true, effect);
|
||||
if (focus) {
|
||||
newbon->FocusEffects[focus] = static_cast<uint8>(effect);
|
||||
continue;
|
||||
}
|
||||
|
||||
// SE_NegateSpellEffects
|
||||
if (!(GetSuppresionSpellMask(effect) & SM_AAs))
|
||||
continue;
|
||||
|
||||
/* TODO: Other SPAs that need special handling
|
||||
* SE_ProcOnKillShot
|
||||
* SE_SpellOnDeath
|
||||
*/
|
||||
|
||||
// special handling for AAs that live doesn't appear to do
|
||||
// Alternative to this would be to just iterate over the AA list
|
||||
// each of these are special and we skip below
|
||||
switch (effect) {
|
||||
case SE_SkillAttackProc:
|
||||
m_spell_cache.InsertSkillAttackProc(base1, e.base2, rank.spell);
|
||||
continue;
|
||||
case SE_SkillProc:
|
||||
last_skill_proc = effect;
|
||||
m_spell_cache.InsertSkillProcAttempt(SpellCache::eSkillProc::AA, base1, e.base2);
|
||||
continue;
|
||||
case SE_SkillProcSuccess:
|
||||
last_skill_proc = effect;
|
||||
m_spell_cache.InsertSkillProcSuccess(SpellCache::eSkillProc::AA, base1, e.base2);
|
||||
continue;
|
||||
case SE_LimitToSkill:
|
||||
m_spell_cache.InsertSkillLimit(SpellCache::eSkillProc::AA, static_cast<EQEmu::skills::SkillType>(base1), last_skill_proc == SE_SkillProcSuccess);
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Okay, effects that use base2
|
||||
switch (effect) {
|
||||
case SE_SpellEffectResistChance:
|
||||
if (!RuleB(Spells, Oct182017AAResistOtherSPAFix)) // this is broken before this patch
|
||||
break;
|
||||
case SE_Levitate:
|
||||
case SE_CriticalHitChance:
|
||||
case SE_MeleeSkillCheck:
|
||||
case SE_HitChance:
|
||||
case SE_DamageModifier:
|
||||
case SE_MinDamageModifier:
|
||||
case SE_SkillDamageTaken:
|
||||
case SE_Accuracy:
|
||||
case SE_SkillDamageAmount:
|
||||
case SE_GiveDoubleRiposte:
|
||||
case SE_ReduceSkillTimer:
|
||||
case SE_RaiseSkillCap:
|
||||
case SE_AddSingingMod:
|
||||
case SE_RaiseStatCap:
|
||||
case SE_HastenedAASkill:
|
||||
case SE_AddPetCommand:
|
||||
case SE_ReduceTradeskillFail:
|
||||
case SE_CriticalDamageMob:
|
||||
case SE_SkillDamageAmount2:
|
||||
base2 = e.base2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// these don't stack and some treat base2 differently and don't stack
|
||||
switch (effect) {
|
||||
case SE_MasteryofPast:
|
||||
case SE_Assassinate:
|
||||
case SE_AssassinateLevel:
|
||||
case SE_HeadShot:
|
||||
case SE_HeadShotLevel:
|
||||
case SE_FinishingBlowLvl:
|
||||
case SE_FinishingBlow:
|
||||
case SE_DivineSave: // non stacking
|
||||
if (base1 > m_spell_cache.GetCachedAltEffect(effect))
|
||||
m_spell_cache.InsertAltEffect(effect, base1, 0);
|
||||
break;
|
||||
default: //stacking
|
||||
m_spell_cache.InsertAltEffect(effect, base1 + m_spell_cache.GetCachedAltEffect(effect), base2);
|
||||
break;
|
||||
}
|
||||
|
||||
// RoF2 doesn't have SE_CriticalSpellChance here, but our AA data looks like it's processed like this
|
||||
// client doesn't handle SE_DivineSave like this
|
||||
// we support older Frenzied Devastation SPA too, this was revamped on live at some point
|
||||
// SE_SlayUndead isn't done here on client, but it should work for how it works
|
||||
switch (effect) {
|
||||
case SE_Assassinate:
|
||||
case SE_HeadShot:
|
||||
case SE_FinishingBlow:
|
||||
case SE_AssassinateLevel:
|
||||
case SE_HeadShotLevel:
|
||||
case SE_CriticalSpellChance:
|
||||
case SE_DivineSave:
|
||||
case SE_FrenziedDevastation:
|
||||
case SE_SlayUndead:
|
||||
case SE_PC_Pet_Rampage:
|
||||
base2 = e.base2; // uses base2 for fun times!
|
||||
if (base2 > m_spell_cache.GetCachedAltEffect(effect, 1))
|
||||
m_spell_cache.InsertAltEffect(effect, base2, 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
switch (effect) {
|
||||
case SE_ACv2:
|
||||
case SE_ArmorClass:
|
||||
|
|
|
|||
|
|
@ -437,6 +437,10 @@ public:
|
|||
void EnableAreaRegens(int value);
|
||||
void DisableAreaRegens();
|
||||
|
||||
// new spell cache stuff
|
||||
virtual void RecacheItemEffects();
|
||||
virtual void RecacheSuppressionItems(); // work around for NPC invs being dumb
|
||||
|
||||
void ServerFilter(SetServerFilter_Struct* filter);
|
||||
void BulkSendTraderInventory(uint32 char_id);
|
||||
void SendSingleTraderItem(uint32 char_id, int uniqueid);
|
||||
|
|
|
|||
|
|
@ -286,6 +286,61 @@ struct Buffs_Struct {
|
|||
bool UpdateClient;
|
||||
};
|
||||
|
||||
// we will use this for item stat bonus now only rather then spell effects
|
||||
// TODO: rename when other is removed
|
||||
struct StatBonuses2 {
|
||||
int STR;
|
||||
int STA;
|
||||
int CHA;
|
||||
int DEX;
|
||||
int INT;
|
||||
int AGI;
|
||||
int WIS;
|
||||
int MR;
|
||||
int CR;
|
||||
int FR;
|
||||
int PR;
|
||||
int DR;
|
||||
int Corrup;
|
||||
int HP;
|
||||
int Mana;
|
||||
int Endurance;
|
||||
int AC;
|
||||
int ProcChance; // ProcChance/10 == % increase i = CombatEffects
|
||||
int MeleeMitigation; //i = Shielding
|
||||
int SpellShield;
|
||||
int AvoidMeleeChance; //AvoidMeleeChance/10 == % chance i = Avoidance (item mod)
|
||||
int HitChance; //HitChance/15 == % increase i = Accuracy (Item: Accuracy)
|
||||
int StunResist; //i
|
||||
int DoTShielding;
|
||||
int DSMitigation; // Item Effect
|
||||
int HeroicSTR;
|
||||
int HeroicINT;
|
||||
int HeroicWIS;
|
||||
int HeroicAGI;
|
||||
int HeroicDEX;
|
||||
int HeroicSTA;
|
||||
int HeroicCHA;
|
||||
int HeroicMR;
|
||||
int HeroicFR;
|
||||
int HeroicCR;
|
||||
int HeroicDR;
|
||||
int HeroicPR;
|
||||
int HeroicCorrup;
|
||||
int HealAmt; // Item Effect
|
||||
int SpellDmg; // Item Effect
|
||||
int Clairvoyance; // Item Effect
|
||||
int StrikeThrough; // PoP: Strike Through %
|
||||
int ATK;
|
||||
int HPRegen;
|
||||
int ManaRegen;
|
||||
int EnduranceRegen;
|
||||
int DamageShield; // this is damage done to mobs that attack this
|
||||
int PotionBeltSlots;
|
||||
int SkillMinDamageMod[9];
|
||||
int haste;
|
||||
};
|
||||
|
||||
struct StatBonuses {
|
||||
int32 AC;
|
||||
int32 HP;
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) {
|
|||
value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch
|
||||
|
||||
chance = RuleI(Spells, BaseCritChance); //Wizard base critical chance is 2% (Does not scale with level)
|
||||
chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance;
|
||||
chance += itembonuses.FrenziedDevastation + spellbonuses.FrenziedDevastation + aabonuses.FrenziedDevastation;
|
||||
chance += TotalEffect(SE_CriticalSpellChance, 0);
|
||||
chance += TotalEffect(SE_FrenziedDevastation, 1);
|
||||
|
||||
//Crtical Hit Calculation pathway
|
||||
if (chance > 0 || (IsClient() && GetClass() == WIZARD && GetLevel() >= RuleI(Spells, WizCritLevel))) {
|
||||
|
|
@ -79,8 +79,8 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) {
|
|||
|
||||
if (zone->random.Roll(chance)) {
|
||||
Critical = true;
|
||||
ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease;
|
||||
ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack;
|
||||
ratio += TotalEffect(SE_SpellCritDmgIncrease);
|
||||
ratio += TotalEffect(SE_CriticalSpellChance, 1);
|
||||
}
|
||||
|
||||
else if ((IsClient() && GetClass() == WIZARD) || (IsMerc() && GetClass() == CASTERDPS)) {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ Mob::Mob(const char* in_name,
|
|||
fix_z_timer_engaged(100),
|
||||
attack_anim_timer(1000),
|
||||
position_update_melee_push_timer(1000),
|
||||
bItemDirty(false),
|
||||
mHateListCleanup(6000)
|
||||
{
|
||||
targeted = 0;
|
||||
|
|
|
|||
32
zone/mob.h
32
zone/mob.h
|
|
@ -25,6 +25,7 @@
|
|||
#include "position.h"
|
||||
#include "aa_ability.h"
|
||||
#include "aa.h"
|
||||
#include "spell_cache.h"
|
||||
#include "../common/light_source.h"
|
||||
#include "../common/emu_constants.h"
|
||||
#include <set>
|
||||
|
|
@ -537,6 +538,35 @@ public:
|
|||
inline const int8 GetFlyMode() const { return flymode; }
|
||||
bool IsBoat() const;
|
||||
|
||||
// Spell Cache stuff
|
||||
int TotalEffect(int spaID, int subindex = 0, bool bIncludeItems = true, bool bIncludeAA = true, bool bIncludeBuffs = true);
|
||||
// this one just skips special behavior, it's /not/ usable with SPAs with special behavior
|
||||
int TotalEffectSimple(int spaID, int subindex = 0) { return GetCachedPlayerEffect(spaID, subindex) + GetCachedItemEffect(spaID, subindex) + GetCachedAltEffect(spaID, subindex); }
|
||||
// returns the highest, also not usable with SPAs that need special behavior, but I guess this it's own special behavior
|
||||
int BestEffect(int spaID, int subindex = 0) { return std::max(GetCachedPlayerEffect(spaID, subindex), std::max(GetCachedItemEffect(spaID, subindex), GetCachedAltEffect(spaID, subindex))); }
|
||||
void RecacheSpellEffects();
|
||||
virtual void RecacheItemEffects() {} // virtual since mob/client have different inv structs
|
||||
void RecacheSuppressionSpells();
|
||||
virtual void RecacheSuppressionItems() {} // work around for invs being different for mobs/clients
|
||||
inline int GetSuppresionSpellMask(int spaID) {
|
||||
return GetCachedPlayerEffect(SE_NegateSpellEffect, spaID) | GetCachedItemEffect(SE_NegateSpellEffect, spaID) | GetCachedAltEffect(SE_NegateSpellEffect, spaID);
|
||||
}
|
||||
inline int GetCachedPlayerEffect(int spaID, int subindex = 0) {
|
||||
if (!m_spell_cache.IsSpellCached())
|
||||
RecacheSpellEffects();
|
||||
return m_spell_cache.GetCachedPlayerEffect(spaID, subindex);
|
||||
}
|
||||
inline int GetCachedItemEffect(int spaID, int subindex = 0) {
|
||||
if (!m_spell_cache.IsItemCached())
|
||||
RecacheItemEffects();
|
||||
return m_spell_cache.GetCachedItemEffect(spaID, subindex);
|
||||
}
|
||||
inline int GetCachedAltEffect(int spaID, int subindex = 0) {
|
||||
if (!m_spell_cache.IsAltCached())
|
||||
CalcAABonuses(&aabonuses);
|
||||
return m_spell_cache.GetCachedAltEffect(spaID, subindex);
|
||||
}
|
||||
|
||||
//Group
|
||||
virtual bool HasRaid() = 0;
|
||||
virtual bool HasGroup() = 0;
|
||||
|
|
@ -1215,9 +1245,11 @@ protected:
|
|||
uint32 scalerate;
|
||||
Buffs_Struct *buffs;
|
||||
uint32 current_buff_count;
|
||||
bool bItemDirty;
|
||||
StatBonuses itembonuses;
|
||||
StatBonuses spellbonuses;
|
||||
StatBonuses aabonuses;
|
||||
SpellCache m_spell_cache;
|
||||
uint16 petid;
|
||||
uint16 ownerid;
|
||||
PetType typeofpet;
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float
|
|||
//Only iterate through NPCs
|
||||
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
|
||||
NPC* mob = it->second;
|
||||
|
||||
|
||||
if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -482,7 +482,7 @@ void Mob::AI_Start(uint32 iMoveDelay) {
|
|||
|
||||
if(CastToNPC()->WillAggroNPCs())
|
||||
AI_scan_area_timer = std::unique_ptr<Timer>(new Timer(RandomTimer(RuleI(NPC, NPCToNPCAggroTimerMin), RuleI(NPC, NPCToNPCAggroTimerMax))));
|
||||
|
||||
|
||||
AI_check_signal_timer = std::unique_ptr<Timer>(new Timer(AI_check_signal_timer_delay));
|
||||
|
||||
|
||||
|
|
@ -1439,7 +1439,7 @@ void Mob::AI_Process() {
|
|||
}
|
||||
else
|
||||
{
|
||||
CalculateNewPosition2(owner->GetX(),
|
||||
CalculateNewPosition2(owner->GetX(),
|
||||
owner->GetY(), owner->GetZ(), speed);
|
||||
}
|
||||
}
|
||||
|
|
@ -1579,14 +1579,14 @@ void NPC::AI_DoMovement() {
|
|||
roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1);
|
||||
if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y)
|
||||
roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1);
|
||||
Log(Logs::Detail, Logs::AI,
|
||||
Log(Logs::Detail, Logs::AI,
|
||||
"Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)",
|
||||
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y,
|
||||
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y,
|
||||
roambox_max_y, roambox_movingto_x, roambox_movingto_y);
|
||||
}
|
||||
|
||||
// Keep calling with updates, using wherever we are in Z.
|
||||
if (!MakeNewPositionAndSendUpdate(roambox_movingto_x,
|
||||
if (!MakeNewPositionAndSendUpdate(roambox_movingto_x,
|
||||
roambox_movingto_y, m_Position.z, walksp))
|
||||
{
|
||||
this->FixZ(); // FixZ on final arrival point.
|
||||
|
|
@ -1617,7 +1617,7 @@ void NPC::AI_DoMovement() {
|
|||
if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY())
|
||||
{ // are we there yet? then stop
|
||||
Log(Logs::Detail, Logs::AI, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid());
|
||||
|
||||
|
||||
SetWaypointPause();
|
||||
SetAppearance(eaStanding, false);
|
||||
SetMoving(false);
|
||||
|
|
|
|||
|
|
@ -160,6 +160,10 @@ public:
|
|||
virtual void InitializeBuffSlots();
|
||||
virtual void UninitializeBuffSlots();
|
||||
|
||||
// new spell cache stuff
|
||||
virtual void RecacheItemEffects();
|
||||
virtual void RecacheSuppressionItems(); // work around for NPC invs being dumb
|
||||
|
||||
virtual void SetAttackTimer();
|
||||
virtual void RangedAttack(Mob* other);
|
||||
virtual void ThrowingAttack(Mob* other) { }
|
||||
|
|
@ -457,7 +461,7 @@ protected:
|
|||
|
||||
uint32 npc_spells_id;
|
||||
uint8 casting_spell_AIindex;
|
||||
|
||||
|
||||
uint32* pDontCastBefore_casting_spell;
|
||||
std::vector<AISpells_Struct> AIspells;
|
||||
bool HasAISpell;
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
|
|||
memcpy(npc_type, base, sizeof(NPCType));
|
||||
|
||||
// If pet power is set to -1 in the DB, use stat scaling
|
||||
if ((this->IsClient()
|
||||
if ((this->IsClient()
|
||||
#ifdef BOTS
|
||||
|| this->IsBot()
|
||||
#endif
|
||||
|
|
@ -658,7 +658,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) {
|
|||
|
||||
if (item2) {
|
||||
bool noDrop=(item2->NoDrop == 0); // Field is reverse logic
|
||||
bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
|
||||
bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
|
||||
_CLIENTPET(this) && GetPetType() <= petOther);
|
||||
|
||||
if (!noDrop || petCanHaveNoDrop) {
|
||||
|
|
|
|||
|
|
@ -2014,9 +2014,8 @@ int Mob::TryHeadShot(Mob *defender, EQEmu::skills::SkillType skillInUse)
|
|||
// Only works on YOUR target.
|
||||
if (defender && defender->GetBodyType() == BT_Humanoid && !defender->IsClient() &&
|
||||
skillInUse == EQEmu::skills::SkillArchery && GetTarget() == defender) {
|
||||
uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1];
|
||||
uint8 HeadShot_Level = 0; // Get Highest Headshot Level
|
||||
HeadShot_Level = std::max({aabonuses.HSLevel[0], spellbonuses.HSLevel[0], itembonuses.HSLevel[0]});
|
||||
int HeadShot_Dmg = TotalEffect(SE_HeadShot, 1);
|
||||
int HeadShot_Level = BestEffect(SE_HeadShotLevel);
|
||||
|
||||
if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) {
|
||||
int chance = GetDEX();
|
||||
|
|
@ -2024,10 +2023,10 @@ int Mob::TryHeadShot(Mob *defender, EQEmu::skills::SkillType skillInUse)
|
|||
if (IsClient())
|
||||
chance += CastToClient()->GetHeroicDEX() / 25;
|
||||
chance *= 10;
|
||||
int norm = aabonuses.HSLevel[1];
|
||||
int norm = TotalEffect(SE_HeadShotLevel, 1);
|
||||
if (norm > 0)
|
||||
chance = chance * norm / 100;
|
||||
chance += aabonuses.HeadShot[0] + spellbonuses.HeadShot[0] + itembonuses.HeadShot[0];
|
||||
chance += TotalEffect(SE_HeadShot, 0);
|
||||
if (zone->random.Int(1, 1000) <= chance) {
|
||||
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT,
|
||||
GetName());
|
||||
|
|
@ -2049,7 +2048,7 @@ int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse)
|
|||
if (IsClient())
|
||||
chance += CastToClient()->GetHeroicDEX();
|
||||
chance *= 10;
|
||||
int norm = aabonuses.AssassinateLevel[1];
|
||||
int norm = TotalEffect(SE_AssassinateLevel, 1);
|
||||
if (norm > 0)
|
||||
chance = chance * norm / 100;
|
||||
} else if (skillInUse == EQEmu::skills::SkillThrowing) {
|
||||
|
|
@ -2059,14 +2058,11 @@ int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse)
|
|||
chance += 5;
|
||||
}
|
||||
|
||||
chance += aabonuses.Assassinate[0] + spellbonuses.Assassinate[0] + itembonuses.Assassinate[0];
|
||||
chance += TotalEffect(SE_Assassinate, 0); // base2 == 0 is chance
|
||||
|
||||
uint32 Assassinate_Dmg =
|
||||
aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1];
|
||||
int Assassinate_Dmg = TotalEffect(SE_Assassinate, 1); // base2 == 1 is dmg
|
||||
|
||||
uint8 Assassinate_Level = 0; // Get Highest Headshot Level
|
||||
Assassinate_Level = std::max(
|
||||
{aabonuses.AssassinateLevel[0], spellbonuses.AssassinateLevel[0], itembonuses.AssassinateLevel[0]});
|
||||
int Assassinate_Level = BestEffect(SE_AssassinateLevel, 0);
|
||||
|
||||
// revamped AAs require AA line I believe?
|
||||
if (!Assassinate_Level)
|
||||
|
|
|
|||
227
zone/spell_cache.cpp
Normal file
227
zone/spell_cache.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#include "spell_cache.h"
|
||||
|
||||
void SpellCache::InsertSpellEffect(int affect, int value, int subindex)
|
||||
{
|
||||
auto range = m_spelleffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
break;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
if (effect_iter == range.second) {// we didn't find one
|
||||
sEffectCache e{affect, value, subindex};
|
||||
m_spelleffect.emplace(affect, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// we gotta update
|
||||
effect_iter->second.base1 = value;
|
||||
}
|
||||
|
||||
void SpellCache::InsertItemEffect(int affect, int value, int subindex)
|
||||
{
|
||||
auto range = m_itemeffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
break;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
if (effect_iter == range.second) {// we didn't find one
|
||||
sEffectCache e{affect, value, subindex};
|
||||
m_itemeffect.emplace(affect, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// we gotta update
|
||||
effect_iter->second.base1 = value;
|
||||
}
|
||||
|
||||
void SpellCache::InsertAltEffect(int affect, int value, int subindex)
|
||||
{
|
||||
auto range = m_alteffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
break;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
if (effect_iter == range.second) {// we didn't find one
|
||||
sEffectCache e{affect, value, subindex};
|
||||
m_alteffect.emplace(affect, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// we gotta update
|
||||
effect_iter->second.base1 = value;
|
||||
}
|
||||
|
||||
void SpellCache::InsertSkillAttackProc(int chance, int skill, int spell)
|
||||
{
|
||||
m_skill_attack_proc.push_back({chance, skill, spell});
|
||||
}
|
||||
|
||||
const SpellCache::sEffectCache *SpellCache::GetSpellCached(int affect, int subindex) const
|
||||
{
|
||||
auto range = m_spelleffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
return &effect_iter->second;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SpellCache::sEffectCache *SpellCache::GetItemCached(int affect, int subindex) const
|
||||
{
|
||||
auto range = m_itemeffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
return &effect_iter->second;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SpellCache::sEffectCache *SpellCache::GetAltCached(int affect, int subindex) const
|
||||
{
|
||||
auto range = m_alteffect.equal_range(affect);
|
||||
auto effect_iter = range.first;
|
||||
|
||||
while (effect_iter != range.second) {
|
||||
if (effect_iter->second.base2 == subindex)
|
||||
return &effect_iter->second;
|
||||
++effect_iter;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SpellCache::InsertSkillProcAttempt(SpellCache::eSkillProc type, int spell, int chance, int slot)
|
||||
{
|
||||
sSkillProc e;
|
||||
e.spell = spell;
|
||||
e.chance = chance;
|
||||
e.slot = slot;
|
||||
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
m_skill_proc_attempt.aa.push_back(e);
|
||||
return;
|
||||
case eSkillProc::Buff:
|
||||
m_skill_proc_attempt.buff.push_back(e);
|
||||
return;
|
||||
case eSkillProc::Worn:
|
||||
m_skill_proc_attempt.worn.push_back(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellCache::InsertSkillProcSuccess(SpellCache::eSkillProc type, int spell, int chance, int slot)
|
||||
{
|
||||
sSkillProc e;
|
||||
e.spell = spell;
|
||||
e.chance = chance;
|
||||
e.slot = slot;
|
||||
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
m_skill_proc_success.aa.push_back(e);
|
||||
return;
|
||||
case eSkillProc::Buff:
|
||||
m_skill_proc_success.buff.push_back(e);
|
||||
return;
|
||||
case eSkillProc::Worn:
|
||||
m_skill_proc_success.worn.push_back(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellCache::InsertSkillLimit(SpellCache::eSkillProc type, EQEmu::skills::SkillType skill, bool on_success)
|
||||
{
|
||||
auto &which = on_success ? m_skill_proc_success : m_skill_proc_attempt;
|
||||
|
||||
// we have to assume the last one inserted is correct, otherwise it would be bad data
|
||||
switch (type) {
|
||||
case eSkillProc::AA: {
|
||||
auto it = which.aa.rbegin();
|
||||
if (it != which.aa.rend())
|
||||
it->skills.insert(skill);
|
||||
return;
|
||||
}
|
||||
case eSkillProc::Buff: {
|
||||
auto it = which.buff.rbegin();
|
||||
if (it != which.buff.rend())
|
||||
it->skills.insert(skill);
|
||||
return;
|
||||
}
|
||||
case eSkillProc::Worn: {
|
||||
auto it = which.worn.rbegin();
|
||||
if (it != which.worn.rend())
|
||||
it->skills.insert(skill);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpellCache::sSkillProc>::const_iterator SpellCache::skill_proc_attempt_begin(SpellCache::eSkillProc type) const
|
||||
{
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
return m_skill_proc_attempt.aa.cbegin();
|
||||
case eSkillProc::Buff:
|
||||
return m_skill_proc_attempt.buff.cbegin();
|
||||
case eSkillProc::Worn:
|
||||
return m_skill_proc_attempt.worn.cbegin();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpellCache::sSkillProc>::const_iterator SpellCache::skill_proc_attempt_end(SpellCache::eSkillProc type) const
|
||||
{
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
return m_skill_proc_attempt.aa.cend();
|
||||
case eSkillProc::Buff:
|
||||
return m_skill_proc_attempt.buff.cend();
|
||||
case eSkillProc::Worn:
|
||||
return m_skill_proc_attempt.worn.cend();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpellCache::sSkillProc>::const_iterator SpellCache::skill_proc_success_begin(SpellCache::eSkillProc type) const
|
||||
{
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
return m_skill_proc_success.aa.cbegin();
|
||||
case eSkillProc::Buff:
|
||||
return m_skill_proc_success.buff.cbegin();
|
||||
case eSkillProc::Worn:
|
||||
return m_skill_proc_success.worn.cbegin();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SpellCache::sSkillProc>::const_iterator SpellCache::skill_proc_success_end(SpellCache::eSkillProc type) const
|
||||
{
|
||||
switch (type) {
|
||||
case eSkillProc::AA:
|
||||
return m_skill_proc_success.aa.cend();
|
||||
case eSkillProc::Buff:
|
||||
return m_skill_proc_success.buff.cend();
|
||||
case eSkillProc::Worn:
|
||||
return m_skill_proc_success.worn.cend();
|
||||
}
|
||||
}
|
||||
|
||||
144
zone/spell_cache.h
Normal file
144
zone/spell_cache.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#ifndef SPELL_CACHE_H
|
||||
#define SPELL_CACHE_H
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "../common/skills.h"
|
||||
|
||||
class SpellCache
|
||||
{
|
||||
public:
|
||||
struct sEffectCache {
|
||||
int affect;
|
||||
int base1;
|
||||
int base2;
|
||||
};
|
||||
|
||||
// can't really get away with doing a cache trick with this since you can have multiple
|
||||
// (see Decapitation)
|
||||
// This is for SE_SkillAttackProc (SPA 288) only, which is purely AA
|
||||
struct sSkillAttackProc {
|
||||
int chance;
|
||||
int skill;
|
||||
int spell;
|
||||
};
|
||||
|
||||
// enum for skill proc functions
|
||||
enum class eSkillProc {
|
||||
Buff,
|
||||
Worn,
|
||||
AA
|
||||
};
|
||||
|
||||
// SE_SpellOnDeath
|
||||
struct sDeathProc {
|
||||
int chance;
|
||||
int spell;
|
||||
int slot;
|
||||
};
|
||||
|
||||
// this is used for SE_SkillProc and SE_SkillProcSuccess which are matched with SE_LimitToSkill
|
||||
struct sSkillProc {
|
||||
int chance;
|
||||
int spell;
|
||||
int slot; // used for buff slot when it's a buff
|
||||
std::set<EQEmu::skills::SkillType> skills;
|
||||
};
|
||||
|
||||
// we need to recache these separately
|
||||
template <typename T>
|
||||
struct sProcs {
|
||||
std::vector<T> buff;
|
||||
std::vector<T> worn;
|
||||
std::vector<T> aa;
|
||||
};
|
||||
|
||||
SpellCache() : spell_cached(false), item_cached(false), alt_cached(false) {}
|
||||
~SpellCache() {}
|
||||
|
||||
void InsertSpellEffect(int affect, int value, int subindex);
|
||||
void InsertItemEffect(int affect, int value, int subindex);
|
||||
void InsertAltEffect(int affect, int value, int subindex);
|
||||
void InsertSkillAttackProc(int chace, int skill, int spell);
|
||||
|
||||
inline void ClearSpellEffect() { m_spelleffect.clear(); m_skill_proc_attempt.buff.clear(); m_skill_proc_success.buff.clear(); m_death_proc.buff.clear(); }
|
||||
inline void ClearItemEffect() { m_itemeffect.clear(); m_skill_proc_attempt.worn.clear(); m_skill_proc_success.worn.clear(); m_death_proc.worn.clear(); }
|
||||
inline void ClearAltEffect() { m_alteffect.clear(); m_skill_attack_proc.clear(); m_skill_proc_attempt.aa.clear(); m_skill_proc_success.aa.clear(); m_death_proc.aa.clear(); }
|
||||
|
||||
inline void SetSpellCached(bool v) { spell_cached = v; }
|
||||
inline void SetItemCached(bool v) { item_cached = v; }
|
||||
inline void SetAltCached(bool v) { alt_cached = v; }
|
||||
|
||||
inline bool IsSpellCached() const { return spell_cached; }
|
||||
inline bool IsItemCached() const { return item_cached; }
|
||||
inline bool IsAltCached() const { return alt_cached; }
|
||||
inline bool HasSkillAttackProcs() const { return !m_skill_attack_proc.empty(); }
|
||||
|
||||
void InsertSkillProcAttempt(eSkillProc type, int spell, int chance, int slot = -1);
|
||||
void InsertSkillProcSuccess(eSkillProc type, int spell, int chance, int slot = -1);
|
||||
void InsertSkillLimit(eSkillProc type, EQEmu::skills::SkillType skill, bool on_success);
|
||||
inline bool HasSkillProcAttempt() const { return !m_skill_proc_attempt.aa.empty() || !m_skill_proc_attempt.buff.empty() || !m_skill_proc_attempt.worn.empty(); }
|
||||
inline bool HasSkillProcSuccess() const { return !m_skill_proc_success.aa.empty() || !m_skill_proc_success.buff.empty() || !m_skill_proc_success.worn.empty(); }
|
||||
|
||||
void InsertDeathProc(eSkillProc type, int spell, int chance, int slot = -1);
|
||||
|
||||
const sEffectCache *GetSpellCached(int affect, int subindex = 0) const;
|
||||
const sEffectCache *GetItemCached(int affect, int subindex = 0) const;
|
||||
const sEffectCache *GetAltCached(int affect, int subindex = 0) const;
|
||||
|
||||
std::vector<sSkillAttackProc>::const_iterator skill_attack_proc_begin() const { return m_skill_attack_proc.cbegin(); }
|
||||
std::vector<sSkillAttackProc>::const_iterator skill_attack_proc_end() const { return m_skill_attack_proc.cend(); }
|
||||
|
||||
std::vector<sSkillProc>::const_iterator skill_proc_attempt_begin(eSkillProc type) const;
|
||||
std::vector<sSkillProc>::const_iterator skill_proc_attempt_end(eSkillProc type) const;
|
||||
|
||||
std::vector<sSkillProc>::const_iterator skill_proc_success_begin(eSkillProc type) const;
|
||||
std::vector<sSkillProc>::const_iterator skill_proc_success_end(eSkillProc type) const;
|
||||
|
||||
std::vector<sDeathProc>::const_iterator death_proc_begin(eSkillProc type) const;
|
||||
std::vector<sDeathProc>::const_iterator death_proc_end(eSkillProc type) const;
|
||||
|
||||
// inlines for common operations
|
||||
inline int GetCachedPlayerEffect(int affect, int subindex = 0) const {
|
||||
auto res = GetSpellCached(affect, subindex);
|
||||
if (res)
|
||||
return res->base1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int GetCachedItemEffect(int affect, int subindex = 0) const {
|
||||
auto res = GetItemCached(affect, subindex);
|
||||
if (res)
|
||||
return res->base1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int GetCachedAltEffect(int affect, int subindex = 0) const {
|
||||
auto res = GetAltCached(affect, subindex);
|
||||
if (res)
|
||||
return res->base1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool spell_cached;
|
||||
bool item_cached;
|
||||
bool alt_cached;
|
||||
// maybe use unordered_multimap
|
||||
std::multimap<int, sEffectCache> m_spelleffect;
|
||||
std::multimap<int, sEffectCache> m_itemeffect;
|
||||
std::multimap<int, sEffectCache> m_alteffect;
|
||||
|
||||
// this should be fine for how it works with live AAs
|
||||
// if custom servers want to give someone a million of these ahhh
|
||||
// we need something better :P
|
||||
std::vector<sSkillAttackProc> m_skill_attack_proc;
|
||||
|
||||
sProcs<sSkillProc> m_skill_proc_attempt; // SE_SkillProc
|
||||
sProcs<sSkillProc> m_skill_proc_success; // SE_SkillProcSuccess
|
||||
sProcs<sDeathProc> m_death_proc; // SE_SpellOnDeath
|
||||
};
|
||||
|
||||
#endif /* !SPELL_CACHE_H */
|
||||
|
|
@ -1308,7 +1308,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
|||
#endif
|
||||
if (caster)
|
||||
effect_value = caster->ApplySpellEffectiveness(spell_id, effect_value);
|
||||
|
||||
|
||||
buffs[buffslot].melee_rune = effect_value;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1800,7 +1800,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
|||
else if (caster) {
|
||||
char level[4];
|
||||
ConvertArray(effect_value, level);
|
||||
caster->Message_StringID(MT_SpellFailure,
|
||||
caster->Message_StringID(MT_SpellFailure,
|
||||
SPELL_LEVEL_REQ, level);
|
||||
}
|
||||
}
|
||||
|
|
@ -2281,7 +2281,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
|||
}
|
||||
|
||||
case SE_AETaunt:
|
||||
{
|
||||
{
|
||||
#ifdef SPELL_EFFECT_SPAM
|
||||
snprintf(effect_desc, _EDLEN, "AE Taunt");
|
||||
#endif
|
||||
|
|
@ -5837,16 +5837,16 @@ bool Mob::TryDivineSave()
|
|||
-If desired, additional spells can be triggered from the AA/item/spell effect, generally a heal.
|
||||
*/
|
||||
|
||||
int32 SuccessChance = aabonuses.DivineSaveChance[0] + itembonuses.DivineSaveChance[0] + spellbonuses.DivineSaveChance[0];
|
||||
int32 SuccessChance = TotalEffect(SE_DivineSave, 0); // base2 == 0 is chances
|
||||
if (SuccessChance && zone->random.Roll(SuccessChance))
|
||||
{
|
||||
SetHP(1);
|
||||
|
||||
int32 EffectsToTry[] =
|
||||
{
|
||||
aabonuses.DivineSaveChance[1],
|
||||
itembonuses.DivineSaveChance[1],
|
||||
spellbonuses.DivineSaveChance[1]
|
||||
m_spell_cache.GetCachedPlayerEffect(SE_DivineSave, 1), // base2 == 1 is spell
|
||||
m_spell_cache.GetCachedItemEffect(SE_DivineSave, 1),
|
||||
m_spell_cache.GetCachedAltEffect(SE_DivineSave, 1)
|
||||
};
|
||||
//Fade the divine save effect here after saving the old effects off.
|
||||
//That way, if desired, the effect could apply SE_DivineSave again.
|
||||
|
|
@ -6710,24 +6710,24 @@ void Mob::ResourceTap(int32 damage, uint16 spellid)
|
|||
for (int i = 0; i < EFFECT_COUNT; i++) {
|
||||
if (spells[spellid].effectid[i] == SE_ResourceTap) {
|
||||
damage = (damage * spells[spellid].base[i]) / 1000;
|
||||
|
||||
|
||||
if (damage) {
|
||||
if (spells[spellid].max[i] && (damage > spells[spellid].max[i]))
|
||||
damage = spells[spellid].max[i];
|
||||
|
||||
|
||||
if (spells[spellid].base2[i] == 0) { // HP Tap
|
||||
if (damage > 0)
|
||||
HealDamage(damage);
|
||||
else
|
||||
Damage(this, -damage, 0, EQEmu::skills::SkillEvocation, false);
|
||||
}
|
||||
|
||||
|
||||
if (spells[spellid].base2[i] == 1) // Mana Tap
|
||||
SetMana(GetMana() + damage);
|
||||
|
||||
|
||||
if (spells[spellid].base2[i] == 2 && IsClient()) // Endurance Tap
|
||||
CastToClient()->SetEndurance(CastToClient()->GetEndurance() + damage);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6904,8 +6904,238 @@ void Client::BreakFeignDeathWhenCastOn(bool IsResisted)
|
|||
Message_StringID(MT_SpellFailure,FD_CAST_ON_NO_BREAK);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
SetFeigned(false);
|
||||
Message_StringID(MT_SpellFailure,FD_CAST_ON);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function will recache the buff, item, and AA SE_NegateSpellEffects
|
||||
* This has to be done before the other effects are cached, since they make use of this data
|
||||
* This function does not clear the cache, so we will have to do it before we call it
|
||||
*/
|
||||
void Mob::RecacheSuppressionSpells()
|
||||
{
|
||||
// first we cache buffs
|
||||
int buff_count = GetMaxTotalSlots();
|
||||
for (int i = buff_count; i < buff_count; ++i) {
|
||||
if (!IsValidSpell(buffs[i].spellid))
|
||||
continue;
|
||||
const auto &spell = spells[buffs[i].spellid];
|
||||
for (int j = 0; j < EFFECT_COUNT; ++j) {
|
||||
if (spell.effectid[j] != SE_NegateSpellEffect)
|
||||
continue;
|
||||
auto base = spell.base[j] == 0 ? SM_All : spell.base[j];
|
||||
m_spell_cache.InsertSpellEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedPlayerEffect(SE_NegateSpellEffect, spell.base2[j]) | base, spell.base2[j]);
|
||||
}
|
||||
}
|
||||
m_spell_cache.InsertSpellEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache
|
||||
|
||||
// now we cache items -- NPC invs are dumb, so work around
|
||||
RecacheSuppressionItems();
|
||||
|
||||
m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache
|
||||
// now we cache AAs
|
||||
for (const auto &aa : aa_ranks) {
|
||||
auto ability_rank = zone->GetAlternateAdvancementAbilityAndRank(aa.first, aa.second.first);
|
||||
auto ability = ability_rank.first;
|
||||
auto rank = ability_rank.second;
|
||||
|
||||
if (!ability || rank->effects.empty())
|
||||
continue;
|
||||
|
||||
for (const auto &e : rank->effects) {
|
||||
if (e.effect_id != SE_NegateSpellEffect)
|
||||
continue;
|
||||
|
||||
auto base = e.base1 == 0 ? SM_All : e.base1;
|
||||
m_spell_cache.InsertAltEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedAltEffect(SE_NegateSpellEffect, e.base2) | base, e.base2);
|
||||
}
|
||||
}
|
||||
m_spell_cache.InsertAltEffect(SE_NegateSpellEffect, SM_All, SE_NegateSpellEffect); // this will cause it to be ignored in recache
|
||||
}
|
||||
|
||||
// NPCs and clients have incompatible inventory systems, so we work around that issue here
|
||||
void NPC::RecacheSuppressionItems()
|
||||
{
|
||||
// luckily we get to skip augs and shit here! and tributes!
|
||||
for (int i = 0; i < EQEmu::legacy::EQUIPMENT_SIZE; ++i) {
|
||||
const auto cur = database.GetItem(equipment[i]);
|
||||
if (cur->Worn.Effect > 0 && cur->Worn.Type == EQEmu::item::ItemEffectWorn) {
|
||||
if (!IsValidSpell(cur->Worn.Effect))
|
||||
continue;
|
||||
const auto &spell = spells[cur->Worn.Effect];
|
||||
for (int j = 0; j < EFFECT_COUNT; ++j) {
|
||||
if (spell.effectid[j] != SE_NegateSpellEffect)
|
||||
continue;
|
||||
auto base = spell.base[j] == 0 ? SM_All : spell.base[j];
|
||||
m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedItemEffect(SE_NegateSpellEffect, spell.base2[j]) | base, spell.base2[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::RecacheSuppressionItems()
|
||||
{
|
||||
auto apply_effect = [this] (const EQEmu::ItemInstance *inst) {
|
||||
const auto item = inst->GetItem();
|
||||
if (item->Worn.Effect > 0 && item->Worn.Type == EQEmu::item::ItemEffectWorn) {
|
||||
if (!IsValidSpell(item->Worn.Effect))
|
||||
return;
|
||||
const auto &spell = spells[item->Worn.Effect];
|
||||
for (int i = 0; i < EFFECT_COUNT; ++i) {
|
||||
if (spell.effectid[i] != SE_NegateSpellEffect)
|
||||
continue;
|
||||
auto base = spell.base[i] == 0 ? SM_All : spell.base[i];
|
||||
m_spell_cache.InsertItemEffect(SE_NegateSpellEffect, m_spell_cache.GetCachedItemEffect(SE_NegateSpellEffect, spell.base2[i]) | base, spell.base2[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// we don't do ammo slot here. The client also does not seem to be visiting augs
|
||||
for (int i = EQEmu::inventory::slotCharm; i < EQEmu::inventory::slotAmmo; ++i) {
|
||||
const auto inst = m_inv[i];
|
||||
if (inst == nullptr || !inst->IsClassCommon() || inst->GetExp() < 0)
|
||||
continue;
|
||||
apply_effect(inst);
|
||||
}
|
||||
|
||||
// powersource ...
|
||||
if (ClientVersion() >= EQEmu::versions::ClientVersion::SoF) {
|
||||
const auto inst = m_inv[EQEmu::inventory::slotPowerSource];
|
||||
if (inst)
|
||||
apply_effect(inst);
|
||||
}
|
||||
|
||||
for (int i = 0; i < EQEmu::legacy::TRIBUTE_SIZE; ++i) {
|
||||
const auto inst = m_inv[EQEmu::legacy::TRIBUTE_BEGIN + i];
|
||||
if (inst == nullptr || !inst->IsClassCommon() || inst->GetExp() < 0)
|
||||
continue;
|
||||
apply_effect(inst);
|
||||
}
|
||||
}
|
||||
|
||||
int Mob::TotalEffect(int spaID, int subindex, bool bIncludeItems, bool bIncludeAA, bool bIncludeBuffs)
|
||||
{
|
||||
// pure melee don't have mana regen
|
||||
if ((spaID == SE_CurrentMana || spaID == SE_ManaRegen_v2) &&
|
||||
(GetClass() == MONK || GetClass() == ROGUE || GetClass() == WARRIOR || GetClass() == BERSERKER))
|
||||
return 0;
|
||||
int total = 0, item_total = 0, aa_total = 0;
|
||||
|
||||
// invis / see invis has special capping
|
||||
switch (spaID) {
|
||||
case SE_Invisibility:
|
||||
case SE_SeeInvis:
|
||||
case SE_InvisVsUndead:
|
||||
case SE_InvisVsAnimals:
|
||||
case SE_Invisibility2:
|
||||
case SE_InvisVsUndead2:
|
||||
case SE_ImprovedInvisAnimals:
|
||||
if (bIncludeBuffs)
|
||||
total = GetCachedPlayerEffect(spaID);
|
||||
if (bIncludeItems)
|
||||
item_total = GetCachedItemEffect(spaID);
|
||||
if (bIncludeAA)
|
||||
aa_total = GetCachedAltEffect(spaID);
|
||||
return std::min(3000, std::max({total, item_total, aa_total}));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// SE_MovementSpeed is only items or buffs (this is client behavior so useless for custom)
|
||||
// and they don't stack, take best or worst if one is negative
|
||||
if (spaID == SE_MovementSpeed) {
|
||||
if (bIncludeBuffs)
|
||||
total = GetCachedPlayerEffect(spaID);
|
||||
if (bIncludeItems)
|
||||
item_total = GetCachedItemEffect(spaID);
|
||||
if (total < 0 || item_total < 0)
|
||||
return std::min(total, item_total);
|
||||
return std::max(total, item_total);
|
||||
}
|
||||
|
||||
// bunch of special behavior here!
|
||||
if (spaID == SE_AttackSpeed) {
|
||||
int haste = GetCachedPlayerEffect(SE_AttackSpeed);
|
||||
int haste_v2 = GetCachedPlayerEffect(SE_AttackSpeed2);
|
||||
int overhaste = GetCachedPlayerEffect(SE_AttackSpeed3);
|
||||
int slow = GetCachedPlayerEffect(SE_AttackSpeed4);
|
||||
int item_haste = 0; /* bIncludeItems ? item_bonuses.Haste : 0; //or something */
|
||||
|
||||
if (haste) { // we have a non-zero haste
|
||||
if (haste < 100) { // we're slowed! special case for SE_AttackSpeed4
|
||||
int temp = 100 - slow;
|
||||
if (haste > temp) // SE_AttackSpeed4 is hurting us more, so use that
|
||||
haste = temp;
|
||||
} else { // SE_AttackSpeed4 will lower our SE_AttackSpeed
|
||||
haste -= slow;
|
||||
}
|
||||
} else {
|
||||
haste = 100 - slow;
|
||||
}
|
||||
if (haste <= 0)
|
||||
haste = 1;
|
||||
// if we have no attack speed buffs, haste will be 100, if it's less than 100 we abort adn we are done :P
|
||||
if (haste >= 100) {
|
||||
total = haste + item_haste;
|
||||
if (overhaste > 0) {
|
||||
if (overhaste > 100)
|
||||
overhaste -= 100;
|
||||
if (GetLevel() <= 50)
|
||||
overhaste = std::min(10, overhaste);
|
||||
else
|
||||
overhaste = std::min(25, overhaste);
|
||||
}
|
||||
|
||||
// ItemHaste function returns value + 100
|
||||
if (item_haste >= 100)
|
||||
total -= 100;
|
||||
|
||||
if (haste_v2 > 100 && GetLevel() > 49)
|
||||
total += std::min(haste_v2, 110) - 100;
|
||||
|
||||
int haste_cap = 0;
|
||||
if (IsClient()) {
|
||||
if (GetLevel() > 59)
|
||||
haste_cap = 200;
|
||||
else if (GetLevel() > 50)
|
||||
haste_cap = 185;
|
||||
else
|
||||
haste_cap = GetLevel() + 125;
|
||||
} else { // Pets/NPC
|
||||
if (IsPetOwnerClient()) { // client checks the bSummoned flag, which seems true for PC summoned pets/swarm pets
|
||||
haste_cap = GetLevel() + 110;
|
||||
Mob *owner = nullptr;
|
||||
if (IsPet())
|
||||
owner = GetOwner();
|
||||
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
|
||||
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
|
||||
if (owner)
|
||||
haste_cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60);
|
||||
} else {
|
||||
haste_cap = 250; // normal NPCs
|
||||
}
|
||||
}
|
||||
|
||||
haste_cap += overhaste;
|
||||
|
||||
if (total)
|
||||
total += overhaste;
|
||||
else if (overhaste)
|
||||
total = overhaste + 100;
|
||||
|
||||
return std::min(total, haste_cap);
|
||||
}
|
||||
}
|
||||
|
||||
if (bIncludeBuffs)
|
||||
total = GetCachedPlayerEffect(spaID, subindex);
|
||||
if (bIncludeAA)
|
||||
total += GetCachedAltEffect(spaID, subindex);
|
||||
if (bIncludeItems) // the client is using 0 for the mana regens in items, but subindex for others ...
|
||||
total +=
|
||||
GetCachedItemEffect(spaID, (spaID == SE_CurrentMana || spaID == SE_ManaRegen_v2) ? 0 : subindex);
|
||||
return total;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ void Trade::SendItemData(const EQEmu::ItemInstance* inst, int16 dest_slot_id)
|
|||
{
|
||||
if (inst == nullptr)
|
||||
return;
|
||||
|
||||
|
||||
// @merth: This needs to be redone with new item classes
|
||||
Mob* mob = With();
|
||||
if (!mob->IsClient())
|
||||
|
|
@ -886,11 +886,11 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||
|
||||
const EQEmu::ItemData* item = inst->GetItem();
|
||||
if(item && quest_npc == false) {
|
||||
bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
|
||||
_CLIENTPET(tradingWith) &&
|
||||
bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
|
||||
_CLIENTPET(tradingWith) &&
|
||||
tradingWith->GetPetType()<=petOther);
|
||||
// if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it
|
||||
if(GetGM() || (inst->IsAttuned() == false &&
|
||||
if(GetGM() || (inst->IsAttuned() == false &&
|
||||
(item->NoDrop != 0 || isPetAndCanHaveNoDrop))) {
|
||||
// pets need to look inside bags and try to equip items found there
|
||||
if (item->IsClassBag() && item->BagSlots > 0) {
|
||||
|
|
@ -944,7 +944,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||
parse->AddVar(temp1, temp2);
|
||||
snprintf(temp1, 100, "platinum.%d", tradingWith->GetNPCTypeID());
|
||||
snprintf(temp2, 100, "%u", trade->pp);
|
||||
parse->AddVar(temp1, temp2);
|
||||
parse->AddVar(temp1, temp2);
|
||||
|
||||
if(tradingWith->GetAppearance() != eaDead) {
|
||||
tradingWith->FaceTarget(this);
|
||||
|
|
@ -1434,7 +1434,7 @@ void Client::FindAndNukeTraderItem(int32 SerialNumber, uint16 Quantity, Client*
|
|||
int16 Charges=0;
|
||||
|
||||
uint16 SlotID = FindTraderItem(SerialNumber, Quantity);
|
||||
|
||||
|
||||
if(SlotID > 0) {
|
||||
|
||||
item = this->GetInv().GetItem(SlotID);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue