diff options
| author | Rat <none@none> | 2010-06-05 23:40:08 +0200 |
|---|---|---|
| committer | Rat <none@none> | 2010-06-05 23:40:08 +0200 |
| commit | 75b80d9f5b02a643c983b2fb1ededed79fd5d133 (patch) | |
| tree | ebd1c2cc12a2715909dd04c1ed147a260c6ceb14 /src/server/game/AI/ScriptedAI | |
| parent | 6a9357b13d7ea6bd7d77dbfc6587af9028caa401 (diff) | |
rearranged core files
--HG--
branch : trunk
Diffstat (limited to 'src/server/game/AI/ScriptedAI')
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedCreature.cpp | 740 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedCreature.h | 290 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp | 506 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedEscortAI.h | 116 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp | 387 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h | 67 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedGossip.h | 187 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp | 194 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedGuardAI.h | 45 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedInstance.h | 20 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp | 278 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h | 71 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp | 8 | ||||
| -rw-r--r-- | src/server/game/AI/ScriptedAI/ScriptedSmartAI.h | 8 |
14 files changed, 2917 insertions, 0 deletions
diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp new file mode 100644 index 00000000000..8c4ddd14f07 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -0,0 +1,740 @@ +/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#include "ScriptedPch.h" +#include "Item.h" +#include "Spell.h" +#include "ObjectMgr.h" +#include "TemporarySummon.h" + +// Spell summary for ScriptedAI::SelectSpell +struct TSpellSummary +{ + uint8 Targets; // set of enum SelectTarget + uint8 Effects; // set of enum SelectEffect +} *SpellSummary; + +void SummonList::DoZoneInCombat(uint32 entry) +{ + for (iterator i = begin(); i != end();) + { + Creature *summon = Unit::GetCreature(*me, *i); + ++i; + if (summon && summon->IsAIEnabled + && (!entry || summon->GetEntry() == entry)) + summon->AI()->DoZoneInCombat(); + } +} + +void SummonList::DoAction(uint32 entry, uint32 info) +{ + for (iterator i = begin(); i != end();) + { + Creature *summon = Unit::GetCreature(*me, *i); + ++i; + if (summon && summon->IsAIEnabled + && (!entry || summon->GetEntry() == entry)) + summon->AI()->DoAction(info); + } +} + +void SummonList::DespawnEntry(uint32 entry) +{ + for (iterator i = begin(); i != end();) + { + Creature *summon = Unit::GetCreature(*me, *i); + if (!summon) + erase(i++); + else if (summon->GetEntry() == entry) + { + erase(i++); + summon->setDeathState(JUST_DIED); + summon->RemoveCorpse(); + } + else + ++i; + } +} + +void SummonList::DespawnAll() +{ + while (!empty()) + { + Creature *summon = Unit::GetCreature(*me, *begin()); + if (!summon) + erase(begin()); + else + { + erase(begin()); + if (summon->isSummon()) + { + summon->DestroyForNearbyPlayers(); + CAST_SUM(summon)->UnSummon(); + } + else + summon->DisappearAndDie(); + } + } +} + +ScriptedAI::ScriptedAI(Creature* pCreature) : CreatureAI(pCreature), + me(pCreature), + IsFleeing(false), + m_bCombatMovement(true), + m_uiEvadeCheckCooldown(2500) +{ + m_heroicMode = me->GetMap()->IsHeroic(); + m_difficulty = Difficulty(me->GetMap()->GetSpawnMode()); +} + +void ScriptedAI::AttackStartNoMove(Unit* pWho) +{ + if (!pWho) + return; + + if (me->Attack(pWho, false)) + DoStartNoMovement(pWho); +} + +void ScriptedAI::UpdateAI(const uint32 /*uiDiff*/) +{ + //Check if we have a current target + if (!UpdateVictim()) + return; + + if (me->isAttackReady()) + { + //If we are within range melee the target + if (me->IsWithinMeleeRange(me->getVictim())) + { + me->AttackerStateUpdate(me->getVictim()); + me->resetAttackTimer(); + } + } +} + +void ScriptedAI::DoStartMovement(Unit* pVictim, float fDistance, float fAngle) +{ + if (pVictim) + me->GetMotionMaster()->MoveChase(pVictim, fDistance, fAngle); +} + +void ScriptedAI::DoStartNoMovement(Unit* pVictim) +{ + if (!pVictim) + return; + + me->GetMotionMaster()->MoveIdle(); +} + +void ScriptedAI::DoStopAttack() +{ + if (me->getVictim()) + me->AttackStop(); +} + +void ScriptedAI::DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered) +{ + if (!pTarget || me->IsNonMeleeSpellCasted(false)) + return; + + me->StopMoving(); + me->CastSpell(pTarget, pSpellInfo, bTriggered); +} + +void ScriptedAI::DoPlaySoundToSet(WorldObject* pSource, uint32 uiSoundId) +{ + if (!pSource) + return; + + if (!GetSoundEntriesStore()->LookupEntry(uiSoundId)) + { + error_log("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", uiSoundId, pSource->GetTypeId(), pSource->GetGUIDLow()); + return; + } + + pSource->PlayDirectSound(uiSoundId); +} + +Creature* ScriptedAI::DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime) +{ + return me->SummonCreature(uiId, me->GetPositionX()+fX, me->GetPositionY()+fY, me->GetPositionZ()+fZ, fAngle, (TempSummonType)uiType, uiDespawntime); +} + +Unit* ScriptedAI::SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition) +{ + //ThreatList m_threatlist; + std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList(); + std::list<HostileReference*>::iterator itr = threatlist.begin(); + std::list<HostileReference*>::reverse_iterator ritr = threatlist.rbegin(); + + if (uiPosition >= threatlist.size() || !threatlist.size()) + return NULL; + + switch (pTarget) + { + case SELECT_TARGET_RANDOM: + advance (itr , uiPosition + (rand() % (threatlist.size() - uiPosition))); + return Unit::GetUnit((*me),(*itr)->getUnitGuid()); + break; + + case SELECT_TARGET_TOPAGGRO: + advance (itr , uiPosition); + return Unit::GetUnit((*me),(*itr)->getUnitGuid()); + break; + + case SELECT_TARGET_BOTTOMAGGRO: + advance (ritr , uiPosition); + return Unit::GetUnit((*me),(*ritr)->getUnitGuid()); + break; + + default: + return UnitAI::SelectTarget(pTarget, uiPosition); + } +} + +SpellEntry const* ScriptedAI::SelectSpell(Unit* pTarget, uint32 uiSchool, uint32 uiMechanic, SelectTargetType selectTargets, uint32 uiPowerCostMin, uint32 uiPowerCostMax, float fRangeMin, float fRangeMax, SelectEffect selectEffects) +{ + //No target so we can't cast + if (!pTarget) + return false; + + //Silenced so we can't cast + if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED)) + return false; + + //Using the extended script system we first create a list of viable spells + SpellEntry const* apSpell[CREATURE_MAX_SPELLS]; + memset(apSpell, 0, sizeof(SpellEntry*)*CREATURE_MAX_SPELLS); + + uint32 uiSpellCount = 0; + + SpellEntry const* pTempSpell; + SpellRangeEntry const* pTempRange; + + //Check if each spell is viable(set it to null if not) + for (uint32 i = 0; i < CREATURE_MAX_SPELLS; i++) + { + pTempSpell = GetSpellStore()->LookupEntry(me->m_spells[i]); + + //This spell doesn't exist + if (!pTempSpell) + continue; + + // Targets and Effects checked first as most used restrictions + //Check the spell targets if specified + if (selectTargets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (selectTargets-1)))) + continue; + + //Check the type of spell if we are looking for a specific spell type + if (selectEffects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (selectEffects-1)))) + continue; + + //Check for school if specified + if (uiSchool && (pTempSpell->SchoolMask & uiSchool) == 0) + continue; + + //Check for spell mechanic if specified + if (uiMechanic && pTempSpell->Mechanic != uiMechanic) + continue; + + //Make sure that the spell uses the requested amount of power + if (uiPowerCostMin && pTempSpell->manaCost < uiPowerCostMin) + continue; + + if (uiPowerCostMax && pTempSpell->manaCost > uiPowerCostMax) + continue; + + //Continue if we don't have the mana to actually cast this spell + if (pTempSpell->manaCost > me->GetPower((Powers)pTempSpell->powerType)) + continue; + + //Get the Range + pTempRange = GetSpellRangeStore()->LookupEntry(pTempSpell->rangeIndex); + + //Spell has invalid range store so we can't use it + if (!pTempRange) + continue; + + //Check if the spell meets our range requirements + if (fRangeMin && me->GetSpellMinRangeForTarget(pTarget, pTempRange) < fRangeMin) + continue; + if (fRangeMax && me->GetSpellMaxRangeForTarget(pTarget, pTempRange) > fRangeMax) + continue; + + //Check if our target is in range + if (me->IsWithinDistInMap(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange)) || !me->IsWithinDistInMap(pTarget, me->GetSpellMaxRangeForTarget(pTarget, pTempRange))) + continue; + + //All good so lets add it to the spell list + apSpell[uiSpellCount] = pTempSpell; + ++uiSpellCount; + } + + //We got our usable spells so now lets randomly pick one + if (!uiSpellCount) + return NULL; + + return apSpell[rand()%uiSpellCount]; +} + +bool ScriptedAI::CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered) +{ + //No target so we can't cast + if (!pTarget || !pSpell) + return false; + + //Silenced so we can't cast + if (!bTriggered && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED)) + return false; + + //Check for power + if (!bTriggered && me->GetPower((Powers)pSpell->powerType) < pSpell->manaCost) + return false; + + SpellRangeEntry const* pTempRange = GetSpellRangeStore()->LookupEntry(pSpell->rangeIndex); + + //Spell has invalid range store so we can't use it + if (!pTempRange) + return false; + + //Unit is out of range of this spell + if (me->IsInRange(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange), me->GetSpellMaxRangeForTarget(pTarget, pTempRange))) + return false; + + return true; +} + +void FillSpellSummary() +{ + SpellSummary = new TSpellSummary[GetSpellStore()->GetNumRows()]; + + SpellEntry const* pTempSpell; + + for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i) + { + SpellSummary[i].Effects = 0; + SpellSummary[i].Targets = 0; + + pTempSpell = GetSpellStore()->LookupEntry(i); + //This spell doesn't exist + if (!pTempSpell) + continue; + + for (uint32 j = 0; j < 3; ++j) + { + //Spell targets self + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SELF-1); + + //Spell targets a single enemy + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_ENEMY-1); + + //Spell targets AoE at enemy + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST || + pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_ENEMY-1); + + //Spell targets an enemy + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST || + pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_ENEMY-1); + + //Spell targets a single friend(or self) + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_FRIEND-1); + + //Spell targets aoe friends + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET || + pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_FRIEND-1); + + //Spell targets any friend(or self) + if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER || + pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET || + pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER) + SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_FRIEND-1); + + //Make sure that this spell includes a damage effect + if (pTempSpell->Effect[j] == SPELL_EFFECT_SCHOOL_DAMAGE || + pTempSpell->Effect[j] == SPELL_EFFECT_INSTAKILL || + pTempSpell->Effect[j] == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE || + pTempSpell->Effect[j] == SPELL_EFFECT_HEALTH_LEECH) + SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_DAMAGE-1); + + //Make sure that this spell includes a healing effect (or an apply aura with a periodic heal) + if (pTempSpell->Effect[j] == SPELL_EFFECT_HEAL || + pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MAX_HEALTH || + pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MECHANICAL || + (pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA && pTempSpell->EffectApplyAuraName[j] == 8)) + SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_HEALING-1); + + //Make sure that this spell applies an aura + if (pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA) + SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_AURA-1); + } + } +} + +void ScriptedAI::DoResetThreat() +{ + if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty()) + { + error_log("TSCR: DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry()); + return; + } + + std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList(); + + for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr) + { + Unit* pUnit = Unit::GetUnit((*me), (*itr)->getUnitGuid()); + + if (pUnit && DoGetThreat(pUnit)) + DoModifyThreatPercent(pUnit, -100); + } +} + +float ScriptedAI::DoGetThreat(Unit* pUnit) +{ + if (!pUnit) return 0.0f; + return me->getThreatManager().getThreat(pUnit); +} + +void ScriptedAI::DoModifyThreatPercent(Unit* pUnit, int32 pct) +{ + if (!pUnit) return; + me->getThreatManager().modifyThreatPercent(pUnit, pct); +} + +void ScriptedAI::DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime) +{ + me->Relocate(fX, fY, fZ); + me->SendMonsterMove(fX, fY, fZ, uiTime); +} + +void ScriptedAI::DoTeleportTo(const float fPos[4]) +{ + me->NearTeleportTo(fPos[0], fPos[1], fPos[2], fPos[3]); +} + +void ScriptedAI::DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO) +{ + if (!pUnit || pUnit->GetTypeId() != TYPEID_PLAYER) + { + if (pUnit) + error_log("TSCR: Creature %u (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: %u) to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), pUnit->GetTypeId(), pUnit->GetGUID(), fX, fY, fZ, fO); + return; + } + + CAST_PLR(pUnit)->TeleportTo(pUnit->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT); +} + +void ScriptedAI::DoTeleportAll(float fX, float fY, float fZ, float fO) +{ + Map *map = me->GetMap(); + if (!map->IsDungeon()) + return; + + Map::PlayerList const &PlayerList = map->GetPlayers(); + for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i) + if (Player* i_pl = i->getSource()) + if (i_pl->isAlive()) + i_pl->TeleportTo(me->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT); +} + +Unit* ScriptedAI::DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff) +{ + Unit* pUnit = NULL; + Trinity::MostHPMissingInRange u_check(me, fRange, uiMinHPDiff); + Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, pUnit, u_check); + me->VisitNearbyObject(fRange, searcher); + + return pUnit; +} + +std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float fRange) +{ + std::list<Creature*> pList; + Trinity::FriendlyCCedInRange u_check(me, fRange); + Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, pList, u_check); + me->VisitNearbyObject(fRange, searcher); + return pList; +} + +std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellid) +{ + std::list<Creature*> pList; + Trinity::FriendlyMissingBuffInRange u_check(me, fRange, uiSpellid); + Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, pList, u_check); + me->VisitNearbyObject(fRange, searcher); + return pList; +} + +Player* ScriptedAI::GetPlayerAtMinimumRange(float fMinimumRange) +{ + Player* pPlayer = NULL; + + CellPair pair(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY())); + Cell cell(pair); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::PlayerAtMinimumRangeAway check(me, fMinimumRange); + Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, pPlayer, check); + TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher); + + cell.Visit(pair, visitor, *(me->GetMap())); + + return pPlayer; +} + +void ScriptedAI::SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand, int32 uiOffHand, int32 uiRanged) +{ + if (bLoadDefault) + { + if (CreatureInfo const* pInfo = GetCreatureTemplateStore(me->GetEntry())) + me->LoadEquipment(pInfo->equipmentId,true); + + return; + } + + if (uiMainHand >= 0) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(uiMainHand)); + + if (uiOffHand >= 0) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(uiOffHand)); + + if (uiRanged >= 0) + me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(uiRanged)); +} + +void ScriptedAI::SetCombatMovement(bool bCombatMove) +{ + m_bCombatMovement = bCombatMove; +} + +enum eNPCs +{ + NPC_BROODLORD = 12017, + NPC_VOID_REAVER = 19516, + NPC_JAN_ALAI = 23578, + NPC_SARTHARION = 28860 +}; + +// Hacklike storage used for misc creatures that are expected to evade of outside of a certain area. +// It is assumed the information is found elswehere and can be handled by the core. So far no luck finding such information/way to extract it. +bool ScriptedAI::EnterEvadeIfOutOfCombatArea(const uint32 uiDiff) +{ + if (m_uiEvadeCheckCooldown <= uiDiff) + m_uiEvadeCheckCooldown = 2500; + else + { + m_uiEvadeCheckCooldown -= uiDiff; + return false; + } + + if (me->IsInEvadeMode() || !me->getVictim()) + return false; + + float fX = me->GetPositionX(); + float fY = me->GetPositionY(); + float fZ = me->GetPositionZ(); + + switch(me->GetEntry()) + { + case NPC_BROODLORD: // broodlord (not move down stairs) + if (fZ > 448.60f) + return false; + break; + case NPC_VOID_REAVER: // void reaver (calculate from center of room) + if (me->GetDistance2d(432.59f, 371.93f) < 105.0f) + return false; + break; + case NPC_JAN_ALAI: // jan'alai (calculate by Z) + if (fZ > 12.0f) + return false; + break; + case NPC_SARTHARION: // sartharion (calculate box) + if (fX > 3218.86f && fX < 3275.69f && fY < 572.40f && fY > 484.68f) + return false; + break; + default: + error_log("TSCR: EnterEvadeIfOutOfCombatArea used for creature entry %u, but does not have any definition.", me->GetEntry()); + return false; + } + + EnterEvadeMode(); + return true; +} + +void Scripted_NoMovementAI::AttackStart(Unit* pWho) +{ + if (!pWho) + return; + + if (me->Attack(pWho, true)) + { + DoStartNoMovement(pWho); + } +} + +BossAI::BossAI(Creature *c, uint32 id) : ScriptedAI(c) +, bossId(id), summons(me), instance(c->GetInstanceData()) +, boundary(instance ? instance->GetBossBoundary(id) : NULL) +{ +} + +void BossAI::_Reset() +{ + if (!me->isAlive()) + return; + + events.Reset(); + summons.DespawnAll(); + if (instance) + instance->SetBossState(bossId, NOT_STARTED); +} + +void BossAI::_JustDied() +{ + events.Reset(); + summons.DespawnAll(); + if (instance) + { + instance->SetBossState(bossId, DONE); + instance->SaveToDB(); + } +} + +void BossAI::_EnterCombat() +{ + me->setActive(true); + DoZoneInCombat(); + if (instance) + instance->SetBossState(bossId, IN_PROGRESS); +} + +void BossAI::TeleportCheaters() +{ + float x, y, z; + me->GetPosition(x, y, z); + std::list<HostileReference*> &m_threatlist = me->getThreatManager().getThreatList(); + for (std::list<HostileReference*>::iterator itr = m_threatlist.begin(); itr != m_threatlist.end(); ++itr) + if ((*itr)->getTarget()->GetTypeId() == TYPEID_PLAYER && !CheckBoundary((*itr)->getTarget())) + (*itr)->getTarget()->NearTeleportTo(x, y, z, 0); +} + +bool BossAI::CheckBoundary(Unit *who) +{ + if (!boundary || !who) + return true; + + for (BossBoundaryMap::const_iterator itr = boundary->begin(); itr != boundary->end(); ++itr) + { + switch (itr->first) + { + case BOUNDARY_N: + if (me->GetPositionX() > itr->second) + return false; + break; + case BOUNDARY_S: + if (me->GetPositionX() < itr->second) + return false; + break; + case BOUNDARY_E: + if (me->GetPositionY() < itr->second) + return false; + break; + case BOUNDARY_W: + if (me->GetPositionY() > itr->second) + return false; + break; + case BOUNDARY_NW: + if (me->GetPositionX() + me->GetPositionY() > itr->second) + return false; + break; + case BOUNDARY_SE: + if (me->GetPositionX() + me->GetPositionY() < itr->second) + return false; + break; + case BOUNDARY_NE: + if (me->GetPositionX() - me->GetPositionY() > itr->second) + return false; + break; + case BOUNDARY_SW: + if (me->GetPositionX() - me->GetPositionY() < itr->second) + return false; + break; + } + } + + return true; +} + +void BossAI::JustSummoned(Creature *summon) +{ + summons.Summon(summon); + if (me->isInCombat()) + DoZoneInCombat(summon); +} + +void BossAI::SummonedCreatureDespawn(Creature *summon) +{ + summons.Despawn(summon); +} + +#define GOBJECT(x) (const_cast<GameObjectInfo*>(GetGameObjectInfo(x))) + +void LoadOverridenSQLData() +{ + GameObjectInfo *goInfo; + + // Sunwell Plateau : Kalecgos : Spectral Rift + goInfo = GOBJECT(187055); + if (goInfo) + if (goInfo->type == GAMEOBJECT_TYPE_GOOBER) + goInfo->goober.lockId = 57; // need LOCKTYPE_QUICK_OPEN + + // Naxxramas : Sapphiron Birth + goInfo = GOBJECT(181356); + if (goInfo) + if (goInfo->type == GAMEOBJECT_TYPE_TRAP) + goInfo->trap.radius = 50; +} + +// SD2 grid searchers. +Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive) +{ + return pSource->FindNearestCreature(uiEntry, fMaxSearchRange, bAlive); +} +GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange) +{ + return pSource->FindNearestGameObject(uiEntry, fMaxSearchRange); +} +void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange) +{ + return pSource->GetCreatureListWithEntryInGrid(lList, uiEntry, fMaxSearchRange); +} +void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange) +{ + return pSource->GetGameObjectListWithEntryInGrid(lList, uiEntry, fMaxSearchRange); +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h new file mode 100644 index 00000000000..a7b8b530f66 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -0,0 +1,290 @@ +/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_CREATURE_H +#define SC_CREATURE_H + +#include "Creature.h" +#include "CreatureAI.h" +#include "CreatureAIImpl.h" +#include "InstanceData.h" + +#define SCRIPT_CAST_TYPE dynamic_cast +//#define SCRIPT_CAST_TYPE static_cast + +#define CAST_PLR(a) (SCRIPT_CAST_TYPE<Player*>(a)) +#define CAST_CRE(a) (SCRIPT_CAST_TYPE<Creature*>(a)) +#define CAST_SUM(a) (SCRIPT_CAST_TYPE<TempSummon*>(a)) +#define CAST_PET(a) (SCRIPT_CAST_TYPE<Pet*>(a)) +#define CAST_AI(a,b) (SCRIPT_CAST_TYPE<a*>(b)) + +#define GET_SPELL(a) (const_cast<SpellEntry*>(GetSpellStore()->LookupEntry(a))) + +class ScriptedInstance; + +class SummonList : public std::list<uint64> +{ + public: + explicit SummonList(Creature* creature) : me(creature) {} + void Summon(Creature *summon) { push_back(summon->GetGUID()); } + void Despawn(Creature *summon) { remove(summon->GetGUID()); } + void DespawnEntry(uint32 entry); + void DespawnAll(); + void DoAction(uint32 entry, uint32 info); + void DoZoneInCombat(uint32 entry = 0); + private: + Creature *me; +}; + +struct ScriptedAI : public CreatureAI +{ + explicit ScriptedAI(Creature* pCreature); + virtual ~ScriptedAI() {} + + //************* + //CreatureAI Functions + //************* + + void AttackStartNoMove(Unit *pTarget); + + // Called at any Damage from any attacker (before damage apply) + void DamageTaken(Unit* /*pDone_by*/, uint32& /*uiDamage*/) {} + + //Called at World update tick + void UpdateAI(const uint32); + + //Called at creature death + void JustDied(Unit* /*who*/){} + + //Called at creature killing another unit + void KilledUnit(Unit* /*who*/){} + + // Called when the creature summon successfully other creature + void JustSummoned(Creature*) {} + + // Called when a summoned creature is despawned + void SummonedCreatureDespawn(Creature*) {} + + // Called when hit by a spell + void SpellHit(Unit* /*caster*/, const SpellEntry * /*spell*/) {} + + // Called when spell hits a target + void SpellHitTarget(Unit * /*pTarget*/, const SpellEntry * /*spell*/) {} + + //Called at waypoint reached or PointMovement end + void MovementInform(uint32 /*type*/, uint32 /*id*/){} + + // Called when AI is temporarily replaced or put back when possess is applied or removed + void OnPossess(bool /*apply*/) {} + + //************* + // Variables + //************* + + //Pointer to creature we are manipulating + Creature* me; + + //For fleeing + bool IsFleeing; + + //************* + //Pure virtual functions + //************* + + //Called at creature reset either by death or evade + void Reset() {} + + //Called at creature aggro either by MoveInLOS or Attack Start + void EnterCombat(Unit* /*who*/) {} + + //************* + //AI Helper Functions + //************* + + //Start movement toward victim + void DoStartMovement(Unit* pVictim, float fDistance = 0, float fAngle = 0); + + //Start no movement on victim + void DoStartNoMovement(Unit* pVictim); + + //Stop attack of current victim + void DoStopAttack(); + + //Cast spell by spell info + void DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered = false); + + //Plays a sound to all nearby players + void DoPlaySoundToSet(WorldObject* pSource, uint32 sound); + + //Drops all threat to 0%. Does not remove players from the threat list + void DoResetThreat(); + + float DoGetThreat(Unit* u); + void DoModifyThreatPercent(Unit* pUnit, int32 pct); + + void DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime = 0); + void DoTeleportTo(const float pos[4]); + + void DoAction(const int32 /*param*/) {} + + //Teleports a player without dropping threat (only teleports to same map) + void DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO); + void DoTeleportAll(float fX, float fY, float fZ, float fO); + + //Returns friendly unit with the most amount of hp missing from max hp + Unit* DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff = 1); + + //Returns a list of friendly CC'd units within range + std::list<Creature*> DoFindFriendlyCC(float fRange); + + //Returns a list of all friendly units missing a specific buff within range + std::list<Creature*> DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellId); + + //Return a player with at least minimumRange from me + Player* GetPlayerAtMinimumRange(float fMinimumRange); + + //Spawns a creature relative to me + Creature* DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime); + + //Selects a unit from the creature's current aggro list + Unit* SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition); + + bool HealthBelowPct(uint32 pct) const { return me->GetHealth() * 100 < me->GetMaxHealth() * pct; } + + //Returns spells that meet the specified criteria from the creatures spell list + SpellEntry const* SelectSpell(Unit* Target, uint32 School, uint32 Mechanic, SelectTargetType Targets, uint32 PowerCostMin, uint32 PowerCostMax, float RangeMin, float RangeMax, SelectEffect Effect); + + //Checks if you can cast the specified spell + bool CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered = false); + + void SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand = EQUIP_NO_CHANGE, int32 uiOffHand = EQUIP_NO_CHANGE, int32 uiRanged = EQUIP_NO_CHANGE); + + //Generally used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims + void SetCombatMovement(bool CombatMove); + bool IsCombatMovement() { return m_bCombatMovement; } + + bool EnterEvadeIfOutOfCombatArea(const uint32 uiDiff); + + // return true for heroic mode. i.e. + // - for dungeon in mode 10-heroic, + // - for raid in mode 10-Heroic + // - for raid in mode 25-heroic + // DO NOT USE to check raid in mode 25-normal. + bool IsHeroic() { return m_heroicMode; } + + // return the dungeon or raid difficulty + Difficulty getDifficulty() { return m_difficulty; } + + template<class T> inline + const T& DUNGEON_MODE(const T& normal5, const T& heroic10) + { + switch(m_difficulty) + { + case DUNGEON_DIFFICULTY_NORMAL: + return normal5; + case DUNGEON_DIFFICULTY_HEROIC: + return heroic10; + } + + return heroic10; + } + + template<class T> inline + const T& RAID_MODE(const T& normal10, const T& normal25) + { + switch(m_difficulty) + { + case RAID_DIFFICULTY_10MAN_NORMAL: + return normal10; + case RAID_DIFFICULTY_25MAN_NORMAL: + return normal25; + } + + return normal25; + } + + template<class T> inline + const T& RAID_MODE(const T& normal10, const T& normal25, const T& heroic10, const T& heroic25) + { + switch(m_difficulty) + { + case RAID_DIFFICULTY_10MAN_NORMAL: + return normal10; + case RAID_DIFFICULTY_25MAN_NORMAL: + return normal25; + case RAID_DIFFICULTY_10MAN_HEROIC: + return heroic10; + case RAID_DIFFICULTY_25MAN_HEROIC: + return heroic25; + } + + return heroic25; + } + + private: + bool m_bCombatMovement; + uint32 m_uiEvadeCheckCooldown; + + bool m_heroicMode; + Difficulty m_difficulty; +}; + +struct Scripted_NoMovementAI : public ScriptedAI +{ + Scripted_NoMovementAI(Creature* creature) : ScriptedAI(creature) {} + virtual ~Scripted_NoMovementAI() {} + + //Called at each attack of me by any victim + void AttackStart(Unit* who); +}; + +struct BossAI : public ScriptedAI +{ + BossAI(Creature *c, uint32 id); + virtual ~BossAI() {} + + const uint32 bossId; + EventMap events; + SummonList summons; + InstanceData * const instance; + const BossBoundaryMap * const boundary; + + void JustSummoned(Creature *summon); + void SummonedCreatureDespawn(Creature *summon); + + void UpdateAI(const uint32 diff) = 0; + + void Reset() { _Reset(); } + void EnterCombat(Unit * /*who*/) { _EnterCombat(); } + void JustDied(Unit * /*killer*/) { _JustDied(); } + void JustReachedHome() { me->setActive(false); } + + protected: + void _Reset(); + void _EnterCombat(); + void _JustDied(); + void _JustReachedHome() { me->setActive(false); } + + bool CheckInRoom() + { + if (CheckBoundary(me)) + return true; + EnterEvadeMode(); + return false; + } + bool CheckBoundary(Unit *who); + void TeleportCheaters(); +}; + +// SD2 grid searchers. +Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive = true); +GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange); +void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange); +void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange); + +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp new file mode 100644 index 00000000000..8b7aca888e1 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -0,0 +1,506 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +/* ScriptData +SDName: Npc_EscortAI +SD%Complete: 100 +SDComment: +SDCategory: Npc +EndScriptData */ + +#include "ScriptedPch.h" +#include "ScriptedEscortAI.h" + +enum ePoints +{ + POINT_LAST_POINT = 0xFFFFFF, + POINT_HOME = 0xFFFFFE +}; + +npc_escortAI::npc_escortAI(Creature* pCreature) : ScriptedAI(pCreature), + m_uiPlayerGUID(0), + MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE), + m_uiPlayerCheckTimer(1000), + m_uiWPWaitTimer(2500), + m_uiEscortState(STATE_ESCORT_NONE), + m_bIsActiveAttacker(true), + m_bIsRunning(false), + DespawnAtEnd(true), + DespawnAtFar(true), + m_pQuestForEscort(NULL), + m_bCanInstantRespawn(false), + m_bCanReturnToStart(false), + ScriptWP(false) +{} + +void npc_escortAI::AttackStart(Unit* pWho) +{ + if (!pWho) + return; + + if (me->Attack(pWho, true)) + { + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) + me->GetMotionMaster()->MovementExpired(); + + if (IsCombatMovement()) + me->GetMotionMaster()->MoveChase(pWho); + } +} + +//see followerAI +bool npc_escortAI::AssistPlayerInCombat(Unit* pWho) +{ + if (!pWho || !pWho->getVictim()) + return false; + + //experimental (unknown) flag not present + if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13)) + return false; + + //not a player + if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) + return false; + + //never attack friendly + if (me->IsFriendlyTo(pWho)) + return false; + + //too far away and no free sight? + if (me->IsWithinDistInMap(pWho, GetMaxPlayerDistance()) && me->IsWithinLOSInMap(pWho)) + { + //already fighting someone? + if (!me->getVictim()) + { + AttackStart(pWho); + return true; + } + else + { + pWho->SetInCombatWith(me); + me->AddThreat(pWho, 0.0f); + return true; + } + } + + return false; +} + +void npc_escortAI::MoveInLineOfSight(Unit* pWho) +{ + if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me)) + { + if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombat(pWho)) + return; + + if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE) + return; + + if (me->IsHostileTo(pWho)) + { + float fAttackRadius = me->GetAttackDistance(pWho); + if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho)) + { + if (!me->getVictim()) + { + pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH); + AttackStart(pWho); + } + else if (me->GetMap()->IsDungeon()) + { + pWho->SetInCombatWith(me); + me->AddThreat(pWho, 0.0f); + } + } + } + } +} + +void npc_escortAI::JustDied(Unit* /*pKiller*/) +{ + if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID || !m_pQuestForEscort) + return; + + if (Player* pPlayer = GetPlayerForEscort()) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + if (Player* pMember = pRef->getSource()) + { + if (pMember->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pMember->FailQuest(m_pQuestForEscort->GetQuestId()); + } + } + } + else + { + if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForEscort->GetQuestId()); + } + } +} + +void npc_escortAI::JustRespawned() +{ + m_uiEscortState = STATE_ESCORT_NONE; + + if (!IsCombatMovement()) + SetCombatMovement(true); + + //add a small delay before going to first waypoint, normal in near all cases + m_uiWPWaitTimer = 2500; + + if (me->getFaction() != me->GetCreatureInfo()->faction_A) + me->RestoreFaction(); + + Reset(); +} + +void npc_escortAI::ReturnToLastPoint() +{ + float x, y, z, o; + me->GetHomePosition(x, y, z, o); + me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z); +} + +void npc_escortAI::EnterEvadeMode() +{ + me->RemoveAllAuras(); + me->DeleteThreatList(); + me->CombatStop(true); + me->SetLootRecipient(NULL); + + if (HasEscortState(STATE_ESCORT_ESCORTING)) + { + AddEscortState(STATE_ESCORT_RETURNING); + ReturnToLastPoint(); + debug_log("TSCR: EscortAI has left combat and is now returning to last point"); + } + else + { + me->GetMotionMaster()->MoveTargetedHome(); + Reset(); + } +} + +bool npc_escortAI::IsPlayerOrGroupInRange() +{ + if (Player* pPlayer = GetPlayerForEscort()) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && me->IsWithinDistInMap(pMember, GetMaxPlayerDistance())) + { + return true; + break; + } + } + } + else + { + if (me->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance())) + return true; + } + } + return false; +} + +void npc_escortAI::UpdateAI(const uint32 uiDiff) +{ + //Waypoint Updating + if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->getVictim() && m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING)) + { + if (m_uiWPWaitTimer <= uiDiff) + { + //End of the line + if (CurrentWP == WaypointList.end()) + { + if (DespawnAtEnd) + { + debug_log("TSCR: EscortAI reached end of waypoints"); + + if (m_bCanReturnToStart) + { + float fRetX, fRetY, fRetZ; + me->GetRespawnCoord(fRetX, fRetY, fRetZ); + + me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ); + + m_uiWPWaitTimer = 0; + + debug_log("TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ); + return; + } + + if (m_bCanInstantRespawn) + { + me->setDeathState(JUST_DIED); + me->Respawn(); + } + else + me->ForcedDespawn(); + + return; + } + else + { + debug_log("TSCR: EscortAI reached end of waypoints with Despawn off"); + + return; + } + } + + if (!HasEscortState(STATE_ESCORT_PAUSED)) + { + me->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); + debug_log("TSCR: EscortAI start waypoint %u (%f, %f, %f).", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); + + WaypointStart(CurrentWP->id); + + m_uiWPWaitTimer = 0; + } + } + else + m_uiWPWaitTimer -= uiDiff; + } + + //Check if player or any member of his group is within range + if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID && !me->getVictim() && !HasEscortState(STATE_ESCORT_RETURNING)) + { + if (m_uiPlayerCheckTimer <= uiDiff) + { + if (DespawnAtFar && !IsPlayerOrGroupInRange()) + { + debug_log("TSCR: EscortAI failed because player/group was to far away or not found"); + + if (m_bCanInstantRespawn) + { + me->setDeathState(JUST_DIED); + me->Respawn(); + } + else + me->ForcedDespawn(); + + return; + } + + m_uiPlayerCheckTimer = 1000; + } + else + m_uiPlayerCheckTimer -= uiDiff; + } + + UpdateEscortAI(uiDiff); +} + +void npc_escortAI::UpdateEscortAI(const uint32 /*uiDiff*/) +{ + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); +} + +void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId) +{ + if (uiMoveType != POINT_MOTION_TYPE || !HasEscortState(STATE_ESCORT_ESCORTING)) + return; + + //Combat start position reached, continue waypoint movement + if (uiPointId == POINT_LAST_POINT) + { + debug_log("TSCR: EscortAI has returned to original position before combat"); + + if (m_bIsRunning && me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) + me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else if (!m_bIsRunning && !me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE)) + me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + + RemoveEscortState(STATE_ESCORT_RETURNING); + + if (!m_uiWPWaitTimer) + m_uiWPWaitTimer = 1; + } + else if (uiPointId == POINT_HOME) + { + debug_log("TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list."); + + CurrentWP = WaypointList.begin(); + m_uiWPWaitTimer = 1; + } + else + { + //Make sure that we are still on the right waypoint + if (CurrentWP->id != uiPointId) + { + error_log("TSCR ERROR: EscortAI reached waypoint out of order %u, expected %u, creature entry %u", uiPointId, CurrentWP->id, me->GetEntry()); + return; + } + + debug_log("TSCR: EscortAI Waypoint %u reached", CurrentWP->id); + + //Call WP function + WaypointReached(CurrentWP->id); + + m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1; + + ++CurrentWP; + } +} + +/* +void npc_escortAI::OnPossess(bool apply) +{ + // We got possessed in the middle of being escorted, store the point + // where we left off to come back to when possess is removed + if (HasEscortState(STATE_ESCORT_ESCORTING)) + { + if (apply) + me->GetPosition(LastPos.x, LastPos.y, LastPos.z); + else + { + Returning = true; + me->GetMotionMaster()->MovementExpired(); + me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z); + } + } +} +*/ + +void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs) +{ + Escort_Waypoint t(id, x, y, z, WaitTimeMs); + + WaypointList.push_back(t); + + // i think SD2 no longer uses this function + ScriptWP = true; + /*PointMovement wp; + wp.m_uiCreatureEntry = me->GetEntry(); + wp.m_uiPointId = id; + wp.m_fX = x; + wp.m_fY = y; + wp.m_fZ = z; + wp.m_uiWaitTime = WaitTimeMs; + PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/ +} + +void npc_escortAI::FillPointMovementListForCreature() +{ + std::vector<ScriptPointMove> const &pPointsEntries = pSystemMgr.GetPointMoveList(me->GetEntry()); + + if (pPointsEntries.empty()) + return; + + std::vector<ScriptPointMove>::const_iterator itr; + + for (itr = pPointsEntries.begin(); itr != pPointsEntries.end(); ++itr) + { + Escort_Waypoint pPoint(itr->uiPointId, itr->fX, itr->fY, itr->fZ, itr->uiWaitTime); + WaypointList.push_back(pPoint); + } +} + +void npc_escortAI::SetRun(bool bRun) +{ + if (bRun) + { + if (!m_bIsRunning) + me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + debug_log("TSCR: EscortAI attempt to set run mode, but is already running."); + } + else + { + if (m_bIsRunning) + me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + debug_log("TSCR: EscortAI attempt to set walk mode, but is already walking."); + } + m_bIsRunning = bRun; +} + +//TODO: get rid of this many variables passed in function. +void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID, const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath) +{ + if (me->getVictim()) + { + error_log("TSCR ERROR: EscortAI attempt to Start while in combat. Scriptname: %s, creature entry: %u", me->GetScriptName().c_str(), me->GetEntry()); + return; + } + + if (HasEscortState(STATE_ESCORT_ESCORTING)) + { + error_log("TSCR: EscortAI attempt to Start while already escorting."); + return; + } + + if (!ScriptWP) // sd2 never adds wp in script, but tc does + { + + if (!WaypointList.empty()) + WaypointList.clear(); + + FillPointMovementListForCreature(); + + } + + if (WaypointList.empty()) + { + error_db_log("TSCR: EscortAI Start with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).", pQuest ? pQuest->GetQuestId() : 0); + return; + } + + //set variables + m_bIsActiveAttacker = bIsActiveAttacker; + m_bIsRunning = bRun; + + m_uiPlayerGUID = uiPlayerGUID; + m_pQuestForEscort = pQuest; + + m_bCanInstantRespawn = bInstantRespawn; + m_bCanReturnToStart = bCanLoopPath; + + if (m_bCanReturnToStart && m_bCanInstantRespawn) + debug_log("TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); + + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + me->GetMotionMaster()->MovementExpired(); + me->GetMotionMaster()->MoveIdle(); + debug_log("TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle."); + } + + //disable npcflags + me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + + debug_log("TSCR: EscortAI started with %u waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = %u", WaypointList.size(), m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID); + + CurrentWP = WaypointList.begin(); + + //Set initial speed + if (m_bIsRunning) + me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + else + me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE); + + AddEscortState(STATE_ESCORT_ESCORTING); +} + +void npc_escortAI::SetEscortPaused(bool bPaused) +{ + if (!HasEscortState(STATE_ESCORT_ESCORTING)) + return; + + if (bPaused) + AddEscortState(STATE_ESCORT_PAUSED); + else + RemoveEscortState(STATE_ESCORT_PAUSED); +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h new file mode 100644 index 00000000000..807a5644eb8 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -0,0 +1,116 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_ESCORTAI_H +#define SC_ESCORTAI_H + +#include "ScriptSystem.h" + +#define DEFAULT_MAX_PLAYER_DISTANCE 50 + +struct Escort_Waypoint +{ + Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w) + { + id = _id; + x = _x; + y = _y; + z = _z; + WaitTimeMs = _w; + } + + uint32 id; + float x; + float y; + float z; + uint32 WaitTimeMs; +}; + +enum eEscortState +{ + STATE_ESCORT_NONE = 0x000, //nothing in progress + STATE_ESCORT_ESCORTING = 0x001, //escort are in progress + STATE_ESCORT_RETURNING = 0x002, //escort is returning after being in combat + STATE_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed +}; + +struct npc_escortAI : public ScriptedAI +{ + public: + explicit npc_escortAI(Creature* pCreature); + ~npc_escortAI() {} + + // CreatureAI functions + void AttackStart(Unit* who); + + void MoveInLineOfSight(Unit* who); + + void JustDied(Unit*); + + void JustRespawned(); + + void ReturnToLastPoint(); + + void EnterEvadeMode(); + + void UpdateAI(const uint32); //the "internal" update, calls UpdateEscortAI() + virtual void UpdateEscortAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc) + + void MovementInform(uint32, uint32); + + // EscortAI functions + void AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs = 0); + + virtual void WaypointReached(uint32 uiPointId) = 0; + virtual void WaypointStart(uint32 /*uiPointId*/) {} + + void Start(bool bIsActiveAttacker = true, bool bRun = false, uint64 uiPlayerGUID = 0, const Quest* pQuest = NULL, bool bInstantRespawn = false, bool bCanLoopPath = false); + + void SetRun(bool bRun = true); + void SetEscortPaused(bool uPaused); + + bool HasEscortState(uint32 uiEscortState) { return (m_uiEscortState & uiEscortState); } + virtual bool IsEscorted() { return (m_uiEscortState & STATE_ESCORT_ESCORTING); } + + void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } + float GetMaxPlayerDistance() { return MaxPlayerDistance; } + + void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; } + void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; } + bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override + void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } + uint64 GetEventStarterGUID() { return m_uiPlayerGUID; } + + protected: + Player* GetPlayerForEscort() { return (Player*)Unit::GetUnit(*me, m_uiPlayerGUID); } + + private: + bool AssistPlayerInCombat(Unit* pWho); + bool IsPlayerOrGroupInRange(); + void FillPointMovementListForCreature(); + + void AddEscortState(uint32 uiEscortState) { m_uiEscortState |= uiEscortState; } + void RemoveEscortState(uint32 uiEscortState) { m_uiEscortState &= ~uiEscortState; } + + uint64 m_uiPlayerGUID; + uint32 m_uiWPWaitTimer; + uint32 m_uiPlayerCheckTimer; + uint32 m_uiEscortState; + float MaxPlayerDistance; + + const Quest* m_pQuestForEscort; //generally passed in Start() when regular escort script. + + std::list<Escort_Waypoint> WaypointList; + std::list<Escort_Waypoint>::iterator CurrentWP; + + bool m_bIsActiveAttacker; //obsolete, determined by faction. + bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK) + bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used) + bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests. + bool DespawnAtEnd; + bool DespawnAtFar; + bool ScriptWP; +}; +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp new file mode 100644 index 00000000000..4f1543dc778 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp @@ -0,0 +1,387 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +/* ScriptData +SDName: FollowerAI +SD%Complete: 50 +SDComment: This AI is under development +SDCategory: Npc +EndScriptData */ + +#include "ScriptedPch.h" +#include "ScriptedFollowerAI.h" + +const float MAX_PLAYER_DISTANCE = 100.0f; + +enum ePoints +{ + POINT_COMBAT_START = 0xFFFFFF +}; + +FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature), + m_uiLeaderGUID(0), + m_pQuestForFollow(NULL), + m_uiUpdateFollowTimer(2500), + m_uiFollowState(STATE_FOLLOW_NONE) +{} + +void FollowerAI::AttackStart(Unit* pWho) +{ + if (!pWho) + return; + + if (me->Attack(pWho, true)) + { + me->AddThreat(pWho, 0.0f); + me->SetInCombatWith(pWho); + pWho->SetInCombatWith(me); + + if (me->hasUnitState(UNIT_STAT_FOLLOW)) + me->clearUnitState(UNIT_STAT_FOLLOW); + + if (IsCombatMovement()) + me->GetMotionMaster()->MoveChase(pWho); + } +} + +//This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range +//It will cause me to attack pWho that are attacking _any_ player (which has been confirmed may happen also on offi) +//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate. +bool FollowerAI::AssistPlayerInCombat(Unit* pWho) +{ + if (!pWho || !pWho->getVictim()) + return false; + + //experimental (unknown) flag not present + if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13)) + return false; + + //not a player + if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) + return false; + + //never attack friendly + if (me->IsFriendlyTo(pWho)) + return false; + + //too far away and no free sight? + if (me->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(pWho)) + { + //already fighting someone? + if (!me->getVictim()) + { + AttackStart(pWho); + return true; + } + else + { + pWho->SetInCombatWith(me); + me->AddThreat(pWho, 0.0f); + return true; + } + } + + return false; +} + +void FollowerAI::MoveInLineOfSight(Unit* pWho) +{ + if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me)) + { + if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(pWho)) + return; + + if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE) + return; + + if (me->IsHostileTo(pWho)) + { + float fAttackRadius = me->GetAttackDistance(pWho); + if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho)) + { + if (!me->getVictim()) + { + pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH); + AttackStart(pWho); + } + else if (me->GetMap()->IsDungeon()) + { + pWho->SetInCombatWith(me); + me->AddThreat(pWho, 0.0f); + } + } + } + } +} + +void FollowerAI::JustDied(Unit* /*pKiller*/) +{ + if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_uiLeaderGUID || !m_pQuestForFollow) + return; + + //TODO: need a better check for quests with time limit. + if (Player* pPlayer = GetLeaderForFollower()) + { + if (Group* pGroup = pPlayer->GetGroup()) + { + for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + if (Player* pMember = pRef->getSource()) + { + if (pMember->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pMember->FailQuest(m_pQuestForFollow->GetQuestId()); + } + } + } + else + { + if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE) + pPlayer->FailQuest(m_pQuestForFollow->GetQuestId()); + } + } +} + +void FollowerAI::JustRespawned() +{ + m_uiFollowState = STATE_FOLLOW_NONE; + + if (!IsCombatMovement()) + SetCombatMovement(true); + + if (me->getFaction() != me->GetCreatureInfo()->faction_A) + me->setFaction(me->GetCreatureInfo()->faction_A); + + Reset(); +} + +void FollowerAI::EnterEvadeMode() +{ + me->RemoveAllAuras(); + me->DeleteThreatList(); + me->CombatStop(true); + me->SetLootRecipient(NULL); + + if (HasFollowState(STATE_FOLLOW_INPROGRESS)) + { + debug_log("TSCR: FollowerAI left combat, returning to CombatStartPosition."); + + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE) + { + float fPosX, fPosY, fPosZ; + me->GetPosition(fPosX, fPosY, fPosZ); + me->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ); + } + } + else + { + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE) + me->GetMotionMaster()->MoveTargetedHome(); + } + + Reset(); +} + +void FollowerAI::UpdateAI(const uint32 uiDiff) +{ + if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !me->getVictim()) + { + if (m_uiUpdateFollowTimer <= uiDiff) + { + if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT)) + { + debug_log("TSCR: FollowerAI is set completed, despawns."); + me->ForcedDespawn(); + return; + } + + bool bIsMaxRangeExceeded = true; + + if (Player* pPlayer = GetLeaderForFollower()) + { + if (HasFollowState(STATE_FOLLOW_RETURNING)) + { + debug_log("TSCR: FollowerAI is returning to leader."); + + RemoveFollowState(STATE_FOLLOW_RETURNING); + me->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + return; + } + + if (Group* pGroup = pPlayer->GetGroup()) + { + for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE)) + { + bIsMaxRangeExceeded = false; + break; + } + } + } + else + { + if (me->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE)) + bIsMaxRangeExceeded = false; + } + } + + if (bIsMaxRangeExceeded) + { + debug_log("TSCR: FollowerAI failed because player/group was to far away or not found"); + me->ForcedDespawn(); + return; + } + + m_uiUpdateFollowTimer = 1000; + } + else + m_uiUpdateFollowTimer -= uiDiff; + } + + UpdateFollowerAI(uiDiff); +} + +void FollowerAI::UpdateFollowerAI(const uint32 /*uiDiff*/) +{ + if (!UpdateVictim()) + return; + + DoMeleeAttackIfReady(); +} + +void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId) +{ + if (uiMotionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS)) + return; + + if (uiPointId == POINT_COMBAT_START) + { + if (GetLeaderForFollower()) + { + if (!HasFollowState(STATE_FOLLOW_PAUSED)) + AddFollowState(STATE_FOLLOW_RETURNING); + } + else + me->ForcedDespawn(); + } +} + +void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest) +{ + if (me->getVictim()) + { + debug_log("TSCR: FollowerAI attempt to StartFollow while in combat."); + return; + } + + if (HasFollowState(STATE_FOLLOW_INPROGRESS)) + { + error_log("TSCR: FollowerAI attempt to StartFollow while already following."); + return; + } + + //set variables + m_uiLeaderGUID = pLeader->GetGUID(); + + if (uiFactionForFollower) + me->setFaction(uiFactionForFollower); + + m_pQuestForFollow = pQuest; + + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + debug_log("TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle."); + } + + me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); + + AddFollowState(STATE_FOLLOW_INPROGRESS); + + me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + + debug_log("TSCR: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID); +} + +Player* FollowerAI::GetLeaderForFollower() +{ + if (Player* pLeader = Unit::GetPlayer(m_uiLeaderGUID)) + { + if (pLeader->isAlive()) + return pLeader; + else + { + if (Group* pGroup = pLeader->GetGroup()) + { + for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next()) + { + Player* pMember = pRef->getSource(); + + if (pMember && pMember->isAlive() && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE)) + { + debug_log("TSCR: FollowerAI GetLeader changed and returned new leader."); + m_uiLeaderGUID = pMember->GetGUID(); + return pMember; + break; + } + } + } + } + } + + debug_log("TSCR: FollowerAI GetLeader can not find suitable leader."); + return NULL; +} + +void FollowerAI::SetFollowComplete(bool bWithEndEvent) +{ + if (me->hasUnitState(UNIT_STAT_FOLLOW)) + { + me->clearUnitState(UNIT_STAT_FOLLOW); + + me->StopMoving(); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + } + + if (bWithEndEvent) + AddFollowState(STATE_FOLLOW_POSTEVENT); + else + { + if (HasFollowState(STATE_FOLLOW_POSTEVENT)) + RemoveFollowState(STATE_FOLLOW_POSTEVENT); + } + + AddFollowState(STATE_FOLLOW_COMPLETE); +} + +void FollowerAI::SetFollowPaused(bool bPaused) +{ + if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE)) + return; + + if (bPaused) + { + AddFollowState(STATE_FOLLOW_PAUSED); + + if (me->hasUnitState(UNIT_STAT_FOLLOW)) + { + me->clearUnitState(UNIT_STAT_FOLLOW); + + me->StopMoving(); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveIdle(); + } + } + else + { + RemoveFollowState(STATE_FOLLOW_PAUSED); + + if (Player* pLeader = GetLeaderForFollower()) + me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + } +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h new file mode 100644 index 00000000000..d352141e3e9 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_FOLLOWERAI_H +#define SC_FOLLOWERAI_H + +#include "ScriptSystem.h" + +enum eFollowState +{ + STATE_FOLLOW_NONE = 0x000, + STATE_FOLLOW_INPROGRESS = 0x001, //must always have this state for any follow + STATE_FOLLOW_RETURNING = 0x002, //when returning to combat start after being in combat + STATE_FOLLOW_PAUSED = 0x004, //disables following + STATE_FOLLOW_COMPLETE = 0x008, //follow is completed and may end + STATE_FOLLOW_PREEVENT = 0x010, //not implemented (allow pre event to run, before follow is initiated) + STATE_FOLLOW_POSTEVENT = 0x020 //can be set at complete and allow post event to run +}; + +class FollowerAI : public ScriptedAI +{ + public: + explicit FollowerAI(Creature* pCreature); + ~FollowerAI() {} + + //virtual void WaypointReached(uint32 uiPointId) = 0; + + void MovementInform(uint32 uiMotionType, uint32 uiPointId); + + void AttackStart(Unit*); + + void MoveInLineOfSight(Unit*); + + void EnterEvadeMode(); + + void JustDied(Unit*); + + void JustRespawned(); + + void UpdateAI(const uint32); //the "internal" update, calls UpdateFollowerAI() + virtual void UpdateFollowerAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc) + + void StartFollow(Player* pPlayer, uint32 uiFactionForFollower = 0, const Quest* pQuest = NULL); + + void SetFollowPaused(bool bPaused); //if special event require follow mode to hold/resume during the follow + void SetFollowComplete(bool bWithEndEvent = false); + + bool HasFollowState(uint32 uiFollowState) { return (m_uiFollowState & uiFollowState); } + + protected: + Player* GetLeaderForFollower(); + + private: + void AddFollowState(uint32 uiFollowState) { m_uiFollowState |= uiFollowState; } + void RemoveFollowState(uint32 uiFollowState) { m_uiFollowState &= ~uiFollowState; } + + bool AssistPlayerInCombat(Unit* pWho); + + uint64 m_uiLeaderGUID; + uint32 m_uiUpdateFollowTimer; + uint32 m_uiFollowState; + + const Quest* m_pQuestForFollow; //normally we have a quest +}; + +#endif diff --git a/src/server/game/AI/ScriptedAI/ScriptedGossip.h b/src/server/game/AI/ScriptedAI/ScriptedGossip.h new file mode 100644 index 00000000000..6f1da291c45 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedGossip.h @@ -0,0 +1,187 @@ +/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_GOSSIP_H +#define SC_GOSSIP_H + +#include "Player.h" +#include "GossipDef.h" +#include "QuestDef.h" + +// Gossip Item Text +#define GOSSIP_TEXT_BROWSE_GOODS "I'd like to browse your goods." +#define GOSSIP_TEXT_TRAIN "Train me!" + +#define GOSSIP_TEXT_BANK "The bank" +#define GOSSIP_TEXT_IRONFORGE_BANK "The bank of Ironforge" +#define GOSSIP_TEXT_STORMWIND_BANK "The bank of Stormwind" +#define GOSSIP_TEXT_WINDRIDER "The wind rider master" +#define GOSSIP_TEXT_GRYPHON "The gryphon master" +#define GOSSIP_TEXT_BATHANDLER "The bat handler" +#define GOSSIP_TEXT_HIPPOGRYPH "The hippogryph master" +#define GOSSIP_TEXT_ZEPPLINMASTER "The zeppelin master" +#define GOSSIP_TEXT_DEEPRUNTRAM "The Deeprun Tram" +#define GOSSIP_TEXT_FERRY "The Rut'theran Ferry" +#define GOSSIP_TEXT_FLIGHTMASTER "The flight master" +#define GOSSIP_TEXT_AUCTIONHOUSE "The auction house" +#define GOSSIP_TEXT_GUILDMASTER "The guild master" +#define GOSSIP_TEXT_INN "The inn" +#define GOSSIP_TEXT_MAILBOX "The mailbox" +#define GOSSIP_TEXT_STABLEMASTER "The stable master" +#define GOSSIP_TEXT_WEAPONMASTER "The weapon master" +#define GOSSIP_TEXT_OFFICERS "The officers' lounge" +#define GOSSIP_TEXT_BATTLEMASTER "The battlemaster" +#define GOSSIP_TEXT_BARBER "Barber" +#define GOSSIP_TEXT_CLASSTRAINER "A class trainer" +#define GOSSIP_TEXT_PROFTRAINER "A profession trainer" +#define GOSSIP_TEXT_LEXICON "Lexicon of Power" + +#define GOSSIP_TEXT_ALTERACVALLEY "Alterac Valley" +#define GOSSIP_TEXT_ARATHIBASIN "Arathi Basin" +#define GOSSIP_TEXT_WARSONGULCH "Warsong Gulch" +#define GOSSIP_TEXT_ARENA "Arena" +#define GOSSIP_TEXT_EYEOFTHESTORM "Eye of The Storm" +#define GOSSIP_TEXT_STRANDOFANCIENT "Strand of the Ancients" + +#define GOSSIP_TEXT_DEATH_KNIGHT "Death Knight" +#define GOSSIP_TEXT_DRUID "Druid" +#define GOSSIP_TEXT_HUNTER "Hunter" +#define GOSSIP_TEXT_PRIEST "Priest" +#define GOSSIP_TEXT_ROGUE "Rogue" +#define GOSSIP_TEXT_WARRIOR "Warrior" +#define GOSSIP_TEXT_PALADIN "Paladin" +#define GOSSIP_TEXT_SHAMAN "Shaman" +#define GOSSIP_TEXT_MAGE "Mage" +#define GOSSIP_TEXT_WARLOCK "Warlock" + +#define GOSSIP_TEXT_ALCHEMY "Alchemy" +#define GOSSIP_TEXT_BLACKSMITHING "Blacksmithing" +#define GOSSIP_TEXT_COOKING "Cooking" +#define GOSSIP_TEXT_ENCHANTING "Enchanting" +#define GOSSIP_TEXT_ENGINEERING "Engineering" +#define GOSSIP_TEXT_FIRSTAID "First Aid" +#define GOSSIP_TEXT_HERBALISM "Herbalism" +#define GOSSIP_TEXT_INSCRIPTION "Inscription" +#define GOSSIP_TEXT_JEWELCRAFTING "Jewelcrafting" +#define GOSSIP_TEXT_LEATHERWORKING "Leatherworking" +#define GOSSIP_TEXT_TAILORING "Tailoring" +#define GOSSIP_TEXT_MINING "Mining" +#define GOSSIP_TEXT_FISHING "Fishing" +#define GOSSIP_TEXT_SKINNING "Skinning" + +enum eTradeskill +{ +// Skill defines + TRADESKILL_ALCHEMY = 1, + TRADESKILL_BLACKSMITHING = 2, + TRADESKILL_COOKING = 3, + TRADESKILL_ENCHANTING = 4, + TRADESKILL_ENGINEERING = 5, + TRADESKILL_FIRSTAID = 6, + TRADESKILL_HERBALISM = 7, + TRADESKILL_LEATHERWORKING = 8, + TRADESKILL_POISONS = 9, + TRADESKILL_TAILORING = 10, + TRADESKILL_MINING = 11, + TRADESKILL_FISHING = 12, + TRADESKILL_SKINNING = 13, + TRADESKILL_JEWLCRAFTING = 14, + TRADESKILL_INSCRIPTION = 15, + + TRADESKILL_LEVEL_NONE = 0, + TRADESKILL_LEVEL_APPRENTICE = 1, + TRADESKILL_LEVEL_JOURNEYMAN = 2, + TRADESKILL_LEVEL_EXPERT = 3, + TRADESKILL_LEVEL_ARTISAN = 4, + TRADESKILL_LEVEL_MASTER = 5, + TRADESKILL_LEVEL_GRAND_MASTER = 6, + +// Gossip defines + GOSSIP_ACTION_TRADE = 1, + GOSSIP_ACTION_TRAIN = 2, + GOSSIP_ACTION_TAXI = 3, + GOSSIP_ACTION_GUILD = 4, + GOSSIP_ACTION_BATTLE = 5, + GOSSIP_ACTION_BANK = 6, + GOSSIP_ACTION_INN = 7, + GOSSIP_ACTION_HEAL = 8, + GOSSIP_ACTION_TABARD = 9, + GOSSIP_ACTION_AUCTION = 10, + GOSSIP_ACTION_INN_INFO = 11, + GOSSIP_ACTION_UNLEARN = 12, + GOSSIP_ACTION_INFO_DEF = 1000, + + GOSSIP_SENDER_MAIN = 1, + GOSSIP_SENDER_INN_INFO = 2, + GOSSIP_SENDER_INFO = 3, + GOSSIP_SENDER_SEC_PROFTRAIN = 4, + GOSSIP_SENDER_SEC_CLASSTRAIN = 5, + GOSSIP_SENDER_SEC_BATTLEINFO = 6, + GOSSIP_SENDER_SEC_BANK = 7, + GOSSIP_SENDER_SEC_INN = 8, + GOSSIP_SENDER_SEC_MAILBOX = 9, + GOSSIP_SENDER_SEC_STABLEMASTER = 10 +}; + +extern uint32 GetSkillLevel(Player *player,uint32 skill); + +// Defined fuctions to use with player. + +// This fuction add's a menu item, +// a - Icon Id +// b - Text +// c - Sender(this is to identify the current Menu with this item) +// d - Action (identifys this Menu Item) +// e - Text to be displayed in pop up box +// f - Money value in pop up box +#define ADD_GOSSIP_ITEM(a,b,c,d) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,"",0) +#define ADD_GOSSIP_ITEM_EXTENDED(a,b,c,d,e,f,g) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,e,f,g) + +// This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32) , b - npc guid(uint64) +#define SEND_GOSSIP_MENU(a,b) PlayerTalkClass->SendGossipMenu(a,b) + +// This fuction shows POI(point of interest) to client. +// a - position X +// b - position Y +// c - Icon Id +// d - Flags +// e - Data +// f - Location Name +#define SEND_POI(a,b,c,d,e,f) PlayerTalkClass->SendPointOfInterest(a,b,c,d,e,f) + +// Closes the Menu +#define CLOSE_GOSSIP_MENU() PlayerTalkClass->CloseGossip() + +// Fuction to tell to client the details +// a - quest object +// b - npc guid(uint64) +// c - Activate accept(bool) +#define SEND_QUEST_DETAILS(a,b,c) PlayerTalkClass->SendQuestDetails(a,b,c) + +// Fuction to tell to client the requested items to complete quest +// a - quest object +// b - npc guid(uint64) +// c - Iscompletable(bool) +// d - close at cancel(bool) - in case single incomplite ques +#define SEND_REQUESTEDITEMS(a,b,c,d) PlayerTalkClass->SendRequestedItems(a,b,c,d) + +// Fuctions to send NPC lists, a - is always the npc guid(uint64) +#define SEND_VENDORLIST(a) GetSession()->SendListInventory(a) +#define SEND_TRAINERLIST(a) GetSession()->SendTrainerList(a) + +// Ressurect's the player if is dead. +#define SEND_SPRESURRECT() GetSession()->SendSpiritResurrect() + +// Get the player's honor rank. +#define GET_HONORRANK() GetHonorRank() +// ----------------------------------- + +// defined fuctions to use with Creature + +#define QUEST_DIALOG_STATUS(a,b,c) GetSession()->getDialogStatus(a,b,c) +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp new file mode 100644 index 00000000000..68dc8690470 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.cpp @@ -0,0 +1,194 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>.sourceforge.net/> + * 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 + */ + +/* ScriptData +SDName: Guard_AI +SD%Complete: 90 +SDComment: +SDCategory: Guards +EndScriptData */ + +#include "ScriptedPch.h" +#include "ScriptedGuardAI.h" + +// **** This script is for use within every single guard to save coding time **** + +#define GENERIC_CREATURE_COOLDOWN 5000 + +#define SAY_GUARD_SIL_AGGRO1 -1070001 +#define SAY_GUARD_SIL_AGGRO2 -1070002 +#define SAY_GUARD_SIL_AGGRO3 -1070003 + +guardAI::guardAI(Creature* pCreature) : ScriptedAI(pCreature), + GlobalCooldown(0), + BuffTimer(0) +{} + +void guardAI::Reset() +{ + GlobalCooldown = 0; + BuffTimer = 0; //Rebuff as soon as we can +} + +void guardAI::EnterCombat(Unit *who) +{ + if (me->GetEntry() == 15184) + DoScriptText(RAND(SAY_GUARD_SIL_AGGRO1,SAY_GUARD_SIL_AGGRO2,SAY_GUARD_SIL_AGGRO3), me, who); + + if (SpellEntry const *spell = me->reachWithSpellAttack(who)) + DoCastSpell(who, spell); +} + +void guardAI::JustDied(Unit *Killer) +{ + //Send Zone Under Attack message to the LocalDefense and WorldDefense Channels + if (Player* pKiller = Killer->GetCharmerOrOwnerPlayerOrPlayerItself()) + me->SendZoneUnderAttackMessage(pKiller); +} + +void guardAI::UpdateAI(const uint32 diff) +{ + //Always decrease our global cooldown first + if (GlobalCooldown > diff) + GlobalCooldown -= diff; + else GlobalCooldown = 0; + + //Buff timer (only buff when we are alive and not in combat + if (me->isAlive() && !me->isInCombat()) + if (BuffTimer <= diff) + { + //Find a spell that targets friendly and applies an aura (these are generally buffs) + SpellEntry const *info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_AURA); + + if (info && !GlobalCooldown) + { + //Cast the buff spell + DoCastSpell(me, info); + + //Set our global cooldown + GlobalCooldown = GENERIC_CREATURE_COOLDOWN; + + //Set our timer to 10 minutes before rebuff + BuffTimer = 600000; + } //Try again in 30 seconds + else BuffTimer = 30000; + } else BuffTimer -= diff; + + //Return since we have no target + if (!UpdateVictim()) + return; + + // Make sure our attack is ready and we arn't currently casting + if (me->isAttackReady() && !me->IsNonMeleeSpellCasted(false)) + { + //If we are within range melee the target + if (me->IsWithinMeleeRange(me->getVictim())) + { + bool Healing = false; + SpellEntry const *info = NULL; + + //Select a healing spell if less than 30% hp + if (me->GetHealth()*100 / me->GetMaxHealth() < 30) + info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING); + + //No healing spell available, select a hostile spell + if (info) Healing = true; + else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, 0, 0, SELECT_EFFECT_DONTCARE); + + //20% chance to replace our white hit with a spell + if (info && rand() % 5 == 0 && !GlobalCooldown) + { + //Cast the spell + if (Healing)DoCastSpell(me, info); + else DoCastSpell(me->getVictim(), info); + + //Set our global cooldown + GlobalCooldown = GENERIC_CREATURE_COOLDOWN; + } + else me->AttackerStateUpdate(me->getVictim()); + + me->resetAttackTimer(); + } + } + else + { + //Only run this code if we arn't already casting + if (!me->IsNonMeleeSpellCasted(false)) + { + bool Healing = false; + SpellEntry const *info = NULL; + + //Select a healing spell if less than 30% hp ONLY 33% of the time + if (me->GetHealth()*100 / me->GetMaxHealth() < 30 && rand() % 3 == 0) + info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING); + + //No healing spell available, See if we can cast a ranged spell (Range must be greater than ATTACK_DISTANCE) + if (info) Healing = true; + else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, NOMINAL_MELEE_RANGE, 0, SELECT_EFFECT_DONTCARE); + + //Found a spell, check if we arn't on cooldown + if (info && !GlobalCooldown) + { + //If we are currently moving stop us and set the movement generator + if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != IDLE_MOTION_TYPE) + { + (*me).GetMotionMaster()->Clear(false); + (*me).GetMotionMaster()->MoveIdle(); + } + + //Cast spell + if (Healing) DoCastSpell(me,info); + else DoCastSpell(me->getVictim(),info); + + //Set our global cooldown + GlobalCooldown = GENERIC_CREATURE_COOLDOWN; + + } //If no spells available and we arn't moving run to target + else if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != TARGETED_MOTION_TYPE) + { + //Cancel our current spell and then mutate new movement generator + me->InterruptNonMeleeSpells(false); + (*me).GetMotionMaster()->Clear(false); + (*me).GetMotionMaster()->MoveChase(me->getVictim()); + } + } + } +} + +void guardAI::DoReplyToTextEmote(uint32 em) +{ + switch(em) + { + case TEXTEMOTE_KISS: me->HandleEmoteCommand(EMOTE_ONESHOT_BOW); break; + case TEXTEMOTE_WAVE: me->HandleEmoteCommand(EMOTE_ONESHOT_WAVE); break; + case TEXTEMOTE_SALUTE: me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); break; + case TEXTEMOTE_SHY: me->HandleEmoteCommand(EMOTE_ONESHOT_FLEX); break; + case TEXTEMOTE_RUDE: + case TEXTEMOTE_CHICKEN: me->HandleEmoteCommand(EMOTE_ONESHOT_POINT); break; + } +} + +void guardAI_orgrimmar::ReceiveEmote(Player* pPlayer, uint32 text_emote) +{ + if (pPlayer->GetTeam() == HORDE) + DoReplyToTextEmote(text_emote); +} + +void guardAI_stormwind::ReceiveEmote(Player* pPlayer, uint32 text_emote) +{ + if (pPlayer->GetTeam() == ALLIANCE) + DoReplyToTextEmote(text_emote); +} diff --git a/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h new file mode 100644 index 00000000000..d28f612625e --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedGuardAI.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_GUARDAI_H +#define SC_GUARDAI_H + +#define GENERIC_CREATURE_COOLDOWN 5000 + +struct guardAI : public ScriptedAI +{ + public: + explicit guardAI(Creature* pCreature); + ~guardAI() {} + + uint32 GlobalCooldown; //This variable acts like the global cooldown that players have (1.5 seconds) + uint32 BuffTimer; //This variable keeps track of buffs + + void Reset(); + + void EnterCombat(Unit * /*who*/); + + void JustDied(Unit *Killer); + + void UpdateAI(const uint32 diff); + + //common used for guards in main cities + void DoReplyToTextEmote(uint32 em); +}; + +struct guardAI_orgrimmar : public guardAI +{ + guardAI_orgrimmar(Creature *c) : guardAI(c) {} + + void ReceiveEmote(Player *player, uint32 text_emote); +}; + +struct guardAI_stormwind : public guardAI +{ + guardAI_stormwind(Creature *c) : guardAI(c) {} + + void ReceiveEmote(Player *player, uint32 text_emote); +}; +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedInstance.h b/src/server/game/AI/ScriptedAI/ScriptedInstance.h new file mode 100644 index 00000000000..25593e05300 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedInstance.h @@ -0,0 +1,20 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> +* This program is free software licensed under GPL version 2 +* Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_INSTANCE_H +#define SC_INSTANCE_H + +#include "InstanceData.h" +#include "Map.h" + +#define OUT_SAVE_INST_DATA debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d)", instance->GetMapName(), instance->GetId(), instance->GetInstanceId()) +#define OUT_SAVE_INST_DATA_COMPLETE debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d) completed.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId()) +#define OUT_LOAD_INST_DATA(a) debug_log("TSCR: Loading Instance Data for Instance %s (Map %d, Instance Id %d). Input is '%s'", instance->GetMapName(), instance->GetId(), instance->GetInstanceId(), a) +#define OUT_LOAD_INST_DATA_COMPLETE debug_log("TSCR: Instance Data Load for Instance %s (Map %d, Instance Id: %d) is complete.",instance->GetMapName(), instance->GetId(), instance->GetInstanceId()) +#define OUT_LOAD_INST_DATA_FAIL error_log("TSCR: Unable to load Instance Data for Instance %s (Map %d, Instance Id: %d).",instance->GetMapName(), instance->GetId(), instance->GetInstanceId()) + +#define ScriptedInstance InstanceData + +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp new file mode 100644 index 00000000000..08797515837 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.cpp @@ -0,0 +1,278 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> +* 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 +*/ + +/* ScriptData +SDName: SimpleAI +SD%Complete: 100 +SDComment: Base Class for SimpleAI creatures +SDCategory: Creatures +EndScriptData */ + +#include "ScriptedPch.h" +#include "ScriptedSimpleAI.h" + +SimpleAI::SimpleAI(Creature *c) : ScriptedAI(c) +{ + //Clear all data + Aggro_TextId[0] = 0; + Aggro_TextId[1] = 0; + Aggro_TextId[2] = 0; + Aggro_Sound[0] = 0; + Aggro_Sound[1] = 0; + Aggro_Sound[2] = 0; + + Death_TextId[0] = 0; + Death_TextId[1] = 0; + Death_TextId[2] = 0; + Death_Sound[0] = 0; + Death_Sound[1] = 0; + Death_Sound[2] = 0; + Death_Spell = 0; + Death_Target_Type = 0; + + Kill_TextId[0] = 0; + Kill_TextId[1] = 0; + Kill_TextId[2] = 0; + Kill_Sound[0] = 0; + Kill_Sound[1] = 0; + Kill_Sound[2] = 0; + Kill_Spell = 0; + Kill_Target_Type = 0; + + memset(Spell,0,sizeof(Spell)); + + EnterEvadeMode(); +} + +void SimpleAI::Reset() +{ +} + +void SimpleAI::EnterCombat(Unit *who) +{ + //Reset cast timers + if (Spell[0].First_Cast >= 0) + Spell_Timer[0] = Spell[0].First_Cast; + else Spell_Timer[0] = 1000; + if (Spell[1].First_Cast >= 0) + Spell_Timer[1] = Spell[1].First_Cast; + else Spell_Timer[1] = 1000; + if (Spell[2].First_Cast >= 0) + Spell_Timer[2] = Spell[2].First_Cast; + else Spell_Timer[2] = 1000; + if (Spell[3].First_Cast >= 0) + Spell_Timer[3] = Spell[3].First_Cast; + else Spell_Timer[3] = 1000; + if (Spell[4].First_Cast >= 0) + Spell_Timer[4] = Spell[4].First_Cast; + else Spell_Timer[4] = 1000; + if (Spell[5].First_Cast >= 0) + Spell_Timer[5] = Spell[5].First_Cast; + else Spell_Timer[5] = 1000; + if (Spell[6].First_Cast >= 0) + Spell_Timer[6] = Spell[6].First_Cast; + else Spell_Timer[6] = 1000; + if (Spell[7].First_Cast >= 0) + Spell_Timer[7] = Spell[7].First_Cast; + else Spell_Timer[7] = 1000; + if (Spell[8].First_Cast >= 0) + Spell_Timer[8] = Spell[8].First_Cast; + else Spell_Timer[8] = 1000; + if (Spell[9].First_Cast >= 0) + Spell_Timer[9] = Spell[9].First_Cast; + else Spell_Timer[9] = 1000; + + uint8 random_text = urand(0,2); + + //Random text + if (Aggro_TextId[random_text]) + DoScriptText(Aggro_TextId[random_text], me, who); + + //Random sound + if (Aggro_Sound[random_text]) + DoPlaySoundToSet(me, Aggro_Sound[random_text]); +} + +void SimpleAI::KilledUnit(Unit *victim) +{ + uint8 random_text = urand(0,2); + + //Random yell + if (Kill_TextId[random_text]) + DoScriptText(Kill_TextId[random_text], me, victim); + + //Random sound + if (Kill_Sound[random_text]) + DoPlaySoundToSet(me, Kill_Sound[random_text]); + + if (!Kill_Spell) + return; + + Unit *pTarget = NULL; + + switch (Kill_Target_Type) + { + case CAST_SELF: + pTarget = me; + break; + case CAST_HOSTILE_TARGET: + pTarget = me->getVictim(); + break; + case CAST_HOSTILE_SECOND_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1); + break; + case CAST_HOSTILE_LAST_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0); + break; + case CAST_HOSTILE_RANDOM: + pTarget = SelectUnit(SELECT_TARGET_RANDOM,0); + break; + case CAST_KILLEDUNIT_VICTIM: + pTarget = victim; + break; + } + + //Target is ok, cast a spell on it + if (pTarget) + DoCast(pTarget, Kill_Spell); +} + +void SimpleAI::DamageTaken(Unit *killer, uint32 &damage) +{ + //Return if damage taken won't kill us + if (me->GetHealth() > damage) + return; + + uint8 random_text = urand(0,2); + + //Random yell + if (Death_TextId[random_text]) + DoScriptText(Death_TextId[random_text], me, killer); + + //Random sound + if (Death_Sound[random_text]) + DoPlaySoundToSet(me, Death_Sound[random_text]); + + if (!Death_Spell) + return; + + Unit *pTarget = NULL; + + switch (Death_Target_Type) + { + case CAST_SELF: + pTarget = me; + break; + case CAST_HOSTILE_TARGET: + pTarget = me->getVictim(); + break; + case CAST_HOSTILE_SECOND_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1); + break; + case CAST_HOSTILE_LAST_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0); + break; + case CAST_HOSTILE_RANDOM: + pTarget = SelectUnit(SELECT_TARGET_RANDOM,0); + break; + case CAST_JUSTDIED_KILLER: + pTarget = killer; + break; + } + + //Target is ok, cast a spell on it + if (pTarget) + DoCast(pTarget, Death_Spell); +} + +void SimpleAI::UpdateAI(const uint32 diff) +{ + //Return since we have no target + if (!UpdateVictim()) + return; + + //Spells + for (uint32 i = 0; i < 10; ++i) + { + //Spell not valid + if (!Spell[i].Enabled || !Spell[i].Spell_Id) + continue; + + if (Spell_Timer[i] <= diff) + { + //Check if this is a percentage based + if (Spell[i].First_Cast < 0 && Spell[i].First_Cast > -100 && me->GetHealth()*100 / me->GetMaxHealth() > -Spell[i].First_Cast) + continue; + + //Check Current spell + if (!(Spell[i].InterruptPreviousCast && me->IsNonMeleeSpellCasted(false))) + { + Unit *pTarget = NULL; + + switch (Spell[i].Cast_Target_Type) + { + case CAST_SELF: + pTarget = me; + break; + case CAST_HOSTILE_TARGET: + pTarget = me->getVictim(); + break; + case CAST_HOSTILE_SECOND_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1); + break; + case CAST_HOSTILE_LAST_AGGRO: + pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0); + break; + case CAST_HOSTILE_RANDOM: + pTarget = SelectUnit(SELECT_TARGET_RANDOM,0); + break; + } + + //Target is ok, cast a spell on it and then do our random yell + if (pTarget) + { + if (me->IsNonMeleeSpellCasted(false)) + me->InterruptNonMeleeSpells(false); + + DoCast(pTarget, Spell[i].Spell_Id); + + //Yell and sound use the same number so that you can make + //the Creature yell with the correct sound effect attached + uint8 random_text = urand(0,2); + + //Random yell + if (Spell[i].TextId[random_text]) + DoScriptText(Spell[i].TextId[random_text], me, pTarget); + + //Random sound + if (Spell[i].Text_Sound[random_text]) + DoPlaySoundToSet(me, Spell[i].Text_Sound[random_text]); + } + + } + + //Spell will cast agian when the cooldown is up + if (Spell[i].CooldownRandomAddition) + Spell_Timer[i] = Spell[i].Cooldown + (rand() % Spell[i].CooldownRandomAddition); + else Spell_Timer[i] = Spell[i].Cooldown; + + } else Spell_Timer[i] -= diff; + + } + + DoMeleeAttackIfReady(); +} + diff --git a/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h new file mode 100644 index 00000000000..c4689ff3fab --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedSimpleAI.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> +* This program is free software licensed under GPL version 2 +* Please see the included DOCS/LICENSE.TXT for more information */ + +#ifndef SC_SIMPLEAI_H +#define SC_SIMPLEAI_H + +enum CastTarget +{ + CAST_SELF = 0, //Self cast + CAST_HOSTILE_TARGET, //Our current target (ie: highest aggro) + CAST_HOSTILE_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks) + CAST_HOSTILE_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for) + CAST_HOSTILE_RANDOM, //Just any random target on our threat list + CAST_FRIENDLY_RANDOM, //NOT YET IMPLEMENTED + + //Special cases + CAST_KILLEDUNIT_VICTIM, //Only works within KilledUnit function + CAST_JUSTDIED_KILLER, //Only works within JustDied function +}; + +struct SimpleAI : public ScriptedAI +{ + SimpleAI(Creature *c);// : ScriptedAI(c); + + void Reset(); + + void EnterCombat(Unit * /*who*/); + + void KilledUnit(Unit * /*victim*/); + + void DamageTaken(Unit *killer, uint32 &damage); + + void UpdateAI(const uint32 diff); + +public: + + int32 Aggro_TextId[3]; + uint32 Aggro_Sound[3]; + + int32 Death_TextId[3]; + uint32 Death_Sound[3]; + uint32 Death_Spell; + uint32 Death_Target_Type; + + int32 Kill_TextId[3]; + uint32 Kill_Sound[3]; + uint32 Kill_Spell; + uint32 Kill_Target_Type; + + struct SimpleAI_Spell + { + uint32 Spell_Id; //Spell ID to cast + int32 First_Cast; //Delay for first cast + uint32 Cooldown; //Cooldown between casts + uint32 CooldownRandomAddition; //Random addition to cooldown (in range from 0 - CooldownRandomAddition) + uint32 Cast_Target_Type; //Target type (note that certain spells may ignore this) + bool InterruptPreviousCast; //Interrupt a previous cast if this spell needs to be cast + bool Enabled; //Spell enabled or disabled (default: false) + + //3 texts to many? + int32 TextId[3]; + uint32 Text_Sound[3]; + }Spell[10]; + +protected: + uint32 Spell_Timer[10]; +}; + +#endif + diff --git a/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp new file mode 100644 index 00000000000..c5c11d5bef9 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.cpp @@ -0,0 +1,8 @@ +/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#include "ScriptedSmartAI.h"
\ No newline at end of file diff --git a/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h new file mode 100644 index 00000000000..a1e10a06bb2 --- /dev/null +++ b/src/server/game/AI/ScriptedAI/ScriptedSmartAI.h @@ -0,0 +1,8 @@ +/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/> + * + * This program is free software licensed under GPL version 2 + * Please see the included DOCS/LICENSE.TXT for more information */ + +#include "Creature.h"
\ No newline at end of file |
