diff --git a/common/spdat.h b/common/spdat.h index c8e039f15..938b659e1 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -544,7 +544,7 @@ typedef enum { //#define SE_SummonCorpseZone 388 // *not implemented - summons a corpse from any zone(nec AA) #define SE_FcTimerRefresh 389 // implemented - Refresh spell icons //#define SE_FcTimerLockout 390 // *not implemented - Sets recast timers to specific value, focus limited. -#define SE_MeleeVulnerability 391 // implemented [Live SPA has this as LimitManaMax however that is clearly not the effect used] +#define SE_LimitManaMax 391 // implemented #define SE_FcHealAmt 392 // implemented - Adds or removes healing from spells #define SE_FcHealPctIncoming 393 // implemented - HealRate with focus restrictions. #define SE_FcHealAmtIncoming 394 // implemented - Adds/Removes amount of healing on target by X value with foucs restrictions. diff --git a/zone/aa.cpp b/zone/aa.cpp index 974ccef13..5cbb5da14 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -828,7 +828,7 @@ void Client::SendAlternateAdvancementRank(int aa_id, int level) { if(!CanUseAlternateAdvancementRank(rank)) { return; } - + int size = sizeof(AARankInfo_Struct) + (sizeof(AARankEffect_Struct) * rank->effects.size()) + (sizeof(AARankPrereq_Struct) * rank->prereqs.size()); EQApplicationPacket *outapp = new EQApplicationPacket(OP_SendAATable, size); AARankInfo_Struct *aai = (AARankInfo_Struct*)outapp->pBuffer; @@ -996,7 +996,7 @@ void Client::PurchaseAlternateAdvancementRank(int rank_id) { if(!CanPurchaseAlternateAdvancementRank(rank, true)) { return; } - + if(rank->base_ability->charges > 0) { uint32 charges = 0; GetAA(rank_id, &charges); @@ -1004,7 +1004,7 @@ void Client::PurchaseAlternateAdvancementRank(int rank_id) { if(charges > 0) { return; } - + SetAA(rank_id, rank->current_value, rank->base_ability->charges); } else { SetAA(rank_id, rank->current_value, 0); @@ -1022,10 +1022,10 @@ void Client::PurchaseAlternateAdvancementRank(int rank_id) { SendAlternateAdvancementStats(); if(rank->prev) { - Message_StringID(15, AA_IMPROVE, - std::to_string(rank->title_sid).c_str(), - std::to_string(rank->prev->current_value).c_str(), - std::to_string(rank->cost).c_str(), + Message_StringID(15, AA_IMPROVE, + std::to_string(rank->title_sid).c_str(), + std::to_string(rank->prev->current_value).c_str(), + std::to_string(rank->cost).c_str(), std::to_string(AA_POINTS).c_str()); /* QS: Player_Log_AA_Purchases */ @@ -1034,9 +1034,9 @@ void Client::PurchaseAlternateAdvancementRank(int rank_id) { QServ->PlayerLogEvent(Player_Log_AA_Purchases, CharacterID(), event_desc); } } else { - Message_StringID(15, AA_GAIN_ABILITY, - std::to_string(rank->title_sid).c_str(), - std::to_string(rank->cost).c_str(), + Message_StringID(15, AA_GAIN_ABILITY, + std::to_string(rank->title_sid).c_str(), + std::to_string(rank->cost).c_str(), std::to_string(AA_POINTS).c_str()); /* QS: Player_Log_AA_Purchases */ if (RuleB(QueryServ, PlayerLogAAPurchases)){ @@ -1125,7 +1125,7 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { if(!IsValidSpell(rank->spell)) { return; } - + if(!CanUseAlternateAdvancementRank(rank)) { return; } @@ -1448,7 +1448,7 @@ bool Mob::CanPurchaseAlternateAdvancementRank(AA::Rank *rank, bool check_price) if(!ability) return false; - + if(!CanUseAlternateAdvancementRank(rank)) { return false; } @@ -1474,7 +1474,7 @@ bool Mob::CanPurchaseAlternateAdvancementRank(AA::Rank *rank, bool check_price) } //if expendable only let us purchase if we have no charges already - //not quite sure on how this functions client side atm + //not quite sure on how this functions client side atm //I intend to look into it later to make sure the behavior is right if(ability->charges > 0 && current_charges > 0) { return false; @@ -1568,7 +1568,7 @@ void Zone::LoadAlternateAdvancement() { } bool ZoneDatabase::LoadAlternateAdvancementAbilities(std::unordered_map> &abilities, - std::unordered_map> &ranks) + std::unordered_map> &ranks) { Log.Out(Logs::General, Logs::Status, "Loading Alternate Advancement Abilities..."); abilities.clear(); @@ -1730,3 +1730,18 @@ void Mob::GrantAlternateAdvancementAbility(int aa_id, int points) { c->CalcBonuses(); } } + +bool Mob::CheckAATimer(int timer) +{ + if (timer >= aaTimerMax) + return false; + if (aa_timers[timer].Enabled()) { + if (aa_timers[timer].Check(false)) { + aa_timers[timer].Disable(); + return false; + } else { + return true; + } + } + return false; +} diff --git a/zone/attack.cpp b/zone/attack.cpp index 6a8125507..202ad77b6 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5056,3 +5056,39 @@ void NPC::SetAttackTimer() TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true, true); } } + +void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) +{ + if (!target) + return; + + Attack(target, hand, false, false, IsFromSpell); + + if (CanThisClassDoubleAttack()) { + CheckIncreaseSkill(SkillDoubleAttack, target, -10); + if (CheckDoubleAttack()) + Attack(target, hand, false, false, IsFromSpell); + if (hand == MainPrimary && GetLevel() >= 60 && + (GetClass() == MONK || GetClass() == WARRIOR || GetClass() == RANGER || GetClass() == BERSERKER) && + CheckDoubleAttack(true)) + Attack(target, hand, false, false, IsFromSpell); + } + if (hand == MainPrimary) { + auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + if (flurrychance && zone->random.Roll(flurrychance)) { + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + Attack(target, hand, false, false, IsFromSpell); + Attack(target, hand, false, false, IsFromSpell); + } + + auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance; + if (extraattackchance) { + auto wpn = GetInv().GetItem(MainPrimary); + if (wpn && (wpn->GetItem()->ItemType == ItemType2HBlunt || + wpn->GetItem()->ItemType == ItemType2HSlash || + wpn->GetItem()->ItemType == ItemType2HPiercing)) + if (zone->random.Roll(extraattackchance)) + Attack(target, hand, false, false, IsFromSpell); + } + } +} diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index e90f9626a..7ff8e7fc7 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1333,10 +1333,6 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->PetMeleeMitigation += base1; break; - case SE_MeleeVulnerability: - newbon->MeleeVulnerability += base1; - break; - case SE_FactionModPct: { if ((base1 < 0) && (newbon->FactionModPct > base1)) newbon->FactionModPct = base1; @@ -3008,10 +3004,6 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->PetMeleeMitigation += effect_value; break; - case SE_MeleeVulnerability: - new_bonus->MeleeVulnerability += effect_value; - break; - case SE_Sanctuary: new_bonus->Sanctuary = true; break; @@ -4588,12 +4580,6 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) aabonuses.FactionModPct = effect_value; break; - case SE_MeleeVulnerability: - spellbonuses.MeleeVulnerability = effect_value; - itembonuses.MeleeVulnerability = effect_value; - aabonuses.MeleeVulnerability = effect_value; - break; - case SE_IllusionPersistence: spellbonuses.IllusionPersistence = false; itembonuses.IllusionPersistence = false; diff --git a/zone/client.h b/zone/client.h index 07d42a313..44e86f971 100644 --- a/zone/client.h +++ b/zone/client.h @@ -226,6 +226,7 @@ public: virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); virtual void SetAttackTimer(); float GetQuiverHaste(); + void DoAttackRounds(Mob *target, int hand, bool IsFromSpell = false); void AI_Init(); void AI_Start(uint32 iMoveDelay = 0); @@ -774,7 +775,7 @@ public: void AddAAPoints(uint32 points) { m_pp.aapoints += points; SendAlternateAdvancementStats(); } int GetAAPoints() { return m_pp.aapoints; } int GetSpentAA() { return m_pp.aapoints_spent; } - + //old AA methods that we still use void ResetAA(); void RefundAA(); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 603636a82..42c244e6b 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -391,74 +391,12 @@ bool Client::Process() { } else if (auto_attack_target->GetHP() > -10) // -10 so we can watch people bleed in PvP { - //old aa - //if(CheckAAEffect(aaEffectRampage)) - //{ - // entity_list.AEAttack(this, 30); - //} else { - Attack(auto_attack_target, MainPrimary); // Kaiyodo - added attacking hand to arguments - //} ItemInst *wpn = GetInv().GetItem(MainPrimary); TryWeaponProc(wpn, auto_attack_target, MainPrimary); - bool tripleAttackSuccess = false; - if( auto_attack_target && CanThisClassDoubleAttack() ) { - - CheckIncreaseSkill(SkillDoubleAttack, auto_attack_target, -10); - if(CheckDoubleAttack()) { - //should we allow rampage on double attack? - //if(CheckAAEffect(aaEffectRampage)) { - // entity_list.AEAttack(this, 30); - //} else { - Attack(auto_attack_target, MainPrimary, false); - //} - } - - //triple attack: rangers, monks, warriors, berserkers over level 60 - if((((GetClass() == MONK || GetClass() == WARRIOR || GetClass() == RANGER || GetClass() == BERSERKER) - && GetLevel() >= 60) || GetSpecialAbility(SPECATK_TRIPLE)) - && CheckDoubleAttack(true)) - { - tripleAttackSuccess = true; - Attack(auto_attack_target, MainPrimary, false); - } - - //quad attack, does this belong here?? - if(GetSpecialAbility(SPECATK_QUAD) && CheckDoubleAttack(true)) - { - Attack(auto_attack_target, MainPrimary, false); - } - } - - //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). - int16 flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; - - if (auto_attack_target && flurrychance) - { - if(zone->random.Int(0, 99) < flurrychance) - { - Message_StringID(MT_NPCFlurry, YOU_FLURRY); - Attack(auto_attack_target, MainPrimary, false); - Attack(auto_attack_target, MainPrimary, false); - } - } - - int16 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; - - if (auto_attack_target && ExtraAttackChanceBonus) { - ItemInst *wpn = GetInv().GetItem(MainPrimary); - if(wpn){ - if(wpn->GetItem()->ItemType == ItemType2HSlash || - wpn->GetItem()->ItemType == ItemType2HBlunt || - wpn->GetItem()->ItemType == ItemType2HPiercing ) - { - if(zone->random.Int(0, 99) < ExtraAttackChanceBonus) - { - Attack(auto_attack_target, MainPrimary, false); - } - } - } - } + DoAttackRounds(auto_attack_target, MainPrimary); + if (CheckAATimer(aaTimerRampage)) + entity_list.AEAttack(this, 30); } } @@ -499,23 +437,11 @@ bool Client::Process() { float random = zone->random.Real(0, 1); CheckIncreaseSkill(SkillDualWield, auto_attack_target, -10); - if (random < DualWieldProbability){ // Max 78% of DW - //if(CheckAAEffect(aaEffectRampage)) { - // entity_list.AEAttack(this, 30, MainSecondary); - //} else { - Attack(auto_attack_target, MainSecondary); // Single attack with offhand - //} + if (random < DualWieldProbability) { // Max 78% of DW ItemInst *wpn = GetInv().GetItem(MainSecondary); TryWeaponProc(wpn, auto_attack_target, MainSecondary); - if( CanThisClassDoubleAttack() && CheckDoubleAttack()) { - //if(CheckAAEffect(aaEffectRampage)) { - // entity_list.AEAttack(this, 30, MainSecondary); - //} else { - // if(auto_attack_target && auto_attack_target->GetHP() > -10) - Attack(auto_attack_target, MainSecondary); // Single attack with offhand - //} - } + DoAttackRounds(auto_attack_target, MainSecondary); } } } diff --git a/zone/common.h b/zone/common.h index d21a0039d..e9521cb94 100644 --- a/zone/common.h +++ b/zone/common.h @@ -400,7 +400,6 @@ struct StatBonuses { int32 Metabolism; // Food/drink consumption rates. bool Sanctuary; // Sanctuary effect, lowers place on hate list until cast on others. int32 FactionModPct; // Modifies amount of faction gained. - int32 MeleeVulnerability; // Weakness/mitigation to melee damage bool LimitToSkill[HIGHEST_SKILL+2]; // Determines if we need to search for a skill proc. uint32 SkillProc[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs. uint32 SkillProcSuccess[MAX_SKILL_PROCS]; // Max number of spells containing skill_procs_success. diff --git a/zone/effects.cpp b/zone/effects.cpp index 55e56cdac..85a858c94 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -421,12 +421,6 @@ int32 Mob::GetActSpellDuration(uint16 spell_id, int32 duration) int tic_inc = 0; tic_inc = GetFocusEffect(focusSpellDurByTic, spell_id); - // unsure on the exact details, but bard songs that don't cost mana at some point get an extra tick, 60 for now - // a level 53 bard reported getting 2 tics - // bard DOTs do get this extra tick, but beneficial long bard songs don't? (invul, crescendo) - if ((IsShortDurationBuff(spell_id) || IsDetrimentalSpell(spell_id)) && IsBardSong(spell_id) && - spells[spell_id].mana == 0 && GetClass() == BARD && GetLevel() > 60) - tic_inc++; float focused = ((duration * increase) / 100.0f) + tic_inc; int ifocused = static_cast(focused); @@ -878,7 +872,7 @@ void EntityList::AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool aff caster->CastToClient()->CheckSongSkillIncrease(spell_id); } -//Dook- Rampage and stuff for clients. +// Rampage and stuff for clients. Normal and Duration rampages //NPCs handle it differently in Mob::Rampage void EntityList::AEAttack(Mob *attacker, float dist, int Hand, int count, bool IsFromSpell) { //Dook- Will need tweaking, currently no pets or players or horses @@ -896,7 +890,10 @@ void EntityList::AEAttack(Mob *attacker, float dist, int Hand, int count, bool I && curmob->GetRace() != 216 && curmob->GetRace() != 472 /* dont attack horses */ && (DistanceSquared(curmob->GetPosition(), attacker->GetPosition()) <= dist2) ) { - attacker->Attack(curmob, Hand, false, false, IsFromSpell); + if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) + attacker->Attack(curmob, Hand, false, false, IsFromSpell); + else + attacker->CastToClient()->DoAttackRounds(curmob, Hand, IsFromSpell); hit++; if (count != 0 && hit >= count) return; diff --git a/zone/mob.cpp b/zone/mob.cpp index 4eb87f7ad..1777bb7a3 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -3769,11 +3769,8 @@ int16 Mob::GetSkillDmgTaken(const SkillUseTypes skill_used) skilldmg_mod += itembonuses.SkillDmgTaken[HIGHEST_SKILL+1] + spellbonuses.SkillDmgTaken[HIGHEST_SKILL+1] + itembonuses.SkillDmgTaken[skill_used] + spellbonuses.SkillDmgTaken[skill_used]; - skilldmg_mod += SkillDmgTaken_Mod[skill_used] + SkillDmgTaken_Mod[HIGHEST_SKILL+1]; - skilldmg_mod += spellbonuses.MeleeVulnerability + itembonuses.MeleeVulnerability + aabonuses.MeleeVulnerability; - if(skilldmg_mod < -100) skilldmg_mod = -100; diff --git a/zone/mob.h b/zone/mob.h index 8352d764e..c9ba4471d 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -971,6 +971,7 @@ public: void CalcAABonuses(StatBonuses* newbon); void ApplyAABonuses(const AA::Rank &rank, StatBonuses* newbon); void GrantAlternateAdvancementAbility(int aa_id, int points); + bool CheckAATimer(int timer); protected: void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillUseTypes attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 3c0c8fad4..71740e589 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -638,42 +638,23 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove snprintf(effect_desc, _EDLEN, "Group Fear Immunity"); #endif //Added client messages to give some indication this effect is active. - uint32 group_id_caster = 0; - uint32 time = spell.base[i]*10; - if(caster->IsClient()) - { - if(caster->IsGrouped()) - { - group_id_caster = GetGroup()->GetID(); - } - else if(caster->IsRaidGrouped()) - { - group_id_caster = (GetRaid()->GetGroup(CastToClient()) == 0xFFFF) ? 0 : (GetRaid()->GetGroup(CastToClient()) + 1); + // Is there a message generated? Too disgusted by raids. + uint32 time = spell.base[i] * 10 * 1000; + if (caster->IsClient()) { + if (caster->IsGrouped()) { + auto group = caster->GetGroup(); + for (int i = 0; i < 6; ++i) + if (group->members[i]) + group->members[i]->aa_timers[aaTimerWarcry].Start(time); + } else if (caster->IsRaidGrouped()) { + auto raid = caster->GetRaid(); + uint32 gid = raid->GetGroup(caster->CastToClient()); + if (gid < 12) + for (int i = 0; i < MAX_RAID_MEMBERS; ++i) + if (raid->members[i].member && raid->members[i].GroupNumber == gid) + raid->members[i].member->aa_timers[aaTimerWarcry].Start(time); } } - //old aa - //if(group_id_caster){ - // Group *g = entity_list.GetGroupByID(group_id_caster); - // uint32 time = spell.base[i]*10; - // if(g){ - // for(int gi=0; gi < 6; gi++){ - // if(g->members[gi] && g->members[gi]->IsClient()) - // { - // g->members[gi]->CastToClient()->EnableAAEffect(aaEffectWarcry , time); - // if (g->members[gi]->GetID() != caster->GetID()) - // g->members[gi]->Message(13, "You hear the war cry."); - // else - // Message(13, "You let loose a fierce war cry."); - // } - // } - // } - //} - // - //else{ - // CastToClient()->EnableAAEffect(aaEffectWarcry , time); - // Message(13, "You let loose a fierce war cry."); - //} - break; } @@ -2238,9 +2219,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Duration Rampage"); #endif - //if (caster && caster->IsClient()) { // will tidy this up later so that NPCs can duration ramp from spells too - // CastToClient()->DurationRampage(effect_value*12); - //} + aa_timers[aaTimerRampage].Start(effect_value * 10 * 1000); // Live bug, was suppose to be 1 second per value break; } @@ -2969,7 +2948,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_FcIncreaseNumHits: case SE_CastonFocusEffect: case SE_FcHealAmtIncoming: - case SE_MeleeVulnerability: + case SE_LimitManaMax: case SE_DoubleRangedAttack: case SE_ShieldEquipHateMod: case SE_ShieldEquipDmgMod: @@ -3377,7 +3356,7 @@ void Mob::BuffProcess() { --buffs[buffs_i].ticsremaining; - if ((buffs[buffs_i].ticsremaining == 0 && !IsShortDurationBuff(buffs[buffs_i].spellid)) || buffs[buffs_i].ticsremaining < 0) { + if ((buffs[buffs_i].ticsremaining == 0 && !(IsShortDurationBuff(buffs[buffs_i].spellid) || IsBardSong(buffs[buffs_i].spellid))) || buffs[buffs_i].ticsremaining < 0) { Log.Out(Logs::Detail, Logs::Spells, "Buff %d in slot %d has expired. Fading.", buffs[buffs_i].spellid, buffs_i); BuffFadeBySlot(buffs_i); } @@ -4322,6 +4301,11 @@ int16 Client::CalcAAFocus(focusType type, const AA::Rank &rank, uint16 spell_id) LimitFailure = true; break; + case SE_LimitManaMax: + if (spell.mana > base1) + LimitFailure = true; + break; + case SE_LimitTarget: if (base1 < 0) { if (-base1 == spell.targettype) // Exclude @@ -4740,6 +4724,11 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo return 0; break; + case SE_LimitManaMax: + if (spell.mana > focus_spell.base[i]) + return 0; + break; + case SE_LimitTarget: if (focus_spell.base[i] < 0) { if (-focus_spell.base[i] == spell.targettype) // Exclude @@ -5199,11 +5188,8 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { return 0; } -int16 Client::GetFocusEffect(focusType type, uint16 spell_id) { - - if (IsBardSong(spell_id) && type != focusFcBaseEffects) - return 0; - +int16 Client::GetFocusEffect(focusType type, uint16 spell_id) +{ int16 realTotal = 0; int16 realTotal2 = 0; int16 realTotal3 = 0; diff --git a/zone/spells.cpp b/zone/spells.cpp index 4f492c343..c3e777b5c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2280,7 +2280,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 if(rank && rank->base_ability) { ExpendAlternateAdvancementCharge(rank->base_ability->id); } - } + } else if(spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) { //aa new todo: aa expendable charges here @@ -4124,13 +4124,13 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) } return true; } - //else if (IsClient() && CastToClient()->CheckAAEffect(aaEffectWarcry)) //old aa - //{ - // Message(13, "Your are immune to fear."); - // Log.Out(Logs::Detail, Logs::Spells, "Clients has WarCry effect, immune to fear!"); - // caster->Message_StringID(MT_Shout, IMMUNE_FEAR); - // return true; - //} + else if (CheckAATimer(aaTimerWarcry)) + { + Message(13, "Your are immune to fear."); + Log.Out(Logs::Detail, Logs::Spells, "Clients has WarCry effect, immune to fear!"); + caster->Message_StringID(MT_Shout, IMMUNE_FEAR); + return true; + } } if(IsCharmSpell(spell_id))