void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { if(!sender || !attacker) return; if (sender->GetPrimaryFaction() == 0 ) return; // well, if we dont have a faction set, we're gonna be indiff to everybody LinkedListIterator<NPC*> iterator(npc_list); for(iterator.Reset(); iterator.MoreElements(); iterator.Advance()) { NPC* mob = iterator.GetData(); if(!mob){ continue; } float r = mob->GetAssistRange(); r = r * r; if ( mob != sender && mob != attacker // && !mob->IsCorpse() // && mob->IsAIControlled() && mob->GetPrimaryFaction() != 0 && mob->DistNoRoot(*sender) <= r && !mob->IsEngaged() && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) // If we're a pet we don't react to any calls for help if our owner is a client ) { //if they are in range, make sure we are not green... //then jump in if they are our friend if(attacker->GetLevelCon(mob->GetLevel()) != CON_GREEN) { bool useprimfaction = false; if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); if(cf){ if(cf->assistprimaryfaction != 0) useprimfaction = true; } } if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) { //attacking someone on same faction, or a friend //Father Nitwit: make sure we can see them. if(mob->CheckLosFN(sender)) { #if (EQDEBUG>=5) LogFile->write(EQEMuLog::Debug, "AIYellForHelp(\"%s\",\"%s\") %s attacking %s Dist %f Z %f", sender->GetName(), attacker->GetName(), mob->GetName(), attacker->GetName(), mob->DistNoRoot(*sender), fabs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 1, 0, false); } } } } } }
void Tribe::SendPerception(const char* pcpt, csArray<NPC*> npcs) { Perception perception(pcpt); for(size_t i=0; i<npcs.GetSize(); i++) { NPC* npc = npcs[i]; RDebug(this, 5, "--> Percept npc %s(%s): %s",npc->GetName(),ShowID(npc->GetEID()),perception.ToString(npc).GetDataSafe()); npc->TriggerEvent(&perception); } }
void Room::SpawnMob(int masterId) { NPC& copyFrom = (NPC&)*(GetWorld().GetMasterMobs().GetEntity(masterId)); NPC* spawnMob = new NPC(GetWorld().GetMobs().GetNextId(), copyFrom); spawnMob->SetRoomId(GetId()); GetWorld().GetMobs().AddEntity(*spawnMob); AddMob(*spawnMob); //std::cout << "Spawning " << spawnItem->GetName() << " in " << GetName() << " (Master ID: " << spawnItem->GetMasterId() << ")" << std::endl; std::ostringstream outString; outString << "\n" << cGreen << spawnMob->GetName() << cDefault << " enters the room.\n"; std::vector<GameEntity*> roomPlayers = players_->GetEntityVector(); for (GameEntity* p : roomPlayers) { Message* msg = new Message(outString.str(), ((Player*)p)->GetConnectionId(), Message::outputMessage); GetWorld().GetParent().PutMessage(msg); } }
Mob* EntityList::AICheckCloseAggro(Mob* sender, float iAggroRange, float iAssistRange) { if (!sender || !sender->IsNPC()) return(nullptr); #ifdef REVERSE_AGGRO //with reverse aggro, npc->client is checked elsewhere, no need to check again auto it = npc_list.begin(); while (it != npc_list.end()) { #else auto it = mob_list.begin(); while (it != mob_list.end()) { #endif Mob *mob = it->second; if (sender->CheckWillAggro(mob)) return mob; ++it; } //LogFile->write(EQEMuLog::Debug, "Check aggro for %s no target.", sender->GetName()); return nullptr; } int EntityList::GetHatedCount(Mob *attacker, Mob *exclude) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker if (!attacker) return 0; int Count = 0; for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob || (mob == exclude)) continue; if (!mob->IsEngaged()) continue; if (mob->IsFeared() || mob->IsMezzed()) continue; if (attacker->GetLevelCon(mob->GetLevel()) == CON_GREEN) continue; if (!mob->CheckAggro(attacker)) continue; float AggroRange = mob->GetAggroRange(); // Square it because we will be using DistNoRoot AggroRange *= AggroRange; if (DistanceSquared(mob->GetPosition(), attacker->GetPosition()) > AggroRange) continue; Count++; } return Count; } void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { if(!sender || !attacker) return; if (sender->GetPrimaryFaction() == 0 ) return; // well, if we dont have a faction set, we're gonna be indiff to everybody if (sender->HasAssistAggro()) return; for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob) continue; if (mob->CheckAggro(attacker)) continue; if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) break; float r = mob->GetAssistRange(); r = r * r; if ( mob != sender && mob != attacker // && !mob->IsCorpse() // && mob->IsAIControlled() && mob->GetPrimaryFaction() != 0 && DistanceSquared(mob->GetPosition(), sender->GetPosition()) <= r && !mob->IsEngaged() && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) // If we're a pet we don't react to any calls for help if our owner is a client ) { //if they are in range, make sure we are not green... //then jump in if they are our friend if(attacker->GetLevelCon(mob->GetLevel()) != CON_GREEN) { bool useprimfaction = false; if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); if(cf){ if(cf->assistprimaryfaction != 0) useprimfaction = true; } } if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) { //attacking someone on same faction, or a friend //Father Nitwit: make sure we can see them. if(mob->CheckLosFN(sender)) { #if (EQDEBUG>=5) Log.Out(Logs::General, Logs::None, "AIYellForHelp(\"%s\",\"%s\") %s attacking %s Dist %f Z %f", sender->GetName(), attacker->GetName(), mob->GetName(), attacker->GetName(), DistanceSquared(mob->GetPosition(), sender->GetPosition()), fabs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 25, 0, false); sender->AddAssistCap(); } } } } } } /* returns false if attack should not be allowed I try to list every type of conflict that's possible here, so it's easy to see how the decision is made. Yea, it could be condensed and made faster, but I'm doing it this way to make it readable and easy to modify */ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) { Mob *mob1, *mob2, *tempmob; Client *c1, *c2, *becomenpc; // NPC *npc1, *npc2; int reverse; if(!zone->CanDoCombat()) return false; // some special cases if(!target) return false; if(this == target) // you can attack yourself return true; if(target->GetSpecialAbility(NO_HARM_FROM_CLIENT)){ return false; } // can't damage own pet (applies to everthing) Mob *target_owner = target->GetOwner(); Mob *our_owner = GetOwner(); if(target_owner && target_owner == this) return false; else if(our_owner && our_owner == target) return false; // invalidate for swarm pets for later on if their owner is a corpse if (IsNPC() && CastToNPC()->GetSwarmInfo() && our_owner && our_owner->IsCorpse() && !our_owner->IsPlayerCorpse()) our_owner = nullptr; if (target->IsNPC() && target->CastToNPC()->GetSwarmInfo() && target_owner && target_owner->IsCorpse() && !target_owner->IsPlayerCorpse()) target_owner = nullptr; //cannot hurt untargetable mobs bodyType bt = target->GetBodyType(); if(bt == BT_NoTarget || bt == BT_NoTarget2) { if (RuleB(Pets, UnTargetableSwarmPet)) { if (target->IsNPC()) { if (!target->CastToNPC()->GetSwarmOwner()) { return(false); } } else { return(false); } } else { return(false); } } if(!isSpellAttack) { if(GetClass() == LDON_TREASURE) { return false; } } // the format here is a matrix of mob type vs mob type. // redundant ones are omitted and the reverse is tried if it falls through. // first figure out if we're pets. we always look at the master's flags. // no need to compare pets to anything mob1 = our_owner ? our_owner : this; mob2 = target_owner ? target_owner : target; reverse = 0; do { if(_CLIENT(mob1)) { if(_CLIENT(mob2)) // client vs client { c1 = mob1->CastToClient(); c2 = mob2->CastToClient(); if // if both are pvp they can fight ( c1->GetPVP() && c2->GetPVP() ) return true; else if // if they're dueling they can go at it ( c1->IsDueling() && c2->IsDueling() && c1->GetDuelTarget() == c2->GetID() && c2->GetDuelTarget() == c1->GetID() ) return true; else return false; } else if(_NPC(mob2)) // client vs npc { return true; } else if(_BECOMENPC(mob2)) // client vs becomenpc { c1 = mob1->CastToClient(); becomenpc = mob2->CastToClient(); if(c1->GetLevel() > becomenpc->GetBecomeNPCLevel()) return false; else return true; } else if(_CLIENTCORPSE(mob2)) // client vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // client vs npc corpse { return false; } } else if(_NPC(mob1)) { if(_NPC(mob2)) // npc vs npc { /* this says that an NPC can NEVER attack a faction ally... this is stupid... somebody else should check this rule if they want to enforce it, this just says 'can they possibly fight based on their type', in which case, the answer is yes. */ /* npc1 = mob1->CastToNPC(); npc2 = mob2->CastToNPC(); if ( npc1->GetPrimaryFaction() != 0 && npc2->GetPrimaryFaction() != 0 && ( npc1->GetPrimaryFaction() == npc2->GetPrimaryFaction() || npc1->IsFactionListAlly(npc2->GetPrimaryFaction()) ) ) return false; else */ return true; } else if(_BECOMENPC(mob2)) // npc vs becomenpc { return true; } else if(_CLIENTCORPSE(mob2)) // npc vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // npc vs npc corpse { return false; } } else if(_BECOMENPC(mob1)) { if(_BECOMENPC(mob2)) // becomenpc vs becomenpc { return true; } else if(_CLIENTCORPSE(mob2)) // becomenpc vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // becomenpc vs npc corpse { return false; } } else if(_CLIENTCORPSE(mob1)) { if(_CLIENTCORPSE(mob2)) // client corpse vs client corpse { return false; } else if(_NPCCORPSE(mob2)) // client corpse vs npc corpse { return false; } } else if(_NPCCORPSE(mob1)) { if(_NPCCORPSE(mob2)) // npc corpse vs npc corpse { return false; } } #ifdef BOTS bool HasRuleDefined = false; bool IsBotAttackAllowed = false; IsBotAttackAllowed = Bot::IsBotAttackAllowed(mob1, mob2, HasRuleDefined); if(HasRuleDefined) return IsBotAttackAllowed; #endif //BOTS // we fell through, now we swap the 2 mobs and run through again once more tempmob = mob1; mob1 = mob2; mob2 = tempmob; } while( reverse++ == 0 ); Log.Out(Logs::General, Logs::None, "Mob::IsAttackAllowed: don't have a rule for this - %s vs %s\n", this->GetName(), target->GetName()); return false; }
bool Spawn2::Process() { _ZP(Spawn2_Process); IsDespawned = false; if(!Enabled()) return true; //grab our spawn group SpawnGroup* sg = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); if(NPCPointerValid() && (sg->despawn == 0 || condition_id != 0)) return true; if (timer.Check()) { timer.Disable(); _log(SPAWNS__MAIN, "Spawn2 %d: Timer has triggered", spawn2_id); //first check our spawn condition, if this isnt active //then we reset the timer and try again next time. if(condition_id != SC_AlwaysEnabled && !zone->spawn_conditions.Check(condition_id, condition_min_value)) { _log(SPAWNS__CONDITIONS, "Spawn2 %d: spawning prevented by spawn condition %d", spawn2_id, condition_id); Reset(); return(true); } if (sg == NULL) { database.LoadSpawnGroupsByID(spawngroup_id_,&zone->spawn_group_list); sg = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); } if (sg == NULL) { _log(SPAWNS__MAIN, "Spawn2 %d: Unable to locate spawn group %d. Disabling.", spawn2_id, spawngroup_id_); return false; } //have the spawn group pick an NPC for us uint32 npcid = sg->GetNPCType(); if (npcid == 0) { _log(SPAWNS__MAIN, "Spawn2 %d: Spawn group %d did not yeild an NPC! not spawning.", spawn2_id, spawngroup_id_); Reset(); //try again later (why?) return(true); } //try to find our NPC type. const NPCType* tmp = database.GetNPCType(npcid); if (tmp == NULL) { _log(SPAWNS__MAIN, "Spawn2 %d: Spawn group %d yeilded an invalid NPC type %d", spawn2_id, spawngroup_id_, npcid); Reset(); //try again later return(true); } if(tmp->unique_spawn_by_name) { if(!entity_list.LimitCheckName(tmp->name)) { _log(SPAWNS__MAIN, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is unique and one already exists.", spawn2_id, spawngroup_id_, npcid); timer.Start(5000); //try again in five seconds. return(true); } } if(tmp->spawn_limit > 0) { if(!entity_list.LimitCheckType(npcid, tmp->spawn_limit)) { _log(SPAWNS__MAIN, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is over its spawn limit (%d)", spawn2_id, spawngroup_id_, npcid, tmp->spawn_limit); timer.Start(5000); //try again in five seconds. return(true); } } if(sg->despawn != 0 && condition_id == 0) zone->Despawn(spawn2_id); if(IsDespawned) return true; if(spawn2_id) database.UpdateSpawn2Timeleft(spawn2_id, zone->GetInstanceID(), 0); currentnpcid = npcid; NPC* npc = new NPC(tmp, this, x, y, z, heading, FlyMode3); //DCBOOKMARK npc->mod_prespawn(this); npcthis = npc; npc->AddLootTable(); npc->SetSp2(spawngroup_id_); npc->SaveGuardPointAnim(anim); npc->SetAppearance((EmuAppearance)anim); entity_list.AddNPC(npc); //this limit add must be done after the AddNPC since we need the entity ID. entity_list.LimitAddNPC(npc); if(sg->roamdist && sg->roambox[0] && sg->roambox[1] && sg->roambox[2] && sg->roambox[3] && sg->delay) npc->AI_SetRoambox(sg->roamdist,sg->roambox[0],sg->roambox[1],sg->roambox[2],sg->roambox[3],sg->delay); if(zone->InstantGrids()) { _log(SPAWNS__MAIN, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f).", spawn2_id, spawngroup_id_, npc->GetName(), npcid, x, y, z); LoadGrid(); } else { _log(SPAWNS__MAIN, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f). Grid loading delayed.", spawn2_id, spawngroup_id_, tmp->name, npcid, x, y, z); } } return true; }
bool Spawn2::Process() { IsDespawned = false; if (!Enabled()) return true; //grab our spawn group SpawnGroup *spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); if (NPCPointerValid() && (spawn_group->despawn == 0 || condition_id != 0)) { return true; } if (timer.Check()) { timer.Disable(); Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Timer has triggered", spawn2_id); //first check our spawn condition, if this isnt active //then we reset the timer and try again next time. if (condition_id != SC_AlwaysEnabled && !zone->spawn_conditions.Check(condition_id, condition_min_value)) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: spawning prevented by spawn condition %d", spawn2_id, condition_id); Reset(); return (true); } if (spawn_group == nullptr) { database.LoadSpawnGroupsByID(spawngroup_id_, &zone->spawn_group_list); spawn_group = zone->spawn_group_list.GetSpawnGroup(spawngroup_id_); } if (spawn_group == nullptr) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Unable to locate spawn group %d. Disabling.", spawn2_id, spawngroup_id_); return false; } //have the spawn group pick an NPC for us uint32 npcid = spawn_group->GetNPCType(); if (npcid == 0) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d did not yeild an NPC! not spawning.", spawn2_id, spawngroup_id_); Reset(); //try again later (why?) return (true); } //try to find our NPC type. const NPCType *tmp = database.LoadNPCTypesData(npcid); if (tmp == nullptr) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded an invalid NPC type %d", spawn2_id, spawngroup_id_, npcid); Reset(); //try again later return (true); } if (tmp->unique_spawn_by_name) { if (!entity_list.LimitCheckName(tmp->name)) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is unique and one already exists.", spawn2_id, spawngroup_id_, npcid); timer.Start(5000); //try again in five seconds. return (true); } } if (tmp->spawn_limit > 0) { if (!entity_list.LimitCheckType(npcid, tmp->spawn_limit)) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Spawn group %d yeilded NPC type %d, which is over its spawn limit (%d)", spawn2_id, spawngroup_id_, npcid, tmp->spawn_limit); timer.Start(5000); //try again in five seconds. return (true); } } bool ignore_despawn = false; if (npcthis) { ignore_despawn = npcthis->IgnoreDespawn(); } if (ignore_despawn) { return true; } if (spawn_group->despawn != 0 && condition_id == 0 && !ignore_despawn) { zone->Despawn(spawn2_id); } if (IsDespawned) { return true; } currentnpcid = npcid; NPC *npc = new NPC(tmp, this, glm::vec4(x, y, z, heading), GravityBehavior::Water); npc->mod_prespawn(this); npcthis = npc; npc->AddLootTable(); if (npc->DropsGlobalLoot()) { npc->CheckGlobalLootTables(); } npc->SetSp2(spawngroup_id_); npc->SaveGuardPointAnim(anim); npc->SetAppearance((EmuAppearance) anim); entity_list.AddNPC(npc); //this limit add must be done after the AddNPC since we need the entity ID. entity_list.LimitAddNPC(npc); /** * Roambox init */ if (spawn_group->roamdist && spawn_group->roambox[0] && spawn_group->roambox[1] && spawn_group->roambox[2] && spawn_group->roambox[3] && spawn_group->delay && spawn_group->min_delay) { npc->AI_SetRoambox( spawn_group->roamdist, spawn_group->roambox[0], spawn_group->roambox[1], spawn_group->roambox[2], spawn_group->roambox[3], spawn_group->delay, spawn_group->min_delay ); } if (zone->InstantGrids()) { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f).", spawn2_id, spawngroup_id_, npc->GetName(), npcid, x, y, z); LoadGrid(); } else { Log(Logs::Detail, Logs::Spawns, "Spawn2 %d: Group %d spawned %s (%d) at (%.3f, %.3f, %.3f). Grid loading delayed.", spawn2_id, spawngroup_id_, tmp->name, npcid, x, y, z); } } return true; }
void Tribe::Advance(csTicks when, EventManager* eventmgr) { int delta = when - lastAdvance; if(delta < 0) // Handle wrappover of tick { delta = 250; // We just set it to the event timer. } lastAdvance = when; // Manage Wealth if(when - lastGrowth > 1000) { float growth; // We need to help tribes that have no members with some resources // so that they can spawn the first entity if(AliveCount() <= 0 && CountResource(wealthResourceName) < reproductionCost) { growth = wealthResourceGrowth; } else if(CountResource(wealthResourceName) < wealthResourceGrowthActiveLimit) { // Some tribes need constant growth in wealth, though capped to a limit // to prevent tribes with no strain on the resources to grow // infinit in wealth growth = wealthResourceGrowthActive; } else { growth = 0; } // Now calculate the growth. Adding what part that wasn't added // the last time this code where run. accWealthGrowth += growth* ((when - lastGrowth)/1000.0); int amount = int(floor(accWealthGrowth)); accWealthGrowth -= amount; if(amount != 0) AddResource(wealthResourceName, amount); lastGrowth = when; } else if(when - lastGrowth < 0) // Handle wrappoer of tick { lastGrowth = when; } // And manage tribe assignments csString perc; int decreaseValue = delta; // Set it to change the scale on recipe wait times // Manage cyclic recipes for(size_t i=0; i<cyclicRecipes.GetSize(); i++) { cyclicRecipes[i].timeLeft -= decreaseValue; if(cyclicRecipes[i].timeLeft <= 0) { // Add the recipe and reset counter RecipeTreeNode* newNode = new RecipeTreeNode(cyclicRecipes[i].recipe, 0); tribalRecipe->AddChild(newNode); newNode->priority = CYCLIC_RECIPE_PRIORITY; cyclicRecipes[i].timeLeft = cyclicRecipes[i].timeTotal; } } // Manage standard recipes for(size_t i=0; i < members.GetSize(); i++) { NPC* npc = members[i]; Behavior* behavior = npc->GetCurrentBehavior(); // For dead npcs just resurrect them if(!npc->IsAlive()) { // Issue the resurrect perception if we have enough // resources perc = "tribe:resurrect"; Perception pcpt(perc); if(AliveCount() == 0 && (CountResource(wealthResourceName) >= 10 * reproductionCost)) { AddResource(wealthResourceName, -10*reproductionCost); npc->TriggerEvent(&pcpt); } else if(CanGrow()) { AddResource(wealthResourceName,-reproductionCost); npc->TriggerEvent(&pcpt); } continue; } // If we have any npcs with no assignments then // we can parse recipes and send them to work if(behavior && strcasecmp(behavior->GetName(),npcIdleBehavior.GetDataSafe())==0) { RDebug(this, 5, "*** Found Idle NPC %s(%s) checking recipes ***", npc->GetName(),ShowID(npc->GetEID())); // Update recipes wait times UpdateRecipeData(decreaseValue); // Get best recipe. (highest level, no wait time) RecipeTreeNode* bestRecipe = tribalRecipe->GetNextRecipe(); if(!bestRecipe) { // Something went wrong... it should never re-parse the tribal recipe // High chances to be a scripting error break; } if(bestRecipe->wait <= 0) { RDebug(this, 5, "Applying recipe %s.", bestRecipe->recipe->GetName().GetDataSafe()); bestRecipe->nextStep = recipeManager->ApplyRecipe( bestRecipe, this, bestRecipe->nextStep); } // If nextStep is -1 => Recipe is Completed if(bestRecipe->nextStep == -1) { // TODO -- Remove TimeStamp csString rName = bestRecipe->recipe->GetName(); if(rName != "do nothing") { RDebug(this, 5, "Recipe %s completed.", rName.GetData()); } tribalRecipe->RemoveChild(bestRecipe->recipe); } // If nextStep is -2, the recipe has unmet requirements else if(bestRecipe->nextStep == -2) { bestRecipe->nextStep = 0; } } } }
void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { if(!sender || !attacker) return; if (sender->GetPrimaryFaction() == 0 ) return; // well, if we dont have a faction set, we're gonna be indiff to everybody if (sender->HasAssistAggro()) return; for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob) continue; if (mob->CheckAggro(attacker)) continue; if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) break; float r = mob->GetAssistRange(); r = r * r; if ( mob != sender && mob != attacker // && !mob->IsCorpse() // && mob->IsAIControlled() && mob->GetPrimaryFaction() != 0 && DistanceSquared(mob->GetPosition(), sender->GetPosition()) <= r && !mob->IsEngaged() && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) // If we're a pet we don't react to any calls for help if our owner is a client ) { //if they are in range, make sure we are not green... //then jump in if they are our friend if(mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) { bool useprimfaction = false; if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); if(cf){ if(cf->assistprimaryfaction != 0) useprimfaction = true; } } if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) { //attacking someone on same faction, or a friend //Father Nitwit: make sure we can see them. if(mob->CheckLosFN(sender)) { #if (EQDEBUG>=5) Log(Logs::General, Logs::None, "AIYellForHelp(\"%s\",\"%s\") %s attacking %s Dist %f Z %f", sender->GetName(), attacker->GetName(), mob->GetName(), attacker->GetName(), DistanceSquared(mob->GetPosition(), sender->GetPosition()), fabs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 25, 0, false); sender->AddAssistCap(); } } } } } }