diff --git a/common/ruletypes.h b/common/ruletypes.h index 37be4a438..99c798546 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -404,7 +404,7 @@ RULE_BOOL(Spells, July242002PetResists, true, "Enable Pets using PCs resist chan RULE_INT(Spells, AOEMaxTargets, 0, "Max number of targets a Targeted AOE spell can cast on. Set to 0 for no limit.") RULE_BOOL(Spells, CazicTouchTargetsPetOwner, true, "If True, causes Cazic Touch to swap targets from pet to pet owner if a pet is tanking.") RULE_BOOL(Spells, PreventFactionWarOnCharmBreak, false, "Enable spell interupts and dot removal on charm break to prevent faction wars.") -RULE_BOOL(Spells, AllowDoubleInvis, false, "Allows you to cast invisibility spells on a player that is already invisible") +RULE_BOOL(Spells, AllowDoubleInvis, true, "Allows you to cast invisibility spells on a player that is already invisible, live like behavior.") RULE_BOOL(Spells, AllowSpellMemorizeFromItem, false, "Allows players to memorize spells by right-clicking spell scrolls") RULE_BOOL(Spells, InvisRequiresGroup, false, "Invis requires the the target to be in group.") RULE_INT(Spells, ClericInnateHealFocus, 5, "Clerics on live get a 5 pct innate heal focus") diff --git a/common/spdat.h b/common/spdat.h index caa80ce54..b544c2b80 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -191,6 +191,8 @@ #define MAX_APPEARANCE_EFFECTS 20 //Up to 20 Appearance Effects can be saved to a mobs appearance effect array, these will be sent to other clients when they enter a zone (This is arbitrary) #define MAX_CAST_ON_SKILL_USE 36 //Actual amount is MAX/3 +#define MAX_INVISIBILTY_LEVEL 254 + //instrument item id's used as song components #define INSTRUMENT_HAND_DRUM 13000 #define INSTRUMENT_WOODEN_FLUTE 13001 @@ -540,6 +542,13 @@ enum ReflectSpellType RELFECT_ALL_SINGLE_TARGET_SPELLS = 3, REFLECT_ALL_SPELLS = 4, }; + +enum InvisType { + T_INVISIBLE = 0, + T_INVISIBLE_VERSE_UNDEAD = 1, + T_INVISIBLE_VERSE_ANIMAL = 2, +}; + //For better organizing in proc effects, not used in spells. enum ProcType { @@ -1039,7 +1048,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 // implemented #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 diff --git a/zone/attack.cpp b/zone/attack.cpp index 5a5077dce..8fac3e20c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5626,28 +5626,12 @@ void Mob::DoShieldDamageOnShielder(Mob *shield_target, int hit_damage_done, EQ:: void Mob::CommonBreakInvisibleFromCombat() { //break invis when you attack - if (invisible) { - LogCombat("Removing invisibility due to melee attack"); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if (invisible_undead) { - LogCombat("Removing invisibility vs. undead due to melee attack"); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if (invisible_animals) { - LogCombat("Removing invisibility vs. animals due to melee attack"); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - + BreakInvisibleSpells(); CancelSneakHide(); - if (spellbonuses.NegateIfCombat) + if (spellbonuses.NegateIfCombat) { BuffFadeByEffect(SE_NegateIfCombat); + } hidden = false; improved_hidden = false; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 951e2b562..e66b93d73 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -47,6 +47,8 @@ void Mob::CalcBonuses() CalcMaxMana(); SetAttackTimer(); CalcAC(); + CalcSeeInvisibleLevel(); + CalcInvisibleLevel(); /* Fast walking NPC's are prone to disappear into walls/hills We set this here because NPC's can cast spells to change walkspeed/runspeed @@ -81,6 +83,9 @@ void Client::CalcBonuses() CalcSpellBonuses(&spellbonuses); CalcAABonuses(&aabonuses); + CalcSeeInvisibleLevel(); + CalcInvisibleLevel(); + ProcessItemCaps(); // caps that depend on spell/aa bonuses RecalcWeight(); @@ -867,7 +872,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) newbon->MaxBindWound += base_value; break; case SE_SeeInvis: - newbon->SeeInvis = base_value; + base_value = std::min({ base_value, MAX_INVISIBILTY_LEVEL }); + if (newbon->SeeInvis < base_value) { + newbon->SeeInvis = base_value; + } break; case SE_BaseMovementSpeed: newbon->BaseMovementSpeed += base_value; @@ -3825,6 +3833,32 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; } + case SE_Invisibility: + case SE_Invisibility2: + effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); + if (new_bonus->invisibility < effect_value) + new_bonus->invisibility = effect_value; + break; + + case SE_InvisVsUndead: + case SE_InvisVsUndead2: + if (new_bonus->invisibility_verse_undead < effect_value) + new_bonus->invisibility_verse_undead = effect_value; + break; + + case SE_InvisVsAnimals: + effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); + if (new_bonus->invisibility_verse_animal < effect_value) + new_bonus->invisibility_verse_animal = effect_value; + break; + + case SE_SeeInvis: + effect_value = std::min({ effect_value, MAX_INVISIBILTY_LEVEL }); + if (new_bonus->SeeInvis < effect_value) { + new_bonus->SeeInvis = effect_value; + } + break; + case SE_ZoneSuspendMinion: new_bonus->ZoneSuspendMinion = effect_value; break; diff --git a/zone/bot.cpp b/zone/bot.cpp index dcb8af574..4bdaf13bb 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7438,6 +7438,8 @@ void Bot::CalcBonuses() { CalcSpellBonuses(&spellbonuses); CalcAABonuses(&aabonuses); SetAttackTimer(); + CalcSeeInvisibleLevel(); + CalcInvisibleLevel(); CalcATK(); CalcSTR(); CalcSTA(); diff --git a/zone/bot_command.h b/zone/bot_command.h index 34ced3872..29e251ca0 100644 --- a/zone/bot_command.h +++ b/zone/bot_command.h @@ -109,7 +109,7 @@ public: } AType; static const int AilmentTypeCount = 5; - typedef enum InvisibilityType { + typedef enum InvisType { IT_None = 0, IT_Animal, IT_Undead, diff --git a/zone/client.cpp b/zone/client.cpp index 2857f86db..b1bdc3e80 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -3454,7 +3454,6 @@ void Client::Escape() { entity_list.RemoveFromTargets(this, true); SetInvisible(Invisibility::Invisible); - MessageString(Chat::Skills, ESCAPE); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index fcc2c76e2..c154ff2e6 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -682,8 +682,7 @@ void Client::CompleteConnect() case SE_Invisibility2: case SE_Invisibility: { - invisible = true; - SendAppearancePacket(AT_Invis, 1); + SendAppearancePacket(AT_Invis, Invisibility::Invisible); break; } case SE_Levitate: @@ -707,17 +706,6 @@ void Client::CompleteConnect() } break; } - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { - invisible_undead = true; - break; - } - case SE_InvisVsAnimals: - { - invisible_animals = true; - break; - } case SE_AddMeleeProc: case SE_WeaponProc: { @@ -3819,6 +3807,9 @@ void Client::Handle_OP_BoardBoat(const EQApplicationPacket *app) void Client::Handle_OP_Buff(const EQApplicationPacket *app) { + /* + Note: if invisibility is on client, this will force it to drop. + */ if (app->size != sizeof(SpellBuffPacket_Struct)) { LogError("Size mismatch in OP_Buff. expected [{}] got [{}]", sizeof(SpellBuffPacket_Struct), app->size); @@ -3833,10 +3824,12 @@ void Client::Handle_OP_Buff(const EQApplicationPacket *app) //something about IsDetrimentalSpell() crashes this portion of code.. //tbh we shouldn't use it anyway since this is a simple red vs blue buff check and //isdetrimentalspell() is much more complex - if (spid == 0xFFFF || (IsValidSpell(spid) && (spells[spid].good_effect == 0))) + if (spid == 0xFFFF || (IsValidSpell(spid) && (spells[spid].good_effect == 0))) { QueuePacket(app); - else + } + else { BuffFadeBySpellID(spid); + } return; } @@ -3990,12 +3983,20 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app) return; } + if (invisible) { + ZeroInvisibleVars(InvisType::T_INVISIBLE); + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + } + // Hack for broken RoF2 which allows casting after a zoned IVU/IVA - if (invisible_undead || invisible_animals) { - BuffFadeByEffect(SE_InvisVsAnimals); + if (invisible_undead) { BuffFadeByEffect(SE_InvisVsUndead); BuffFadeByEffect(SE_InvisVsUndead2); - BuffFadeByEffect(SE_Invisibility); // Included per JJ for completeness - client handles this one atm + } + if (invisible_animals) { + BuffFadeByEffect(SE_InvisVsAnimals); + BuffFadeByEffect(SE_ImprovedInvisAnimals); } CastSpell_Struct* castspell = (CastSpell_Struct*)app->pBuffer; @@ -4826,7 +4827,7 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app) // this could be done better, but this is only called when you con so w/e // Shroud of Stealth has a special message - if (improved_hidden && (!tmob->see_improved_hide && (tmob->see_invis || tmob->see_hide))) + if (improved_hidden && (!tmob->see_improved_hide && (tmob->SeeInvisible() || tmob->see_hide))) MessageString(Chat::NPCQuestSay, SOS_KEEPS_HIDDEN); // we are trying to hide but they can see us else if ((invisible || invisible_undead || hidden || invisible_animals) && !IsInvisible(tmob)) @@ -13565,7 +13566,7 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app) } return; } - invisible = false; + ZeroInvisibleVars(InvisType::T_INVISIBLE); hidden = false; improved_hidden = false; entity_list.QueueClients(this, app, true); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 454787517..af3609355 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -1163,9 +1163,9 @@ void Client::BreakInvis() sa_out->parameter = 0; entity_list.QueueClients(this, outapp, true); safe_delete(outapp); - invisible = false; - invisible_undead = false; - invisible_animals = false; + ZeroInvisibleVars(InvisType::T_INVISIBLE); + ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_UNDEAD); + ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_ANIMAL); hidden = false; improved_hidden = false; } diff --git a/zone/common.h b/zone/common.h index bb32856d0..a282d7c5d 100644 --- a/zone/common.h +++ b/zone/common.h @@ -561,6 +561,9 @@ struct StatBonuses { bool ZoneSuspendMinion; // base 1 allows suspended minions to zone bool CompleteHealBuffBlocker; // Use in SPA 101 to prevent recast of complete heal from this effect till blocker buff is removed. int32 Illusion; // illusion spell id + uint8 invisibility; // invisibility level + uint8 invisibility_verse_undead; // IVU level + uint8 invisibility_verse_animal; // IVA level // AAs int32 TrapCircumvention; // reduce chance to trigger a trap. diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index e19f93906..4cbf0227c 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -2118,7 +2118,7 @@ uint8 Lua_Mob::SeeInvisible() { return self->SeeInvisible(); } -bool Lua_Mob::SeeInvisibleUndead() { +uint8 Lua_Mob::SeeInvisibleUndead() { Lua_Safe_Call_Bool(); return self->SeeInvisibleUndead(); } @@ -2423,6 +2423,31 @@ Lua_NPC Lua_Mob::GetHateRandomNPC() { return Lua_NPC(self->GetHateRandomNPC()); } +uint8 Lua_Mob::GetInvisibleLevel() +{ + Lua_Safe_Call_Int(); + return self->GetInvisibleLevel(); +} + +uint8 Lua_Mob::GetInvisibleUndeadLevel() +{ + Lua_Safe_Call_Int(); + return self->GetInvisibleUndeadLevel(); +} + +void Lua_Mob::SetSeeInvisibleLevel(uint8 invisible_level) +{ + Lua_Safe_Call_Void(); + self->SetInnateSeeInvisible(invisible_level); + self->CalcSeeInvisibleLevel(); +} + +void Lua_Mob::SetSeeInvisibleUndeadLevel(uint8 invisible_level) +{ + Lua_Safe_Call_Void(); + self->SetSeeInvisibleUndead(invisible_level); +} + luabind::scope lua_register_mob() { return luabind::class_("Mob") .def(luabind::constructor<>()) @@ -2618,6 +2643,8 @@ luabind::scope lua_register_mob() { .def("GetHelmTexture", &Lua_Mob::GetHelmTexture) .def("GetHerosForgeModel", (int32(Lua_Mob::*)(uint8))&Lua_Mob::GetHerosForgeModel) .def("GetINT", &Lua_Mob::GetINT) + .def("GetInvisibleLevel", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetInvisibleLevel) + .def("GetInvisibleUndeadLevel", (uint8(Lua_Mob::*)(void))&Lua_Mob::GetInvisibleUndeadLevel) .def("GetInvul", (bool(Lua_Mob::*)(void))&Lua_Mob::GetInvul) .def("GetItemBonuses", &Lua_Mob::GetItemBonuses) .def("GetItemHPBonuses", &Lua_Mob::GetItemHPBonuses) @@ -2763,7 +2790,9 @@ luabind::scope lua_register_mob() { .def("SeeHide", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeHide) .def("SeeImprovedHide", (bool(Lua_Mob::*)(bool))&Lua_Mob::SeeImprovedHide) .def("SeeInvisible", (uint8(Lua_Mob::*)(void))&Lua_Mob::SeeInvisible) - .def("SeeInvisibleUndead", (bool(Lua_Mob::*)(void))&Lua_Mob::SeeInvisibleUndead) + .def("SeeInvisibleUndead", (uint8(Lua_Mob::*)(void))&Lua_Mob::SeeInvisibleUndead) + .def("SetSeeInvisibleLevel", (void(Lua_Mob::*)(uint8))&Lua_Mob::SetSeeInvisibleLevel) + .def("SetSeeInvisibleUndeadLevel", (void(Lua_Mob::*)(uint8))&Lua_Mob::SetSeeInvisibleUndeadLevel) .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32))&Lua_Mob::SendAppearanceEffect) .def("SendAppearanceEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,uint32,uint32,Lua_Client))&Lua_Mob::SendAppearanceEffect) .def("SendBeginCast", &Lua_Mob::SendBeginCast) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index f6b678d36..fdcecce55 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -86,6 +86,10 @@ public: bool IsInvisible(); bool IsInvisible(Lua_Mob other); void SetInvisible(int state); + uint8 GetInvisibleLevel(); + uint8 GetInvisibleUndeadLevel(); + void SetSeeInvisibleLevel(uint8 invisible_level); + void SetSeeInvisibleUndeadLevel(uint8 invisible_level); bool FindBuff(int spell_id); uint16 FindBuffBySlot(int slot); uint32 BuffCount(); @@ -406,7 +410,7 @@ public: int CanBuffStack(int spell_id, int caster_level, bool fail_if_overwrite); void SetPseudoRoot(bool in); uint8 SeeInvisible(); - bool SeeInvisibleUndead(); + uint8 SeeInvisibleUndead(); bool SeeHide(); bool SeeImprovedHide(); uint8 GetNimbusEffect1(); diff --git a/zone/mob.cpp b/zone/mob.cpp index 05e93dd3a..17dba13b4 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -81,8 +81,8 @@ Mob::Mob( uint32 in_drakkin_details, EQ::TintProfile in_armor_tint, uint8 in_aa_title, - uint8 in_see_invis, // see through invis/ivu - uint8 in_see_invis_undead, + uint16 in_see_invis, // see through invis/ivu + uint16 in_see_invis_undead, uint8 in_see_hide, uint8 in_see_improved_hide, int32 in_hp_regen, @@ -269,8 +269,8 @@ Mob::Mob( maxlevel = in_maxlevel; scalerate = in_scalerate; invisible = 0; - invisible_undead = false; - invisible_animals = false; + invisible_undead = 0; + invisible_animals = 0; sneaking = false; hidden = false; improved_hidden = false; @@ -440,10 +440,13 @@ Mob::Mob( pStandingPetOrder = SPO_Follow; pseudo_rooted = false; - see_invis = GetSeeInvisible(in_see_invis); - see_invis_undead = GetSeeInvisible(in_see_invis_undead); - see_hide = GetSeeInvisible(in_see_hide); - see_improved_hide = GetSeeInvisible(in_see_improved_hide); + nobuff_invisible = 0; + see_invis = 0; + + innate_see_invis = GetSeeInvisibleLevelFromNPCStat(in_see_invis); + see_invis_undead = GetSeeInvisibleLevelFromNPCStat(in_see_invis_undead); + see_hide = GetSeeInvisibleLevelFromNPCStat(in_see_hide); + see_improved_hide = GetSeeInvisibleLevelFromNPCStat(in_see_improved_hide); qglobal = in_qglobal != 0; @@ -584,10 +587,52 @@ uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) { return(ANIM_STAND); } -void Mob::SetInvisible(uint8 state) + +void Mob::CalcSeeInvisibleLevel() { - if (state != Invisibility::Special) { - invisible = state; + see_invis = std::max({ spellbonuses.SeeInvis, itembonuses.SeeInvis, aabonuses.SeeInvis, innate_see_invis }); +} + +void Mob::CalcInvisibleLevel() +{ + bool is_invisible = invisible; + + invisible = std::max({ spellbonuses.invisibility, nobuff_invisible }); + invisible_undead = spellbonuses.invisibility_verse_undead; + invisible_animals = spellbonuses.invisibility_verse_animal; + + if (!is_invisible && invisible) { + SetInvisible(Invisibility::Invisible, true); + return; + } + + if (is_invisible && !invisible) { + SetInvisible(invisible, true); + return; + } +} + +void Mob::SetInvisible(uint8 state, bool set_on_bonus_calc) +{ + /* + If you set an NPC to invisible you will only be able to see it on + your client if your see invisible level is greater than equal to the invisible level. + Note, the clients spell file must match the servers see invisible level on the spell. + */ + + if (state == Invisibility::Visible) { + SendAppearancePacket(AT_Invis, Invisibility::Visible); + ZeroInvisibleVars(InvisType::T_INVISIBLE); + } + else { + /* + if your setting invisible from a script, or escape/fading memories effect then + we use the internal invis variable which allows invisible without a buff on mob. + */ + if (!set_on_bonus_calc) { + nobuff_invisible = state; + CalcInvisibleLevel(); + } SendAppearancePacket(AT_Invis, invisible); } @@ -597,35 +642,53 @@ void Mob::SetInvisible(uint8 state) if (RuleB(Pets, LivelikeBreakCharmOnInvis) || IsInvisible(pet)) { pet->BuffFadeByEffect(SE_Charm); } - LogRules("Pets:LivelikeBreakCharmOnInvis for [{}] | Invis [{}] - Hidden [{}] - Shroud of Stealth [{}] - IVA [{}] - IVU [{}]", GetCleanName(), invisible, hidden, improved_hidden, invisible_animals, invisible_undead); } } +void Mob::ZeroInvisibleVars(uint8 invisible_type) +{ + switch (invisible_type) { + + case T_INVISIBLE: + invisible = 0; + nobuff_invisible = 0; + break; + + case T_INVISIBLE_VERSE_UNDEAD: + invisible_undead = 0; + break; + + case T_INVISIBLE_VERSE_ANIMAL: + invisible_animals = 0; + break; + } +} + //check to see if `this` is invisible to `other` bool Mob::IsInvisible(Mob* other) const { - if(!other) + if (!other) { return(false); - - uint8 SeeInvisBonus = 0; - if (IsClient()) - SeeInvisBonus = aabonuses.SeeInvis; + } //check regular invisibility - if (invisible && invisible > (other->SeeInvisible())) + if (invisible && (invisible > other->SeeInvisible())) { return true; + } //check invis vs. undead if (other->GetBodyType() == BT_Undead || other->GetBodyType() == BT_SummonedUndead) { - if(invisible_undead && !other->SeeInvisibleUndead()) + if (invisible_undead && (invisible_undead > other->SeeInvisibleUndead())) { return true; + } } - //check invis vs. animals... + //check invis vs. animals. //TODO: should we have a specific see invisible animal stat or this how live does it? if (other->GetBodyType() == BT_Animal){ - if(invisible_animals && !other->SeeInvisible()) + if (invisible_animals && (invisible_animals > other->SeeInvisible())) { return true; + } } if(hidden){ @@ -642,8 +705,9 @@ bool Mob::IsInvisible(Mob* other) const //handle sneaking if(sneaking) { - if(BehindMob(other, GetX(), GetY()) ) + if (BehindMob(other, GetX(), GetY())) { return true; + } } return(false); @@ -6110,19 +6174,47 @@ float Mob::HeadingAngleToMob(float other_x, float other_y) return CalculateHeadingAngleBetweenPositions(this_x, this_y, other_x, other_y); } -bool Mob::GetSeeInvisible(uint8 see_invis) +uint8 Mob::GetSeeInvisibleLevelFromNPCStat(uint16 in_see_invis) { - if(see_invis > 0) - { - if(see_invis == 1) - return true; - else - { - if (zone->random.Int(0, 99) < see_invis) - return true; + /* + Returns the NPC's see invisible level based on 'see_invs' value in npc_types. + 1 = See Invs Level 1, 2-99 will gives a random roll to apply see invs level 1 + 100 = See Invs Level 2, where 101-199 gives a random roll to apply see invs 2, if fails get see invs 1 + ect... for higher levels, 200,300 ect. + MAX 25499, which can give you level 254. + */ + + //npc does not have see invis + if (!in_see_invis) { + return 0; + } + //npc has basic see invis + if (in_see_invis == 1) { + return 1; + } + + //random chance to apply standard level 1 see invs + if (in_see_invis > 1 && in_see_invis < 100) { + if (zone->random.Int(0, 99) < in_see_invis) { + return 1; } } - return false; + //covers npcs with see invis levels beyond level 1, max calculated level allowed is 254 + int see_invis_level = 1; + see_invis_level += (in_see_invis / 100); + + int see_invis_chance = in_see_invis % 100; + + //has enhanced see invis level + if (see_invis_chance == 0) { + return std::min(see_invis_level, MAX_INVISIBILTY_LEVEL); + } + //has chance for enhanced see invis level + if (zone->random.Int(0, 99) < see_invis_chance) { + return std::min(see_invis_level, MAX_INVISIBILTY_LEVEL); + } + //failed chance at attempted enhanced see invs level, use previous level. + return std::min((see_invis_level - 1), MAX_INVISIBILTY_LEVEL); } int32 Mob::GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot) diff --git a/zone/mob.h b/zone/mob.h index 7eff736ee..d676cc046 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -147,8 +147,8 @@ public: uint32 in_drakkin_details, EQ::TintProfile in_armor_tint, uint8 in_aa_title, - uint8 in_see_invis, // see through invis - uint8 in_see_invis_undead, // see through invis vs. undead + uint16 in_see_invis, // see through invis + uint16 in_see_invis_undead, // see through invis vs. undead uint8 in_see_hide, uint8 in_see_improved_hide, int32 in_hp_regen, @@ -226,10 +226,6 @@ public: virtual inline bool IsBerserk() { return false; } // only clients void RogueEvade(Mob *other); void CommonOutgoingHitSuccess(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); - void BreakInvisibleSpells(); - virtual void CancelSneakHide(); - void CommonBreakInvisible(); - void CommonBreakInvisibleFromCombat(); bool HasDied(); virtual bool CheckDualWield(); void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); @@ -246,6 +242,39 @@ public: return; } + //Invisible + bool IsInvisible(Mob* other = 0) const; + void SetInvisible(uint8 state, bool set_on_bonus_calc = false); + + void CalcSeeInvisibleLevel(); + void CalcInvisibleLevel(); + void ZeroInvisibleVars(uint8 invisible_type); + + inline uint8 GetSeeInvisibleLevelFromNPCStat(uint16 in_see_invis); + + void BreakInvisibleSpells(); + virtual void CancelSneakHide(); + void CommonBreakInvisible(); + void CommonBreakInvisibleFromCombat(); + + inline uint8 GetInvisibleLevel() const { return invisible; } + inline uint8 GetInvisibleUndeadLevel() const { return invisible_undead; } + + inline bool SeeHide() const { return see_hide; } + inline bool SeeImprovedHide() const { return see_improved_hide; } + inline uint8 SeeInvisibleUndead() const { return see_invis_undead; } + inline uint8 SeeInvisible() const { return see_invis; } + + inline void SetInnateSeeInvisible(uint8 val) { innate_see_invis = val; } + inline void SetSeeInvisibleUndead(uint8 val) { see_invis_undead = val; } + + uint32 tmHidden; // timestamp of hide, only valid while hidden == true + uint8 invisible, nobuff_invisible, invisible_undead, invisible_animals; + uint8 see_invis, innate_see_invis, see_invis_undead; //TODO: do we need a see_invis_animal ? + + bool sneaking, hidden, improved_hidden; + bool see_hide, see_improved_hide; + /** ************************************************ * Appearance @@ -254,16 +283,8 @@ public: EQ::InternalTextureProfile mob_texture_profile = {}; - bool IsInvisible(Mob* other = 0) const; - EQ::skills::SkillType AttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse = EQ::skills::Skill1HBlunt); - inline bool GetSeeInvisible(uint8 see_invis); - inline bool SeeHide() const { return see_hide; } - inline bool SeeImprovedHide() const { return see_improved_hide; } - inline bool SeeInvisibleUndead() const { return see_invis_undead; } - inline uint8 SeeInvisible() const { return see_invis; } - int32 GetTextureProfileMaterial(uint8 material_slot) const; int32 GetTextureProfileColor(uint8 material_slot) const; int32 GetTextureProfileHeroForgeModel(uint8 material_slot) const; @@ -282,7 +303,6 @@ public: void SendLevelAppearance(); void SendStunAppearance(); void SendTargetable(bool on, Client *specific_target = nullptr); - void SetInvisible(uint8 state); void SetMobTextureProfile(uint8 material_slot, uint16 texture, uint32 color = 0, uint32 hero_forge_model = 0); //Spell @@ -974,10 +994,7 @@ public: inline const bodyType GetOrigBodyType() const { return orig_bodytype; } void SetBodyType(bodyType new_body, bool overwrite_orig); - uint32 tmHidden; // timestamp of hide, only valid while hidden == true - uint8 invisible, see_invis; - bool invulnerable, invisible_undead, invisible_animals, sneaking, hidden, improved_hidden; - bool see_invis_undead, see_hide, see_improved_hide; + bool invulnerable; bool qglobal; virtual void SetAttackTimer(); diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index efa71d2dc..fba859354 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -916,6 +916,35 @@ XS(XS_Mob_SetInvisible) { XSRETURN_EMPTY; } +XS(XS_Mob_SetSeeInvisibleLevel); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_SetSeeInvisibleLevel) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Mob::SetSeeInvisibleLevel(THIS, uint8 see_invis_level)"); // @categories Script Utility + { + Mob *THIS; + uint8 see_invis_level = (uint8)SvUV(ST(1)); + VALIDATE_THIS_IS_MOB; + THIS->SetInnateSeeInvisible(see_invis_level); + THIS->CalcSeeInvisibleLevel(); + } + XSRETURN_EMPTY; +} + +XS(XS_Mob_SetSeeInvisibleUndeadLevel); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Mob_SetSeeInvisibleUndeadLevel) { + dXSARGS; + if (items != 2) + Perl_croak(aTHX_ "Usage: Mob::SetSeeInvisibleUndeadLevel(THIS, uint8 see_invis_undead_level)"); // @categories Script Utility + { + Mob *THIS; + uint8 see_invis_undead_level = (uint8)SvUV(ST(1)); + VALIDATE_THIS_IS_MOB; + THIS->SetSeeInvisibleUndead(see_invis_undead_level); + } + XSRETURN_EMPTY; +} + XS(XS_Mob_FindBuff); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_FindBuff) { dXSARGS; @@ -5777,6 +5806,41 @@ XS(XS_Mob_IsBlind) { XSRETURN(1); } +XS(XS_Mob_GetInvisibleLevel); +XS(XS_Mob_GetInvisibleLevel) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetInvisibleLevel(THIS)"); // @categories Stats and Attributes + { + Mob *THIS; + uint8 RETVAL; + dXSTARG; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->GetInvisibleLevel(); + XSprePUSH; + PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Mob_GetInvisibleUndeadLevel); +XS(XS_Mob_GetInvisibleUndeadLevel) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: Mob::GetInvisibleUndeadLevel(THIS)"); // @categories Stats and Attributes + { + Mob *THIS; + uint8 RETVAL; + dXSTARG; + VALIDATE_THIS_IS_MOB; + RETVAL = THIS->GetInvisibleUndeadLevel(); + XSprePUSH; + PUSHu((UV)RETVAL); + } + XSRETURN(1); +} + + XS(XS_Mob_SeeInvisible); XS(XS_Mob_SeeInvisible) { dXSARGS; @@ -5801,11 +5865,12 @@ XS(XS_Mob_SeeInvisibleUndead) { Perl_croak(aTHX_ "Usage: Mob::SeeInvisibleUndead(THIS)"); // @categories Stats and Attributes { Mob *THIS; - bool RETVAL; + uint8 RETVAL; + dXSTARG; VALIDATE_THIS_IS_MOB; RETVAL = THIS->SeeInvisibleUndead(); - ST(0) = boolSV(RETVAL); - sv_2mortal(ST(0)); + XSprePUSH; + PUSHu((UV)RETVAL); } XSRETURN(1); } @@ -6698,6 +6763,8 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "GetHerosForgeModel"), XS_Mob_GetHerosForgeModel, file, "$$"); newXSproto(strcpy(buf, "GetID"), XS_Mob_GetID, file, "$"); newXSproto(strcpy(buf, "GetINT"), XS_Mob_GetINT, file, "$"); + newXSproto(strcpy(buf, "GetInvisibleLevel"), XS_Mob_GetInvisibleLevel, file, "$"); + newXSproto(strcpy(buf, "GetInvisibleUndeadLevel"), XS_Mob_GetInvisibleUndeadLevel, file, "$"); newXSproto(strcpy(buf, "GetInvul"), XS_Mob_GetInvul, file, "$"); newXSproto(strcpy(buf, "GetItemHPBonuses"), XS_Mob_GetItemHPBonuses, file, "$"); newXSproto(strcpy(buf, "GetItemStat"), XS_Mob_GetItemStat, file, "$$$"); @@ -6877,6 +6944,8 @@ XS(boot_Mob) { newXSproto(strcpy(buf, "SetRace"), XS_Mob_SetRace, file, "$$"); newXSproto(strcpy(buf, "SetRunAnimSpeed"), XS_Mob_SetRunAnimSpeed, file, "$$"); newXSproto(strcpy(buf, "SetRunning"), XS_Mob_SetRunning, file, "$$"); + newXSproto(strcpy(buf, "SetSeeInvisibleLevel"), XS_Mob_SetSeeInvisibleLevel, file, "$$"); + newXSproto(strcpy(buf, "SetSeeInvisibleUndeadLevel"), XS_Mob_SetSeeInvisibleUndeadLevel, file, "$$"); newXSproto(strcpy(buf, "SetSlotTint"), XS_Mob_SetSlotTint, file, "$$$$$"); newXSproto(strcpy(buf, "SetSpecialAbility"), XS_Mob_SetSpecialAbility, file, "$$$"); newXSproto(strcpy(buf, "SetSpecialAbilityParam"), XS_Mob_SetSpecialAbilityParam, file, "$$$$"); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 1d68f1ec6..f69d3fc32 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -583,45 +583,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove break; } - case SE_Invisibility: - case SE_Invisibility2: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Invisibility"); -#endif - SetInvisible(spell.base_value[i]); - break; - } - - case SE_InvisVsAnimals: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Invisibility to Animals"); -#endif - invisible_animals = true; - SetInvisible(Invisibility::Special); - break; - } - - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "Invisibility to Undead"); -#endif - invisible_undead = true; - SetInvisible(Invisibility::Special); - break; - } - case SE_SeeInvis: - { -#ifdef SPELL_EFFECT_SPAM - snprintf(effect_desc, _EDLEN, "See Invisible"); -#endif - see_invis = spell.base_value[i]; - break; - } - case SE_FleshToBone: { #ifdef SPELL_EFFECT_SPAM @@ -3277,6 +3238,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Proc_Timer_Modifier: case SE_FFItemClass: case SE_SpellEffectResistChance: + case SE_SeeInvis: { break; } @@ -3946,10 +3908,13 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) } } } + case SE_ImprovedInvisAnimals: case SE_Invisibility2: case SE_InvisVsUndead2: { - if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) { - MessageString(Chat::Spells, INVIS_BEGIN_BREAK); + if (!IsBardSong(buff.spellid)) { + if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) { + MessageString(Chat::Spells, INVIS_BEGIN_BREAK); + } } break; } @@ -4186,32 +4151,6 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } - case SE_Invisibility2: - case SE_Invisibility: - { - SetInvisible(Invisibility::Visible); - break; - } - - case SE_InvisVsUndead2: - case SE_InvisVsUndead: - { - invisible_undead = false; // Mongrel: No longer IVU - break; - } - - case SE_InvisVsAnimals: - { - invisible_animals = false; - break; - } - - case SE_SeeInvis: - { - see_invis = 0; - break; - } - case SE_Silence: { Silence(false); @@ -9580,18 +9519,19 @@ void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster) void Mob::BreakInvisibleSpells() { if(invisible) { + ZeroInvisibleVars(InvisType::T_INVISIBLE); BuffFadeByEffect(SE_Invisibility); BuffFadeByEffect(SE_Invisibility2); - invisible = false; } if(invisible_undead) { + ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_UNDEAD); BuffFadeByEffect(SE_InvisVsUndead); BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; } if(invisible_animals){ + ZeroInvisibleVars(InvisType::T_INVISIBLE_VERSE_ANIMAL); + BuffFadeByEffect(SE_ImprovedInvisAnimals); BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; } } @@ -9600,22 +9540,20 @@ void Client::BreakSneakWhenCastOn(Mob *caster, bool IsResisted) bool IsCastersTarget = false; // Chance to avoid only applies to AOE spells when not targeted. if (hidden || improved_hidden) { if (caster) { - Mob *target = nullptr; - target = caster->GetTarget(); - IsCastersTarget = target && target == this; + Mob *spell_target = caster->GetTarget(); + if (spell_target && spell_target == this) { + IsCastersTarget = true; + } } - if (!IsCastersTarget) { - int chance = - spellbonuses.NoBreakAESneak + itembonuses.NoBreakAESneak + aabonuses.NoBreakAESneak; - - if (IsResisted) + int chance = spellbonuses.NoBreakAESneak + itembonuses.NoBreakAESneak + aabonuses.NoBreakAESneak; + if (IsResisted) { chance *= 2; - - if (chance && zone->random.Roll(chance)) + } + if (chance && zone->random.Roll(chance)) { return; // Do not drop Sneak/Hide + } } - CancelSneakHide(); } } diff --git a/zone/spells.cpp b/zone/spells.cpp index e955bdb33..ab0fff31a 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3622,7 +3622,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes // Prevent double invising, which made you uninvised // Not sure if all 3 should be stacking - + //This is not live like behavior (~Kayen confirmed 2/2/22) if (!RuleB(Spells, AllowDoubleInvis)) { if (IsEffectInSpell(spell_id, SE_Invisibility)) { diff --git a/zone/zonedump.h b/zone/zonedump.h index 4bbdf47c8..56a89b920 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -108,8 +108,8 @@ struct NPCType int32 mana_regen; int32 aggroradius; // added for AI improvement - neotokyo int32 assistradius; // assist radius, defaults to aggroradis if not set - uint8 see_invis; // See Invis flag added - bool see_invis_undead; // See Invis vs. Undead flag added + uint16 see_invis; // See Invis flag added + uint16 see_invis_undead; // See Invis vs. Undead flag added bool see_hide; bool see_improved_hide; bool qglobal;