mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 12:41:30 +00:00
[Improvement] Flee Overhaul (#4407)
* Lots of flee updates primarily based on TAKPs source * Update Values to EQEmu values. * Add rule * Adjustments to fear pathing * Flee/Pathing adjustments (More TAKP code adjusted) * updates * Updates (Massaged functions from TAKP source) --------- Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
This commit is contained in:
parent
e49ab924cc
commit
2ef959c5ed
@ -699,6 +699,7 @@ RULE_BOOL(Aggro, UndeadAlwaysAggro, true, "should undead always aggro?")
|
||||
RULE_INT(Aggro, BardAggroCap, 40, "per song bard aggro cap.")
|
||||
RULE_INT(Aggro, InitialAggroBonus, 100, "Initial Aggro Bonus, Default: 100")
|
||||
RULE_INT(Aggro, InitialPetAggroBonus, 100, "Initial Pet Aggro Bonus, Default 100")
|
||||
RULE_STRING(Aggro, ExcludedFleeAllyFactionIDs, "0|5013|5014|5023|5032", "Common Faction IDs that are excluded from faction checks in EntityList::FleeAllyCount")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(TaskSystem)
|
||||
|
||||
@ -582,6 +582,76 @@ bool Mob::CheckWillAggro(Mob *mob) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int EntityList::FleeAllyCount(Mob* attacker, Mob* skipped)
|
||||
{
|
||||
// Return a list of how many NPCs of the same faction or race are within aggro range of the given exclude Mob.
|
||||
if (!attacker) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (const auto& e : npc_list) {
|
||||
NPC* n = e.second;
|
||||
if (!n || n == skipped) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float aggro_range = n->GetAggroRange();
|
||||
const float assist_range = n->GetAssistRange();
|
||||
|
||||
if (assist_range > aggro_range) {
|
||||
aggro_range = assist_range;
|
||||
}
|
||||
|
||||
// Square it because we will be using DistNoRoot
|
||||
aggro_range *= aggro_range;
|
||||
|
||||
if (DistanceSquared(n->GetPosition(), skipped->GetPosition()) > aggro_range) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& excluded = Strings::Split(RuleS(Aggro, ExcludedFleeAllyFactionIDs));
|
||||
|
||||
const auto& f = std::find_if(
|
||||
excluded.begin(),
|
||||
excluded.end(),
|
||||
[&](std::string x) {
|
||||
return Strings::ToUnsignedInt(x) == skipped->GetPrimaryFaction();
|
||||
}
|
||||
);
|
||||
|
||||
const bool is_excluded = f != excluded.end();
|
||||
|
||||
// If exclude doesn't have a faction, check for buddies based on race.
|
||||
// Also exclude common factions such as noob monsters, indifferent, kos, kos animal
|
||||
if (!is_excluded) {
|
||||
if (n->GetPrimaryFaction() != skipped->GetPrimaryFaction()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (n->GetBaseRace() != skipped->GetBaseRace() || n->IsCharmedPet()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LogFleeDetail(
|
||||
"[{}] on faction [{}] with aggro_range [{}] is at [{}], [{}], [{}] and will count as an ally for [{}]",
|
||||
n->GetName(),
|
||||
n->GetPrimaryFaction(),
|
||||
aggro_range,
|
||||
n->GetX(),
|
||||
n->GetY(),
|
||||
n->GetZ(),
|
||||
skipped->GetName()
|
||||
);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con)
|
||||
{
|
||||
// Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker
|
||||
|
||||
@ -2186,6 +2186,19 @@ bool Client::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::Skil
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::CheckIfAlreadyDead()
|
||||
{
|
||||
if (!ClientFinishedLoading()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dead) {
|
||||
return false; //cant die more than once...
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//SYNC WITH: tune.cpp, mob.h TuneNPCAttack
|
||||
bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts)
|
||||
{
|
||||
@ -2460,11 +2473,6 @@ void NPC::Damage(Mob* other, int64 damage, uint16 spell_id, EQ::skills::SkillTyp
|
||||
|
||||
//do a majority of the work...
|
||||
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic, special);
|
||||
|
||||
if (damage > 0) {
|
||||
//see if we are gunna start fleeing
|
||||
if (!IsPet()) CheckFlee();
|
||||
}
|
||||
}
|
||||
|
||||
bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillType attack_skill, KilledByTypes killed_by, bool is_buff_tic)
|
||||
@ -4132,6 +4140,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
AddToHateList(attacker, 0, damage, true, false, iBuffTic, spell_id);
|
||||
}
|
||||
|
||||
bool died = false;
|
||||
if (damage > 0) {
|
||||
//if there is some damage being done and theres an attacker involved
|
||||
int previous_hp_ratio = GetHPRatio();
|
||||
@ -4256,6 +4265,8 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
}
|
||||
|
||||
//final damage has been determined.
|
||||
int old_hp_ratio = (int)GetHPRatio();
|
||||
|
||||
SetHP(int64(GetHP() - damage));
|
||||
|
||||
const auto has_bot_given_event = parse->BotHasQuestSub(EVENT_DAMAGE_GIVEN);
|
||||
@ -4355,11 +4366,21 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
if (HasDied()) {
|
||||
bool IsSaved = false;
|
||||
|
||||
if (TryDivineSave())
|
||||
if (TryDivineSave()) {
|
||||
IsSaved = true;
|
||||
}
|
||||
|
||||
if (!IsSaved && !TrySpellOnDeath()) {
|
||||
SetHP(-500);
|
||||
if (IsNPC()) {
|
||||
died = !CastToNPC()->GetDepop();
|
||||
} else if (IsClient()) {
|
||||
died = CastToClient()->CheckIfAlreadyDead();
|
||||
}
|
||||
|
||||
if (died) {
|
||||
SetHP(-500);
|
||||
}
|
||||
|
||||
// killedByType is clarified in Client::Death if we are client.
|
||||
if (Death(attacker, damage, spell_id, skill_used, KilledByTypes::Killed_NPC, iBuffTic)) {
|
||||
return;
|
||||
@ -4529,8 +4550,21 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
|
||||
}
|
||||
|
||||
//send an HP update if we are hurt
|
||||
if (GetHP() < GetMaxHP()) {
|
||||
SendHPUpdate(); // the OP_Damage actually updates the client in these cases, so we skip the HP update for them
|
||||
if(GetHP() < GetMaxHP())
|
||||
{
|
||||
// Don't send a HP update for melee damage unless we've damaged ourself.
|
||||
if (IsNPC()) {
|
||||
int cur_hp_ratio = (int)GetHPRatio();
|
||||
if (cur_hp_ratio != old_hp_ratio) {
|
||||
SendHPUpdate(true);
|
||||
}
|
||||
} else if (!iBuffTic || died) { // Let regen handle buff tics unless this tic killed us.
|
||||
SendHPUpdate(true);
|
||||
}
|
||||
|
||||
if (!died && IsNPC()) {
|
||||
CheckFlee();
|
||||
}
|
||||
}
|
||||
} //end `if damage was done`
|
||||
|
||||
|
||||
@ -278,6 +278,8 @@ public:
|
||||
|
||||
std::vector<Mob*> GetRaidOrGroupOrSelf(bool clients_only = false);
|
||||
|
||||
bool CheckIfAlreadyDead();
|
||||
|
||||
void AI_Init();
|
||||
void AI_Start(uint32 iMoveDelay = 0);
|
||||
void AI_Stop();
|
||||
|
||||
@ -134,6 +134,7 @@ int command_init(void)
|
||||
command_add("fish", "Fish for an item", AccountStatus::QuestTroupe, command_fish) ||
|
||||
command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) ||
|
||||
command_add("flagedit", "Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) ||
|
||||
command_add("fleeinfo", "- Gives info about whether a NPC will flee or not, using the command issuer as top hate.", AccountStatus::QuestTroupe, command_fleeinfo) ||
|
||||
command_add("forage", "Forage an item", AccountStatus::QuestTroupe, command_forage) ||
|
||||
command_add("gearup", "Developer tool to quickly equip yourself or your target", AccountStatus::GMMgmt, command_gearup) ||
|
||||
command_add("giveitem", "[itemid] [charges] - Summon an item onto your target's cursor. Charges are optional.", AccountStatus::GMMgmt, command_giveitem) ||
|
||||
@ -829,6 +830,7 @@ void command_bot(Client *c, const Seperator *sep)
|
||||
#include "gm_commands/fish.cpp"
|
||||
#include "gm_commands/fixmob.cpp"
|
||||
#include "gm_commands/flagedit.cpp"
|
||||
#include "gm_commands/fleeinfo.cpp"
|
||||
#include "gm_commands/forage.cpp"
|
||||
#include "gm_commands/gearup.cpp"
|
||||
#include "gm_commands/giveitem.cpp"
|
||||
|
||||
@ -87,6 +87,7 @@ void command_find(Client *c, const Seperator *sep);
|
||||
void command_fish(Client* c, const Seperator* sep);
|
||||
void command_fixmob(Client *c, const Seperator *sep);
|
||||
void command_flagedit(Client *c, const Seperator *sep);
|
||||
void command_fleeinfo(Client *c, const Seperator *sep);
|
||||
void command_forage(Client* c, const Seperator* sep);
|
||||
void command_gearup(Client *c, const Seperator *sep);
|
||||
void command_giveitem(Client *c, const Seperator *sep);
|
||||
@ -136,7 +137,7 @@ void command_nukebuffs(Client *c, const Seperator *sep);
|
||||
void command_nukeitem(Client *c, const Seperator *sep);
|
||||
void command_object(Client *c, const Seperator *sep);
|
||||
void command_oocmute(Client *c, const Seperator *sep);
|
||||
void command_parcels(Client *c, const Seperator *sep);
|
||||
void command_parcels(Client *c, const Seperator *sep);
|
||||
void command_path(Client *c, const Seperator *sep);
|
||||
void command_peqzone(Client *c, const Seperator *sep);
|
||||
void command_petitems(Client *c, const Seperator *sep);
|
||||
|
||||
@ -4483,6 +4483,36 @@ void EntityList::QuestJournalledSayClose(
|
||||
delete outapp;
|
||||
}
|
||||
|
||||
bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z,
|
||||
float trg_x, float trg_y, float trg_z, float perwalk)
|
||||
{
|
||||
if (zone->zonemap == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::vec3 myloc;
|
||||
glm::vec3 oloc;
|
||||
glm::vec3 hit;
|
||||
|
||||
myloc.x = cur_x;
|
||||
myloc.y = cur_y;
|
||||
myloc.z = cur_z+5;
|
||||
|
||||
oloc.x = trg_x;
|
||||
oloc.y = trg_y;
|
||||
oloc.z = trg_z+5;
|
||||
|
||||
if (myloc.x == oloc.x && myloc.y == oloc.y && myloc.z == oloc.z) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name)
|
||||
{
|
||||
if (!sender)
|
||||
|
||||
@ -116,6 +116,7 @@ public:
|
||||
inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; }
|
||||
|
||||
virtual const char* GetName() { return ""; }
|
||||
bool CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z, float trg_x, float trg_y, float trg_z, float perwalk=1);
|
||||
|
||||
Bot* CastToBot();
|
||||
const Bot* CastToBot() const;
|
||||
@ -503,9 +504,10 @@ public:
|
||||
Mob* GetTargetForMez(Mob* caster);
|
||||
uint32 CheckNPCsClose(Mob *center);
|
||||
|
||||
int FleeAllyCount(Mob* attacker, Mob* skipped);
|
||||
Corpse* GetClosestCorpse(Mob* sender, const char *Name);
|
||||
void TryWakeTheDead(Mob* sender, Mob* target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets);
|
||||
NPC* GetClosestBanker(Mob* sender, uint32 &distance);
|
||||
NPC* GetClosestBanker(Mob* sender, uint32 &distance);
|
||||
void CameraEffect(uint32 duration, float intensity);
|
||||
Mob* GetClosestMobByBodyType(Mob* sender, uint8 BodyType, bool skip_client_pets=false);
|
||||
void ForceGroupUpdate(uint32 gid);
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include "../common/rulesys.h"
|
||||
|
||||
#include "map.h"
|
||||
#include "water_map.h"
|
||||
#include "zone.h"
|
||||
|
||||
#ifdef _WINDOWS
|
||||
@ -29,17 +30,52 @@ extern Zone* zone;
|
||||
|
||||
#define FEAR_PATHING_DEBUG
|
||||
|
||||
int Mob::GetFleeRatio(Mob* other)
|
||||
{
|
||||
int flee_ratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists
|
||||
Mob *hate_top = GetHateTop();
|
||||
|
||||
if (other != nullptr) {
|
||||
hate_top = other;
|
||||
}
|
||||
|
||||
if (!hate_top) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If no special flee_percent check for Gray or Other con rates
|
||||
if (flee_ratio == 0) {
|
||||
flee_ratio = RuleI(Combat, FleeHPRatio);
|
||||
if (GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && RuleB(Combat, FleeGray) &&
|
||||
GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) {
|
||||
flee_ratio = RuleI(Combat, FleeGrayHPRatio);
|
||||
LogFlee("Mob [{}] using combat flee gray flee_ratio [{}]", GetCleanName(), flee_ratio);
|
||||
}
|
||||
}
|
||||
|
||||
return flee_ratio;
|
||||
}
|
||||
|
||||
//this is called whenever we are damaged to process possible fleeing
|
||||
void Mob::CheckFlee()
|
||||
{
|
||||
|
||||
// if mob is dead why would you run?
|
||||
if (GetHP() == 0) {
|
||||
if (IsPet() || IsCasting() || GetHP() == 0 || GetBodyType() == BodyType::Undead || (IsNPC() && CastToNPC()->IsUnderwaterOnly())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if were already fleeing, don't need to check more...
|
||||
//if were already fleeing, we only need to check speed. Speed changes will trigger pathing updates.
|
||||
if (flee_mode && currently_fleeing) {
|
||||
int flee_speed = GetFearSpeed();
|
||||
if (flee_speed < 1) {
|
||||
flee_speed = 0;
|
||||
}
|
||||
|
||||
SetRunAnimSpeed(flee_speed);
|
||||
|
||||
if (IsMoving() && flee_speed < 1) {
|
||||
StopNavigation();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -49,38 +85,17 @@ void Mob::CheckFlee()
|
||||
return;
|
||||
}
|
||||
|
||||
// Undead do not flee
|
||||
if (GetBodyType() == BodyType::Undead) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Flee Timer is cleared
|
||||
if (!flee_timer.Check()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hp_ratio = GetIntHPRatio();
|
||||
int flee_ratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists
|
||||
int flee_ratio = GetFleeRatio();
|
||||
Mob *hate_top = GetHateTop();
|
||||
|
||||
LogFlee("Mob [{}] hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
|
||||
|
||||
// Sanity Check for race conditions
|
||||
if (hate_top == nullptr) {
|
||||
if(!hate_top) {
|
||||
//this should never happen...
|
||||
StartFleeing();
|
||||
return;
|
||||
}
|
||||
|
||||
// If no special flee_percent check for Gray or Other con rates
|
||||
if (GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && flee_ratio == 0 && RuleB(Combat, FleeGray) &&
|
||||
GetLevel() <= RuleI(Combat, FleeGrayMaxLevel)) {
|
||||
flee_ratio = RuleI(Combat, FleeGrayHPRatio);
|
||||
LogFlee("Mob [{}] using combat flee gray hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
|
||||
}
|
||||
else if (flee_ratio == 0) {
|
||||
flee_ratio = RuleI(Combat, FleeHPRatio);
|
||||
LogFlee("Mob [{}] using combat flee hp_ratio [{}] flee_ratio [{}]", GetCleanName(), hp_ratio, flee_ratio);
|
||||
}
|
||||
|
||||
bool mob_has_low_enough_health_to_flee = hp_ratio >= flee_ratio;
|
||||
if (mob_has_low_enough_health_to_flee) {
|
||||
LogFlee(
|
||||
@ -141,7 +156,7 @@ void Mob::CheckFlee()
|
||||
// if FleeIfNotAlone is true, we skip alone check
|
||||
// roll chance
|
||||
if (GetSpecialAbility(SpecialAbility::AlwaysFlee) ||
|
||||
((RuleB(Combat, FleeIfNotAlone) || entity_list.GetHatedCount(hate_top, this, true) == 0) &&
|
||||
((RuleB(Combat, FleeIfNotAlone) || entity_list.FleeAllyCount(hate_top, this) == 0) &&
|
||||
zone->random.Roll(flee_chance))) {
|
||||
|
||||
LogFlee(
|
||||
@ -158,9 +173,63 @@ void Mob::CheckFlee()
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::StopFleeing()
|
||||
{
|
||||
if (!flee_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
flee_mode = false;
|
||||
|
||||
//see if we are legitimately feared or blind now
|
||||
if (!spellbonuses.IsFeared && !IsBlind()) {
|
||||
currently_fleeing = false;
|
||||
StopNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::FleeInfo(Mob* client)
|
||||
{
|
||||
float other_ratio = client->GetHPRatio();
|
||||
bool wontflee = false;
|
||||
std::string reason;
|
||||
std::string flee;
|
||||
|
||||
int allycount = entity_list.FleeAllyCount(client, this);
|
||||
|
||||
if (flee_mode && currently_fleeing) {
|
||||
wontflee = true;
|
||||
reason = "NPC is already fleeing!";
|
||||
} else if (GetSpecialAbility(SpecialAbility::FleeingImmunity)) {
|
||||
wontflee = true;
|
||||
reason = "NPC is immune to fleeing.";
|
||||
} else if (other_ratio < 20) {
|
||||
wontflee = true;
|
||||
reason = "Player has low health.";
|
||||
} else if (GetSpecialAbility(SpecialAbility::AlwaysFlee)) {
|
||||
flee = "NPC has ALWAYS_FLEE set.";
|
||||
} else if (RuleB(Combat, FleeIfNotAlone) || (!RuleB(Combat, FleeIfNotAlone) && allycount == 0)) {
|
||||
flee = "NPC has no allies nearby or the rule to flee when not alone is enabled.";
|
||||
} else {
|
||||
wontflee = true;
|
||||
reason = "NPC likely has allies nearby.";
|
||||
}
|
||||
|
||||
|
||||
if (!wontflee) {
|
||||
client->Message(Chat::Green, "%s will flee at %d percent because %s", GetName(), GetFleeRatio(client), flee.c_str());
|
||||
} else {
|
||||
client->Message(Chat::Red, "%s will not flee because %s", GetName(), reason.c_str());
|
||||
}
|
||||
|
||||
client->Message(Chat::Default, "NPC ally count %d", allycount);
|
||||
}
|
||||
|
||||
void Mob::ProcessFlee()
|
||||
{
|
||||
if (!flee_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Stop fleeing if effect is applied after they start to run.
|
||||
//When ImmuneToFlee effect fades it will turn fear back on and check if it can still flee.
|
||||
@ -170,46 +239,201 @@ void Mob::ProcessFlee()
|
||||
return;
|
||||
}
|
||||
|
||||
int hpratio = GetIntHPRatio();
|
||||
int fleeratio = GetSpecialAbility(SpecialAbility::FleePercent); // if a special SpecialAbility::FleePercent exists
|
||||
Mob *hate_top = GetHateTop();
|
||||
bool dying = GetIntHPRatio() < GetFleeRatio();
|
||||
|
||||
// If no special flee_percent check for Gray or Other con rates
|
||||
if(hate_top != nullptr && GetLevelCon(hate_top->GetLevel(), GetLevel()) == ConsiderColor::Gray && fleeratio == 0 && RuleB(Combat, FleeGray)) {
|
||||
fleeratio = RuleI(Combat, FleeGrayHPRatio);
|
||||
} else if(fleeratio == 0) {
|
||||
fleeratio = RuleI(Combat, FleeHPRatio );
|
||||
// We have stopped fleeing for an unknown reason (couldn't find a node is possible) restart.
|
||||
if (flee_mode && !currently_fleeing) {
|
||||
if(dying) {
|
||||
StartFleeing();
|
||||
}
|
||||
}
|
||||
|
||||
// Mob is still too low. Keep Running
|
||||
if(hpratio < fleeratio) {
|
||||
//see if we are still dying, if so, do nothing
|
||||
if (dying) {
|
||||
return;
|
||||
}
|
||||
|
||||
//we are not dying anymore... see what we do next
|
||||
|
||||
flee_mode = false;
|
||||
|
||||
//see if we are legitimately feared or blind now
|
||||
if (!spellbonuses.IsFeared && !spellbonuses.IsBlind) {
|
||||
//not feared or blind... were done...
|
||||
currently_fleeing = false;
|
||||
return;
|
||||
}
|
||||
//we are not dying anymore, check to make sure we're not blind or feared and cancel flee.
|
||||
StopFleeing();
|
||||
}
|
||||
|
||||
void Mob::CalculateNewFearpoint()
|
||||
{
|
||||
if (RuleB(Pathing, Fear) && zone->pathing) {
|
||||
auto Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetY(), GetZ()));
|
||||
if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) {
|
||||
m_FearWalkTarget = Node;
|
||||
currently_fleeing = true;
|
||||
|
||||
return;
|
||||
// blind waypoint logic isn't the same as fear's. Has a chance to run toward the player
|
||||
// chance is very high if the player is moving, otherwise it's low
|
||||
if (IsBlind() && !IsFeared() && GetTarget()) {
|
||||
int roll = 20;
|
||||
if (GetTarget()->GetCurrentSpeed() > 0.1f || (GetTarget()->IsClient() && GetTarget()->animation != 0)) {
|
||||
roll = 80;
|
||||
}
|
||||
|
||||
LogPathing("No path found to selected node during CalculateNewFearpoint.");
|
||||
if (zone->random.Roll(roll)) {
|
||||
m_FearWalkTarget = glm::vec3(GetTarget()->GetPosition());
|
||||
currently_fleeing = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (RuleB(Pathing, Fear) && zone->pathing) {
|
||||
glm::vec3 Node;
|
||||
int flags = PathingNotDisabled ^ PathingZoneLine;
|
||||
|
||||
if (IsNPC() && CastToNPC()->IsUnderwaterOnly() && !zone->IsWaterZone(GetZOffset())) {
|
||||
Node = glm::vec3(0.0f);
|
||||
} else {
|
||||
Node = zone->pathing->GetRandomLocation(glm::vec3(GetX(), GetY(), GetZOffset()), flags);
|
||||
}
|
||||
|
||||
if (Node.x != 0.0f || Node.y != 0.0f || Node.z != 0.0f) {
|
||||
Node.z = GetFixedZ(Node);
|
||||
PathfinderOptions opts;
|
||||
opts.smooth_path = true;
|
||||
opts.step_size = RuleR(Pathing, NavmeshStepSize);
|
||||
opts.offset = GetZOffset();
|
||||
opts.flags = flags;
|
||||
auto partial = false;
|
||||
auto stuck = false;
|
||||
auto route = zone->pathing->FindPath(
|
||||
glm::vec3(GetX(), GetY(), GetZOffset()),
|
||||
glm::vec3(Node.x, Node.y, Node.z),
|
||||
partial,
|
||||
stuck,
|
||||
opts
|
||||
);
|
||||
glm::vec3 last_good_loc = Node;
|
||||
int route_size = route.size();
|
||||
int route_count = 0;
|
||||
bool have_los = true;
|
||||
|
||||
if (route_size == 2) {
|
||||
// FindPath() often fails to compute a route in some places, so to prevent running through walls we need to check LOS on all 2 node routes
|
||||
// size 2 route usually means FindPath() bugged out. sometimes it returns locs outside the geometry
|
||||
if (CheckLosFN(Node.x, Node.y, Node.z, 6.0)) {
|
||||
LogPathingDetail("Direct route to fearpoint [{}], [{}], [{}] calculated for [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName());
|
||||
m_FearWalkTarget = last_good_loc;
|
||||
currently_fleeing = true;
|
||||
return;
|
||||
} else {
|
||||
LogPathingDetail("FindRoute() returned single hop route to destination without LOS: [{}], [{}], [{}] for [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName());
|
||||
}
|
||||
// use fallback logic if LOS fails
|
||||
} else if (!stuck) {
|
||||
// check route for LOS failures to prevent mobs ending up outside of playable area
|
||||
// only checking the last few hops because LOS will often fail in a valid route which can result in mobs getting undesirably trapped
|
||||
auto iter = route.begin();
|
||||
glm::vec3 previous_pos(GetX(), GetY(), GetZOffset());
|
||||
while (iter != route.end() && have_los == true) {
|
||||
auto ¤t_node = (*iter);
|
||||
iter++;
|
||||
route_count++;
|
||||
|
||||
if (iter == route.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_pos = current_node.pos;
|
||||
auto &next_node = (*iter);
|
||||
|
||||
if (next_node.teleport) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((route_size - route_count) < 5 && !zone->zonemap->CheckLoS(previous_pos, next_node.pos)) {
|
||||
have_los = false;
|
||||
break;
|
||||
} else {
|
||||
last_good_loc = next_node.pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (have_los || route_count > 2) {
|
||||
if (have_los) {
|
||||
LogPathingDetail("Route to fearpoint [{}], [{}], [{}] calculated for [{}]; route size: [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName(), route_size);
|
||||
} else {
|
||||
LogPathingDetail("Using truncated route to fearpoint [{}], [{}], [{}] for [{}]; node count: [{}]; route size [{}]", last_good_loc.x, last_good_loc.y, last_good_loc.z, GetName(), route_count, route_size);
|
||||
}
|
||||
|
||||
m_FearWalkTarget = last_good_loc;
|
||||
currently_fleeing = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback logic if pathing system can't be used
|
||||
bool inliquid = zone->HasWaterMap() && zone->watermap->InLiquid(glm::vec3(GetPosition())) || zone->IsWaterZone(GetZ());
|
||||
bool stay_inliquid = (inliquid && IsNPC() && CastToNPC()->IsUnderwaterOnly());
|
||||
bool levitating = IsClient() && (FindType(SE_Levitate) || flymode != GravityBehavior::Ground);
|
||||
bool open_outdoor_zone = !zone->CanCastOutdoor() && !zone->IsCity();
|
||||
|
||||
int loop = 0;
|
||||
float ranx, rany, ranz;
|
||||
currently_fleeing = false;
|
||||
glm::vec3 myloc(GetX(), GetY(), GetZ());
|
||||
glm::vec3 myceil = myloc;
|
||||
float ceil = zone->zonemap->FindCeiling(myloc, &myceil);
|
||||
|
||||
if (ceil != BEST_Z_INVALID) {
|
||||
ceil -= 1.0f;
|
||||
}
|
||||
|
||||
while (loop < 100) { //Max 100 tries
|
||||
int ran = 250 - (loop * 2);
|
||||
loop++;
|
||||
|
||||
if (open_outdoor_zone && loop < 20) { // try a distant loc first; other way will likely pick a close loc
|
||||
ranx = zone->random.Int(0, ran);
|
||||
rany = zone->random.Int(0, ran);
|
||||
if (ranx + rany < 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ranx = GetX() + (zone->random.Int(0, 1) == 1 ? ranx : -ranx);
|
||||
rany = GetY() + (zone->random.Int(0, 1) == 1 ? rany : -rany);
|
||||
} else {
|
||||
ranx = GetX() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1);
|
||||
rany = GetY() + zone->random.Int(0, ran - 1) - zone->random.Int(0, ran - 1);
|
||||
}
|
||||
|
||||
ranz = BEST_Z_INVALID;
|
||||
glm::vec3 newloc(ranx, rany, ceil != BEST_Z_INVALID ? ceil : GetZ());
|
||||
|
||||
if (stay_inliquid || levitating || (loop > 50 && inliquid)) {
|
||||
if (zone->zonemap->CheckLoS(myloc, newloc)) {
|
||||
ranz = GetZ();
|
||||
currently_fleeing = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (ceil != BEST_Z_INVALID) {
|
||||
ranz = zone->zonemap->FindGround(newloc, &myceil);
|
||||
} else {
|
||||
ranz = zone->zonemap->FindBestZ(newloc, &myceil);
|
||||
}
|
||||
|
||||
if (ranz != BEST_Z_INVALID) {
|
||||
ranz = SetBestZ(ranz);
|
||||
}
|
||||
}
|
||||
|
||||
if (ranz == BEST_Z_INVALID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float fdist = ranz - GetZ();
|
||||
if (fdist >= -50 && fdist <= 50 && CheckCoordLosNoZLeaps(GetX(), GetY(), GetZ(), ranx, rany, ranz)) {
|
||||
currently_fleeing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currently_fleeing) {
|
||||
m_FearWalkTarget = glm::vec3(ranx, rany, ranz);
|
||||
LogPathingDetail("Non-pathed fearpoint [{}], [{}], [{}] selected for [{}]", ranx, rany, ranz, GetName());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
14
zone/gm_commands/fleeinfo.cpp
Normal file
14
zone/gm_commands/fleeinfo.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include "../client.h"
|
||||
|
||||
void command_fleeinfo(Client *c, const Seperator *sep)
|
||||
{
|
||||
if (c->GetTarget() && c->GetTarget()->IsNPC()) {
|
||||
Mob* client = entity_list.GetMob(c->GetID());
|
||||
if (client) {
|
||||
c->GetTarget()->FleeInfo(client);
|
||||
}
|
||||
} else {
|
||||
c->Message(Chat::Red, "Please target a NPC to use this command on.");
|
||||
}
|
||||
|
||||
}
|
||||
59
zone/map.cpp
59
zone/map.cpp
@ -2,6 +2,7 @@
|
||||
#include "../common/misc_functions.h"
|
||||
#include "../common/compression.h"
|
||||
|
||||
#include "client.h"
|
||||
#include "map.h"
|
||||
#include "raycast_mesh.h"
|
||||
#include "zone.h"
|
||||
@ -95,6 +96,64 @@ float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) const {
|
||||
return ClosestZ;
|
||||
}
|
||||
|
||||
float Map::FindCeiling(glm::vec3 &start, glm::vec3 *result) const {
|
||||
// Unlike FindBestZ, this method finds the closest Z above point.
|
||||
|
||||
if (!imp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 tmp;
|
||||
if (!result) {
|
||||
result = &tmp;
|
||||
}
|
||||
|
||||
glm::vec3 from(start.x, start.y, start.z);
|
||||
glm::vec3 to(start.x, start.y, -BEST_Z_INVALID);
|
||||
float hit_distance;
|
||||
bool hit = false;
|
||||
|
||||
// Find nearest Z above us
|
||||
hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance);
|
||||
if (hit) {
|
||||
return result->z;
|
||||
}
|
||||
|
||||
return BEST_Z_INVALID;
|
||||
}
|
||||
|
||||
float Map::FindGround(glm::vec3 &start, glm::vec3 *result) const {
|
||||
// Unlike FindBestZ, this method finds the closest Z below point.
|
||||
|
||||
if (!imp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 tmp;
|
||||
|
||||
if (!result) {
|
||||
result = &tmp;
|
||||
}
|
||||
|
||||
glm::vec3 from(start.x, start.y, start.z);
|
||||
glm::vec3 to(start.x, start.y, BEST_Z_INVALID);
|
||||
float hit_distance;
|
||||
bool hit = false;
|
||||
|
||||
// Find nearest Z below us
|
||||
hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance);
|
||||
|
||||
if (hit && zone->newzone_data.underworld != 0.0f && result->z < zone->newzone_data.underworld) {
|
||||
hit = false;
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
return result->z;
|
||||
}
|
||||
|
||||
return BEST_Z_INVALID;
|
||||
}
|
||||
|
||||
bool Map::LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result) const {
|
||||
if(!imp)
|
||||
return false;
|
||||
|
||||
@ -39,6 +39,8 @@ public:
|
||||
|
||||
float FindBestZ(glm::vec3 &start, glm::vec3 *result) const;
|
||||
float FindClosestZ(glm::vec3 &start, glm::vec3 *result) const;
|
||||
float FindCeiling(glm::vec3 &start, glm::vec3 *result) const;
|
||||
float FindGround(glm::vec3 &start, glm::vec3 *result) const;
|
||||
bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result) const;
|
||||
bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, glm::vec3 *result) const;
|
||||
bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc) const;
|
||||
|
||||
@ -877,8 +877,9 @@ int Mob::_GetRunSpeed() const {
|
||||
|
||||
int Mob::_GetFearSpeed() const {
|
||||
|
||||
if (IsRooted() || IsStunned() || IsMezzed())
|
||||
if (IsRooted() || IsStunned() || IsMezzed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//float speed_mod = fearspeed;
|
||||
int speed_mod = GetBaseFearSpeed();
|
||||
|
||||
@ -678,6 +678,7 @@ public:
|
||||
inline const float GetRelativeHeading() const { return m_RelativePosition.w; }
|
||||
inline const float GetSize() const { return size; }
|
||||
inline const float GetBaseSize() const { return base_size; }
|
||||
inline const float SetBestZ(float z_coord) const { return z_coord + GetZOffset(); }
|
||||
inline const GravityBehavior GetFlyMode() const { return flymode; }
|
||||
bool IsBoat() const; // Checks races - used on mob instantiation
|
||||
bool GetIsBoat() const { return is_boat; } // Set on instantiation for speed
|
||||
@ -717,6 +718,7 @@ public:
|
||||
float GetMovespeed() const { return IsRunning() ? GetRunspeed() : GetWalkspeed(); }
|
||||
bool IsRunning() const { return m_is_running; }
|
||||
void SetRunning(bool val) { m_is_running = val; }
|
||||
float GetCurrentSpeed() { return current_speed; }
|
||||
virtual void GMMove(float x, float y, float z, float heading = 0.01, bool save_guard_spot = true);
|
||||
virtual void GMMove(const glm::vec4 &position, bool save_guard_spot = true);
|
||||
void SetDelta(const glm::vec4& delta);
|
||||
@ -1074,6 +1076,7 @@ public:
|
||||
inline virtual bool HasOwner() { if (!GetOwnerID()){ return false; } return entity_list.GetMob(GetOwnerID()) != 0; }
|
||||
inline virtual bool IsPet() { return HasOwner() && !IsMerc(); }
|
||||
bool HasPet() const;
|
||||
virtual bool IsCharmedPet() { return IsPet() && IsCharmed(); }
|
||||
inline bool HasTempPetsActive() const { return(hasTempPet); }
|
||||
inline void SetTempPetsActive(bool i) { hasTempPet = i; }
|
||||
inline int16 GetTempPetCount() const { return count_TempPet; }
|
||||
@ -1210,8 +1213,12 @@ public:
|
||||
int GetFearSpeed() { return _GetFearSpeed(); }
|
||||
bool IsFeared() { return (spellbonuses.IsFeared || flee_mode); } // This returns true if the mob is feared or fleeing due to low HP
|
||||
inline void StartFleeing() { flee_mode = true; CalculateNewFearpoint(); }
|
||||
void StopFleeing();
|
||||
inline bool IsFleeing() { return flee_mode; }
|
||||
void ProcessFlee();
|
||||
void CheckFlee();
|
||||
void FleeInfo(Mob* client);
|
||||
int GetFleeRatio(Mob* other = nullptr);
|
||||
inline bool IsBlind() { return spellbonuses.IsBlind; }
|
||||
|
||||
inline bool CheckAggro(Mob* other) {return hate_list.IsEntOnHateList(other);}
|
||||
|
||||
@ -75,7 +75,7 @@ public:
|
||||
|
||||
virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled) = 0;
|
||||
virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts) = 0;
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start) = 0;
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start, int flags = PathingNotDisabled) = 0;
|
||||
virtual void DebugCommand(Client *c, const Seperator *sep) = 0;
|
||||
|
||||
static IPathfinder *Load(const std::string &zone);
|
||||
|
||||
@ -300,7 +300,7 @@ IPathfinder::IPath PathfinderNavmesh::FindPath(const glm::vec3 &start, const glm
|
||||
return IPath();
|
||||
}
|
||||
|
||||
glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start)
|
||||
glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start, int flags)
|
||||
{
|
||||
if (start.x == 0.0f && start.y == 0.0)
|
||||
return glm::vec3(0.f);
|
||||
@ -315,7 +315,7 @@ glm::vec3 PathfinderNavmesh::GetRandomLocation(const glm::vec3 &start)
|
||||
}
|
||||
|
||||
dtQueryFilter filter;
|
||||
filter.setIncludeFlags(65535U ^ 2048);
|
||||
filter.setIncludeFlags(flags);
|
||||
filter.setAreaCost(0, 1.0f); //Normal
|
||||
filter.setAreaCost(1, 3.0f); //Water
|
||||
filter.setAreaCost(2, 5.0f); //Lava
|
||||
|
||||
@ -12,7 +12,7 @@ public:
|
||||
|
||||
virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled);
|
||||
virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start, int flags = PathingNotDisabled);
|
||||
virtual void DebugCommand(Client *c, const Seperator *sep);
|
||||
|
||||
private:
|
||||
|
||||
@ -20,7 +20,7 @@ IPathfinder::IPath PathfinderNull::FindPath(const glm::vec3 &start, const glm::v
|
||||
return ret;
|
||||
}
|
||||
|
||||
glm::vec3 PathfinderNull::GetRandomLocation(const glm::vec3 &start)
|
||||
glm::vec3 PathfinderNull::GetRandomLocation(const glm::vec3 &start, int flags)
|
||||
{
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
@ -10,6 +10,6 @@ public:
|
||||
|
||||
virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled);
|
||||
virtual IPath FindPath(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, const PathfinderOptions& opts);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start, int flags = PathingNotDisabled);
|
||||
virtual void DebugCommand(Client *c, const Seperator *sep) { }
|
||||
};
|
||||
|
||||
@ -181,7 +181,7 @@ IPathfinder::IPath PathfinderWaypoint::FindRoute(const glm::vec3 &start, const g
|
||||
return IPath();
|
||||
}
|
||||
|
||||
glm::vec3 PathfinderWaypoint::GetRandomLocation(const glm::vec3 &start)
|
||||
glm::vec3 PathfinderWaypoint::GetRandomLocation(const glm::vec3 &start, int flags)
|
||||
{
|
||||
if (m_impl->Nodes.size() > 0) {
|
||||
auto idx = zone->random.Int(0, (int)m_impl->Nodes.size() - 1);
|
||||
|
||||
@ -12,7 +12,7 @@ public:
|
||||
virtual ~PathfinderWaypoint();
|
||||
|
||||
virtual IPath FindRoute(const glm::vec3 &start, const glm::vec3 &end, bool &partial, bool &stuck, int flags = PathingNotDisabled);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start);
|
||||
virtual glm::vec3 GetRandomLocation(const glm::vec3 &start, int flags = PathingNotDisabled);
|
||||
virtual void DebugCommand(Client *c, const Seperator *sep);
|
||||
|
||||
private:
|
||||
|
||||
@ -2686,6 +2686,22 @@ uint32 Zone::GetSpawnKillCount(uint32 in_spawnid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Zone::IsWaterZone(float z)
|
||||
{
|
||||
|
||||
switch (GetZoneID()) {
|
||||
case Zones::KEDGE:
|
||||
return true;
|
||||
case Zones::POWATER:
|
||||
if (z < 0.0f) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Zone::SetIsHotzone(bool is_hotzone)
|
||||
{
|
||||
Zone::is_hotzone = is_hotzone;
|
||||
|
||||
@ -125,6 +125,7 @@ public:
|
||||
bool CanCastOutdoor() const { return (can_castoutdoor); } //qadar
|
||||
bool CanDoCombat() const { return (can_combat); }
|
||||
bool CanLevitate() const { return (can_levitate); } // Magoth78
|
||||
bool IsWaterZone(float z);
|
||||
bool Depop(bool StartSpawnTimer = false);
|
||||
bool did_adventure_actions;
|
||||
bool GetAuth(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user