[Spells] Rework of Virus Effect code (#1593)

* start of rework

* functional

* virus updates

* Update npc.cpp

* updates

* updates

* update v2

* pre remove old code

* removed old code1

* remove debugs

* description

* Update spell_effects.cpp

* changed function name

* remove unused var

* merge error fix

* fix formating issue

* Update spdat.cpp

* Update spell_effects.cpp

* Convert virus entity range code to use vectors and GetCloseMobList

* Formatting [skip ci]

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
KayenEQ 2021-10-24 19:27:51 -04:00 committed by GitHub
parent 1c5f9f2e0f
commit 060be606e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 242 additions and 165 deletions

View File

@ -1572,3 +1572,26 @@ int GetSpellStatValue(uint32 spell_id, const char* stat_identifier, uint8 slot)
return 0;
}
bool IsVirusSpell(int32 spell_id)
{
if (GetViralMinSpreadTime(spell_id) && GetViralMaxSpreadTime(spell_id) && GetViralSpreadRange(spell_id)){
return true;
}
return false;
}
int32 GetViralMinSpreadTime(int32 spell_id)
{
return spells[spell_id].viral_targets;
}
int32 GetViralMaxSpreadTime(int32 spell_id)
{
return spells[spell_id].viral_timer;
}
int32 GetViralSpreadRange(int32 spell_id)
{
return spells[spell_id].viral_range;
}

View File

@ -1342,8 +1342,8 @@ struct SPDat_Spell_Struct
/* 188 */ //int npc_usefulness; // -- NPC_USEFULNESS
/* 189 */ int MinResist; // -- MIN_RESIST
/* 190 */ int MaxResist; // -- MAX_RESIST
/* 191 */ uint8 viral_targets; // -- MIN_SPREAD_TIME
/* 192 */ uint8 viral_timer; // -- MAX_SPREAD_TIME
/* 191 */ int viral_targets; // -- MIN_SPREAD_TIME
/* 192 */ int viral_timer; // -- MAX_SPREAD_TIME
/* 193 */ int NimbusEffect; // -- DURATION_PARTICLE_EFFECT
/* 194 */ float directional_start; //Cone Start Angle: -- CONE_START_ANGLE
/* 195 */ float directional_end; // Cone End Angle: -- CONE_END_ANGLE
@ -1509,6 +1509,10 @@ bool IsCastWhileInvis(uint16 spell_id);
bool IsEffectIgnoredInStacking(int spa);
bool IsFocusLimit(int spa);
bool SpellRequiresTarget(int targettype);
bool IsVirusSpell(int32 spell_id);
int GetViralMinSpreadTime(int32 spell_id);
int GetViralMaxSpreadTime(int32 spell_id);
int GetViralSpreadRange(int32 spell_id);
bool IsInstrumentModAppliedToSpellEffect(int32 spell_id, int effect);
int CalcPetHp(int levelb, int classb, int STA = 75);

View File

@ -478,7 +478,6 @@ Json::Value ApiGetMobListDetail(EQ::Net::WebsocketServerConnection *connection,
row["has_temp_pets_active"] = mob->HasTempPetsActive();
row["has_two_hand_blunt_equiped"] = mob->HasTwoHandBluntEquiped();
row["has_two_hander_equipped"] = mob->HasTwoHanderEquipped();
row["has_virus"] = mob->HasVirus();
row["hate_summon"] = mob->HateSummon();
row["helm_texture"] = mob->GetHelmTexture();
row["hp"] = mob->GetHP();

View File

@ -444,19 +444,8 @@ bool Client::Process() {
}
}
if (HasVirus()) {
if (viral_timer.Check()) {
viral_timer_counter++;
for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i += 2) {
if (viral_spells[i]) {
if (viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) {
SpreadVirus(viral_spells[i], viral_spells[i + 1]);
}
}
}
}
if (viral_timer_counter > 999)
viral_timer_counter = 0;
if (viral_timer.Check() && !dead) {
VirusEffectProcess();
}
ProjectileAttack();

View File

@ -330,6 +330,7 @@ struct Buffs_Struct {
uint32 instrument_mod;
int16 focusproclimit_time; //timer to limit number of procs from focus effects
int16 focusproclimit_procamt; //amount of procs that can be cast before timer limiter is set
int32 virus_spread_time; //time till next attempted viral spread
bool persistant_buff;
bool client; //True if the caster is a client
bool UpdateClient;

View File

@ -1219,7 +1219,7 @@ uint32 EntityList::CountSpawnedNPCs(std::vector<uint32> npc_ids)
npc_ids.begin(),
npc_ids.end(),
current_npc.second->GetNPCTypeID()
) != npc_ids.end() &&
) != npc_ids.end() &&
current_npc.second->GetID() != 0
) {
npc_count++;
@ -5222,59 +5222,94 @@ Client *EntityList::FindCorpseDragger(uint16 CorpseID)
return nullptr;
}
Mob *EntityList::GetTargetForVirus(Mob *spreader, int range)
std::vector<Mob*> EntityList::GetTargetsForVirusEffect(Mob *spreader, Mob *original_caster, int range, int pcnpc, int32 spell_id)
{
int max_spread_range = RuleI(Spells, VirusSpreadDistance);
/*
Live Mechanics
Virus spreader does NOT need LOS
There is no max target limit
*/
if (!spreader) {
return {};
}
if (range)
max_spread_range = range;
std::vector<Mob *> spreader_list = {};
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
for (auto &it : entity_list.GetCloseMobList(spreader, range)) {
Mob *mob = it.second;
if (mob == spreader) {
continue;
}
std::vector<Mob *> TargetsInRange;
// check PC/NPC only flag 1 = PCs, 2 = NPCs
if (pcnpc == 1 && !mob->IsClient() && !mob->IsMerc() && !mob->IsBot()) {
continue;
}
else if (pcnpc == 2 && (mob->IsClient() || mob->IsMerc() || mob->IsBot())) {
continue;
}
if (mob->IsClient() && !mob->CastToClient()->ClientFinishedLoading()) {
continue;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *cur = it->second;
// Make sure the target is in range, has los and is not the mob doing the spreading
if ((cur->GetID() != spreader->GetID()) &&
(cur->CalculateDistance(spreader->GetX(), spreader->GetY(),
spreader->GetZ()) <= max_spread_range) &&
(spreader->CheckLosFN(cur))) {
// If the spreader is an npc it can only spread to other npc controlled mobs
if (spreader->IsNPC() && !spreader->IsPet() && !spreader->CastToNPC()->GetSwarmOwner() && cur->IsNPC()) {
TargetsInRange.push_back(cur);
if (mob->IsAura() || mob->IsTrap()) {
continue;
}
// Make sure the target is in range
if (mob->CalculateDistance(spreader->GetX(), spreader->GetY(), spreader->GetZ()) <= range) {
//Do not allow detrimental spread to anything the original caster couldn't normally attack.
if (is_detrimental_spell && !original_caster->IsAttackAllowed(mob, true)) {
continue;
}
// If the spreader is an npc controlled pet it can spread to any other npc or an npc controlled pet
else if (spreader->IsNPC() && spreader->IsPet() && spreader->GetOwner()->IsNPC()) {
if (cur->IsNPC() && !cur->IsPet()) {
TargetsInRange.push_back(cur);
} else if (cur->IsNPC() && cur->IsPet() && cur->GetOwner()->IsNPC()) {
TargetsInRange.push_back(cur);
//For non-NPCs, do not allow beneficial spread to anything the original caster could normally attack.
if (!is_detrimental_spell && !original_caster->IsNPC() && original_caster->IsAttackAllowed(mob, true)) {
continue;
}
// If the spreader is an npc and NOT a PET, then spread to other npc controlled mobs that are not pets
if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && !mob->IsPet() && !mob->IsTempPet()) {
spreader_list.push_back(mob);
}
// If the spreader is an npc and NOT a PET, then spread to npc controlled pet
else if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
// If the spreader is an npc controlled PET it can spread to any other npc or an npc controlled pet
else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && spreader->IsPetOwnerNPC()) {
if (mob->IsNPC() && (!mob->IsPet() || !mob->IsTempPet())) {
spreader_list.push_back(mob);
}
else if (cur->IsNPC() && cur->CastToNPC()->GetSwarmOwner() && cur->GetOwner()->IsNPC()) {
TargetsInRange.push_back(cur);
else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
}
// if the spreader is anything else(bot, pet, etc) then it should spread to everything but non client controlled npcs
else if (!spreader->IsNPC() && !cur->IsNPC()) {
TargetsInRange.push_back(cur);
else if (!spreader->IsNPC() && !mob->IsNPC()) {
spreader_list.push_back(mob);
}
// if its a pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc)
else if (spreader->IsNPC() && (spreader->IsPet() || spreader->CastToNPC()->GetSwarmOwner()) && !spreader->GetOwner()->IsNPC()) {
if (!cur->IsNPC()) {
TargetsInRange.push_back(cur);
// if spreader is not an NPC, and Target is an NPC, then spread to non-NPC controlled pets
else if (!spreader->IsNPC() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
// if spreader is a non-NPC controlled pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc)
else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && !spreader->IsPetOwnerNPC()) {
//Spread to non-NPCs
if (!mob->IsNPC()) {
spreader_list.push_back(mob);
}
else if (cur->IsNPC() && (cur->IsPet() || cur->CastToNPC()->GetSwarmOwner()) && !cur->GetOwner()->IsNPC()) {
TargetsInRange.push_back(cur);
//Spread to other non-NPC Pets
else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
}
}
++it;
}
if(TargetsInRange.empty())
return nullptr;
return TargetsInRange[zone->random.Int(0, TargetsInRange.size() - 1)];
return spreader_list;
}
void EntityList::StopMobAI()

View File

@ -160,7 +160,6 @@ public:
bool IsMobSpawnedByNpcTypeID(uint32 get_id);
bool IsNPCSpawned(std::vector<uint32> npc_ids);
uint32 CountSpawnedNPCs(std::vector<uint32> npc_ids);
Mob *GetTargetForVirus(Mob* spreader, int range);
inline NPC *GetNPCByID(uint16 id)
{
auto it = npc_list.find(id);
@ -512,6 +511,7 @@ public:
void GetDoorsList(std::list<Doors*> &d_list);
void GetSpawnList(std::list<Spawn2*> &d_list);
void GetTargetsForConeArea(Mob *start, float min_radius, float radius, float height, int pcnpc, std::list<Mob*> &m_list);
std::vector<Mob*> GetTargetsForVirusEffect(Mob *spreader, Mob *orginal_caster, int range, int pcnpc, int32 spell_id);
inline const std::unordered_map<uint16, Mob *> &GetMobList() { return mob_list; }
inline const std::unordered_map<uint16, NPC *> &GetNPCList() { return npc_list; }

View File

@ -367,6 +367,7 @@ Mob::Mob(
pet_regroup = false;
_IsTempPet = false;
pet_owner_client = false;
pet_owner_npc = false;
pet_targetlock_id = 0;
attacked_count = 0;
@ -405,10 +406,6 @@ Mob::Mob(
roamer = false;
rooted = false;
charmed = false;
has_virus = false;
for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i++) {
viral_spells[i] = 0;
}
weaponstance.enabled = false;
weaponstance.spellbonus_enabled = false; //Set when bonus is applied
@ -4682,28 +4679,6 @@ void Mob::DoGravityEffect()
}
}
void Mob::SpreadVirus(uint16 spell_id, uint16 casterID)
{
int num_targs = spells[spell_id].viral_targets;
Mob* caster = entity_list.GetMob(casterID);
Mob* target = nullptr;
// Only spread in zones without perm buffs
if(!zone->BuffTimersSuspended()) {
for(int i = 0; i < num_targs; i++) {
target = entity_list.GetTargetForVirus(this, spells[spell_id].viral_range);
if(target) {
// Only spreads to the uninfected
if(!target->FindBuff(spell_id)) {
if(caster)
caster->SpellOnTarget(spell_id, target);
}
}
}
}
}
void Mob::AddNimbusEffect(int effectid)
{
SetNimbusEffect(effectid);

View File

@ -333,8 +333,8 @@ public:
uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1);
void SendBeginCast(uint16 spell_id, uint32 casttime);
virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, int reflect_effectiveness = 0,
bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1);
virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1, int reflect_effectiveness = 0);
bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, int32 duration_override = 0);
virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1, int reflect_effectiveness = 0, int32 duration_override = 0);
virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center,
CastAction_type &CastAction, EQ::spells::CastingSlot slot, bool isproc = false);
virtual bool CheckFizzle(uint16 spell_id);
@ -407,7 +407,6 @@ public:
inline void SetMGB(bool val) { has_MGB = val; }
bool HasProjectIllusion() const { return has_ProjectIllusion ; }
inline void SetProjectIllusion(bool val) { has_ProjectIllusion = val; }
void SpreadVirus(uint16 spell_id, uint16 casterID);
bool IsNimbusEffectActive(uint32 nimbus_effect);
void SetNimbusEffect(uint32 nimbus_effect);
inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; }
@ -861,6 +860,9 @@ public:
void FocusProcLimitProcess();
bool ApplyFocusProcLimiter(int32 spell_id, int buffslot = -1);
void VirusEffectProcess();
void SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining);
void ModSkillDmgTaken(EQ::skills::SkillType skill_num, int value);
int16 GetModSkillDmgTaken(const EQ::skills::SkillType skill_num);
void ModVulnerability(uint8 resist, int16 value);
@ -929,6 +931,8 @@ public:
bool HasPetAffinity() { if (aabonuses.GivePetGroupTarget || itembonuses.GivePetGroupTarget || spellbonuses.GivePetGroupTarget) return true; return false; }
inline bool IsPetOwnerClient() const { return pet_owner_client; }
inline void SetPetOwnerClient(bool value) { pet_owner_client = value; }
inline bool IsPetOwnerNPC() const { return pet_owner_npc; }
inline void SetPetOwnerNPC(bool value) { pet_owner_npc = value; }
inline bool IsTempPet() const { return _IsTempPet; }
inline void SetTempPet(bool value) { _IsTempPet = value; }
inline bool IsHorse() { return is_horse; }
@ -1038,7 +1042,6 @@ public:
inline const bool IsRoamer() const { return roamer; }
inline const int GetWanderType() const { return wandertype; }
inline const bool IsRooted() const { return rooted || permarooted; }
inline const bool HasVirus() const { return has_virus; }
int GetSnaredAmount();
inline const bool IsPseudoRooted() const { return pseudo_rooted; }
inline void SetPseudoRoot(bool prState) { pseudo_rooted = prState; }
@ -1122,7 +1125,7 @@ public:
void SendItemAnimation(Mob *to, const EQ::ItemData *item, EQ::skills::SkillType skillInUse, float velocity = 4.0);
inline int& GetNextIncHPEvent() { return nextinchpevent; }
void SetNextIncHPEvent( int inchpevent );
inline bool DivineAura() const { return spellbonuses.DivineAura; }
inline bool Sanctuary() const { return spellbonuses.Sanctuary; }
@ -1522,8 +1525,6 @@ protected:
bool silenced;
bool amnesiad;
bool inWater; // Set to true or false by Water Detection code if enabled by rules
bool has_virus; // whether this mob has a viral spell on them
uint16 viral_spells[MAX_SPELL_TRIGGER*2]; // Stores the spell ids of the viruses on target and caster ids
bool offhand;
bool has_shieldequiped;
bool has_twohandbluntequiped;
@ -1636,6 +1637,7 @@ protected:
bool _IsTempPet;
int16 count_TempPet;
bool pet_owner_client; //Flags regular and pets as belonging to a client
bool pet_owner_npc; //Flags regular and pets as belonging to a npc
uint32 pet_targetlock_id;
glm::vec3 m_TargetRing;

View File

@ -928,19 +928,8 @@ bool NPC::Process()
SendHPUpdate();
}
if(HasVirus()) {
if(viral_timer.Check()) {
viral_timer_counter++;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) {
if(viral_spells[i] && spells[viral_spells[i]].viral_timer > 0) {
if(viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) {
SpreadVirus(viral_spells[i], viral_spells[i+1]);
}
}
}
}
if(viral_timer_counter > 999)
viral_timer_counter = 0;
if (viral_timer.Check()) {
VirusEffectProcess();
}
if(spellbonuses.GravityEffect == 1) {
@ -2339,6 +2328,10 @@ void NPC::PetOnSpawn(NewSpawn_Struct* ns)
strn0cpy(ns->spawn.lastName, tmp_lastname.c_str(), sizeof(ns->spawn.lastName));
}
}
if (swarmOwner->IsNPC()) {
SetPetOwnerNPC(true);
}
}
else if(GetOwnerID())
{
@ -2354,6 +2347,13 @@ void NPC::PetOnSpawn(NewSpawn_Struct* ns)
if (tmp_lastname.size() < sizeof(ns->spawn.lastName))
strn0cpy(ns->spawn.lastName, tmp_lastname.c_str(), sizeof(ns->spawn.lastName));
}
else
{
if (entity_list.GetNPCByID(GetOwnerID()))
{
SetPetOwnerNPC(true);
}
}
}
}
else

View File

@ -46,7 +46,7 @@ extern WorldServer worldserver;
// the spell can still fail here, if the buff can't stack
// in this case false will be returned, true otherwise
bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override, int reflect_effectiveness)
bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override, int reflect_effectiveness, int32 duration_override)
{
int caster_level, buffslot, effect, effect_value, i;
EQ::ItemInstance *SummonedItem=nullptr;
@ -119,7 +119,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
}
else
{
buffslot = AddBuff(caster, spell_id);
buffslot = AddBuff(caster, spell_id, duration_override);
}
if(buffslot == -1) // stacking failure
return false;
@ -168,7 +168,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
caster ? caster->GetLevel() : 0,
buffslot
);
if (IsClient()) {
if (parse->EventSpell(EVENT_SPELL_EFFECT_CLIENT, nullptr, CastToClient(), spell_id, buf, 0) != 0) {
CalcBonuses();
@ -181,20 +181,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
}
}
if(spells[spell_id].viral_targets > 0) {
if(!viral_timer.Enabled())
viral_timer.Start(1000);
if(IsVirusSpell(spell_id)) {
has_virus = true;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2)
{
if(!viral_spells[i])
{
viral_spells[i] = spell_id;
viral_spells[i+1] = caster->GetID();
break;
}
if (!viral_timer.Enabled()) {
viral_timer.Start(1000);
}
buffs[buffslot].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(spell_id), GetViralMaxSpreadTime(spell_id));
}
@ -1091,8 +1083,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
break;
}
/*
TODO: Parsing shows there is no level modifier. However, a consistent -2% modifer was
found on spell with value 950 (95% spells would have 7% failure rates).
TODO: Parsing shows there is no level modifier. However, a consistent -2% modifer was
found on spell with value 950 (95% spells would have 7% failure rates).
Further investigation is needed. ~ Kayen
*/
int chance = spells[spell_id].base[i];
@ -1121,7 +1113,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
caster->MessageString(Chat::SpellFailure, SPELL_NO_EFFECT, spells[spell_id].name);
break;
}
int chance = spells[spell_id].base[i];
int buff_count = GetMaxTotalSlots();
for(int slot = 0; slot < buff_count; slot++) {
@ -1473,7 +1465,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
spell.base[i],
gender_id
);
if (spell.max[i] > 0) {
if (spell.base2[i] == 0) {
SendIllusionPacket(
@ -1505,7 +1497,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
spell.max[i]
);
}
SendAppearancePacket(AT_Size, race_size);
SendAppearancePacket(AT_Size, race_size);
}
for (int x = EQ::textures::textureBegin; x <= EQ::textures::LastTintableTexture; x++) {
@ -1549,9 +1541,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
if (!CanMemoryBlurFromMez && IsEffectInSpell(spell_id, SE_Mez)) {
break;
}
int wipechance = 0;
if (caster) {
wipechance = caster->GetMemoryBlurChance(effect_value);
}
@ -3360,7 +3352,7 @@ int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level,
effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining);
// this doesn't actually need to be a song to get mods, just the right skill
if (EQ::skills::IsBardInstrumentSkill(spells[spell_id].skill)
if (EQ::skills::IsBardInstrumentSkill(spells[spell_id].skill)
&& IsInstrumentModAppliedToSpellEffect(spell_id, spells[spell_id].effectid[effect_id])){
@ -3770,7 +3762,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
switch (effect) {
case SE_CurrentHP: {
if (spells[buff.spellid].base2[i] && !PassCastRestriction(spells[buff.spellid].base2[i])) {
break;
}
@ -4086,23 +4078,6 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses)
LogSpells("Fading buff [{}] from slot [{}]", buffs[slot].spellid, slot);
if(spells[buffs[slot].spellid].viral_targets > 0) {
bool last_virus = true;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2)
{
if(viral_spells[i] && viral_spells[i] != buffs[slot].spellid)
{
// If we have a virus that doesn't match this one then don't stop the viral timer
last_virus = false;
}
}
// This is the last virus on us so lets stop timer
if(last_virus) {
viral_timer.Disable();
has_virus = false;
}
}
std::string buf = fmt::format(
"{} {} {} {}",
buffs[slot].casterid,
@ -7168,7 +7143,7 @@ bool Mob::PassCastRestriction(int value)
Note: (ID 221 - 249) For effect seen in mage spell 'Shock of Many' which increases damage based on number of pets on targets hatelist. The way it is implemented
works for how our ROF2 spell file handles the effect where each slot fires individually, while on live it only takes the highest
value. In the future the way check is done will need to be adjusted to check a defined range instead of just great than.
value. In the future the way check is done will need to be adjusted to check a defined range instead of just great than.
*/
if (value <= 0) {
@ -7202,8 +7177,8 @@ bool Mob::PassCastRestriction(int value)
break;
case IS_BODY_TYPE_MISC:
if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) ||
(GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal)||
if ((GetBodyType() == BT_Humanoid) || (GetBodyType() == BT_Lycanthrope) || (GetBodyType() == BT_Giant) ||
(GetBodyType() == BT_RaidGiant) || (GetBodyType() == BT_RaidColdain) || (GetBodyType() == BT_Animal)||
(GetBodyType() == BT_Construct) || (GetBodyType() == BT_Dragon) || (GetBodyType() == BT_Insect)||
(GetBodyType() == BT_VeliousDragon) || (GetBodyType() == BT_Muramite) || (GetBodyType() == BT_Magical))
return true;
@ -7342,7 +7317,7 @@ bool Mob::PassCastRestriction(int value)
if (IsHybridClass(GetClass()))
return true;
break;
case IS_CLASS_WARRIOR:
if (GetClass() == WARRIOR)
return true;
@ -7449,7 +7424,7 @@ bool Mob::PassCastRestriction(int value)
return true;
break;
case FRENZIED_BURNOUT_NOT_ACTIVE:
case FRENZIED_BURNOUT_NOT_ACTIVE:
if (!HasBuffWithSpellGroup(SPELLGROUP_FRENZIED_BURNOUT))
return true;
break;
@ -7458,7 +7433,7 @@ bool Mob::PassCastRestriction(int value)
if (GetHPRatio() > 75)
return true;
break;
case IS_HP_LESS_THAN_20_PCT:
if (GetHPRatio() <= 20)
return true;
@ -7605,27 +7580,27 @@ bool Mob::PassCastRestriction(int value)
if (GetHPRatio() > 25 && GetHPRatio() <= 35)
return true;
break;
case IS_HP_BETWEEN_35_AND_45_PCT:
if (GetHPRatio() > 35 && GetHPRatio() <= 45)
return true;
break;
case IS_HP_BETWEEN_45_AND_55_PCT:
if (GetHPRatio() > 45 && GetHPRatio() <= 55)
return true;
break;
case IS_HP_BETWEEN_55_AND_65_PCT:
if (GetHPRatio() > 55 && GetHPRatio() <= 65)
return true;
break;
case IS_HP_BETWEEN_65_AND_75_PCT:
if (GetHPRatio() > 65 && GetHPRatio() <= 75)
return true;
break;
case IS_HP_BETWEEN_75_AND_85_PCT:
if (GetHPRatio() > 75 && GetHPRatio() <= 85)
return true;
@ -7635,7 +7610,7 @@ bool Mob::PassCastRestriction(int value)
if (GetHPRatio() > 85 && GetHPRatio() <= 95)
return true;
break;
case IS_HP_ABOVE_45_PCT:
if (GetHPRatio() > 45)
return true;
@ -7675,7 +7650,7 @@ bool Mob::PassCastRestriction(int value)
if (GetBodyType() != BT_Plant)
return true;
break;
case IS_NOT_CLIENT:
if (!IsClient())
return true;
@ -7689,8 +7664,8 @@ bool Mob::PassCastRestriction(int value)
case IS_LEVEL_ABOVE_42_AND_IS_CLIENT:
if (IsClient() && GetLevel() > 42)
return true;
break;
break;
case IS_TREANT:
if (GetRace() == RT_TREANT || GetRace() == RT_TREANT_2 || GetRace() == RT_TREANT_3)
return true;
@ -7878,7 +7853,7 @@ bool Mob::PassCastRestriction(int value)
}
break;
}
case IS_CLIENT_AND_MALE_PLATE_USER:
if (IsClient() && GetGender() == MALE && IsPlateClass(GetClass()))
return true;
@ -7890,7 +7865,7 @@ bool Mob::PassCastRestriction(int value)
break;
case IS_CLIENT_AND_MALE_BEASTLORD_BERSERKER_MONK_RANGER_OR_ROGUE:
if (IsClient() && GetGender() == MALE &&
if (IsClient() && GetGender() == MALE &&
(GetClass() == BEASTLORD || GetClass() == BERSERKER || GetClass() == MONK || GetClass() == RANGER || GetClass() == ROGUE))
return true;
break;
@ -7967,7 +7942,7 @@ bool Mob::PassCastRestriction(int value)
}
break;
}
case IS_NOT_CLASS_BARD:
if (GetClass() != BARD)
return true;
@ -8012,7 +7987,7 @@ bool Mob::PassCastRestriction(int value)
if (FindBuff(SPELL_INCENDIARY_OOZE_BUFF))
return true;
break;
//Not handled, just allow them to pass for now.
case UNKNOWN_3:
case HAS_CRYSTALLIZED_FLAME_BUFF:
@ -8595,7 +8570,7 @@ int Mob::GetMemoryBlurChance(int base_chance)
*/
int cha_mod = int(GetCHA() / 10);
cha_mod = std::min(cha_mod, 15);
int lvl_mod = 0;
if (GetLevel() < 17) {
lvl_mod = 100;
@ -8614,3 +8589,76 @@ int Mob::GetMemoryBlurChance(int base_chance)
chance += chance * chance_mod / 100;
return chance;
}
void Mob::VirusEffectProcess()
{
/*
Virus Mechanics
To qualify as a virus effect buff, all of the following spell table need to be set. (At some point will correct names)
viral_targets = MIN_SPREAD_TIME
viral_timer = MAX_SPREAD_TIME
viral_range = SPREAD_RADIUS
Once a buff with a viral effect is applied, a 1000 ms timer will begin.
The time at which the virus will attempt to spread is determined by a random value between MIN_SPREAD_TIME and MAX_SPREAD_TIME
Each time the virus attempts to spread the next time interval will be chosen at random again.
If a spreader finds a target for viral buff, when the viral buff spreads the duration on the new target will be the time remaining on the spreaders buff.
Spreaders DOES NOT need LOS to spread. There is no max amount of targets the virus can spread to.
When the spreader no longer has any viral buffs the timer stops.
The current code supports spreading for both detrimental and beneficial spells.
*/
// Only spread in zones without perm buffs
if (zone->BuffTimersSuspended()) {
viral_timer.Disable();
return;
}
bool stop_timer = true;
for (int buffs_i = 0; buffs_i < GetMaxTotalSlots(); ++buffs_i)
{
if (IsValidSpell(buffs[buffs_i].spellid) && IsVirusSpell(buffs[buffs_i].spellid))
{
if (buffs[buffs_i].virus_spread_time > 0) {
buffs[buffs_i].virus_spread_time -= 1;
stop_timer = false;
}
if (buffs[buffs_i].virus_spread_time <= 0) {
buffs[buffs_i].virus_spread_time = zone->random.Int(GetViralMinSpreadTime(buffs[buffs_i].spellid), GetViralMaxSpreadTime(buffs[buffs_i].spellid));
SpreadVirusEffect(buffs[buffs_i].spellid, buffs[buffs_i].casterid, buffs[buffs_i].ticsremaining);
stop_timer = false;
}
}
}
if (stop_timer) {
viral_timer.Disable();
}
}
void Mob::SpreadVirusEffect(int32 spell_id, uint32 caster_id, int32 buff_tics_remaining)
{
Mob *caster = entity_list.GetMob(caster_id);
std::vector<Mob *> targets_in_range = entity_list.GetTargetsForVirusEffect(
this,
caster,
GetViralSpreadRange(spell_id),
spells[spell_id].pcnpc_only_flag,
spell_id
);
for (auto &mob: targets_in_range) {
if (!mob) {
continue;
}
if (!mob->FindBuff(spell_id)) {
if (caster) {
if (buff_tics_remaining) {
//When virus is spread, the buff on new target is applied with the amount of time remaining on the spreaders buff.
caster->SpellOnTarget(spell_id, mob, 0, false, 0, false, -1, buff_tics_remaining);
}
}
}
}
}

View File

@ -3423,6 +3423,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
buffs[emptyslot].RootBreakChance = 0;
buffs[emptyslot].focusproclimit_time = 0;
buffs[emptyslot].focusproclimit_procamt = 0;
buffs[emptyslot].virus_spread_time = 0;
buffs[emptyslot].instrument_mod = caster ? caster->GetInstrumentMod(spell_id) : 10;
if (level_override > 0 || buffs[emptyslot].numhits > 0) {
@ -3533,9 +3534,8 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite)
// break stuff
//
bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectiveness, bool use_resist_adjust, int16 resist_adjust,
bool isproc, int level_override)
bool isproc, int level_override, int32 duration_override)
{
bool is_damage_or_lifetap_spell = IsDamageSpell(spell_id) || IsLifetapSpell(spell_id);
// well we can't cast a spell on target without a target
@ -4078,7 +4078,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes
}
// cause the effects to the target
if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness, level_override, reflect_effectiveness))
if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness, level_override, reflect_effectiveness, duration_override))
{
// if SpellEffect returned false there's a problem applying the
// spell. It's most likely a buff that can't stack.

View File

@ -3672,6 +3672,7 @@ void ZoneDatabase::LoadBuffs(Client *client)
buffs[slot_id].RootBreakChance = 0;
buffs[slot_id].focusproclimit_time = 0;
buffs[slot_id].focusproclimit_procamt = 0;
buffs[slot_id].virus_spread_time = 0;
buffs[slot_id].UpdateClient = false;
buffs[slot_id].instrument_mod = instrument_mod;
}