gdle/Source/MonsterAI.cpp
2020-09-04 17:06:39 -07:00

1035 lines
24 KiB
C++

#include <StdAfx.h>
#include "MonsterAI.h"
#include "WeenieObject.h"
#include "Monster.h"
#include "World.h"
#include "SpellcastingManager.h"
#include "EmoteManager.h"
#include "CombatSystem.h"
bool monster_brawl = 0;
#define DEFAULT_AWARENESS_RANGE 40.0
MonsterAIManager::MonsterAIManager(CMonsterWeenie *pWeenie, const Position &HomePos)
{
m_pWeenie = pWeenie;
_toleranceType = pWeenie->InqIntQuality(TOLERANCE_INT, 0, TRUE);
_aiOptions = pWeenie->InqIntQuality(AI_OPTIONS_INT, 0, TRUE);
_cachedVisualAwarenessRange = m_pWeenie->InqFloatQuality(VISUAL_AWARENESS_RANGE_FLOAT, DEFAULT_AWARENESS_RANGE);
if(!m_pWeenie->GetWieldedCombat(COMBAT_USE_TWO_HANDED))
_meleeWeapon = m_pWeenie->GetWieldedCombat(COMBAT_USE_MELEE);
else {
_meleeWeapon = m_pWeenie->GetWieldedCombat(COMBAT_USE_TWO_HANDED);
}
_missileWeapon = m_pWeenie->GetWieldedCombat(COMBAT_USE_MISSILE);
_shield = m_pWeenie->GetWieldedCombat(COMBAT_USE_SHIELD);
SKILL_ADVANCEMENT_CLASS unarmedSkill;
m_pWeenie->m_Qualities.InqSkillAdvancementClass(LIGHT_WEAPONS_SKILL, unarmedSkill);
_hasUnarmedSkill = (unarmedSkill > UNTRAINED_SKILL_ADVANCEMENT_CLASS);
if (_meleeWeapon != NULL && _missileWeapon != NULL) //if we have both melee and missile weapons, favor missile
{
m_pWeenie->FinishMoveItemToContainer(_meleeWeapon, m_pWeenie, 0, true);
if (_shield && _missileWeapon->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, 0) != ThrownWeapon_CombatStyle)
m_pWeenie->FinishMoveItemToContainer(_shield, m_pWeenie, 0, true);
else
_currentShield = _shield;
_currentWeapon = _missileWeapon;
}
else if (_missileWeapon != NULL)
{
_currentWeapon = _missileWeapon;
if (_shield && _missileWeapon->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, 0) != ThrownWeapon_CombatStyle)
m_pWeenie->FinishMoveItemToContainer(_shield, m_pWeenie, 0, true);
else
_currentShield = _shield;
}
else if (_meleeWeapon != NULL)
{
_currentWeapon = _meleeWeapon;
_currentShield = _shield;
}
if (pWeenie->m_Qualities._emote_table && pWeenie->m_Qualities._emote_table->_emote_table.lookup(Taunt_EmoteCategory))
_nextTaunt = Timer::cur_time + Random::GenUInt(10, 30); //We have taunts so schedule them.
pWeenie->transient_state |= CONTACT_TS;
// List of qualities that could be used to affect the AI behavior:
// AI_CP_THRESHOLD_INT
// AI_PP_THRESHOLD_INT
// AI_ADVANCEMENT_STRATEGY_INT
// AI_ALLOWED_COMBAT_STYLE_INT
// COMBAT_TACTIC_INT
// TARGETING_TACTIC_INT
// HOMESICK_TARGETING_TACTIC_INT
// AI_OPTIONS_INT
// FRIEND_TYPE_INT
// FOE_TYPE_INT
// ATTACKER_AI_BOOL
// AI_USES_MANA_BOOL
// AI_USE_HUMAN_MAGIC_ANIMATIONS_BOOL
// AI_IMMOBILE_BOOL
// AI_ALLOW_TRADE_BOOL
// AI_ACCEPT_EVERYTHING_BOOL
// AI_USE_MAGIC_DELAY_FLOAT
// AI_ACQUIRE_HEALTH_FLOAT
// AI_ACQUIRE_STAMINA_FLOAT
// AI_ACQUIRE_MANA_FLOAT
// AI_COUNTERACT_ENCHANTMENT_FLOAT
// AI_DISPEL_ENCHANTMENT_FLOAT
// AI_TARGETED_DETECTION_RADIUS_FLOAT
// SetHomePosition(HomePos); don't use preset home position
}
MonsterAIManager::~MonsterAIManager()
{
}
void MonsterAIManager::SetHomePosition(const Position &pos)
{
m_HomePosition = pos;
}
void MonsterAIManager::Update()
{
if (!m_HomePosition.objcell_id)
{
// make sure we set a home position
if (!(m_pWeenie->transient_state & ON_WALKABLE_TS))
return;
SetHomePosition(m_pWeenie->m_Position);
}
switch (m_State)
{
case MonsterAIState::Idle:
UpdateIdle();
break;
case MonsterAIState::MeleeModeAttack:
UpdateMeleeModeAttack();
break;
case MonsterAIState::MissileModeAttack:
UpdateMissileModeAttack();
break;
case MonsterAIState::ReturningToSpawn:
UpdateReturningToSpawn();
break;
case MonsterAIState::SeekNewTarget:
UpdateSeekNewTarget();
break;
}
}
void MonsterAIManager::SwitchState(int state)
{
if (state == m_State)
return;
switch (state)
{
case MonsterAIState::Idle:
EndIdle();
break;
case MonsterAIState::MeleeModeAttack:
EndMeleeModeAttack();
break;
case MonsterAIState::MissileModeAttack:
EndMissileModeAttack();
break;
case MonsterAIState::ReturningToSpawn:
EndReturningToSpawn();
break;
case MonsterAIState::SeekNewTarget:
EndSeekNewTarget();
break;
}
EnterState(state);
}
void MonsterAIManager::EnterState(int state)
{
m_State = state;
switch (state)
{
case MonsterAIState::Idle:
BeginIdle();
break;
case MonsterAIState::MeleeModeAttack:
BeginMeleeModeAttack();
break;
case MonsterAIState::MissileModeAttack:
BeginMissileModeAttack();
break;
case MonsterAIState::ReturningToSpawn:
BeginReturningToSpawn();
break;
case MonsterAIState::SeekNewTarget:
BeginSeekNewTarget();
break;
}
}
void MonsterAIManager::BeginIdle()
{
m_fNextPVSCheck = Timer::cur_time;
m_pWeenie->ChangeCombatMode(COMBAT_MODE::NONCOMBAT_COMBAT_MODE, false);
if (monster_brawl)
{
m_pWeenie->m_MonsterAI->_toleranceType = TolerateNothing;
}
}
void MonsterAIManager::EndIdle()
{
}
void MonsterAIManager::UpdateIdle()
{
if (_toleranceType == TolerateNothing || monster_brawl)
{
SeekTarget();
}
}
bool MonsterAIManager::SeekTarget()
{
if (m_fNextPVSCheck <= Timer::cur_time)
{
m_fNextPVSCheck = Timer::cur_time + 2.0f;
std::list<CWeenieObject *> results;
g_pWorld->EnumNearby(m_pWeenie, _cachedVisualAwarenessRange, &results);
std::list<CWeenieObject *> validTargets;
CWeenieObject *pClosestWeenie = NULL;
double fClosestWeenieDist = FLT_MAX;
for (auto weenie : results)
{
if (!IsValidTarget(weenie))
continue;
if (!weenie->IsAttackable())
continue;
validTargets.push_back(weenie);
}
if (!validTargets.empty())
{
CWeenieObject* target = nullptr;
switch ((TargetingTacticType)m_pWeenie->m_Qualities.GetInt(TARGETING_TACTIC_INT, 0))
{
case Target_Nearest:
{
float dist = 10;
for (auto it : validTargets)
{
float currentDist = m_pWeenie->DistanceTo(it);
if(currentDist < dist)
{
dist = currentDist;
target = it;
}
}
break;
}
case Target_Weakest:
{
int level = 275;
for (auto it : validTargets)
{
float currentLevel = it->m_Qualities.GetInt(LEVEL_INT, 0);
if (currentLevel < level)
{
level = currentLevel;
target = it;
}
}
break;
}
case Target_Strongest:
{
int level = 1;
for (auto it : validTargets)
{
float currentLevel = it->m_Qualities.GetInt(LEVEL_INT, 0);
if (currentLevel > level)
{
level = currentLevel;
target = it;
}
}
break;
}
default: // Attack the highest damager
{
int highDamageAmt = 0;
uint32_t highDamager = 0;
for (auto ds : m_pWeenie->m_aDamageSources)
{
if (ds.second > highDamageAmt)
{
highDamageAmt = ds.second;
highDamager = ds.first;
}
}
target = g_pWorld->FindObject(highDamager);
break;
}
}
if (!target) // Random target if no target found by now
{
std::list<CWeenieObject *>::iterator i = validTargets.begin();
std::advance(i, Random::GenInt(0, (unsigned int)(validTargets.size() - 1)));
SetNewTarget(*i);
}
else
SetNewTarget(target);
return true;
}
if (monster_brawl)
{
SetHomePosition(m_pWeenie->m_Position); // this is your home now
}
}
return false;
}
void MonsterAIManager::SetNewTarget(CWeenieObject *pTarget)
{
m_TargetID = pTarget->GetID();
if (_missileWeapon != NULL)
{
double fTargetDist = m_pWeenie->DistanceTo(pTarget, true);
if(fTargetDist > 5 || (_meleeWeapon == NULL && !_hasUnarmedSkill))
SwitchState(MissileModeAttack);
else
SwitchState(MeleeModeAttack);
}
else
SwitchState(MeleeModeAttack);
m_pWeenie->ChanceExecuteEmoteSet(pTarget->GetID(), NewEnemy_EmoteCategory);
}
CWeenieObject *MonsterAIManager::GetTargetWeenie()
{
return g_pWorld->FindObject(m_TargetID);
}
float MonsterAIManager::DistanceToHome()
{
if (!m_pWeenie)
{
return FLT_MAX;
}
if (!m_HomePosition.objcell_id)
return FLT_MAX;
return m_HomePosition.distance(m_pWeenie->m_Position);
}
bool MonsterAIManager::ShouldSeekNewTarget()
{
if (monster_brawl)
{
return true; // we always want a new target
}
if (DistanceToHome() >= m_fMaxHomeRange)
return false;
return true;
}
bool MonsterAIManager::RollDiceCastSpell()
{
if (m_fNextCastTime > Timer::cur_time)
{
return false;
}
if (m_pWeenie->m_Qualities._spell_book)
{
/* not correct, these must be independent events (look at wisps)
float dice = Random::RollDice(0.0f, 1.0f);
auto spellIterator = m_pWeenie->m_Qualities._spell_book->_spellbook.begin();
while (spellIterator != m_pWeenie->m_Qualities._spell_book->_spellbook.end())
{
float likelihood = spellIterator->second._casting_likelihood;
if (dice <= likelihood)
{
return DoCastSpell(spellIterator->first);
}
dice -= likelihood;
spellIterator++;
}
*/
auto spellIterator = m_pWeenie->m_Qualities._spell_book->_spellbook.begin();
while (spellIterator != m_pWeenie->m_Qualities._spell_book->_spellbook.end())
{
float dice = Random::RollDice(0.0f, 1.0f);
float likelihood = spellIterator->second._casting_likelihood;
if (dice <= likelihood)
{
m_fNextCastTime = Timer::cur_time + m_pWeenie->m_Qualities.GetFloat(AI_USE_MAGIC_DELAY_FLOAT, 0.0);
return DoCastSpell(spellIterator->first);
}
spellIterator++;
}
}
return false;
}
bool MonsterAIManager::DoCastSpell(uint32_t spell_id)
{
CWeenieObject *pTarget = GetTargetWeenie();
m_pWeenie->MakeSpellcastingManager()->CreatureBeginCast(pTarget ? pTarget->GetID() : 0, spell_id);
return true;
}
bool MonsterAIManager::DoMeleeAttack()
{
uint32_t motion = 0;
ATTACK_HEIGHT height = ATTACK_HEIGHT::UNDEF_ATTACK_HEIGHT;
float power = 0.0f;
GenerateRandomAttack(&motion, &height, &power);
if (!motion)
{
return false;
}
CWeenieObject *pTarget = GetTargetWeenie();
if (!pTarget)
{
return false;
}
m_pWeenie->TryMeleeAttack(pTarget->GetID(), height, power, motion);
m_fNextAttackTime = Timer::cur_time + 2.0f + power;
m_fNextChaseTime = Timer::cur_time; // chase again anytime
m_fMinCombatStateTime = Timer::cur_time + m_fMinCombatStateDuration;
return true;
}
void MonsterAIManager::GenerateRandomAttack(uint32_t *motion, ATTACK_HEIGHT *height, float *power, CWeenieObject *weapon)
{
*motion = 0;
*height = ATTACK_HEIGHT::UNDEF_ATTACK_HEIGHT;
*power = (float)Random::GenFloat();
if (m_pWeenie->_combatTable)
{
if (weapon == NULL)
{
if (!m_pWeenie->GetWieldedCombat(COMBAT_USE_TWO_HANDED))
weapon = m_pWeenie->GetWieldedCombat(COMBAT_USE_MELEE);
else
weapon = m_pWeenie->GetWieldedCombat(COMBAT_USE_TWO_HANDED);
}
if (weapon)
{
AttackType attackType = (AttackType)weapon->InqIntQuality(ATTACK_TYPE_INT, 0);
uint32_t style = m_pWeenie->get_minterp()->InqStyle();
if (attackType == (Thrust_AttackType | Slash_AttackType))
{
if (*power >= 0.75f)
attackType = Slash_AttackType;
else
attackType = Thrust_AttackType;
}
CombatManeuver *combatManeuver = NULL;
for (uint32_t i = 0; i < m_pWeenie->_combatTable->_num_combat_maneuvers; i++)
{
int m = Random::GenInt(0, m_pWeenie->_combatTable->_num_combat_maneuvers - 1);
if (m_pWeenie->_combatTable->_cmt[m].style == style && m_pWeenie->_combatTable->_cmt[m].attack_type & attackType)
{
combatManeuver = &m_pWeenie->_combatTable->_cmt[m];
break;
}
}
if (combatManeuver)
{
*motion = combatManeuver->motion;
*height = combatManeuver->attack_height;
}
}
else
{
CombatManeuver *combatManeuver = NULL;
for (uint32_t i = 0; i < m_pWeenie->_combatTable->_num_combat_maneuvers; i++)
{
int m = Random::GenInt(0, m_pWeenie->_combatTable->_num_combat_maneuvers - 1);
if (m_pWeenie->_combatTable->_cmt[m].style == Motion_HandCombat)
{
combatManeuver = &m_pWeenie->_combatTable->_cmt[m];
break;
}
}
if (combatManeuver)
{
*motion = combatManeuver->motion;
*height = combatManeuver->attack_height;
}
}
}
if (!*motion)
{
*motion = Motion_AttackHigh1;
}
}
void MonsterAIManager::BeginReturningToSpawn()
{
// m_pWeenie->DoForcedStopCompletely();
MovementParameters params;
params.can_walk = 0;
MovementStruct mvs;
mvs.type = MovementTypes::MoveToPosition;
mvs.pos = m_HomePosition;
mvs.params = &params;
m_pWeenie->PerformMovement(mvs);
m_fReturnTimeoutTime = Timer::cur_time + m_fReturnTimeout;
m_pWeenie->ChanceExecuteEmoteSet(m_TargetID, Homesick_EmoteCategory);
}
void MonsterAIManager::EndReturningToSpawn()
{
}
void MonsterAIManager::UpdateReturningToSpawn()
{
float fDistToHome = m_HomePosition.distance(m_pWeenie->m_Position);
if (fDistToHome < 5.0f)
{
SwitchState(Idle);
return;
}
if (m_fReturnTimeoutTime <= Timer::cur_time)
{
// teleport back to spawn
m_pWeenie->Movement_Teleport(m_HomePosition);
SwitchState(Idle);
return;
}
}
void MonsterAIManager::OnDeath()
{
}
bool MonsterAIManager::IsValidTarget(CWeenieObject *pWeenie)
{
if (!pWeenie)
return false;
if (pWeenie == m_pWeenie)
return false;
if (!pWeenie->AsMonster())
return false;
if (!pWeenie->IsAttackable())
return false;
if (monster_brawl)
{
// if monster fighting is on, don't fight your own kind
return m_pWeenie->InqIntQuality(CREATURE_TYPE_INT, 0) != pWeenie->InqIntQuality(CREATURE_TYPE_INT, 0);
}
if (m_pWeenie->m_Qualities.m_WeenieType == WeenieType::Creature_WeenieType &&
(pWeenie->m_Qualities.m_WeenieType == WeenieType::Creature_WeenieType && !pWeenie->AsPlayer()))
return false;
// if a creature & not a player, don't fight
if (m_pWeenie->m_Qualities.m_WeenieType == WeenieType::CombatPet_WeenieType &&
(pWeenie->AsPlayer() || pWeenie->m_Qualities.m_WeenieType == WeenieType::CombatPet_WeenieType ))
return false;
if (pWeenie->ImmuneToDamage(m_pWeenie)) // only attackable players (not dead, not in portal space, etc.
return false;
return true;
}
void MonsterAIManager::AlertIdleFriendsToAggro(CWeenieObject *pAttacker)
{
std::list<CWeenieObject *> results;
g_pWorld->EnumNearby(m_pWeenie, 20.0f, &results);
int ourType = m_pWeenie->InqIntQuality(CREATURE_TYPE_INT, 0);
int ourFriendType = m_pWeenie->InqIntQuality(FRIEND_TYPE_INT, 0);
for (auto weenie : results)
{
if (weenie == m_pWeenie)
continue;
if (!weenie->IsCreature())
continue;
CMonsterWeenie *creature = (CMonsterWeenie *)weenie;
if (!creature->m_MonsterAI)
continue;
switch (creature->m_MonsterAI->m_State)
{
case Idle:
case ReturningToSpawn:
case SeekNewTarget:
break;
default:
continue;
}
if (!creature->m_MonsterAI->IsValidTarget(pAttacker))
continue;
if (creature->m_MonsterAI->_toleranceType == TolerateEverything)
continue;
if (creature->m_MonsterAI->_toleranceType == TolerateUnlessAttacked && _aiOptions != 1) // _aiOptions = 1 creatures do not tolerate their own kind/friends being attacked.
continue;
int theirType = creature->InqIntQuality(CREATURE_TYPE_INT, 0);
int theirFriendType = creature->InqIntQuality(FRIEND_TYPE_INT, 0);
if (ourType > 0 && theirType > 0)
{
if (ourType != theirType && ourType != theirFriendType && theirType != ourFriendType) //we only help our own kind or friends
continue;
}
creature->m_MonsterAI->m_fAggroTime = Timer::cur_time + 10.0;
creature->m_MonsterAI->SetNewTarget(pAttacker);
}
}
void MonsterAIManager::OnResistSpell(CWeenieObject *attacker)
{
if(attacker)
m_pWeenie->ChanceExecuteEmoteSet(attacker->GetID(), ResistSpell_EmoteCategory);
}
void MonsterAIManager::OnEvadeAttack(CWeenieObject *attacker)
{
}
void MonsterAIManager::OnDealtDamage(DamageEventData &damageData)
{
if (_nextTaunt > 0 && _nextTaunt <= Timer::cur_time)
{
if (damageData.target)
m_pWeenie->ChanceExecuteEmoteSet(damageData.target->GetID(), Taunt_EmoteCategory);
_nextTaunt = Timer::cur_time + Random::GenUInt(10, 30);
}
}
void MonsterAIManager::OnTookDamage(DamageEventData &damageData)
{
CWeenieObject *source = damageData.source;
unsigned int damage = damageData.outputDamageFinal;
if (!source)
return;
HandleAggro(source);
if(damageData.wasCrit)
m_pWeenie->ChanceExecuteEmoteSet(source->GetID(), ReceiveCritical_EmoteCategory);
if (m_pWeenie->m_Qualities._emote_table && !m_pWeenie->IsExecutingEmote())
{
PackableList<EmoteSet> *emoteSetList = m_pWeenie->m_Qualities._emote_table->_emote_table.lookup(WoundedTaunt_EmoteCategory);
if (emoteSetList)
{
double healthPercent = m_pWeenie->GetHealthPercent();
if (m_fLastWoundedTauntHP > healthPercent)
{
double dice = Random::GenFloat(0.0, 1.0);
for (auto &emoteSet : *emoteSetList)
{
// ignore probability?
if (healthPercent >= emoteSet.minhealth && healthPercent < emoteSet.maxhealth)
{
m_fLastWoundedTauntHP = healthPercent;
m_pWeenie->MakeEmoteManager()->ExecuteEmoteSet(emoteSet, source->GetID());
}
}
}
}
}
}
void MonsterAIManager::OnIdentifyAttempted(CWeenieObject *other)
{
if (_toleranceType != TolerateUnlessBothered)
{
return;
}
if (m_pWeenie->DistanceTo(other, true) >= 60.0)
{
return;
}
HandleAggro(other);
}
void MonsterAIManager::HandleAggro(CWeenieObject *pAttacker)
{
if (!pAttacker)
return;
if (_toleranceType == TolerateEverything)
{
return;
}
if (!pAttacker->AsPlayer() && pAttacker->AsMonster() && !monster_brawl)
return;
if (!m_pWeenie->IsDead())
{
switch (m_State)
{
case Idle:
case ReturningToSpawn:
case SeekNewTarget:
{
SetNewTarget(pAttacker);
m_pWeenie->ChanceExecuteEmoteSet(pAttacker->GetID(), Scream_EmoteCategory);
m_fAggroTime = Timer::cur_time + 10.0;
break;
}
}
}
AlertIdleFriendsToAggro(pAttacker);
}
void MonsterAIManager::BeginSeekNewTarget()
{
if (!ShouldSeekNewTarget() || !SeekTarget())
{
SwitchState(ReturningToSpawn);
}
}
void MonsterAIManager::UpdateSeekNewTarget()
{
SwitchState(ReturningToSpawn);
}
void MonsterAIManager::EndSeekNewTarget()
{
}
void MonsterAIManager::BeginMeleeModeAttack()
{
if (_shield != NULL && _currentShield == NULL)
{
if (m_pWeenie->FinishMoveItemToWield(_shield, SHIELD_LOC)) //make sure our shield is equipped
_currentShield = _shield;
}
if (_currentWeapon != _meleeWeapon)
{
if (_currentWeapon)
m_pWeenie->FinishMoveItemToContainer(_currentWeapon, m_pWeenie, 0, true);
if (_meleeWeapon)
{
if (m_pWeenie->FinishMoveItemToWield(_meleeWeapon, MELEE_WEAPON_LOC))
_currentWeapon = _meleeWeapon;
else
_currentWeapon = 0;
}
}
m_pWeenie->ChangeCombatMode(COMBAT_MODE::MELEE_COMBAT_MODE, false);
m_fChaseTimeoutTime = Timer::cur_time + m_fChaseTimeoutDuration;
m_fNextAttackTime = Timer::cur_time;
m_fNextChaseTime = Timer::cur_time;
m_fMinCombatStateTime = Timer::cur_time + m_fMinCombatStateDuration;
m_fMinReturnStateTime = Timer::cur_time + m_fMinReturnStateDuration;
}
void MonsterAIManager::EndMeleeModeAttack()
{
m_pWeenie->unstick_from_object();
}
void MonsterAIManager::UpdateMeleeModeAttack()
{
if (m_pWeenie->IsBusyOrInAction())
{
// still animating or busy (attacking, etc.)
return;
}
// rules:
// dont switch targets to one that is farther than visual awareness range, unless attacked
// dont chase a target that is outside the chase range, unless attacked
// dont chase any new target, even if attacked, outside home range
CWeenieObject *pTarget = GetTargetWeenie();
if (!pTarget || pTarget->IsDead() || !pTarget->IsAttackable() || pTarget->ImmuneToDamage(m_pWeenie) || m_pWeenie->DistanceTo(pTarget) >= m_fChaseRange)
{
if (ShouldSeekNewTarget())
{
SwitchState(SeekNewTarget);
}
else
{
SwitchState(ReturningToSpawn);
}
return;
}
if (DistanceToHome() >= m_fMaxHomeRange)
{
SwitchState(ReturningToSpawn);
return;
}
double fTargetDist = m_pWeenie->DistanceTo(pTarget, true);
if (fTargetDist >= m_pWeenie->InqFloatQuality(VISUAL_AWARENESS_RANGE_FLOAT, DEFAULT_AWARENESS_RANGE) && m_fAggroTime <= Timer::cur_time)
{
SwitchState(ReturningToSpawn);
return;
}
if (m_fNextAttackTime > Timer::cur_time)
{
return;
}
if (!RollDiceCastSpell() && m_pWeenie->DistanceTo(pTarget) < m_fChaseRange)
{
// do physics attack
uint32_t motion = 0;
ATTACK_HEIGHT height = ATTACK_HEIGHT::UNDEF_ATTACK_HEIGHT;
float power = 0.0f;
GenerateRandomAttack(&motion, &height, &power);
m_pWeenie->TryMeleeAttack(pTarget->GetID(), height, power, motion);
m_fNextAttackTime = Timer::cur_time + 2.0f + power;
m_fNextChaseTime = Timer::cur_time; // chase again anytime
m_fMinCombatStateTime = Timer::cur_time + m_fMinCombatStateDuration;
}
}
void MonsterAIManager::BeginMissileModeAttack()
{
if (_missileWeapon == 0)
{
SwitchState(MeleeModeAttack);
return;
}
else
{
if (_missileWeapon->InqIntQuality(DEFAULT_COMBAT_STYLE_INT, 0) != ThrownWeapon_CombatStyle)
{
CWeenieObject *equippedAmmo = m_pWeenie->GetWieldedCombat(COMBAT_USE::COMBAT_USE_AMMO);
if (!equippedAmmo)
{
//we don't have ammo, disable missile mode and switch to melee.
_missileWeapon = 0;
SwitchState(MeleeModeAttack);
return;
}
if (_currentShield != NULL)
{
m_pWeenie->FinishMoveItemToContainer(_currentShield, m_pWeenie, 0, true); //get rid of the shield.
_currentShield = NULL;
}
}
else if (_shield != NULL && _currentShield == NULL)
{
if(m_pWeenie->FinishMoveItemToWield(_shield, SHIELD_LOC)) //shields can be wielded with thrown weapons.
_currentShield = _shield;
}
}
if (_currentWeapon != _missileWeapon)
{
if (_currentWeapon)
{
m_pWeenie->FinishMoveItemToContainer(_currentWeapon, m_pWeenie, 0, true);
if (m_pWeenie->FinishMoveItemToWield(_missileWeapon, MISSILE_WEAPON_LOC))
_currentWeapon = _missileWeapon;
else
{
_currentWeapon = 0;
SwitchState(MeleeModeAttack);
}
}
}
m_pWeenie->ChangeCombatMode(COMBAT_MODE::MISSILE_COMBAT_MODE, false);
m_fChaseTimeoutTime = Timer::cur_time + m_fChaseTimeoutDuration;
m_fNextAttackTime = Timer::cur_time;
m_fNextChaseTime = Timer::cur_time;
m_fMinCombatStateTime = Timer::cur_time + m_fMinCombatStateDuration;
m_fMinReturnStateTime = Timer::cur_time + m_fMinReturnStateDuration;
}
void MonsterAIManager::EndMissileModeAttack()
{
}
void MonsterAIManager::UpdateMissileModeAttack()
{
if (m_pWeenie->IsBusyOrInAction() || m_pWeenie->motions_pending())
{
// still animating or busy (attacking, etc.)
return;
}
// rules:
// dont switch targets to one that is farther than visual awareness range, unless attacked
// dont chase a target that is outside the chase range, unless attacked
// dont chase any new target, even if attacked, outside home range
CWeenieObject *pTarget = GetTargetWeenie();
if (!pTarget || pTarget->IsDead() || !pTarget->IsAttackable() || pTarget->ImmuneToDamage(m_pWeenie) || m_pWeenie->DistanceTo(pTarget) >= m_fChaseRange)
{
if (ShouldSeekNewTarget())
{
SwitchState(SeekNewTarget);
}
else
{
SwitchState(ReturningToSpawn);
}
return;
}
if (DistanceToHome() >= m_fMaxHomeRange)
{
SwitchState(ReturningToSpawn);
return;
}
float weaponMinRange = 1;
float weaponMaxRange = 60; //todo: get the value from the weapon? Players currently have 60 as a fixed max value.
double fTargetDist = m_pWeenie->DistanceTo(pTarget, true);
if (fTargetDist >= max(m_pWeenie->InqFloatQuality(VISUAL_AWARENESS_RANGE_FLOAT, DEFAULT_AWARENESS_RANGE), (double)weaponMaxRange) && m_fAggroTime <= Timer::cur_time)
{
SwitchState(ReturningToSpawn);
return;
}
if (m_fNextAttackTime > Timer::cur_time)
{
return;
}
if (_meleeWeapon != NULL || _hasUnarmedSkill) //we also have a melee weapon(or know how to fight without one)
{
double roll = Random::GenFloat(0.0, 1.0);
if (fTargetDist < weaponMinRange && roll < 0.3) //the target is too close, let's go melee
{
SwitchState(MeleeModeAttack);
return;
}
if (roll < 0.02) //we're tired of doing missile attacks, let's switch it up
{
SwitchState(MeleeModeAttack);
return;
}
}
if (!RollDiceCastSpell())
{
if (m_pWeenie->DistanceTo(pTarget) < weaponMaxRange)
{
// do physics attack
uint32_t motion = 0;
ATTACK_HEIGHT height = (ATTACK_HEIGHT)Random::GenUInt(1, 3);
float power = (float)Random::GenFloat();
m_pWeenie->TryMissileAttack(pTarget->GetID(), height, power);
m_fNextAttackTime = Timer::cur_time + 2.0f;
m_fNextChaseTime = Timer::cur_time; // chase again anytime
m_fMinCombatStateTime = Timer::cur_time + m_fMinCombatStateDuration;
}
}
}