uox3/source/ai.cpp
Xoduz 8e6288e3f7 Misc updates
Fixed bug where player might get stuck as frozen in ghost form and unable to move if dying in the midst of casting a spell
Fixed bug where reagents could not be dropped directly from ground and into newbie reagents bag, as the bag didn't have the proper setup for a container. Now inherits its setup from a regular bag (dfndata/items/magic/reagents.dfn)
Fixed two broken timer references in JS: Timer.SPELLRECOVERYTIME and Timer.LOYALTYTIME
Fixed a bug where players could gain points in Evaluating Intelligence skill by targeting themselves (js/skills/evaluateintel.js)
Updated Evaluating Intelligence skill to show more detailed information about targets (js/skills/evaluateintel.js)
Fixed some issues with sound effects in JS based spells
UOX3 console should now show 100% when done saving, regardless of the amount of data it has to save
Earthquake spell can no longer harm invulnerable characters
Fixed issue where NPC would evade instead of change to a different nearby target if unable to pathfind to current target
Damage dealt by Fire Field spell is now determined by BASEDMG tag (defaults to 2, if resisted, half of that) in spells.dfn
The rate at which the effects of field spells (Fire Field, Poison Field and Paralyze Field) are applied is now determined via the DAMAGEDELAY tag in spells.dfn, which defaults to 1.0 seconds
2021-09-22 02:49:45 +08:00

401 lines
16 KiB
C++

//o-----------------------------------------------------------------------------------------------o
//| File - ai.cpp
//| Date - Pre-1999
//o-----------------------------------------------------------------------------------------------o
//| Purpose - All AI-Related Code goes here (Should eventually be
//| moved out to JavaScript code).
//o-----------------------------------------------------------------------------------------------o
//| Changes - Version History
//|
//| 1.1 30th December, 2003
//| Updated to move the majority of AI functionality
//| into several smaller functions. Should simplify the
//| process of moving it out to JavaScript in the future.
//o-----------------------------------------------------------------------------------------------o
#include "uox3.h"
#include "cRaces.h"
#include "cEffects.h"
#include "regions.h"
#include "combat.h"
#include "Dictionary.h"
#include "StringUtility.hpp"
#undef DBGFILE
#define DBGFILE "ai.cpp"
//o-----------------------------------------------------------------------------------------------o
//| Function - bool isValidAttackTarget( CChar *mChar, CChar *cTarget )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if cTarget is a valid attack target for mChar
//o-----------------------------------------------------------------------------------------------o
bool isValidAttackTarget( CChar& mChar, CChar *cTarget )
{
if( ValidateObject( cTarget ) && &mChar != cTarget )
{
if( mChar.IsNpc() && cTarget->IsNpc() )
{
//We don't want NPCs to attack one another if either of them are water-walking creatures
//as they can't reach one another in any case
bool targetWaterWalk = cwmWorldState->creatures[cTarget->GetID()].IsWater();
bool attackerWaterWalk = cwmWorldState->creatures[mChar.GetID()].IsWater();
if(( attackerWaterWalk && !targetWaterWalk ) || ( !attackerWaterWalk && targetWaterWalk ))
return false;
}
if( cTarget->IsInvulnerable() || cTarget->IsDead() || cTarget->GetVisible() != VT_VISIBLE || cTarget->IsEvading() )
return false;
if( objInRange( &mChar, cTarget, cwmWorldState->ServerData()->CombatMaxRange() ) )
{
if( LineOfSight( nullptr, (&mChar), cTarget->GetX(), cTarget->GetY(), ( cTarget->GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
{
if( isOnline( (*cTarget) ) || cTarget->IsNpc() )
return true;
}
}
}
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool checkForValidOwner( CChar& mChar, CChar *cTarget )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if mChar owns cTarget or vise-versa
//o-----------------------------------------------------------------------------------------------o
bool checkForValidOwner( CChar& mChar, CChar *cTarget )
{
if( ValidateObject( mChar.GetOwnerObj() ) && mChar.GetOwnerObj() == cTarget )
return true;
if( ValidateObject( cTarget->GetOwnerObj() ) && cTarget->GetOwnerObj() == &mChar )
return true;
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleGuardAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Guard AI Type
//o-----------------------------------------------------------------------------------------------o
void HandleGuardAI( CChar& mChar )
{
if( !mChar.IsAtWar() )
{
REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
{
CMapRegion *MapArea = (*rIter);
if( MapArea == nullptr ) // no valid region
continue;
GenericList< CChar * > *regChars = MapArea->GetCharList();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
{
if( isValidAttackTarget( mChar, tempChar ) )
{
if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
{
Combat->AttackTarget( &mChar, tempChar );
mChar.TextMessage( nullptr, 313, TALK, true );
regChars->Pop();
return;
}
}
}
regChars->Pop();
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleFighterAI( CChar& mChar )
//| Date - 06/15/2005
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Fighter AI Type
//o-----------------------------------------------------------------------------------------------o
void HandleFighterAI( CChar& mChar )
{
if( !mChar.IsAtWar() )
{
REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
{
CMapRegion *MapArea = (*rIter);
if( MapArea == nullptr ) // no valid region
continue;
GenericList< CChar * > *regChars = MapArea->GetCharList();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
{
if( isValidAttackTarget( mChar, tempChar ) )
{
if( !tempChar->IsDead() && ( tempChar->IsCriminal() || tempChar->IsMurderer() ) )
{
if( RandomNum( 0, 100 ) >= 85 ) // 85% chance to attack current target, 15% chance to pick another
continue;
Combat->AttackTarget( &mChar, tempChar );
regChars->Pop();
return;
}
}
}
regChars->Pop();
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleHealerAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Wandering Healer AI Type (Raises Innocents)
//o-----------------------------------------------------------------------------------------------o
void HandleHealerAI( CChar& mChar )
{
SOCKLIST nearbyChars = FindNearbyPlayers( &mChar, DIST_NEARBY );
for( SOCKLIST_CITERATOR cIter = nearbyChars.begin(); cIter != nearbyChars.end(); ++cIter )
{
CSocket *mSock = (*cIter);
CChar *realChar = mSock->CurrcharObj();
CMultiObj *multiObj = realChar->GetMultiObj();
if( realChar->IsDead() && ( !ValidateObject( multiObj ) || multiObj->GetOwner() == realChar->GetSerial() ))
{
if( LineOfSight( mSock, realChar, mChar.GetX(), mChar.GetY(), ( mChar.GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
{
if( realChar->IsMurderer() )
mChar.TextMessage( nullptr, 322, TALK, true );
else if( realChar->IsCriminal() )
mChar.TextMessage( nullptr, 770, TALK, true );
else if( realChar->IsInnocent() )
{
if( mChar.GetBodyType() == BT_GARGOYLE ||
(( mChar.GetBodyType() == BT_HUMAN || mChar.GetBodyType() == BT_ELF ) && cwmWorldState->ServerData()->ForceNewAnimationPacket() ))
{
Effects->PlayNewCharacterAnimation( &mChar, N_ACT_SPELL, S_ACT_SPELL_TARGET ); // Action 0x0b, subAction 0x00
}
else // Characters pre-v7.0.0.0
{
Effects->PlayCharacterAnimation( &mChar, ACT_SPELL_TARGET, 0, 7 ); // Action 0x10
}
NpcResurrectTarget( realChar );
Effects->PlayStaticAnimation( realChar, 0x376A, 0x09, 0x06 );
mChar.TextMessage( nullptr, ( 316 + RandomNum( 0, 4 ) ), TALK, false );
}
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleEvilHealerAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Evil Healer AI Type (Raises Murderers)
//o-----------------------------------------------------------------------------------------------o
void HandleEvilHealerAI( CChar& mChar )
{
SOCKLIST nearbyChars = FindNearbyPlayers( &mChar, DIST_NEARBY );
for( SOCKLIST_CITERATOR cIter = nearbyChars.begin(); cIter != nearbyChars.end(); ++cIter )
{
CSocket *mSock = (*cIter);
CChar *realChar = mSock->CurrcharObj();
CMultiObj *multiObj = realChar->GetMultiObj();
if( realChar->IsDead() && ( !ValidateObject( multiObj ) || multiObj->GetOwner() == realChar->GetSerial() ))
{
if( LineOfSight( mSock, realChar, mChar.GetX(), mChar.GetY(), ( mChar.GetZ() + 15 ), WALLS_CHIMNEYS + DOORS + FLOORS_FLAT_ROOFING, false ) )
{
if( realChar->IsMurderer() )
{
if( mChar.GetBodyType() == BT_GARGOYLE ||
(( mChar.GetBodyType() == BT_HUMAN || mChar.GetBodyType() == BT_ELF ) && cwmWorldState->ServerData()->ForceNewAnimationPacket() ))
{
Effects->PlayNewCharacterAnimation( &mChar, N_ACT_SPELL, S_ACT_SPELL_TARGET ); // Action 0x0b, subAction 0x00
}
else // Characters pre-v7.0.0.0
{
Effects->PlayCharacterAnimation( &mChar, ACT_SPELL_TARGET, 0, 7 ); // Action 0x10
}
NpcResurrectTarget( realChar );
Effects->PlayStaticAnimation( realChar, 0x3709, 0x09, 0x19 ); //Flamestrike effect
mChar.TextMessage( nullptr, ( 323 + RandomNum( 0, 4 ) ), TALK, false );
}
else
mChar.TextMessage( nullptr, 329, TALK, true );
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleEvilAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Evil creature AI Type (Attacks innocents)
//o-----------------------------------------------------------------------------------------------o
void HandleEvilAI( CChar& mChar )
{
if( !mChar.IsAtWar() )
{
REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
{
CMapRegion *MapArea = (*rIter);
if( MapArea == nullptr ) // no valid region
continue;
GenericList< CChar * > *regChars = MapArea->GetCharList();
// Chance to reverse list of chars
if( RandomNum( 0, 1 ))
regChars->Reverse();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
{
if( isValidAttackTarget( mChar, tempChar ) && !checkForValidOwner( mChar, tempChar ) )
{
if( tempChar->GetNPCAiType() == AI_EVIL || tempChar->GetNPCAiType() == AI_HEALER_G )
continue;
if( cwmWorldState->creatures[tempChar->GetID()].IsAnimal() )
{
if( !cwmWorldState->ServerData()->CombatMonstersVsAnimals() )
continue;
else if( cwmWorldState->ServerData()->CombatAnimalsAttackChance() < RandomNum( 1, 1000 ) )
continue;
}
if( mChar.GetRace() != 0 && mChar.GetRace() == tempChar->GetRace() && RandomNum( 0, 100 ) >= 10 ) // 10% chance of turning on own race
continue;
RaceRelate raceComp = Races->Compare( tempChar, &mChar );
if( raceComp >= RACE_ALLY ) // Allies
continue;
if( RandomNum( 0, 100 ) >= 85 ) // 85% chance to attack current target, 15% chance to pick another
continue;
Combat->AttackTarget( &mChar, tempChar );
regChars->Pop(); // restore before returning
return;
}
}
regChars->Pop();
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleChaoticAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Chaotic creature AI Type (Attacks everything)
//o-----------------------------------------------------------------------------------------------o
void HandleChaoticAI( CChar& mChar )
{
if( !mChar.IsAtWar() )
{
REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
{
CMapRegion *MapArea = (*rIter);
if( MapArea == nullptr ) // no valid region
continue;
GenericList< CChar * > *regChars = MapArea->GetCharList();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
{
if( isValidAttackTarget( mChar, tempChar ) && !checkForValidOwner( mChar, tempChar ) )
{
if( RandomNum( 0, 100 ) >= 85 ) // 85% chance to attack current target, 15% chance to pick another
continue;
Combat->AttackTarget( &mChar, tempChar );
regChars->Pop();
return;
}
}
regChars->Pop();
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void HandleAnimalAI( CChar& mChar )
//| Date - 21. Feb, 2006
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles Animal AI Type
//o-----------------------------------------------------------------------------------------------o
void HandleAnimalAI( CChar& mChar )
{
if( !mChar.IsAtWar() )
{
REGIONLIST nearbyRegions = MapRegion->PopulateList( &mChar );
for( REGIONLIST_CITERATOR rIter = nearbyRegions.begin(); rIter != nearbyRegions.end(); ++rIter )
{
const SI08 hunger = mChar.GetHunger();
if( hunger <= 4 )
{
CMapRegion *MapArea = (*rIter);
if( MapArea == nullptr ) // no valid region
continue;
GenericList< CChar * > *regChars = MapArea->GetCharList();
regChars->Push();
for( CChar *tempChar = regChars->First(); !regChars->Finished(); tempChar = regChars->Next() )
{
if( isValidAttackTarget( mChar, tempChar ) )
{
if( ( cwmWorldState->creatures[tempChar->GetID()].IsAnimal() && tempChar->GetNPCAiType() != AI_ANIMAL ) || hunger < 2 )
{
if( RandomNum( 0, 100 ) >= 50 ) // 50% chance to attack tempChar, 50% chance to attack next tempChar
continue;
Combat->AttackTarget( &mChar, tempChar );
regChars->Pop();
return;
}
}
}
regChars->Pop();
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void CheckAI( CChar& mChar )
//| Date - 12/30/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Switch to easily handle AI types
//o-----------------------------------------------------------------------------------------------o
void CheckAI( CChar& mChar )
{
CChar *realChar = nullptr;
switch( mChar.GetNPCAiType() )
{
case AI_NONE: // No special AI, default NPC behavior
case AI_DUMMY: // Passive AI - doesn't attack nor fight back
case AI_BANKER: // Banker
case AI_STABLEMASTER: // Stablemaster
case AI_PLAYERVENDOR: // Player Vendors.
break; // No AI for these special NPC's.
case AI_HEALER_G: HandleHealerAI( mChar ); break; // Good Healers
case AI_EVIL: HandleEvilAI( mChar ); break; // Evil NPC's
case AI_GUARD: HandleGuardAI( mChar ); break; // Guard
case AI_FIGHTER: HandleFighterAI( mChar ); break; // Fighter - same as guard, without teleporting & yelling "HALT!"
case AI_ANIMAL: HandleAnimalAI( mChar ); break; // Hungry animals
case AI_CHAOTIC: HandleChaoticAI( mChar ); break; // Energy Vortex / Blade Spirit
case AI_HEALER_E: HandleEvilHealerAI( mChar ); break; // Evil Healers
case AI_PET_GUARD: // Pet Guarding AI
realChar = mChar.GetOwnerObj();
if( !ValidateObject( realChar ) )
{
mChar.SetNPCAiType( AI_NONE );
return;
}
if( ValidateObject( realChar->GetTarg() ) )
Combat->AttackTarget( &mChar, realChar->GetTarg() );
break;
default:
{
std::string mCharName = getNpcDictName( &mChar );
Console.error( strutil::format(" CheckAI() Error npc %s(0x%X) has invalid AI type %i", mCharName.c_str(), mChar.GetSerial(), mChar.GetNPCAiType() )); //Morrolan
return;
}
}
}