/* * Copyright (C) 2005,2006 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "Common.h" #include "Database/DatabaseEnv.h" #include "WorldPacket.h" #include "WorldSession.h" #include "World.h" #include "ObjectMgr.h" #include "Creature.h" #include "QuestDef.h" #include "GossipDef.h" #include "Player.h" #include "Opcodes.h" #include "Log.h" #include "LootMgr.h" #include "Chat.h" #include "MapManager.h" #include "CreatureAI.h" #include "CreatureAISelector.h" #include "Pet.h" #include "Formulas.h" // apply implementation of the singletons #include "Policies/SingletonImp.h" uint32 CreatureInfo::randomDisplayID() const { if(DisplayID_f==0) return DisplayID_m; else if(DisplayID_m==0) return DisplayID_f; else return urand(0,1) ? DisplayID_m : DisplayID_f; } Creature::Creature() : Unit(), i_AI(NULL), lootForPickPocketed(false), lootForBody(false), m_lootMoney(0), m_deathTimer(0), m_respawnTimer(0), m_respawnDelay(25000), m_corpseDelay(60000), m_respawnradius(0.0), itemcount(0), mTaxiNode(0), m_moveBackward(false), m_moveRandom(false), m_moveRun(false), m_emoteState(0), m_isPet(false), m_isTotem(false), m_isTamed(false), m_regenTimer(2000), m_defaultMovementType(IDLE_MOTION_TYPE) { m_valuesCount = UNIT_END; memset(item_list, 0, sizeof(CreatureItem)*MAX_CREATURE_ITEMS); for(int i =0; i<3; ++i) respawn_cord[i] = 0.0; m_spells[0] = 0; m_spells[1] = 0; m_spells[2] = 0; m_spells[3] = 0; m_AlreadyCallAssistence = false; } Creature::~Creature() { CombatStop(); RemoveAllAuras(); for( SpellsList::iterator i = m_tspells.begin( ); i != m_tspells.end( ); i++ ) delete (*i); m_tspells.clear(); delete i_AI; i_AI = NULL; } void Creature::CreateTrainerSpells() { for(SpellsList::iterator i = m_tspells.begin(); i != m_tspells.end(); ++i) delete *i; m_tspells.clear(); TrainerSpell *tspell; Field *fields; QueryResult *result = sDatabase.PQuery("SELECT `spell`,`spellcost`,`reqspell`,`reqskill`,`reqskillvalue`,`reqlevel` FROM `npc_trainer` WHERE `entry` = '%u'", GetCreatureInfo()->Entry); if(!result) return; SpellEntry * spellinfo; do { fields = result->Fetch(); uint32 spellid = fields[0].GetUInt32(); spellinfo = sSpellStore.LookupEntry(spellid); if(!spellinfo) { sLog.outError("Trainer (GUID: %u ID: %u ) have in list non existed spell %u",GetGUIDLow(),GetEntry(),spellid); continue; } tspell = new TrainerSpell; tspell->spell = spellinfo; tspell->spellcost = fields[1].GetUInt32(); tspell->reqspell = fields[2].GetUInt32(); tspell->reqskill = fields[3].GetUInt32(); tspell->reqskillvalue = fields[4].GetUInt32(); tspell->reqlevel = fields[5].GetUInt32(); if(tspell->reqspell && !sSpellStore.LookupEntry(tspell->reqspell)) { sLog.outError("Trainer (GUID: %u ID: %u ) have for spell %u non-existed required spell %u (requirement ignored)",GetGUIDLow(),GetEntry(),spellid,tspell->reqspell); tspell->reqspell = 0; } m_tspells.push_back(tspell); } while( result->NextRow() ); delete result; } bool Creature::isCanSwimOrFly() const { uint32 family = GetCreatureInfo()->family; // Creature that can swim in water or fly above water or walk by bottom (maybe wrong family filter - please fix) return (family != 3 && family != 10 && family != 11 && family != 12 && family != 20 && family != 21 && family != 27 ); } bool Creature::isCanWalkOrFly() const { //uint32 family = GetCreatureInfo()->family; // Creature that can walk by ground or fly above ground (maybe wrong family filter - please fix) // I not found special family for fish :( return true; } void Creature::AIM_Update(const uint32 &diff) { switch( m_deathState ) { case JUST_DIED: assert(false); // Dont must be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting. break; case DEAD: { if( m_respawnTimer <= diff ) { DEBUG_LOG("Respawning..."); CreatureInfo const *cinfo = objmgr.GetCreatureTemplate(this->GetEntry()); SelectLevel(cinfo); SetUInt32Value(UNIT_DYNAMIC_FLAGS, 0); RemoveFlag (UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); SetUInt32Value(UNIT_NPC_FLAGS, cinfo->npcflag); SetHealth(GetMaxHealth()); setDeathState( ALIVE ); clearUnitState(UNIT_STAT_ALL_STATE); i_motionMaster.Clear(); MapManager::Instance().GetMap(GetMapId())->Add(this); } else m_respawnTimer -= diff; break; } case CORPSE: { if( m_deathTimer <= diff ) { m_deathTimer = 0; DEBUG_LOG("Removing corpse... %u ", GetUInt32Value(OBJECT_FIELD_ENTRY)); ObjectAccessor::Instance().RemoveCreatureCorpseFromPlayerView(this); lootForPickPocketed = false; lootForBody = false; loot.clear(); setDeathState(DEAD); m_respawnTimer = m_respawnDelay; if ( isTamed() ) Untamed(); float x,y,z; GetRespawnCoord(x, y, z); MapManager::Instance().GetMap(GetMapId())->CreatureRelocation(this,x,y,z,GetOrientation()); } else m_deathTimer -= diff; break; } case ALIVE: { if(isPet() || isTamed()) { // unsummon pet that lost owner Unit* owner = GetOwner(); if(!owner||!IsWithinDistInMap(owner, OWNER_MAX_DISTANCE)) { if(isPet()) ((Pet*)this)->Unsummon(); else if(isTamed()) Untamed(); return; } } Unit::Update( diff ); i_motionMaster.UpdateMotion(diff); i_AI->UpdateAI(diff); if(m_regenTimer > 0) { if(diff >= m_regenTimer) m_regenTimer = 0; else m_regenTimer -= diff; } if (m_regenTimer != 0) break; if (!isInCombat()) { RegenerateHealth(); RegenerateMana(); } m_regenTimer = 2000; break; } default: break; } } void Creature::RegenerateMana() { uint32 curValue = GetPower(POWER_MANA); uint32 maxValue = GetMaxPower(POWER_MANA); if (curValue >= maxValue) return; float ManaIncreaseRate = sWorld.getRate(RATE_POWER_MANA); float Spirit = GetStat(STAT_SPIRIT); if( ManaIncreaseRate <= 0 ) ManaIncreaseRate = 1; uint32 addvalue = 0; if (isInCombat() || isPet()) addvalue = uint32((Spirit/5 + 17) * ManaIncreaseRate); else addvalue = maxValue/3; ModifyPower(POWER_MANA, addvalue); } void Creature::RegenerateHealth() { uint32 curValue = GetHealth(); uint32 maxValue = GetMaxHealth(); if (curValue >= maxValue) return; float HealthIncreaseRate = sWorld.getRate(RATE_HEALTH); float Spirit = GetStat(STAT_SPIRIT); if( HealthIncreaseRate <= 0 ) HealthIncreaseRate = 1; uint32 addvalue = 0; if(isPet()) { if( GetPower(POWER_MANA) > 0 ) addvalue = uint32(Spirit * 0.25 * HealthIncreaseRate); else addvalue = uint32(Spirit * 0.80 * HealthIncreaseRate); } else addvalue = maxValue/3; ModifyHealth(addvalue); } void Creature::AIM_Initialize() { i_motionMaster.Initialize(this); i_AI = FactorySelector::selectAI(this); } void Creature::Update( uint32 p_time ) { AIM_Update( p_time ); } bool Creature::Create (uint32 guidlow, uint32 mapid, float x, float y, float z, float ang, uint32 Entry) { respawn_cord[0] = x; respawn_cord[1] = y; respawn_cord[2] = z; m_mapId =mapid; m_positionX =x; m_positionY=y; m_positionZ=z; if(!IsPositionValid()) { sLog.outError("ERROR: Creature (guidlow %d, entry %d) not created. Suggested coordinates isn't valid (X: %d Y: ^%d)",guidlow,Entry,x,y); return false; } m_orientation=ang; //oX = x; oY = y; dX = x; dY = y; m_moveTime = 0; m_startMove = 0; return CreateFromProto(guidlow, Entry); } uint32 Creature::getDialogStatus(Player *pPlayer, uint32 defstatus) { uint32 result = DIALOG_STATUS_NONE; uint32 status; uint32 quest_id; Quest *pQuest; for( std::list::iterator i = mInvolvedQuests.begin( ); i != mInvolvedQuests.end( ); i++ ) { quest_id = *i; pQuest = objmgr.QuestTemplates[quest_id]; status = pPlayer->GetQuestStatus( quest_id ); if ((status == QUEST_STATUS_COMPLETE && !pPlayer->GetQuestRewardStatus(quest_id)) || ((strlen(pQuest->GetObjectives()) == 0) && pPlayer->CanTakeQuest(pQuest, false))) { SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); if ( pQuest->IsRepeatable() ) return DIALOG_STATUS_REWARD_REP; else return DIALOG_STATUS_REWARD; } else if ( status == QUEST_STATUS_INCOMPLETE ) result = DIALOG_STATUS_INCOMPLETE; } RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TRACK_UNIT); if ( result == DIALOG_STATUS_INCOMPLETE ) return result; for( std::list::iterator i = mQuests.begin( ); i != mQuests.end( ); i++ ) { pQuest = objmgr.QuestTemplates[*i]; if ( !pQuest ) continue; quest_id = pQuest->GetQuestId(); status = pPlayer->GetQuestStatus( quest_id ); if ( status == QUEST_STATUS_NONE ) { if ( pPlayer->CanSeeStartQuest( quest_id ) ) { if ( pPlayer->SatisfyQuestLevel(quest_id, false) ) { if ( pQuest->IsRepeatable() ) return DIALOG_STATUS_REWARD_REP; else return DIALOG_STATUS_AVAILABLE; } result = DIALOG_STATUS_UNAVAILABLE; } } } // can train and help unlearn talentes (2 action -> chat menu) if( isCanTrainingAndResetTalentsOf(pPlayer) ) return DIALOG_STATUS_CHAT; if ( result == DIALOG_STATUS_UNAVAILABLE ) return result; if ( defstatus == DIALOG_STATUS_NONE ) return DIALOG_STATUS_NONE; else return DIALOG_STATUS_CHAT; } bool Creature::isCanTrainingOf(Player* pPlayer, bool msg) const { if(!isTrainer()) return false; if(m_tspells.empty()) { sLog.outError("Creature %u (Entry: %u) have UNIT_NPC_FLAG_TRAINER but have empty trainer spell list.", GetGUIDLow(),GetCreatureInfo()->Entry); return false; } switch(GetCreatureInfo()->trainer_type) { case TRAINER_TYPE_CLASS: if(pPlayer->getClass()!=GetCreatureInfo()->classNum) { if(msg) { pPlayer->PlayerTalkClass->ClearMenus(); switch(GetCreatureInfo()->classNum) { case CLASS_DRUID: pPlayer->PlayerTalkClass->SendGossipMenu( 4913,GetGUID()); break; case CLASS_HUNTER: pPlayer->PlayerTalkClass->SendGossipMenu(10090,GetGUID()); break; case CLASS_MAGE: pPlayer->PlayerTalkClass->SendGossipMenu( 328,GetGUID()); break; case CLASS_PALADIN:pPlayer->PlayerTalkClass->SendGossipMenu( 1635,GetGUID()); break; case CLASS_PRIEST: pPlayer->PlayerTalkClass->SendGossipMenu( 4436,GetGUID()); break; case CLASS_ROGUE: pPlayer->PlayerTalkClass->SendGossipMenu( 4797,GetGUID()); break; case CLASS_SHAMAN: pPlayer->PlayerTalkClass->SendGossipMenu( 5003,GetGUID()); break; case CLASS_WARLOCK:pPlayer->PlayerTalkClass->SendGossipMenu( 5836,GetGUID()); break; case CLASS_WARRIOR:pPlayer->PlayerTalkClass->SendGossipMenu( 4985,GetGUID()); break; } } return false; } break; case TRAINER_TYPE_PETS: if(pPlayer->getClass()!=CLASS_HUNTER) { pPlayer->PlayerTalkClass->ClearMenus(); pPlayer->PlayerTalkClass->SendGossipMenu(3620,GetGUID()); return false; } break; case TRAINER_TYPE_MOUNTS: if(GetCreatureInfo()->race && pPlayer->getRace() != GetCreatureInfo()->race) { if(msg) { pPlayer->PlayerTalkClass->ClearMenus(); switch(GetCreatureInfo()->classNum) { case RACE_DWARF: pPlayer->PlayerTalkClass->SendGossipMenu(5865,GetGUID()); break; case RACE_GNOME: pPlayer->PlayerTalkClass->SendGossipMenu(4881,GetGUID()); break; case RACE_HUMAN: pPlayer->PlayerTalkClass->SendGossipMenu(5861,GetGUID()); break; case RACE_NIGHTELF: pPlayer->PlayerTalkClass->SendGossipMenu(5862,GetGUID()); break; case RACE_ORC: pPlayer->PlayerTalkClass->SendGossipMenu(5863,GetGUID()); break; case RACE_TAUREN: pPlayer->PlayerTalkClass->SendGossipMenu(5864,GetGUID()); break; case RACE_TROLL: pPlayer->PlayerTalkClass->SendGossipMenu(5816,GetGUID()); break; case RACE_UNDEAD_PLAYER:pPlayer->PlayerTalkClass->SendGossipMenu( 624,GetGUID()); break; } } return false; } break; case TRAINER_TYPE_TRADESKILLS: if(GetCreatureInfo()->trainer_spell && !pPlayer->HasSpell(GetCreatureInfo()->trainer_spell)) { if(msg) { pPlayer->PlayerTalkClass->ClearMenus(); pPlayer->PlayerTalkClass->SendGossipMenu(11031,GetGUID()); } return false; } break; default: sLog.outError("Creature %u (entry: %u) have trainer type %u",GetGUIDLow(),GetCreatureInfo()->Entry,GetCreatureInfo()->trainer_type); return false; } return true; } bool Creature::isCanTrainingAndResetTalentsOf(Player* pPlayer) const { return pPlayer->getLevel() >= 10 && GetCreatureInfo()->trainer_type == TRAINER_TYPE_CLASS && pPlayer->getClass() == GetCreatureInfo()->classNum; } void Creature::prepareGossipMenu( Player *pPlayer,uint32 gossipid ) { PlayerMenu* pm=pPlayer->PlayerTalkClass; pm->ClearMenus(); if(!m_goptions.size()) LoadGossipOptions(); GossipOption* gso; GossipOption* ingso; for( std::list::iterator i = m_goptions.begin( ); i != m_goptions.end( ); i++ ) { gso=*i; if(gso->GossipId == gossipid) { bool cantalking=true; if(gso->Id==1) { uint32 textid=GetNpcTextId(); GossipText * gossiptext=objmgr.GetGossipText(textid); if(!gossiptext) cantalking=false; } else { switch (gso->Action) { case GOSSIP_OPTION_QUESTGIVER: pPlayer->PrepareQuestMenu(GetGUID()); //if (pm->GetQuestMenu()->MenuItemCount() == 0) cantalking=false; //pm->GetQuestMenu()->ClearMenu(); break; case GOSSIP_OPTION_ARMORER: cantalking=false; // added in special mode break; case GOSSIP_OPTION_SPIRITHEALER: if( !pPlayer->isDead() ) cantalking=false; break; case GOSSIP_OPTION_VENDOR: if(!GetItemCount()) { sLog.outError("Creature %u (Entry: %u) have UNIT_NPC_FLAG_VENDOR but have empty trading item list.", GetGUIDLow(),GetCreatureInfo()->Entry); cantalking=false; } break; case GOSSIP_OPTION_TRAINER: if(!isCanTrainingOf(pPlayer,false)) cantalking=false; break; case GOSSIP_OPTION_UNLEARNTALENTS: if(!isCanTrainingAndResetTalentsOf(pPlayer)) cantalking=false; break; case GOSSIP_OPTION_TAXIVENDOR: case GOSSIP_OPTION_GUARD: case GOSSIP_OPTION_INNKEEPER: case GOSSIP_OPTION_BANKER: case GOSSIP_OPTION_PETITIONER: case GOSSIP_OPTION_STABLEPET: case GOSSIP_OPTION_TABARDVENDOR: case GOSSIP_OPTION_BATTLEFIELD: case GOSSIP_OPTION_AUCTIONEER: break; // no checks default: sLog.outError("Creature %u (entry: %u) have unknown gossip option %u",GetGUIDLow(),GetCreatureInfo()->Entry,gso->Action); break; } } if(gso->Option!="" && cantalking ) { pm->GetGossipMenu()->AddMenuItem((uint8)gso->Icon,gso->Option.c_str(), gossipid,gso->Action,false); ingso=gso; } } } if(pm->GetGossipMenu()->MenuItemCount()==0 && HasFlag(UNIT_NPC_FLAGS,UNIT_NPC_FLAG_TRAINER)) isCanTrainingOf(pPlayer,true); /* if(pm->GetGossipMenu()->MenuItemCount()==1 && ingso->Id==8 && GetGossipCount( ingso->GossipId )>0) { pm->ClearMenus(); for( std::list::iterator i = m_goptions.begin( ); i != m_goptions.end( ); i++ ) { gso=*i; if(gso->GossipId==ingso->Id) { if(gso->Option!="") pm->GetGossipMenu()->AddMenuItem((uint8)gso->Icon,gso->Option.c_str(),ingso->GossipId,gso->Action,false); } } } */ } void Creature::sendPreparedGossip(Player* player) { if(!player) return; GossipMenu* gossipmenu = player->PlayerTalkClass->GetGossipMenu(); // in case empty gossip menu open quest menu if any if (gossipmenu->MenuItemCount() == 0) { player->SendPreparedQuest(GetGUID()); return; } // in case non empty gossip menu (that not included quests list size) show it // (quest entries from quest menu wiill be included in list) player->PlayerTalkClass->SendGossipMenu(GetNpcTextId(), GetGUID()); } void Creature::OnGossipSelect(Player* player, uint32 option) { GossipMenu* gossipmenu = player->PlayerTalkClass->GetGossipMenu(); uint32 action=gossipmenu->GetItem(option).m_gAction; uint32 zoneid=GetZoneId(); uint64 guid=GetGUID(); GossipOption *gossip=GetGossipOption( action ); uint32 textid; if(!gossip) { zoneid=0; gossip=GetGossipOption( action ); if(!gossip) return; } textid=GetGossipTextId( action, zoneid); if(textid==0) textid=GetNpcTextId(); switch (gossip->Action) { case GOSSIP_OPTION_GOSSIP: player->PlayerTalkClass->SendTalking( textid ); break; case GOSSIP_OPTION_SPIRITHEALER: if( player->isDead() ) player->GetSession()->SendSpiritResurrect(); break; case GOSSIP_OPTION_QUESTGIVER: player->PrepareQuestMenu( guid ); player->SendPreparedQuest( guid ); break; case GOSSIP_OPTION_VENDOR: case GOSSIP_OPTION_ARMORER: player->GetSession()->SendListInventory(guid); break; case GOSSIP_OPTION_STABLEPET: player->GetSession()->SendStablePet(guid); break; case GOSSIP_OPTION_TRAINER: player->GetSession()->SendTrainerList(guid); break; case GOSSIP_OPTION_UNLEARNTALENTS: player->PlayerTalkClass->CloseGossip(); player->SendTalentWipeConfirm(guid); break; case GOSSIP_OPTION_TAXIVENDOR: player->GetSession()->SendTaxiMenu(guid); break; case GOSSIP_OPTION_INNKEEPER: player->PlayerTalkClass->CloseGossip(); player->SetBindPoint( guid ); break; case GOSSIP_OPTION_BANKER: player->GetSession()->SendShowBank( guid ); break; case GOSSIP_OPTION_PETITIONER: case GOSSIP_OPTION_TABARDVENDOR: player->GetSession()->SendTabardVendorActivate( guid ); break; case GOSSIP_OPTION_AUCTIONEER: player->GetSession()->SendAuctionHello( guid, this ); break; case GOSSIP_OPTION_GUARD: case GOSSIP_GUARD_SPELLTRAINER: case GOSSIP_GUARD_SKILLTRAINER: prepareGossipMenu( player,gossip->Id ); sendPreparedGossip( player ); break; default: OnPoiSelect( player, gossip ); break; } } void Creature::OnPoiSelect(Player* player, GossipOption *gossip) { if(gossip->GossipId==GOSSIP_OPTION_GUARD || gossip->GossipId==GOSSIP_GUARD_SPELLTRAINER || gossip->GossipId==GOSSIP_GUARD_SKILLTRAINER) { float x,y; bool findnpc=false; Poi_Icon icon = ICON_POI_0; QueryResult *result; Field *fields; uint32 mapid=GetMapId(); Map* map=MapManager::Instance().GetMap( mapid ); uint16 areaflag=map->GetAreaFlag(m_positionX,m_positionY); uint32 zoneid=map->GetZoneId(areaflag); std::string areaname= gossip->Option; uint16 pflag; // use the action relate to creaturetemplate.trainer_type ? result= sDatabase.PQuery("SELECT `creature`.`position_x`,`creature`.`position_y` FROM `creature`,`creature_template` WHERE `creature`.`map` = '%u' AND `creature`.`id` = `creature_template`.`entry` AND `creature_template`.`trainer_type` = '%u'", mapid, gossip->Action ); if(!result) return; do { fields = result->Fetch(); x=fields[0].GetFloat(); y=fields[1].GetFloat(); pflag=map->GetAreaFlag(m_positionX,m_positionY); if(pflag==areaflag) { findnpc=true; break; } }while(result->NextRow()); delete result; if(!findnpc) { player->PlayerTalkClass->SendTalking( "$NSorry", "Here no this person."); return; } //need add more case. switch(gossip->Action) { case GOSSIP_GUARD_BANK: icon=ICON_POI_HOUSE; break; case GOSSIP_GUARD_RIDE: icon=ICON_POI_RWHORSE; break; case GOSSIP_GUARD_GUILD: icon=ICON_POI_BLUETOWER; break; default: icon=ICON_POI_TOWER; break; } uint32 textid=GetGossipTextId( gossip->Action, zoneid ); player->PlayerTalkClass->SendTalking( textid ); player->PlayerTalkClass->SendPointOfInterest( x, y, icon, 2, 15, areaname.c_str() ); } } uint32 Creature::GetGossipTextId(uint32 action, uint32 zoneid) { QueryResult *result= sDatabase.PQuery("SELECT `textid` FROM `npc_gossip_textid` WHERE `action` = '%u' AND `zoneid` ='%u'", action, zoneid ); if(!result) return 0; Field *fields = result->Fetch(); uint32 id = fields[0].GetUInt32(); delete result; return id; } uint32 Creature::GetGossipCount( uint32 gossipid ) { uint32 count=0; GossipOption* gso; for( std::list::iterator i = m_goptions.begin( ); i != m_goptions.end( ); i++ ) { gso=*i; if(gso->GossipId == gossipid ) count++; } return count; } uint32 Creature::GetNpcTextId() { QueryResult *result = sDatabase.PQuery("SELECT `textid` FROM `npc_gossip` WHERE `npc_guid`= '%u' LIMIT 1",GetEntry()); if(result) { Field *fields = result->Fetch(); uint32 id = fields[0].GetUInt32(); delete result; return id; } return DEFAULT_GOSSIP_MESSAGE; } std::string Creature::GetGossipTitle(uint8 type,uint32 id) { GossipOption* gso; for( std::list::iterator i = m_goptions.begin( ); i != m_goptions.end( ); i++ ) { gso=*i; if(gso->Id==id && gso->NpcFlag==(uint32)type) return gso->Option; } return NULL; } GossipOption* Creature::GetGossipOption( uint32 id ) { GossipOption* gso; for( std::list::iterator i = m_goptions.begin( ); i != m_goptions.end( ); i++ ) { gso=*i; if(gso->Action==id ) return gso; } return NULL; } void Creature::LoadGossipOptions() { uint32 npcflags=GetUInt32Value(UNIT_NPC_FLAGS); QueryResult *result = sDatabase.PQuery( "SELECT `id`,`gossip_id`,`npcflag`,`icon`,`action`,`option` FROM `npc_option` WHERE (npcflag & %u)!=0", npcflags ); if(!result) return; GossipOption *go; do { Field *fields = result->Fetch(); go=new GossipOption; go->Id= fields[0].GetUInt32(); go->GossipId = fields[1].GetUInt32(); go->NpcFlag=fields[2].GetUInt32(); go->Icon=fields[3].GetUInt32(); go->Action=fields[4].GetUInt32(); go->Option=fields[5].GetCppString(); addGossipOption(go); }while( result->NextRow() ); delete result; } void Creature::generateMoneyLoot() { if (GetCreatureInfo()->maxgold > 0) { uint32 diff = GetCreatureInfo()->maxgold - GetCreatureInfo()->mingold + 1; // rand() result (RAND_MAX) small for large gold loots if(diff > 10000) { uint32 gold_part = rand() % (diff / 10000); uint32 silver_and_copper_part = rand() % (diff % 10000); loot.gold = uint32((gold_part * 10000 + silver_and_copper_part + GetCreatureInfo()->mingold) * sWorld.getRate(RATE_DROP_MONEY)); } else { loot.gold = uint32((rand() % diff + GetCreatureInfo()->mingold) * sWorld.getRate(RATE_DROP_MONEY)); } } } void Creature::AI_SendMoveToPacket(float x, float y, float z, uint32 time, bool run, bool WalkBack) { /* uint32 timeElap = getMSTime(); if ((timeElap - m_startMove) < m_moveTime) { oX = (dX - oX) * ( (timeElap - m_startMove) / m_moveTime ); oY = (dY - oY) * ( (timeElap - m_startMove) / m_moveTime ); } else { oX = dX; oY = dY; } dX = x; dY = y; m_orientation = atan2((oY - dY), (oX - dX)); m_startMove = getMSTime(); m_moveTime = time;*/ SendMonsterMove(x,y,z,WalkBack,run,time); } Player *Creature::GetLootRecipient() const { if (!m_lootRecipient) return NULL; else return ObjectAccessor::Instance().FindPlayer(m_lootRecipient); } void Creature::SetLootRecipient(Player *player) { // set the player whose group should receive the right // to loot the creature after it dies // should be set to NULL after the loot disappears if (!player) m_lootRecipient = 0; else m_lootRecipient = player->GetGUID(); } void Creature::SaveToDB() { sDatabase.PExecute("DELETE FROM `creature` WHERE `guid` = '%u'", GetGUIDLow()); std::ostringstream ss; ss << "INSERT INTO `creature` VALUES (" << GetGUIDLow () << "," << GetEntry() << "," << m_mapId <<"," << m_positionX << "," << m_positionY << "," << m_positionZ << "," << m_orientation << "," << m_respawnDelay << "," //spawn time min // fix me: store x-y delay but not 1 << m_respawnDelay << "," //spawn time max << (float) 0 << "," //spawn distance (float) << (uint32) (0) << "," //currentwaypoint << respawn_cord[0] << "," //spawn_position_x << respawn_cord[1] << "," //spawn_position_y << respawn_cord[2] << "," //spawn_position_z << (float)(0) << "," //spawn_orientation << GetHealth() << "," //curhealth << GetPower(POWER_MANA) << "," //curmana << m_respawnTimer << "," //respawntimer << (uint32)(m_deathState) << "," // is it really death state or just state? //or //<< (uint32)(m_state) << "," // is it really death state or just state? << GetDefaultMovementType() << "," // default movement generator type << "'')"; // should save auras sDatabase.Execute( ss.str( ).c_str( ) ); } void Creature::SelectLevel(const CreatureInfo *cinfo) { uint32 minlevel = min(cinfo->maxlevel, cinfo->minlevel); uint32 maxlevel = max(cinfo->maxlevel, cinfo->minlevel); uint32 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel); SetLevel(level); float rellevel = maxlevel == minlevel ? 0 : (float(level - minlevel))/(maxlevel - minlevel); uint32 minhealth = min(cinfo->maxhealth, cinfo->minhealth); uint32 maxhealth = max(cinfo->maxhealth, cinfo->minhealth); uint32 health = _GetHealthMod(cinfo->rank) * (minhealth + uint32(rellevel*(maxhealth - minhealth))); SetMaxHealth(health); SetUInt32Value(UNIT_FIELD_BASE_HEALTH,health); SetHealth(health); uint32 minmana = min(cinfo->maxmana, cinfo->minmana); uint32 maxmana = max(cinfo->maxmana, cinfo->minmana); uint32 mana = minmana + uint32(rellevel*(maxmana - minmana)); SetMaxPower(POWER_MANA, mana); //MAX Mana SetUInt32Value(UNIT_FIELD_BASE_MANA, mana); SetPower(POWER_MANA, mana); } float Creature::_GetHealthMod(int32 Rank) { switch (Rank) // define rates for each elite rank { case CREATURE_ELITE_NORMAL: return sWorld.getRate(RATE_CREATURE_NORMAL_HP); case CREATURE_ELITE_ELITE: return sWorld.getRate(RATE_CREATURE_ELITE_ELITE_HP); case CREATURE_ELITE_RAREELITE: return sWorld.getRate(RATE_CREATURE_ELITE_RAREELITE_HP); case CREATURE_ELITE_WORLDBOSS: return sWorld.getRate(RATE_CREATURE_ELITE_WORLDBOSS_HP); case CREATURE_ELITE_RARE: return sWorld.getRate(RATE_CREATURE_ELITE_RARE_HP); default: return sWorld.getRate(RATE_CREATURE_ELITE_ELITE_HP); } } float Creature::_GetDamageMod(int32 Rank) { switch (Rank) // define rates for each elite rank { case CREATURE_ELITE_NORMAL: return sWorld.getRate(RATE_CREATURE_NORMAL_DAMAGE); case CREATURE_ELITE_ELITE: return sWorld.getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE); case CREATURE_ELITE_RAREELITE: return sWorld.getRate(RATE_CREATURE_ELITE_RAREELITE_DAMAGE); case CREATURE_ELITE_WORLDBOSS: return sWorld.getRate(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE); case CREATURE_ELITE_RARE: return sWorld.getRate(RATE_CREATURE_ELITE_RARE_DAMAGE); default: return sWorld.getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE); } } bool Creature::CreateFromProto(uint32 guidlow,uint32 Entry) { Object::_Create(guidlow, HIGHGUID_UNIT); SetUInt32Value(OBJECT_FIELD_ENTRY,Entry); CreatureInfo const *cinfo = objmgr.GetCreatureTemplate(Entry); if(!cinfo) { sLog.outString("Error: creature entry %u does not exist.",Entry); return false; } uint32 rank = cinfo->rank; float damagemod = _GetDamageMod(rank);; uint32 display_id = cinfo->randomDisplayID(); SetUInt32Value(UNIT_FIELD_DISPLAYID,display_id ); SetUInt32Value(UNIT_FIELD_NATIVEDISPLAYID,display_id ); SelectLevel(cinfo); SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,cinfo->faction); SetUInt32Value(UNIT_NPC_FLAGS,cinfo->npcflag); SetFloatValue(UNIT_FIELD_MINDAMAGE,cinfo->mindmg * damagemod); SetFloatValue(UNIT_FIELD_MAXDAMAGE,cinfo->maxdmg * damagemod); SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE,cinfo->minrangedmg * damagemod); SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE,cinfo->maxrangedmg * damagemod); SetAttackTime(BASE_ATTACK, cinfo->baseattacktime); SetAttackTime(RANGED_ATTACK,cinfo->rangeattacktime); SetUInt32Value(UNIT_FIELD_FLAGS,cinfo->Flags); SetUInt32Value(UNIT_DYNAMIC_FLAGS,cinfo->dynamicflags); SetArmor(cinfo->armor); SetResistance(SPELL_SCHOOL_HOLY,cinfo->resistance1); SetResistance(SPELL_SCHOOL_FIRE,cinfo->resistance2); SetResistance(SPELL_SCHOOL_NATURE,cinfo->resistance3); SetResistance(SPELL_SCHOOL_FROST,cinfo->resistance4); SetResistance(SPELL_SCHOOL_SHADOW,cinfo->resistance5); SetResistance(SPELL_SCHOOL_ARCANE,cinfo->resistance6); //this is probably wrong SetUInt32Value( UNIT_VIRTUAL_ITEM_SLOT_DISPLAY, cinfo->equipmodel[0]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO , cinfo->equipinfo[0]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO + 1, cinfo->equipslot[0]); SetUInt32Value( UNIT_VIRTUAL_ITEM_SLOT_DISPLAY+1, cinfo->equipmodel[1]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO + 2, cinfo->equipinfo[1]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO + 2 + 1, cinfo->equipslot[1]); SetUInt32Value( UNIT_VIRTUAL_ITEM_SLOT_DISPLAY+2, cinfo->equipmodel[2]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO + 4, cinfo->equipinfo[2]); SetUInt32Value( UNIT_VIRTUAL_ITEM_INFO + 4 + 1, cinfo->equipslot[2]); SetFloatValue(OBJECT_FIELD_SCALE_X, cinfo->size); SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS,cinfo->bounding_radius); SetFloatValue(UNIT_FIELD_COMBATREACH,cinfo->combat_reach ); FactionEntry* factionEntry = sFactionStore.LookupEntry(cinfo->faction); if (factionEntry->team == ALLIANCE || HORDE) { SetPvP(true); } if (cinfo->mount != 0) Mount(cinfo->mount); m_spells[0] = cinfo->spell1; m_spells[1] = cinfo->spell2; m_spells[2] = cinfo->spell3; m_spells[3] = cinfo->spell4; SetSpeed( cinfo->speed ) ; if(cinfo->MovementType < MAX_DB_MOTION_TYPE) m_defaultMovementType = MovementGeneratorType(cinfo->MovementType); else { m_defaultMovementType = IDLE_MOTION_TYPE; sLog.outError("Creature template %u have wrong movement generator type value %u, ignore and set to IDLE.",Entry,cinfo->MovementType); } return true; } bool Creature::LoadFromDB(uint32 guid, QueryResult *result) { bool external = (result != NULL); if (!external) // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 result = sDatabase.PQuery("SELECT `id`,`map`,`position_x`,`position_y`,`position_z`,`orientation`,`spawntimemin`,`spawntimemax`,`spawndist`,`spawn_position_x`,`spawn_position_y`,`spawn_position_z`,`curhealth`,`curmana`,`respawntimer`,`state`,`MovementType`,`auras` FROM `creature` WHERE `guid` = '%u'", guid); if(!result) { sLog.outError("ERROR: Creature (GUID: %u) not found in table `creature`, can't load. ",guid); return false; } Field *fields = result->Fetch(); if(!Create(guid,fields[1].GetUInt32(),fields[2].GetFloat(),fields[3].GetFloat(), fields[4].GetFloat(),fields[5].GetFloat(),fields[0].GetUInt32())) { if (!external) delete result; return false; } SetHealth(fields[12].GetUInt32()); SetPower(POWER_MANA,fields[13].GetUInt32()); m_respawnradius = fields[8].GetFloat(); respawn_cord[0] = fields[9].GetFloat(); respawn_cord[1] = fields[10].GetFloat(); respawn_cord[2] = fields[11].GetFloat(); m_respawnDelay =(fields[6].GetUInt32()+fields[7].GetUInt32())*1000/2; m_respawnTimer = fields[14].GetUInt32(); m_deathState = (DeathState)fields[15].GetUInt32(); { uint32 mtg = fields[16].GetUInt32(); if(mtg < MAX_DB_MOTION_TYPE) m_defaultMovementType = MovementGeneratorType(mtg); else { m_defaultMovementType = IDLE_MOTION_TYPE; sLog.outError("Creature (GUID: %u ID: %u) have wrong movement generator type value %u, ignore and set to IDLE.",guid,GetEntry(),mtg); } } if(!external) delete result; if (HasFlag( UNIT_NPC_FLAGS, UNIT_NPC_FLAG_TRAINER ) ) CreateTrainerSpells(); if ( HasFlag( UNIT_NPC_FLAGS, UNIT_NPC_FLAG_VENDOR ) ) _LoadGoods(); if ( HasFlag( UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER ) ) _LoadQuests(); AIM_Initialize(); return true; } void Creature::_LoadGoods() { itemcount = 0; QueryResult *result = sDatabase.PQuery("SELECT `item`, `maxcount`,`incrtime` FROM `npc_vendor` WHERE `entry` = '%u'", GetEntry()); if(!result) return; do { Field *fields = result->Fetch(); if (GetItemCount() >= MAX_CREATURE_ITEMS) { sLog.outError( "Vendor %u has too many items (%u >= %i). Check the DB!", GetUInt32Value(OBJECT_FIELD_ENTRY), GetItemCount(), MAX_CREATURE_ITEMS ); break; } AddItem( fields[0].GetUInt32(), fields[1].GetUInt32(), fields[2].GetUInt32()); } while( result->NextRow() ); delete result; } void Creature::_LoadQuests() { mQuests.clear(); mInvolvedQuests.clear(); Field *fields; QueryResult *result = sDatabase.PQuery("SELECT `quest` FROM `creature_questrelation` WHERE `id` = '%u'", GetEntry()); if(result) { do { fields = result->Fetch(); Quest* qInfo = objmgr.QuestTemplates[fields[0].GetUInt32()]; if (!qInfo) continue; addQuest(qInfo->GetQuestId()); } while( result->NextRow() ); delete result; } QueryResult *result1 = sDatabase.PQuery("SELECT `quest` FROM `creature_involvedrelation` WHERE `id` = '%u'", GetEntry()); if(!result1) return; do { fields = result1->Fetch(); Quest* qInfo = objmgr.QuestTemplates[fields[0].GetUInt32()]; if (!qInfo) continue; addInvolvedQuest(qInfo->GetQuestId()); } while( result1->NextRow() ); delete result1; } void Creature::DeleteFromDB() { sDatabase.PExecute("DELETE FROM `creature` WHERE `guid` = '%u'", GetGUIDLow()); sDatabase.PExecute("DELETE FROM `creature_involvedrelation` WHERE `id` = '%u'", GetGUIDLow()); } float Creature::GetAttackDistance(Unit *pl) const { int32 playerlevel = pl->getLevel(); int32 creaturelevel = getLevel(); int32 leveldif = playerlevel - creaturelevel; // "The maximum Aggro Radius has a cap of 25 levels under. Example: A level 30 char has the same Aggro Radius of a level 5 char on a level 60 mob." if ( leveldif < - 25) leveldif = -25; // "The aggro radius of a mob having the same level as the player is roughly 20 yards" float RetDistance = 20; // "Aggro Radius varries with level difference at a rate of roughly 1 yard/level" // radius grow if playlevel < creaturelevel RetDistance -= (float)leveldif; // "Minimum Aggro Radius for a mob seems to be combat range (5 yards)" if(RetDistance < 5 && sWorld.getRate(RATE_CREATURE_AGGRO) != 0) RetDistance = 5; return (RetDistance*sWorld.getRate(RATE_CREATURE_AGGRO)); } CreatureInfo const *Creature::GetCreatureInfo() const { return objmgr.GetCreatureTemplate(GetEntry()); } void Creature::setDeathState(DeathState s) { if(s == JUST_DIED) { m_deathTimer = m_corpseDelay; if(!IsStopped()) StopMoving(); } Unit::setDeathState(s); if(s == JUST_DIED) { SetUInt32Value(UNIT_NPC_FLAGS, 0); if(GetCreatureInfo()->SkinLootId) { LootStore skinStore = LootTemplates_Skinning; LootStore::iterator tab = skinStore.find(GetCreatureInfo()->SkinLootId); if ( tab != skinStore.end() ) SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); } Unit::setDeathState(CORPSE); } } void Creature::Say(char const* message, uint32 language) { WorldPacket data; sChatHandler.FillMessageData( &data, NULL, CHAT_MSG_SAY, language, NULL, 0, message ); SendMessageToSet( &data, false ); } void Creature::MonsterSay(char const* message, uint32 language, uint64 targetGUID) { WorldPacket data; sChatHandler.FillMessageData( &data, NULL, CHAT_MSG_MONSTER_SAY, language, NULL, targetGUID, message, this ); SendMessageToSet( &data, false ); } SpellEntry *Creature::reachWithSpellAttack(Unit *pVictim) { if(!pVictim) return NULL; SpellEntry *spellInfo; bool bcontinue; for(uint32 i=0; i < CREATURE_MAX_SPELLS; i++) { if(!m_spells[i]) continue; spellInfo = sSpellStore.LookupEntry(m_spells[i] ); if(!spellInfo) { sLog.outError("WORLD: unknown spell id %i\n", m_spells[i]); continue; } bcontinue = true; for(uint32 j=0;j<3;j++) { if( (spellInfo->Effect[j] == SPELL_EFFECT_SCHOOL_DAMAGE ) || (spellInfo->Effect[j] == SPELL_EFFECT_INSTAKILL) || (spellInfo->Effect[j] == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE) || (spellInfo->Effect[j] == SPELL_EFFECT_HEALTH_LEECH ) ) { bcontinue = false; break; } } if(bcontinue) continue; if(spellInfo->manaCost > GetPower(POWER_MANA)) continue; SpellRangeEntry* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex); float range = GetMaxRange(srange); float minrange = GetMinRange(srange); float dist = GetDistanceSq(pVictim); //if(!isInFront( pVictim, range ) && spellInfo->AttributesEx ) // continue; if( dist > range * range || dist < minrange * minrange ) continue; if(m_silenced) continue; return spellInfo; } return NULL; } SpellEntry *Creature::reachWithSpellCure(Unit *pVictim) { if(!pVictim) return NULL; SpellEntry *spellInfo; bool bcontinue; for(uint32 i=0; i < CREATURE_MAX_SPELLS; i++) { if(!m_spells[i]) continue; spellInfo = sSpellStore.LookupEntry(m_spells[i] ); if(!spellInfo) { sLog.outError("WORLD: unknown spell id %i\n", m_spells[i]); continue; } bcontinue = true; for(uint32 j=0;j<3;j++) { if( (spellInfo->Effect[j] == SPELL_EFFECT_HEAL ) ) { bcontinue = false; break; } } if(bcontinue) continue; if(spellInfo->manaCost > GetPower(POWER_MANA)) continue; SpellRangeEntry* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex); float range = GetMaxRange(srange); float minrange = GetMinRange(srange); float dist = GetDistanceSq(pVictim); //if(!isInFront( pVictim, range ) && spellInfo->AttributesEx ) // continue; if( dist > range * range || dist < minrange * minrange ) continue; if(m_silenced) continue; return spellInfo; } return NULL; } void Creature::Untamed() { if(!isTamed()) return; Unit* owner = GetOwner(); if(owner && owner->GetTypeId()==TYPEID_PLAYER) ((Player*)owner)->UnTamePet(this); else { SetMaxPower(POWER_HAPPINESS,0); SetPower(POWER_HAPPINESS,0); SetMaxPower(POWER_FOCUS,0); SetPower(POWER_FOCUS,0); SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE,GetCreatureInfo()->faction); SetUInt64Value(UNIT_FIELD_CREATEDBY, 0); SetUInt32Value(UNIT_FIELD_PETNUMBER,0); if(owner) owner->SetPet(0); SetTamed(false); AIM_Initialize(); } } void Creature::GivePetXP(uint32 xp) { if(!isPet() || !GetUInt32Value(UNIT_FIELD_PETNUMBER)) return; if ( xp < 1 ) return; uint32 level = getLevel(); // XP to money conversion processed in Player::RewardQuest if(level >= sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) return; uint32 curXP = GetUInt32Value(UNIT_FIELD_PETEXPERIENCE); uint32 nextLvlXP = GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP); uint32 newXP = curXP + xp; if(newXP >= nextLvlXP && level+1 > GetOwner()->getLevel()) { SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, nextLvlXP-1); return; } while( newXP >= nextLvlXP && level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL) ) { newXP -= nextLvlXP; SetLevel( level + 1 ); SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, uint32((MaNGOS::XP::xp_to_level(level+1))/4)); level = getLevel(); nextLvlXP = GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP); GivePetLevel(level); } SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, newXP); } void Creature::GivePetLevel(uint32 level) { if(!level) return; uint32 loyalty = 1; CreatureInfo const *cinfo = GetCreatureInfo(); // pet damage will grow up with the pet level,*1.5f for temp SetFloatValue(UNIT_FIELD_MINDAMAGE, cinfo->mindmg + float(level-cinfo->minlevel)*1.5f); SetFloatValue(UNIT_FIELD_MAXDAMAGE, cinfo->maxdmg + float(level-cinfo->minlevel)*1.5f); SetUInt32Value(UNIT_TRAINING_POINTS, (level<<16) + getUsedTrainPoint()); SetUInt32Value(UNIT_FIELD_BYTES_1,(getloyalty()<<8)); SetHealth( 28 + 10 * level); SetMaxHealth( 28 + 10 * level); SetStat(STAT_STRENGTH,uint32(20+level*1.55)); SetStat(STAT_AGILITY,uint32(20+level*0.64)); SetStat(STAT_STAMINA,uint32(20+level*1.27)); SetStat(STAT_INTELLECT,uint32(20+level*0.18)); SetStat(STAT_SPIRIT,uint32(20+level*0.36)); SetArmor(level*50); if(level - cinfo->minlevel >= 21) loyalty = 7; else if(level - cinfo->minlevel >= 15) loyalty = 6; else if(level - cinfo->minlevel >= 10) loyalty = 5; else if(level - cinfo->minlevel >= 6) loyalty = 4; else if(level - cinfo->minlevel >= 3) loyalty = 3; else if(level - cinfo->minlevel >= 1) loyalty = 2; SetUInt32Value(UNIT_FIELD_BYTES_1,(loyalty << 8)); } void Creature::SaveAsPet() { if(!isTamed() && !isPet()) return; if(!GetEntry()) return; uint32 loyalty =1; if(isTamed()) loyalty = 1; else loyalty = getloyalty(); uint32 owner = GUID_LOPART(GetOwnerGUID()); sDatabase.PExecute("DELETE FROM `character_pet` WHERE `owner` = '%u' AND `entry` = '%u'", owner,GetEntry() ); sDatabase.PExecute("UPDATE `character_pet` SET `current` = 0 WHERE `owner` = '%u' AND `current` = 1", owner ); sDatabase.PExecute("INSERT INTO `character_pet` (`entry`,`owner`,`level`,`exp`,`nextlvlexp`,`spell1`,`spell2`,`spell3`,`spell4`,`action`,`fealty`,`loyalty`,`trainpoint`,`current`) VALUES (%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,1)", GetEntry(), owner, getLevel(), GetUInt32Value(UNIT_FIELD_PETEXPERIENCE), GetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP), m_spells[0], m_spells[1], m_spells[2], m_spells[3], STATE_RA_FOLLOW, GetPower(POWER_HAPPINESS),loyalty,getUsedTrainPoint() ); } bool Creature::IsVisibleInGridForPlayer(Player* pl) const { // Live player see live creatures or death creatures with corpse dissappiring time > 0 if(pl->isAlive()) { return isAlive() || m_deathTimer > 0; } // Dead player see live creatures near own corpse if(pl->getDeathState() == CORPSE) { if(isAlive()) { if(Corpse* corpse = pl->GetCorpse()) { // 20 - aggro distance for same level, 25 - max additinal distance if player level less that creature level if(corpse->IsWithinDistInMap(this,(20+25)*sWorld.getRate(RATE_CREATURE_AGGRO))) return true; } } } // Dead player see Spirit Healer if(pl->isDead() && isSpiritHealer()) return true; // and not see any other return false; } void Creature::CallAssistence() { if (!m_AlreadyCallAssistence) { CastSpell(this,SPELL_ID_AGGRO, true); } }