diff --git a/changelog.txt b/changelog.txt index 0614ff4a8..b561c34ab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 7/1/2017 == +Akkadius: Resolve issues with NPC's hopping to the ceiling in small corridors +Akkadius: Improved grounding issues with NPC's during combat +Akkadius: Improved scenarios where NPC's need to be dragged out of the ground - they should correct themselves far more consistently + - Scenarios where an NPC is coming up from the bottom floor, or from the top floor, they will correct much better + - A video of these tests can be found here: https://www.youtube.com/watch?v=HtC7bVNM7ZQ&feature=youtu.be + == 6/28/2017 == Akkadius: Fixed issues with Z correctness when NPCs are pathing on normal grids Akkadius: Fixed issues with Z correctness when NPCs are engaged with players following diff --git a/common/ruletypes.h b/common/ruletypes.h index 945c2625a..c5fc1b9ce 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -275,6 +275,7 @@ RULE_BOOL(Map, FixPathingZAtWaypoints, false) //alternative to `WhenLoading`, ac RULE_BOOL(Map, FixPathingZWhenMoving, false) //very CPU intensive, but helps hopping with widely spaced waypoints. RULE_BOOL(Map, FixPathingZOnSendTo, false) //try to repair Z coords in the SendTo routine as well. RULE_BOOL(Map, FixZWhenMoving, true) // Automatically fix NPC Z coordinates when moving/pathing/engaged (Far less CPU intensive than its predecessor) +RULE_BOOL(Map, MobZVisualDebug, false) // Displays spell effects determining whether or not NPC is hitting Best Z calcs (blue for hit, red for miss) RULE_REAL(Map, FixPathingZMaxDeltaMoving, 20) //at runtime while pathing: max change in Z to allow the BestZ code to apply. RULE_REAL(Map, FixPathingZMaxDeltaWaypoint, 20) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply. RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20) //at runtime in SendTo: max change in Z to allow the BestZ code to apply. @@ -349,6 +350,7 @@ RULE_INT(Spells, MaxTotalSlotsNPC, 60) // default to Tit's limit RULE_INT(Spells, MaxTotalSlotsPET, 30) // default to Tit's limit RULE_BOOL (Spells, EnableBlockedBuffs, true) RULE_INT(Spells, ReflectType, 3) //0 = disabled, 1 = single target player spells only, 2 = all player spells, 3 = all single target spells, 4 = all spells +RULE_BOOL(Spells, ReflectMessagesClose, true) // Live functionality is for Reflect messages to show to players within close proximity, false shows just player reflecting RULE_INT(Spells, VirusSpreadDistance, 30) // The distance a viral spell will jump to its next victim RULE_BOOL(Spells, LiveLikeFocusEffects, true) // Determines whether specific healing, dmg and mana reduction focuses are randomized RULE_INT(Spells, BaseImmunityLevel, 55) // The level that targets start to be immune to stun, fear and mez spells with a max level of 0. diff --git a/zone/attack.cpp b/zone/attack.cpp index 04cb65c9a..819623148 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2388,12 +2388,6 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil entity_list.UnMarkNPC(GetID()); entity_list.RemoveNPC(GetID()); - /* Fix Z on Corpse Creation */ - glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); - float new_z = zone->zonemap->FindBestZ(dest, nullptr); - corpse->SetFlyMode(1); - corpse->GMMove(m_Position.x, m_Position.y, new_z + 5, m_Position.w); - this->SetID(0); if (killer != 0 && emoteid != 0) diff --git a/zone/beacon.cpp b/zone/beacon.cpp index e5ea9f38f..36075dca5 100644 --- a/zone/beacon.cpp +++ b/zone/beacon.cpp @@ -96,7 +96,9 @@ bool Beacon::Process() Mob *caster = entity_list.GetMob(caster_id); if(caster && spell_iterations-- && max_targets) { - bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()); //NPC AE spells do not affect the NPC caster + // NPCs should never be affected by an AE they cast. PB AEs shouldn't affect caster either + // I don't think any other cases that get here matter + bool affect_caster = (!caster->IsNPC() && !caster->IsAIControlled()) && spells[spell_id].targettype != ST_AECaster; entity_list.AESpell(caster, this, spell_id, affect_caster, resist_adjust, &max_targets); } else diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 232b74b2a..22a323a81 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -48,6 +48,14 @@ void Mob::CalcBonuses() SetAttackTimer(); CalcAC(); + /* 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 + */ + float get_walk_speed = static_cast(0.025f * this->GetWalkspeed()); + if (get_walk_speed >= 0.9 && this->fix_z_timer.GetDuration() != 100) { + this->fix_z_timer.SetTimer(100); + } + rooted = FindType(SE_Root); } diff --git a/zone/client.cpp b/zone/client.cpp index cecd22b45..85956ea08 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -4242,7 +4242,7 @@ bool Client::GroupFollow(Client* inviter) { RemoveAutoXTargets(); } - SetXTargetAutoMgr(GetXTargetAutoMgr()); + SetXTargetAutoMgr(raid->GetXTargetAutoMgr()); if (!GetXTargetAutoMgr()->empty()) SetDirtyAutoHaters(); diff --git a/zone/command.cpp b/zone/command.cpp index a80a0481e..b6c79e235 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -173,6 +173,7 @@ int command_init(void) command_add("checklos", "- Check for line of sight to your target", 50, command_checklos) || command_add("clearinvsnapshots", "[use rule] - Clear inventory snapshot history (true - elapsed entries, false - all entries)", 200, command_clearinvsnapshots) || command_add("corpse", "- Manipulate corpses, use with no arguments for help", 50, command_corpse) || + command_add("corpsefix", "Attempts to bring corpses from underneath the ground within close proximity of the player", 0, command_corpsefix) || command_add("crashtest", "- Crash the zoneserver", 255, command_crashtest) || command_add("cvs", "- Summary of client versions currently online.", 200, command_cvs) || command_add("damage", "[amount] - Damage your target", 100, command_damage) || @@ -2977,6 +2978,11 @@ void command_reloadqst(Client *c, const Seperator *sep) } +void command_corpsefix(Client *c, const Seperator *sep) +{ + entity_list.CorpseFix(c); +} + void command_reloadworld(Client *c, const Seperator *sep) { c->Message(0, "Reloading quest cache and repopping zones worldwide."); diff --git a/zone/command.h b/zone/command.h index 987adf674..0a850fbca 100644 --- a/zone/command.h +++ b/zone/command.h @@ -72,6 +72,7 @@ void command_checklos(Client *c, const Seperator *sep); void command_clearinvsnapshots(Client *c, const Seperator *sep); void command_connectworldserver(Client *c, const Seperator *sep); void command_corpse(Client *c, const Seperator *sep); +void command_corpsefix(Client *c, const Seperator *sep); void command_crashtest(Client *c, const Seperator *sep); void command_cvs(Client *c, const Seperator *sep); void command_d1(Client *c, const Seperator *sep); diff --git a/zone/effects.cpp b/zone/effects.cpp index 95eb44ac8..adc7b16b2 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -730,8 +730,6 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ // test to fix possible cause of random zone crashes..external methods accessing client properties before they're initialized if (curmob->IsClient() && !curmob->CastToClient()->ClientFinishedLoading()) continue; - if (curmob == center) //do not affect center - continue; if (curmob == caster && !affect_caster) //watch for caster too continue; if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && curmob->IsPetOwnerClient()) diff --git a/zone/entity.cpp b/zone/entity.cpp index 5a8e9fa5e..35d67a5d3 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2845,6 +2845,22 @@ int32 EntityList::DeleteNPCCorpses() return x; } +void EntityList::CorpseFix(Client* c) +{ + + auto it = corpse_list.begin(); + while (it != corpse_list.end()) { + Corpse* corpse = it->second; + if (corpse->IsNPCCorpse()) { + if (DistanceNoZ(c->GetPosition(), corpse->GetPosition()) < 100) { + c->Message(15, "Attempting to fix %s", it->second->GetCleanName()); + corpse->GMMove(corpse->GetX(), corpse->GetY(), c->GetZ() + 2, 0); + } + } + ++it; + } +} + // returns the number of corpses deleted. A negative number indicates an error code. int32 EntityList::DeletePlayerCorpses() { diff --git a/zone/entity.h b/zone/entity.h index e2224d05d..7752e97b1 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -386,6 +386,7 @@ public: void FindPathsToAllNPCs(); int32 DeleteNPCCorpses(); int32 DeletePlayerCorpses(); + void CorpseFix(Client* c); void WriteEntityIDs(); void HalveAggro(Mob* who); void DoubleAggro(Mob* who); diff --git a/zone/mob.cpp b/zone/mob.cpp index c411f6a3c..7e04221c3 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -113,7 +113,7 @@ Mob::Mob(const char* in_name, tmHidden(-1), mitigation_ac(0), m_specialattacks(eSpecialAttacks::None), - fix_z_timer(1000), + fix_z_timer(300), fix_z_timer_engaged(100) { targeted = 0; @@ -121,6 +121,8 @@ Mob::Mob(const char* in_name, tar_vector=0; currently_fleeing = false; + last_z = 0; + AI_Init(); SetMoving(false); moved=false; diff --git a/zone/mob.h b/zone/mob.h index 109cd3656..aa4b3fb29 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -492,6 +492,7 @@ public: inline const float GetTarVZ() const { return m_TargetV.z; } inline const float GetTarVector() const { return tar_vector; } inline const uint8 GetTarNDX() const { return tar_ndx; } + inline const int8 GetFlyMode() const { return flymode; } bool IsBoat() const; //Group @@ -1067,6 +1068,8 @@ public: int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); + float last_z; + // Bots HealRotation methods #ifdef BOTS bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 20d22c42f..6b3be34d8 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -998,9 +998,16 @@ void Mob::AI_Process() { /* Fix Z when following during pull, not when engaged and stationary */ if (moving && fix_z_timer_engaged.Check()) - if(this->GetTarget()) - if(DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition()) > 50) + if (this->GetTarget()) { + /* If we are engaged, moving and following client, let's look for best Z more often */ + if (DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition()) > 50) { this->FixZ(); + } + /* If we are close to client and our Z differences aren't big, match the client */ + else if (std::abs(this->GetZ() - this->GetTarget()->GetZ()) <= 5 && this->GetTarget()->IsClient()) { + this->m_Position.z = this->GetTarget()->GetZ(); + } + } if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) SendAddPlayerState(PlayerState::Aggressive); diff --git a/zone/spells.cpp b/zone/spells.cpp index 5504422e0..58121a760 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2241,7 +2241,9 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui if(ae_center && ae_center == this && IsBeneficialSpell(spell_id)) SpellOnTarget(spell_id, this); - bool affect_caster = !IsNPC(); //NPC AE spells do not affect the NPC caster + // NPCs should never be affected by an AE they cast. PB AEs shouldn't affect caster either + // I don't think any other cases that get here matter + bool affect_caster = !IsNPC() && spells[spell_id].targettype != ST_AECaster; if (spells[spell_id].targettype == ST_AETargetHateList) hate_list.SpellCast(this, spell_id, spells[spell_id].aoerange, ae_center); @@ -3804,8 +3806,22 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r break; } if (reflect_chance) { - entity_list.MessageClose_StringID(this, false, RuleI(Range, SpellMessages), MT_Spells, - SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + + if (RuleB(Spells, ReflectMessagesClose)) { + entity_list.MessageClose_StringID( + this, /* Sender */ + false, /* Skip Sender */ + RuleI(Range, SpellMessages), /* Range */ + MT_Spells, /* Type */ + SPELL_REFLECT, /* String ID */ + GetCleanName(), /* Message 1 */ + spelltar->GetCleanName() /* Message 2 */ + ); + } + else { + Message_StringID(MT_Spells, SPELL_REFLECT, GetCleanName(), spelltar->GetCleanName()); + } + CheckNumHitsRemaining(NumHit::ReflectSpell); // caster actually appears to change // ex. During OMM fight you click your reflect mask and you get the recourse from the reflected diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 6ceee481e..9d272472f 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -212,22 +212,6 @@ void NPC::UpdateWaypoint(int wp_index) cur_wp_pause = cur->pause; Log(Logs::Detail, Logs::AI, "Next waypoint %d: (%.3f, %.3f, %.3f, %.3f)", wp_index, m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z, m_CurrentWayPoint.w); - //fix up pathing Z - if (zone->HasMap() && RuleB(Map, FixPathingZAtWaypoints) && !IsBoat()) - { - - if (!RuleB(Watermap, CheckForWaterAtWaypoints) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_CurrentWayPoint)))) - { - glm::vec3 dest(m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z); - - float newz = zone->zonemap->FindBestZ(dest, nullptr); - - if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaWaypoint)) - m_CurrentWayPoint.z = newz + 1; - } - } - } void NPC::CalculateNewWaypoint() @@ -780,20 +764,6 @@ void NPC::AssignWaypoints(int32 grid) newwp.y = atof(row[1]); newwp.z = atof(row[2]); - if (zone->HasMap() && RuleB(Map, FixPathingZWhenLoading)) - { - auto positon = glm::vec3(newwp.x, newwp.y, newwp.z); - if (!RuleB(Watermap, CheckWaypointsInWaterWhenLoading) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(positon))) - { - glm::vec3 dest(newwp.x, newwp.y, newwp.z); - float newz = zone->zonemap->FindBestZ(dest, nullptr); - - if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaLoading)) - newwp.z = newz + 1; - } - } - newwp.pause = atoi(row[3]); newwp.heading = atof(row[4]); Waypoints.push_back(newwp); @@ -870,6 +840,7 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } void Mob::FixZ() { + BenchTimer timer; timer.reset(); @@ -878,9 +849,8 @@ void Mob::FixZ() { if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) { - glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); - - float new_z = zone->zonemap->FindBestZ(dest, nullptr); + /* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */ + float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5); auto duration = timer.elapsed(); @@ -896,8 +866,20 @@ void Mob::FixZ() { duration ); - if ((new_z > -2000) && std::abs(new_z - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) - m_Position.z = new_z + 1; + if ((new_z > -2000) && std::abs(m_Position.z - new_z) < 35) { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(78, 0, 0, 0, 0); + + m_Position.z = new_z; + } + else { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(103, 0, 0, 0, 0); + + Log(Logs::General, Logs::Debug, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z)); + } + + last_z = m_Position.z; } } }