diff --git a/changelog.txt b/changelog.txt index 83b5fbbaa..db22c784e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,14 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 02/14/2014 == +Kayen: Fixes for buffs not fading under certain conditions in revised numhits system, and other fixes. +Kayen: Implemented support for spell_new field CastRestrictions (limits what type of targets spells can effect). +*A detailed list describing what the values used in this field do can be found in Mob::PassCastRestriction* +Kayen: Implemented support for spell_new field not_reflectable, no_partial_resists. + +Required SQL: utils/sql/git/2014_02_13_spells_new_updates.sql +Names many unknown fields in the table. + == 02/13/2014 == Sorvani: Renamed the instance_lockout and instance_lockout_player tables to instance_list and instance_list_player. Cleaned up the Database::GetUnusedInstanceID logic. diff --git a/common/EQStreamFactory.cpp b/common/EQStreamFactory.cpp index 260be7d56..0eb6fe25f 100644 --- a/common/EQStreamFactory.cpp +++ b/common/EQStreamFactory.cpp @@ -241,7 +241,7 @@ void EQStreamFactory::CheckTimeout() //everybody is done, we can delete it now //std::cout << "Removing connection" << std::endl; std::map,EQStream *>::iterator temp=stream_itr; - stream_itr++; + ++stream_itr; //let whoever has the stream outside delete it delete temp->second; Streams.erase(temp); @@ -249,7 +249,7 @@ void EQStreamFactory::CheckTimeout() } } - stream_itr++; + ++stream_itr; } MStreams.unlock(); } @@ -284,7 +284,7 @@ Timer DecayTimer(20); //copy streams into a seperate list so we dont have to keep //MStreams locked while we are writting MStreams.lock(); - for(stream_itr=Streams.begin();stream_itr!=Streams.end();stream_itr++) { + for(stream_itr=Streams.begin();stream_itr!=Streams.end();++stream_itr) { // If it's time to decay the bytes sent, then let's do it before we try to write if (decay) stream_itr->second->Decay(); @@ -306,7 +306,7 @@ Timer DecayTimer(20); //do the actual writes cur = wants_write.begin(); end = wants_write.end(); - for(; cur != end; cur++) { + for(; cur != end; ++cur) { (*cur)->Write(sock); (*cur)->ReleaseFromUse(); } diff --git a/common/EQStreamIdent.cpp b/common/EQStreamIdent.cpp index de1f457b5..d4332c9a0 100644 --- a/common/EQStreamIdent.cpp +++ b/common/EQStreamIdent.cpp @@ -11,7 +11,7 @@ EQStreamIdentifier::~EQStreamIdentifier() { std::vector::iterator cur, end; cur = m_streams.begin(); end = m_streams.end(); - for(; cur != end; cur++) { + for(; cur != end; ++cur) { Record *r = *cur; r->stream->ReleaseFromUse(); delete r; @@ -19,7 +19,7 @@ EQStreamIdentifier::~EQStreamIdentifier() { std::vector::iterator curp, endp; curp = m_patches.begin(); endp = m_patches.end(); - for(; curp != endp; curp++) { + for(; curp != endp; ++curp) { delete *curp; } } @@ -56,7 +56,7 @@ void EQStreamIdentifier::Process() { //if stream hasn't finished initializing then continue; if(r->stream->GetState() == UNESTABLISHED) { - cur++; + ++cur; continue; } if(r->stream->GetState() != ESTABLISHED) { @@ -94,7 +94,7 @@ void EQStreamIdentifier::Process() { //foreach possbile patch... curp = m_patches.begin(); endp = m_patches.end(); - for(; !found_one && curp != endp; curp++) { + for(; !found_one && curp != endp; ++curp) { Patch *p = *curp; //ask the stream to see if it matches the supplied signature @@ -137,7 +137,7 @@ void EQStreamIdentifier::Process() { delete r; cur = m_streams.erase(cur); } else { - cur++; + ++cur; } } //end foreach stream } diff --git a/common/EQStreamLocator.h b/common/EQStreamLocator.h index ab93f1afb..5732c2ef1 100644 --- a/common/EQStreamLocator.h +++ b/common/EQStreamLocator.h @@ -138,7 +138,7 @@ public: iterator cur, end; cur = streams.begin(); end = streams.end(); - for(; cur != end; cur++) { + for(; cur != end; ++cur) { if(cur->second == it) { streams.erase(cur); //lazy recursive delete for now, since we have to redo diff --git a/common/shareddb.cpp b/common/shareddb.cpp index ce0c5602f..aa136c41b 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -1704,11 +1704,13 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].ResistDiff=atoi(row[147]); sp[tempid].dot_stacking_exempt=atoi(row[148]); sp[tempid].RecourseLink = atoi(row[150]); + sp[tempid].no_partial_resist = atoi(row[151]) != 0; sp[tempid].short_buff_box = atoi(row[154]); sp[tempid].descnum = atoi(row[155]); sp[tempid].effectdescnum = atoi(row[157]); + sp[tempid].not_reflectable = atoi(row[161]) != 0; sp[tempid].bonushate=atoi(row[162]); sp[tempid].EndurCost=atoi(row[166]); @@ -1736,6 +1738,8 @@ void SharedDatabase::LoadSpells(void *data, int max_spells) { sp[tempid].powerful_flag=atoi(row[209]); sp[tempid].CastRestriction = atoi(row[211]); sp[tempid].AllowRest = atoi(row[212]) != 0; + sp[tempid].NotOutofCombat = atoi(row[213]) != 0; + sp[tempid].NotInCombat = atoi(row[214]) != 0; sp[tempid].persistdeath = atoi(row[224]) != 0; sp[tempid].DamageShieldType = 0; } diff --git a/common/spdat.cpp b/common/spdat.cpp index a5215cb2e..2cbf8a97d 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -387,6 +387,9 @@ bool IsAERainNukeSpell(uint16 spell_id) bool IsPartialCapableSpell(uint16 spell_id) { + if (spells[spell_id].no_partial_resist) + return false; + if (IsPureNukeSpell(spell_id) || IsFearSpell(spell_id) || IsEffectInSpell(spell_id, SE_Root) || IsEffectInSpell(spell_id, SE_Charm)) @@ -672,6 +675,20 @@ bool IsDiscipline(uint16 spell_id) return false; } +bool IsCombatSkill(uint16 spell_id) +{ + if (!IsValidSpell(spell_id)) + return false; + + //Check if Discipline OR melee proc (from non-castable spell) + if ((spells[spell_id].mana == 0 && + (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep)) || + ((spells[spell_id].cast_time == 0) && (spells[spell_id].recast_time == 0) && (spells[spell_id].recovery_time == 0))) + return true; + + return false; +} + bool IsResurrectionEffects(uint16 spell_id) { // spell id 756 is Resurrection Effects spell diff --git a/common/spdat.h b/common/spdat.h index 35f928789..64b3aedc1 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -680,15 +680,18 @@ struct SPDat_Spell_Struct /* 148 */ int8 dot_stacking_exempt; // If 1 doesn't stack with self cast by others. If -1 (not implemented) doesn't stack with same effect (???) /* 149 */ //int deletable; /* 150 */ uint16 RecourseLink; -/* 151 */ // 151: -1, 0, or 1 +/* 151 */ bool no_partial_resist; // 151: -1, 0, or 1 // 152 & 153: all set to 0 /* 154 */ int8 short_buff_box; // != 0, goes to short buff box. /* 155 */ int descnum; // eqstr of description of spell /* 156 */ //int typedescnum; // eqstr of type description /* 157 */ int effectdescnum; // eqstr of effect description -/* 158 */ +/* 158 */ //Category Desc ID 3 +/* 159 */ //bool npc_no_los; +/* 161 */ bool not_reflectable; /* 162 */ int bonushate; /* 163 */ +/* 164 */ // for most spells this appears to mimic ResistDiff /* 166 */ int EndurCost; /* 167 */ int8 EndurTimerIndex; /* 168 */ //int IsDisciplineBuff; //Will goto the combat window when cast @@ -709,8 +712,8 @@ struct SPDat_Spell_Struct /* 191 */ uint8 viral_targets; /* 192 */ uint8 viral_timer; /* 193 */ int NimbusEffect; -/* 194 */ float directional_start; -/* 195 */ float directional_end; +/* 194 */ float directional_start; //Cone Start Angle: +/* 195 */ float directional_end; // Cone End Angle: /* 196 */ /* 197 */ bool not_extendable; /* 198- 199 */ @@ -719,12 +722,14 @@ struct SPDat_Spell_Struct /* 203 */ //int songcap; // individual song cap (how live currently does it, not implemented) /* 204 - 206 */ /* 207 */ int spellgroup; -/* 208 */ -/* 209 */ int powerful_flag; // Need more investigation to figure out what to call this, for now we know -1 makes charm spells not break before their duration is complete, it does alot more though -/* 210 */ +/* 208 */ // int rank - increments AA effects with same name +/* 209 */ int powerful_flag; // Need more investigation to figure out what to call this, for now we know -1 makes charm spells not break before their duration is complete, it does alot more though +/* 210 */ // bool DurationFrozen; ??? /* 211 */ int CastRestriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat /* 212 */ bool AllowRest; -/* 213 - 218 */ +/* 213 */ bool NotOutofCombat; //Fail if cast out of combat +/* 214 */ bool NotInCombat; //Fail if cast in combat +/* 215 - 218 */ /* 219 */ //int maxtargets; // is used for beam and ring spells for target # limits (not implemented) /* 220 - 223 */ /* 224 */ bool persistdeath; // buff doesn't get stripped on death @@ -795,6 +800,7 @@ int32 CalculateCorruptionCounters(uint16 spell_id); int32 CalculateCounters(uint16 spell_id); bool IsDisciplineBuff(uint16 spell_id); bool IsDiscipline(uint16 spell_id); +bool IsCombatSkill(uint16 spell_id); bool IsResurrectionEffects(uint16 spell_id); bool IsRuneSpell(uint16 spell_id); bool IsMagicRuneSpell(uint16 spell_id); diff --git a/utils/sql/git/required/2014_02_13_spells_new_update.sql b/utils/sql/git/required/2014_02_13_spells_new_update.sql new file mode 100644 index 000000000..3c3675587 --- /dev/null +++ b/utils/sql/git/required/2014_02_13_spells_new_update.sql @@ -0,0 +1,17 @@ +ALTER TABLE `spells_new` CHANGE `field161` `not_reflectable` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field151` `no_partial_resist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field189` `MinResist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field190` `MaxResist` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field194` `ConeStartAngle` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field195` `ConeStopAngle` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field208` `rank` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field159` `npc_no_los` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field213` `NotOutofCombat` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field214` `NotInCombat` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field168` `IsDiscipline` INT(11) NOT NULL DEFAULT '0'; +ALTER TABLE `spells_new` CHANGE `field211` `CastRestriction` INT(11) NOT NULL DEFAULT '0'; + +UPDATE altadv_vars SET sof_next_id = 8261 WHERE skill_id = 8232; +UPDATE altadv_vars SET sof_next_id = 0 WHERE skill_id = 8261; +UPDATE altadv_vars SET sof_current_level = 3 WHERE skill_id = 8261; + diff --git a/zone/attack.cpp b/zone/attack.cpp index d9fea1ff9..18571a08f 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -199,11 +199,6 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c if(IsClient() && other->IsClient()) pvpmode = true; - CheckNumHitsRemaining(1); - - if (attacker) - attacker->CheckNumHitsRemaining(2); - if (chance_mod >= 10000) return true; @@ -1318,19 +1313,10 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b if (IsDead()) return false; - if(damage > 0 && (spellbonuses.MeleeLifetap || itembonuses.MeleeLifetap)) - { - int lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap; - if(lifetap_amt > 100) - lifetap_amt = 100; - - lifetap_amt = damage * lifetap_amt / 100; - - mlog(COMBAT__DAMAGE, "Melee lifetap healing for %d damage.", damage); - //heal self for damage done.. - HealDamage(lifetap_amt); - - } + MeleeLifeTap(damage); + + if (damage > 0) + CheckNumHitsRemaining(5); //break invis when you attack if(invisible) { @@ -1938,6 +1924,11 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if (HasDied()) //killed by damage shield ect return false; + MeleeLifeTap(damage); + + if (damage > 0) + CheckNumHitsRemaining(5); + //break invis when you attack if(invisible) { mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); @@ -3434,7 +3425,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons mlog(COMBAT__DAMAGE, "Avoiding %d damage due to invulnerability.", damage); damage = -5; } - + if( spell_id != SPELL_UNKNOWN || attacker == nullptr ) avoidable = false; @@ -3444,6 +3435,13 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons DamageShield(attacker); } + if (spell_id == SPELL_UNKNOWN && skill_used) { + CheckNumHitsRemaining(1); //Incoming Hit Attempts + + if (attacker) + attacker->CheckNumHitsRemaining(2); //Outgoing Hit Attempts + } + if(attacker){ if(attacker->IsClient()){ if(!RuleB(Combat, EXPFromDmgShield)) { @@ -3514,16 +3512,20 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons } - ReduceAllDamage(damage); + if (skill_used) + CheckNumHitsRemaining(6); //Incomming Hit Success on Defender - if(IsClient() && CastToClient()->sneaking){ - CastToClient()->sneaking = false; - SendAppearancePacket(AT_Sneak, 0); - } - if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){ - attacker->CastToClient()->sneaking = false; - attacker->SendAppearancePacket(AT_Sneak, 0); - } + ReduceAllDamage(damage); + + if(IsClient() && CastToClient()->sneaking){ + CastToClient()->sneaking = false; + SendAppearancePacket(AT_Sneak, 0); + } + if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){ + attacker->CastToClient()->sneaking = false; + attacker->SendAppearancePacket(AT_Sneak, 0); + } + //final damage has been determined. SetHP(GetHP() - damage); @@ -3770,6 +3772,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons } } } //end packet sending + } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 37af3021d..fbd4020eb 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1216,6 +1216,18 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) case SE_HealRate: newbon->HealRate += base1; break; + + case SE_MeleeLifetap: + { + + if((base1 < 0) && (newbon->MeleeLifetap > base1)) + newbon->MeleeLifetap = base1; + + else if(newbon->MeleeLifetap < base1) + newbon->MeleeLifetap = base1; + break; + } + } } } @@ -1230,8 +1242,12 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) uint32 buff_count = GetMaxTotalSlots(); for(i = 0; i < buff_count; i++) { - if(buffs[i].spellid != SPELL_UNKNOWN) + if(buffs[i].spellid != SPELL_UNKNOWN){ ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, false, buffs[i].ticsremaining,i); + + if (buffs[i].numhits > 0) + Numhits(true); + } } //Removes the spell bonuses that are effected by a 'negate' debuff. @@ -1750,7 +1766,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne else if((effect_value < 0) && (newbon->MeleeLifetap > effect_value)) newbon->MeleeLifetap = spells[spell_id].base[i]; - if(newbon->MeleeLifetap < spells[spell_id].base[i]) + else if(newbon->MeleeLifetap < spells[spell_id].base[i]) newbon->MeleeLifetap = spells[spell_id].base[i]; break; } diff --git a/zone/bot.cpp b/zone/bot.cpp index 8b3356205..ff732d50c 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3377,6 +3377,9 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (HasDied()) return; + if (damage > 0) + CheckNumHitsRemaining(5); + if((skillinuse == SkillDragonPunch) && GetAA(aaDragonPunch) && MakeRandomInt(0, 99) < 25){ SpellFinished(904, other, 10, 0, -1, spells[904].ResistDiff); other->Stun(100); @@ -6593,18 +6596,10 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (GetHP() < 0) return false; - if(damage > 0 && (spellbonuses.MeleeLifetap || itembonuses.MeleeLifetap)) - { - int lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap; - if(lifetap_amt > 100) - lifetap_amt = 100; + MeleeLifeTap(damage); - lifetap_amt = damage * lifetap_amt / 100; - - mlog(COMBAT__DAMAGE, "Melee lifetap healing for %d damage.", damage); - //heal self for damage done.. - HealDamage(lifetap_amt); - } + if (damage > 0) + CheckNumHitsRemaining(5); //break invis when you attack if(invisible) { @@ -8083,6 +8078,9 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if(!GetTarget())return; if (HasDied()) return; + if (max_damage > 0) + CheckNumHitsRemaining(5); + //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){ int kb_chance = 25; diff --git a/zone/mob.cpp b/zone/mob.cpp index 6f41e698f..078fed266 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3412,8 +3412,6 @@ int16 Mob::GetSkillDmgTaken(const SkillUseTypes skill_used) if(skilldmg_mod < -100) skilldmg_mod = -100; - CheckNumHitsRemaining(6); - return skilldmg_mod; } @@ -4315,17 +4313,38 @@ int16 Mob::GetSkillDmgAmt(uint16 skill) skill_dmg += spellbonuses.SkillDamageAmount2[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount2[HIGHEST_SKILL+1] + itembonuses.SkillDamageAmount2[skill] + spellbonuses.SkillDamageAmount2[skill]; - CheckNumHitsRemaining(5); - return skill_dmg; } +void Mob::MeleeLifeTap(int32 damage) { + + if(damage > 0 && (spellbonuses.MeleeLifetap || itembonuses.MeleeLifetap || aabonuses.MeleeLifetap )) + { + int lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap + aabonuses.MeleeLifetap; + + if(lifetap_amt > 100) + lifetap_amt = 100; + + else if (lifetap_amt < -99) + lifetap_amt = -99; + + + lifetap_amt = damage * lifetap_amt / 100; + + mlog(COMBAT__DAMAGE, "Melee lifetap healing for %d damage.", damage); + //heal self for damage done.. + HealDamage(lifetap_amt); + } +} + bool Mob::TryReflectSpell(uint32 spell_id) { - if(!GetTarget()) - return false; + if (spells[spell_id].not_reflectable) + return false; - if(MakeRandomInt(0, 99) < (GetTarget()->itembonuses.reflect_chance + GetTarget()->spellbonuses.reflect_chance)) + int chance = itembonuses.reflect_chance + spellbonuses.reflect_chance + aabonuses.reflect_chance; + + if(chance && MakeRandomInt(0, 99) < chance) return true; return false; diff --git a/zone/mob.h b/zone/mob.h index 9fb518f65..a5d594123 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -587,6 +587,8 @@ public: int32 ApplySpellEffectiveness(Mob* caster, int16 spell_id, int32 value, bool IsBard = false); int8 GetDecayEffectValue(uint16 spell_id, uint16 spelleffect); int32 GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_spell_dmg); + void MeleeLifeTap(int32 damage); + bool PassCastRestriction(bool UseCastRestriction = true, int16 value = 0, bool IsDamage = true); void ModSkillDmgTaken(SkillUseTypes skill_num, int value); int16 GetModSkillDmgTaken(const SkillUseTypes skill_num); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index eb0ca4db5..d551962ea 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -155,6 +155,9 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if(!GetTarget())return; if (HasDied()) return; + if (max_damage > 0) + CheckNumHitsRemaining(5); + //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){ int kb_chance = 25; @@ -950,6 +953,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item TryCriticalHit(other, SkillArchery, TotalDmg); other->AddToHateList(this, hate, 0, false); + CheckNumHitsRemaining(5); } } else @@ -1054,6 +1058,7 @@ void NPC::RangedAttack(Mob* other) TryCriticalHit(GetTarget(), SkillArchery, TotalDmg); GetTarget()->AddToHateList(this, hate, 0, false); GetTarget()->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); + CheckNumHitsRemaining(5); } else { @@ -1272,6 +1277,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite TryCriticalHit(other, SkillThrowing, TotalDmg); int32 hate = (2*WDmg); other->AddToHateList(this, hate, 0, false); + CheckNumHitsRemaining(5); } } @@ -2183,6 +2189,8 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (HasDied()) return; + CheckNumHitsRemaining(5); + if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skillinuse){ int kb_chance = 25; kb_chance += kb_chance*(100-aabonuses.SpecialAttackKBProc[0])/100; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 0c4d30ad0..d9438ef90 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -183,7 +183,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) numhit += caster->CastToClient()->GetFocusEffect(focusIncreaseNumHits, spell_id); } - Numhits(true); buffs[buffslot].numhits = numhit; } @@ -218,83 +217,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) int32 dmg = effect_value; if(dmg < 0) { - - /*Special Cases where Base2 is defined - Range 105 : Plant - Range 120 : Undead - Range 123 : Humanoid - Range 190 : No Raid boss flag *not implemented - Range 191 : This spell will deal less damage to 'exceptionally strong targets' - Raid boss flag *not implemented - Range 201 : Damage if HP > 75% - Range 221 - 299 : Causing damage dependent on how many pets/swarmpets are attacking your target. - Range 300 - 303 : UNKOWN *not implemented - Range 399 - 499 : Heal if HP within a specified range (400 = 0-25% 401 = 25 - 35% 402 = 35-45% ect) - Range 500 - 599 : Heal if HP less than a specified value - Range 600 - 699 : Limit to Body Type [base2 - 600 = Body] - Range 818 - 819 : If Undead/If Not Undead - Range 835 - : Unknown *not implemented - Range 836 - 837 : Progression Server / Live Server *not implemented - Range 839 - : Unknown *not implemented - Range 10000+ : Limit to Race [base2 - 10000 = Race] (*Not on live: Too useful a function to not implement) - - */ - - if (spells[spell_id].base2[i] > 0){ - - //It is unlikely these effects would give a fail message (Need to confirm) - if (spells[spell_id].base2[i] == 105){ - if (GetBodyType() != BT_Plant) - break; - } - - else if (spells[spell_id].base2[i] == 120){ - if (GetBodyType() != BT_Undead) - break; - } - - else if (spells[spell_id].base2[i] == 123){ - if (GetBodyType() != BT_Humanoid) - break; - } - - //Limit to Body Type. - else if (spells[spell_id].base2[i] >= 600 && spells[spell_id].base2[i] <= 699){ - if (GetBodyType() != (spells[spell_id].base2[i] - 600)){ - //caster->Message_StringID(13,CANNOT_AFFECT_NPC); - break; - } - } - - else if (spells[spell_id].base2[i] == 201){ - if (GetHPRatio() < 75) - break; - } - - //Limit to Race. *Not implemented on live - else if (spells[spell_id].base2[i] >= 10000 && spells[spell_id].base2[i] <= 11000){ - if (GetRace() != (spells[spell_id].base2[i] - 10000)){ - break; - } - } - - //Limit to amount of pets - else if (spells[spell_id].base2[i] >= 221 && spells[spell_id].base2[i] <= 299){ - bool allow_spell = false; - int count = hate_list.SummonedPetCount(this); - - for (int base2_value = 221; base2_value <= 233; ++base2_value){ - if (spells[spell_id].base2[i] == base2_value){ - if (count >= (base2_value - 220)){ - allow_spell = true; - break; - } - } - } - - if (!allow_spell) - break; - } - } + if (!PassCastRestriction(false, spells[spell_id].base2[i], true)) + break; // take partial damage into account dmg = (int32) (dmg * partial / 100); @@ -308,60 +232,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) } else if(dmg > 0) { //healing spell... - - if (spells[spell_id].base2[i] > 0) - { - bool allow_spell = false; - - //Heal only if HP within specified range. [Doesn't follow a set forumla for all values...] - if (spells[spell_id].base2[i] >= 400 && spells[spell_id].base2[i] <= 408){ - for (int base2_value = 400; base2_value <= 408; ++base2_value){ - if (spells[spell_id].base2[i] == base2_value){ - - if (spells[spell_id].base2[i] == 400){ - if (GetHPRatio() <= 25){ - allow_spell = true; - break; - } - } - - else if (spells[spell_id].base2[i] == base2_value){ - if (GetHPRatio() > 25+((base2_value - 401)*10) && GetHPRatio() <= 35+((base2_value - 401)*10)){ - allow_spell = true; - break; - } - } - } - } - } - - else if (spells[spell_id].base2[i] >= 500 && spells[spell_id].base2[i] <= 520){ - for (int base2_value = 500; base2_value <= 520; ++base2_value){ - if (spells[spell_id].base2[i] == base2_value){ - - if (spells[spell_id].base2[i] == base2_value){ - if (GetHPRatio() < (base2_value - 500)*5) { - allow_spell = true; - break; - } - } - } - } - } + if (!PassCastRestriction(false, spells[spell_id].base2[i], false)) + break; - else if (spells[spell_id].base2[i] == 399){ - if (GetHPRatio() > 15 && GetHPRatio() <= 25){ - allow_spell = true; - break; - } - } - - if(!allow_spell) - break; - } - - if(caster) dmg = caster->GetActSpellHealing(spell_id, dmg, this); @@ -4152,13 +4026,12 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) } break; - case SE_LimitCombatSkills: - if (base1 == 0){ - if((spell.cast_time == 0) && (spell.recast_time == 0) && (spell.recovery_time == 0)) //Exclude procs - LimitFailure = true; - } - break; + if (base1 == 0 && IsCombatSkill(spell_id)) //Exclude Discs + LimitFailure = true; + else if (base1 == 1 && !IsCombatSkill(spell_id)) //Exclude Spells + LimitFailure = true; + break; case SE_LimitSpellGroup: if(base1 < 0) { @@ -4555,10 +4428,10 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo break; case SE_LimitCombatSkills: - if (focus_spell.base[i] == 0){ - if((spell.cast_time == 0) && (spell.recast_time == 0) && (spell.recovery_time == 0)) //Exclude procs - return 0; - } + if (focus_spell.base[i] == 0 && IsCombatSkill(spell_id)) //Exclude Disc + return 0; + else if (focus_spell.base[i] == 1 && !IsCombatSkill(spell_id)) //Include Spells + return 0; break; case SE_LimitSpellGroup: @@ -5704,3 +5577,301 @@ bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ return false; } +bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDamage) +{ + /*If return TRUE spell met all restrictions and can continue (this = target). + This check is used when the spell_new field CastRestriction is defined OR spell effect '0'(DD/Heal) has a defined limit + Range 1 : UNKNOWN + Range 100 : *Animal OR Humanoid + Range 101 : *Dragon + Range 102 : *Animal OR Insect + Range 103 : NOT USED + Range 104 : *Animal + Range 105 : Plant + Range 106 : *Giant + Range 107 : NOT USED + Range 108 : NOT Animal or Humaniod + Range 109 : *Bixie + Range 111 : *Harpy + Range 112 : *Sporali + Range 113 : *Kobold + Range 114 : *Shade Giant + Range 115 : *Drakkin + Range 116 : NOT USED + Range 117 : *Animal OR Plant + Range 118 : *Summoned + Range 119 : *Firepet + Range 120 : Undead + Range 121 : *Living (NOT Undead) + Range 122 : *Fairy + Range 123 : Humanoid + Range 124 : *Undead HP < 10% + Range 125 : *Clockwork HP < 10% + Range 126 : *Wisp HP < 10% + Range 127-130 : UNKNOWN + Range 150 : UNKNOWN + Range 190 : No Raid boss flag *not implemented + Range 191 : This spell will deal less damage to 'exceptionally strong targets' - Raid boss flag *not implemented + Range 201 : Damage if HP > 75% + Range 203 : Damage if HP < 20% + Range 216 : TARGET NOT IN COMBAT + Range 221 - 249 : Causing damage dependent on how many pets/swarmpets are attacking your target. + Range 250 : Damage if HP < 35% + Range 300 - 303 : UNKOWN *not implemented + Range 304 : Chain + Plate class (buffs) + Range 399 - 409 : Heal if HP within a specified range (400 = 0-25% 401 = 25 - 35% 402 = 35-45% ect) + Range 410 - 411 : UNKOWN + Range 500 - 599 : Heal if HP less than a specified value + Range 600 - 699 : Limit to Body Type [base2 - 600 = Body] + Range 700 : UNKNOWN + Range 701 : NOT PET + Range 800 : UKNOWN + Range 818 - 819 : If Undead/If Not Undead + Range 820 - 822 : UKNOWN + Range 835 : Unknown *not implemented + Range 836 - 837 : Progression Server / Live Server *not implemented + Range 839 : Unknown *not implemented + Range 842 - 844 : Humaniod lv MAX ((842 - 800) * 2) + Range 845 - 847 : UNKNOWN + Range 10000+ : Limit to Race [base2 - 10000 = Race] (*Not on live: Too useful a function to not implement) + THIS IS A WORK IN PROGRESS + */ + + if (value <= 0) + return true; + + if (IsDamage || UseCastRestriction) { + + switch(value) + { + case 100: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid)) + return true; + break; + + case 101: + if (GetBodyType() == BT_Dragon) + return true; + break; + + case 102: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Insect)) + return true; + break; + + case 104: + if (GetBodyType() == BT_Animal) + return true; + break; + + case 105: + if (GetBodyType() == BT_Plant) + return true; + break; + + case 106: + if (GetBodyType() == BT_Giant) + return true; + break; + + case 108: + if ((GetBodyType() != BT_Animal) || (GetBodyType() != BT_Humanoid)) + return true; + break; + + case 109: + if ((GetRace() == 520) ||(GetRace() == 79)) + return true; + break; + + case 111: + if ((GetRace() == 527) ||(GetRace() == 11)) + return true; + break; + + case 112: + if ((GetRace() == 456) ||(GetRace() == 28)) + return true; + break; + + case 113: + if ((GetRace() == 456) ||(GetRace() == 48)) + return true; + break; + + case 114: + if (GetRace() == 526) + return true; + break; + + case 115: + if (GetRace() == 522) + return true; + break; + + case 117: + if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Plant)) + return true; + break; + + case 118: + if (GetBodyType() == BT_Summoned) + return true; + break; + + case 119: + if (IsPet() && ((GetRace() == 212) || ((GetRace() == 75) && GetTexture() == 1))) + return true; + break; + + case 120: + if (GetBodyType() == BT_Undead) + return true; + break; + + case 121: + if (GetBodyType() != BT_Undead) + return true; + break; + + case 122: + if ((GetRace() == 473) || (GetRace() == 425)) + return true; + break; + + case 123: + if (GetBodyType() == BT_Humanoid) + return true; + break; + + case 124: + if ((GetBodyType() == BT_Undead) && (GetHPRatio() < 10)) + return true; + break; + + case 125: + if ((GetRace() == 457 || GetRace() == 88) && (GetHPRatio() < 10)) + return true; + break; + + case 126: + if ((GetRace() == 581 || GetRace() == 69) && (GetHPRatio() < 10)) + return true; + break; + + case 201: + if (GetHPRatio() > 75) + return true; + break; + + case 204: + if (GetHPRatio() < 20) + return true; + break; + + case 216: + if (!IsEngaged()) + return true; + break; + + case 250: + if (GetHPRatio() < 35) + return true; + break; + + case 304: + if (IsClient() && + ((GetClass() == WARRIOR) || (GetClass() == BARD) || (GetClass() == SHADOWKNIGHT) || (GetClass() == PALADIN) || (GetClass() == CLERIC) + || (GetClass() == RANGER) || (GetClass() == SHAMAN) || (GetClass() == ROGUE) || (GetClass() == BERSERKER))) + return true; + break; + + case 818: + if (GetBodyType() == BT_Undead) + return true; + break; + + case 819: + if (GetBodyType() != BT_Undead) + return true; + break; + + case 842: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 84) + return true; + break; + + case 843: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 86) + return true; + break; + + case 844: + if (GetBodyType() == BT_Humanoid && GetLevel() <= 88) + return true; + break; + } + + //Limit to amount of pets + if (value >= 221 && value <= 249){ + int count = hate_list.SummonedPetCount(this); + + for (int base2_value = 221; base2_value <= 249; ++base2_value){ + if (value == base2_value){ + if (count >= (base2_value - 220)){ + return true; + } + } + } + } + + //Limit to Body Type + if (value >= 600 && value <= 699){ + if (GetBodyType() == (value - 600)) + return true; + } + + //Limit to Race. *Not implemented on live + if (value >= 10000 && value <= 11000){ + if (GetRace() == (value - 10000)) + return true; + } + } //End Damage + + if (!IsDamage || UseCastRestriction) { + + //Heal only if HP within specified range. [Doesn't follow a set forumla for all values...] + if (value >= 400 && value <= 408){ + for (int base2_value = 400; base2_value <= 408; ++base2_value){ + if (value == base2_value){ + + if (value == 400 && GetHPRatio() <= 25) + return true; + + else if (value == base2_value){ + if (GetHPRatio() > 25+((base2_value - 401)*10) && GetHPRatio() <= 35+((base2_value - 401)*10)) + return true; + } + } + } + } + + else if (value >= 500 && value <= 549){ + for (int base2_value = 500; base2_value <= 520; ++base2_value){ + if (value == base2_value){ + if (GetHPRatio() < (base2_value - 500)*5) + return true; + } + } + } + + else if (value == 399) { + if (GetHPRatio() > 15 && GetHPRatio() <= 25) + return true; + } + } // End Heal + + + return false; +} + diff --git a/zone/spells.cpp b/zone/spells.cpp index d3b24cd78..ca3fef4fa 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1361,6 +1361,11 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce mlog(AA__MESSAGE, "Project Illusion overwrote target caster: %s spell id: %d was ON", GetName(), spell_id); targetType = ST_GroupClientAndPet; } + + if (spell_target && !spell_target->PassCastRestriction(true, spells[spell_id].CastRestriction)){ + Message_StringID(13,SPELL_NEED_TAR); + return false; + } switch (targetType) { @@ -3355,7 +3360,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r } } // Reflect - if(TryReflectSpell(spell_id) && spelltar && !reflect && IsDetrimentalSpell(spell_id) && this != spelltar) { + if(spelltar && spelltar->TryReflectSpell(spell_id) && !reflect && IsDetrimentalSpell(spell_id) && this != spelltar) { int reflect_chance = 0; switch(RuleI(Spells, ReflectType)) { @@ -3402,9 +3407,6 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r } } - if (spelltar && IsDetrimentalSpell(spell_id)) - spelltar->CheckNumHitsRemaining(3); - // resist check - every spell can be resisted, beneficial or not // add: ok this isn't true, eqlive's spell data is fucked up, buffs are // not all unresistable, so changing this to only check certain spells @@ -3438,6 +3440,8 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r } } + spelltar->CheckNumHitsRemaining(3); + safe_delete(action_packet); return false; } @@ -3575,7 +3579,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r safe_delete(action_packet); return false; } - + // cause the effects to the target if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness)) { @@ -3588,6 +3592,10 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r return false; } + + if (spelltar && IsDetrimentalSpell(spell_id)) + spelltar->CheckNumHitsRemaining(3); //Incoming spells + // send the action packet again now that the spell is successful // NOTE: this is what causes the buff icon to appear on the client, if // this is a buff - but it sortof relies on the first packet.