mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
2528 lines
80 KiB
C++
2528 lines
80 KiB
C++
#include "uox3.h"
|
|
#include "msgboard.h"
|
|
#include "movement.h"
|
|
#include "cServerDefinitions.h"
|
|
#include "cSpawnRegion.h"
|
|
#include "skills.h"
|
|
#include "ssection.h"
|
|
#include "CJSMapping.h"
|
|
#include "cScript.h"
|
|
#include "teffect.h"
|
|
#include "CPacketSend.h"
|
|
#include "cEffects.h"
|
|
#include "classes.h"
|
|
#include "regions.h"
|
|
#include "cRaces.h"
|
|
#include "townregion.h"
|
|
#include "Dictionary.h"
|
|
|
|
#include "ObjectFactory.h"
|
|
|
|
using namespace std::string_literals;
|
|
|
|
CCharStuff *Npcs = nullptr;
|
|
GenericList<CChar *> alwaysAwakeNPCs;
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::GetAlwaysAwakeNPCs()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Returns list of NPCs that are always awake
|
|
//o------------------------------------------------------------------------------------------------o
|
|
GenericList<CChar *> * CCharStuff::GetAlwaysAwakeNPCs( void )
|
|
{
|
|
return &alwaysAwakeNPCs;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::AddRandomLoot()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Add loot to monsters packs
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::AddRandomLoot( CItem *s, const std::string& lootlist, bool shouldSave ) ->CItem *
|
|
{
|
|
CItem *retItem = nullptr;
|
|
std::string sect = "LOOTLIST "s + lootlist;
|
|
auto lootList = FileLookup->FindEntry( sect, items_def );
|
|
if( lootList )
|
|
{
|
|
auto i = lootList->NumEntries();
|
|
if( i > 0 )
|
|
{
|
|
i = RandomNum( static_cast<size_t>( 0 ), i - 1 );
|
|
std::string tag = lootList->MoveTo( i );
|
|
std::string tagData = lootList->GrabData();
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( tagData, "//" )), "," );
|
|
auto tcsecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( tag, "//" )), "," );
|
|
|
|
if( !tag.empty() )
|
|
{
|
|
UI16 iAmount = 0;
|
|
|
|
if( oldstrutil::upper( tag ) == "LOOTLIST" )
|
|
{
|
|
if( csecs.size() > 1 ) // Amount specified behind lootlist entry?
|
|
{
|
|
iAmount = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" )), nullptr, 0 ));
|
|
[[maybe_unused]] CItem *retItemNested = nullptr;
|
|
for( UI16 iCount = 0; iCount < iAmount; ++iCount )
|
|
{
|
|
retItemNested = AddRandomLoot( s, oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), shouldSave );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retItem = AddRandomLoot( s, lootList->GrabData(), shouldSave );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( tcsecs.size() > 1 ) // Amount specified behind lootlist entry?
|
|
{
|
|
iAmount = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( tcsecs[1], "//" )), nullptr, 0 ));
|
|
retItem = Items->CreateBaseScriptItem( s, oldstrutil::trim( oldstrutil::removeTrailing( tcsecs[0], "//" )), s->WorldNumber(), iAmount, s->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retItem )
|
|
{
|
|
retItem->SetCont( s );
|
|
retItem->PlaceInPack();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retItem = Items->CreateBaseScriptItem( s, tag, s->WorldNumber(), 1, s->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retItem )
|
|
{
|
|
retItem->SetCont( s );
|
|
retItem->PlaceInPack();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return retItem;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::CreateBaseNPC()
|
|
//| Date - 10/12/2003
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Creates a basic npc from the scripts
|
|
//o------------------------------------------------------------------------------------------------o
|
|
CChar *CCharStuff::CreateBaseNPC( std::string ourNPC, bool shouldSave )
|
|
{
|
|
ourNPC = oldstrutil::trim( oldstrutil::removeTrailing( ourNPC, "//" ));
|
|
CScriptSection *npcCreate = FileLookup->FindEntry( ourNPC, npc_def );
|
|
if( npcCreate == nullptr )
|
|
{
|
|
Console.Error( oldstrutil::format( "CreateBaseNPC(): Bad script npc %s (NPC Not Found).", ourNPC.c_str() ));
|
|
return nullptr;
|
|
}
|
|
|
|
CChar *cCreated = nullptr;
|
|
if( npcCreate->NpcListExist() )
|
|
{
|
|
cCreated = CreateRandomNPC( npcCreate->NpcListData() );
|
|
}
|
|
else
|
|
{
|
|
cCreated = static_cast<CChar *>( ObjectFactory::GetSingleton().CreateObject( OT_CHAR ));
|
|
if( cCreated == nullptr )
|
|
return nullptr;
|
|
|
|
if( !shouldSave )
|
|
{
|
|
cCreated->ShouldSave( false );
|
|
}
|
|
|
|
cCreated->SetSectionId( ourNPC );
|
|
cCreated->SetSkillTitles( true );
|
|
cCreated->SetNpc( true );
|
|
cCreated->SetLoDamage( 1 );
|
|
cCreated->SetHiDamage( 1 );
|
|
cCreated->SetResist( 1, PHYSICAL );
|
|
cCreated->SetSpawn( INVALIDSERIAL );
|
|
|
|
if( !ApplyNpcSection( cCreated, npcCreate, ourNPC ))
|
|
{
|
|
Console.Error( "Trying to apply an npc section failed" );
|
|
}
|
|
|
|
std::vector<UI16> scriptTriggers = cCreated->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
toExecute->OnCreate( cCreated, true, false );
|
|
}
|
|
}
|
|
}
|
|
return cCreated;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::ChooseNpcToCreate()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Selects a random weighted entry from a vector with key-value pair (section and weight)
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::ChooseNpcToCreate( const std::vector<std::pair<std::string, UI16>> npcListVector ) -> std::string
|
|
{
|
|
auto npcListSize = npcListVector.size();
|
|
if( npcListSize <= 0 )
|
|
return "";
|
|
|
|
int sum_of_weight = 0;
|
|
for( const auto& it : npcListVector )
|
|
{
|
|
//const std::string& sectionName = it.first;
|
|
const UI16 §ionWeight = it.second;
|
|
sum_of_weight += sectionWeight;
|
|
}
|
|
|
|
int rndChoice = RandomNum( 0, sum_of_weight - 1 );
|
|
[[maybe_unused]] int npcWeight = 0;
|
|
|
|
std::vector<int> matchingEntries;
|
|
|
|
int weightOfChosenNpc = 0;
|
|
for( size_t i = 0; i < npcListVector.size(); ++i )
|
|
{
|
|
//const std::string §ionName = npcList[i].first;
|
|
const UI16 §ionWeight = npcListVector[i].second;
|
|
|
|
// Ok, section has a weight, let's compare that weight to our chosen random number
|
|
if( rndChoice < sectionWeight )
|
|
{
|
|
// If we find another entry with same weight as the first one found, or if none have been found yet, add to list
|
|
if( weightOfChosenNpc == 0 || weightOfChosenNpc == sectionWeight )
|
|
{
|
|
weightOfChosenNpc = sectionWeight;
|
|
|
|
// Add the entry index to a temporary vector of all entries with shared weight, the continue looking for more!
|
|
matchingEntries.push_back( static_cast<int>(i) );
|
|
continue;
|
|
}
|
|
}
|
|
rndChoice -= sectionWeight;
|
|
}
|
|
|
|
// Did we find one or more entry that matched our random weight criteria?
|
|
int npcEntryToSpawn = ( matchingEntries.size() > 0 ? matchingEntries[static_cast<int>( RandomNum( static_cast<size_t>( 0 ), matchingEntries.size() - 1 ))] : -1 );
|
|
matchingEntries.clear();
|
|
|
|
std::string chosenNpcSection = "";
|
|
if( npcEntryToSpawn != -1 )
|
|
{
|
|
// If entry was selected based on weights, use that
|
|
chosenNpcSection = npcListVector[npcEntryToSpawn].first;
|
|
}
|
|
else
|
|
{
|
|
// else, use a random entry from the list
|
|
chosenNpcSection = npcListVector[RandomNum( static_cast<size_t>( 0 ), npcListSize - 1 )].first;
|
|
}
|
|
|
|
return chosenNpcSection;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::NpcListLookup()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Retrieves entries from a specified NPCLIST and builds a vector that's provided
|
|
//| for ChooseNpcToCreate() function to perform a weighted random selection
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::NpcListLookup( const std::string &npclist ) -> std::string
|
|
{
|
|
auto sect = "NPCLIST "s + npclist;
|
|
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
|
|
|
|
auto npcList = FileLookup->FindEntry( sect, npc_def );
|
|
if( !npcList )
|
|
return "";
|
|
|
|
auto npcListSize = npcList->NumEntries();
|
|
if( npcListSize <= 0 )
|
|
return "";
|
|
|
|
// Stuff each entry from the npcList into a vector
|
|
std::vector<std::pair<std::string, UI16>> npcListVector;
|
|
for( size_t i = 0; i < npcListSize; i++ )
|
|
{
|
|
// Split string for entry into a stringlist based on | as separator
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( npcList->MoveTo( i ), "//" )), "|" );
|
|
|
|
UI16 sectionWeight = 1;
|
|
if( csecs.size() > 1 )
|
|
{
|
|
sectionWeight = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 ));
|
|
}
|
|
|
|
auto npcSection = ( csecs.size() > 1 ? csecs[1] : csecs[0] );
|
|
auto npcData = npcList->GrabData();
|
|
if( !npcData.empty() )
|
|
{
|
|
npcListVector.emplace_back( npcSection + "=" + npcData, sectionWeight );
|
|
}
|
|
else
|
|
{
|
|
npcListVector.emplace_back( npcSection, sectionWeight );
|
|
}
|
|
}
|
|
|
|
auto chosenNpcSection = ChooseNpcToCreate( npcListVector );
|
|
if( chosenNpcSection.empty() )
|
|
return "";
|
|
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( chosenNpcSection, "//" )), "=" );
|
|
if( oldstrutil::upper( csecs[0] ) == "NPCLIST" )
|
|
{
|
|
// Chosen entry contained another NPCLIST! Let's dive back into it...
|
|
chosenNpcSection = NpcListLookup( csecs[1] );
|
|
}
|
|
|
|
return chosenNpcSection;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::CreateRandomNPC()
|
|
//| Date - 10/12/2003
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Creates a random npc from an npclist in specified dfn file
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::CreateRandomNPC( const std::string &npclist ) -> CChar *
|
|
{
|
|
auto sect = "NPCLIST "s + npclist;
|
|
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
|
|
|
|
auto npcList = FileLookup->FindEntry( sect, npc_def );
|
|
if( !npcList )
|
|
return nullptr;
|
|
|
|
auto npcListSize = npcList->NumEntries();
|
|
if( npcListSize <= 0 )
|
|
return nullptr;
|
|
|
|
// Stuff each entry from the npcList into a vector
|
|
std::vector<std::pair<std::string, UI16>> npcListVector;
|
|
for( size_t i = 0; i < npcListSize; i++ )
|
|
{
|
|
// Split string for entry into a stringlist based on | as separator
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( npcList->MoveTo( i ), "//" )), "|" );
|
|
|
|
UI16 sectionWeight = 1;
|
|
if( csecs.size() > 1 )
|
|
{
|
|
sectionWeight = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 ));
|
|
}
|
|
|
|
auto npcSection = ( csecs.size() > 1 ? csecs[1] : csecs[0] );
|
|
auto npcData = npcList->GrabData();
|
|
if( !npcData.empty() )
|
|
{
|
|
npcListVector.emplace_back( npcSection + "=" + npcData, sectionWeight );
|
|
}
|
|
else
|
|
{
|
|
npcListVector.emplace_back( npcSection, sectionWeight );
|
|
}
|
|
}
|
|
|
|
auto chosenNpcSection = ChooseNpcToCreate( npcListVector );
|
|
if( chosenNpcSection.empty() )
|
|
return nullptr;
|
|
|
|
CChar *cCreated = nullptr;
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( chosenNpcSection, "//" )), "=" );
|
|
if( oldstrutil::upper( csecs[0] ) == "NPCLIST" )
|
|
{
|
|
// Chosen entry contained another NPCLIST! Let's dive back into it...
|
|
cCreated = CreateRandomNPC( csecs[1] );
|
|
}
|
|
else
|
|
{
|
|
// Finally, an actual NPC entry. Spawn it!
|
|
cCreated = CreateBaseNPC( chosenNpcSection );
|
|
}
|
|
|
|
return cCreated;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::CreateNPC()
|
|
//| Date - 10/12/2003
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Creates an npc spawned from an item spawner
|
|
//o------------------------------------------------------------------------------------------------o
|
|
CChar *CCharStuff::CreateNPC( CSpawnItem *iSpawner, const std::string &npc )
|
|
{
|
|
const ItemTypes iType = iSpawner->GetType();
|
|
// If the spawner type is 125 and escort quests are not active then abort
|
|
if( iType == IT_ESCORTNPCSPAWNER && !cwmWorldState->ServerData()->EscortsEnabled() )
|
|
return nullptr;
|
|
|
|
CChar *cCreated = nullptr;
|
|
if( iSpawner->IsSectionAList() )
|
|
{
|
|
cCreated = CreateRandomNPC( npc );
|
|
}
|
|
else
|
|
{
|
|
cCreated = CreateBaseNPC( npc );
|
|
}
|
|
if( cCreated == nullptr )
|
|
return nullptr;
|
|
|
|
cCreated->SetSpawn( iSpawner->GetSerial() );
|
|
SI16 awayX = 0, awayY = 0;
|
|
if(( iType == IT_AREASPAWNER || iType == IT_ESCORTNPCSPAWNER ) && iSpawner->GetCont() == nullptr )
|
|
{
|
|
awayX = iSpawner->GetTempVar( CITV_MORE, 3 );
|
|
awayY = iSpawner->GetTempVar( CITV_MORE, 4 );
|
|
}
|
|
|
|
FindSpotForNPC( cCreated, iSpawner->GetX(), iSpawner->GetY(), awayX, awayY, iSpawner->GetZ(), iSpawner->WorldNumber(), iSpawner->GetInstanceId() );
|
|
PostSpawnUpdate( cCreated );
|
|
|
|
if( iType == IT_ESCORTNPCSPAWNER )
|
|
{
|
|
MsgBoardQuestEscortCreate( cCreated );
|
|
}
|
|
|
|
cCreated->SetWipeable( true );
|
|
|
|
return cCreated;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::CreateNPCxyz()
|
|
//| Date - 10/12/2003
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Creates an npc at location xyz
|
|
//o------------------------------------------------------------------------------------------------o
|
|
CChar *CCharStuff::CreateNPCxyz( const std::string &npc, SI16 x, SI16 y, SI08 z, UI08 worldNumber, UI16 instanceId, bool useNpcList )
|
|
{
|
|
CChar *cCreated = nullptr;
|
|
if( useNpcList )
|
|
{
|
|
cCreated = CreateRandomNPC( npc );
|
|
}
|
|
else
|
|
{
|
|
cCreated = CreateBaseNPC( npc );
|
|
}
|
|
if( cCreated == nullptr )
|
|
return nullptr;
|
|
|
|
cCreated->SetLocation( x, y, z, worldNumber, instanceId );
|
|
// Update "old location" for new NPCs straight away
|
|
cCreated->SetOldLocation( x, y, z );
|
|
PostSpawnUpdate( cCreated );
|
|
return cCreated;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::PostSpawnUpdate()
|
|
//| Date - 10/12/2003
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Updates created npc
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CCharStuff::PostSpawnUpdate( CChar *cCreated )
|
|
{
|
|
CTownRegion *tReg = CalcRegionFromXY( cCreated->GetX(), cCreated->GetY(), cCreated->WorldNumber(), cCreated->GetInstanceId(), cCreated );
|
|
cCreated->SetRegion( tReg->GetRegionNum() );
|
|
|
|
for( UI08 z = 0; z < ALLSKILLS; ++z )
|
|
{
|
|
Skills->UpdateSkillLevel( cCreated, z );
|
|
}
|
|
|
|
// Set hunger timer so NPC's hunger level doesn't instantly drop after spawning
|
|
auto hungerRate = Races->GetHungerRate( cCreated->GetRace() );
|
|
cCreated->SetTimer( tCHAR_HUNGER, BuildTimeValue( static_cast<R64>( hungerRate )));
|
|
|
|
UpdateFlag( cCreated );
|
|
cCreated->Update( nullptr, false, true, true );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - GetRadius()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Generate a sensible radius given the values from the NPC DFNs
|
|
//o------------------------------------------------------------------------------------------------o
|
|
SI16 GetRadius( CChar *c )
|
|
{
|
|
// see if they supplied a 'radius'
|
|
if( c->GetFx( 1 ) > 0 )
|
|
{
|
|
// if they were supplying a bounding area, use the radius from that
|
|
if( c->GetFx( 0 ) > 0 )
|
|
{
|
|
c->SetFx( abs(c->GetFx( 0 ) - c->GetFx( 1 )), 1 );
|
|
}
|
|
}
|
|
// ensure its not something bogus
|
|
if( c->GetFx( 1 ) <= 0 || c->GetFx( 1 ) > 100)
|
|
{
|
|
c->SetFx( 10, 1 );
|
|
}
|
|
return c->GetFx( 1 );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CheckBoundingBox()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check bounding box
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool CheckBoundingBox( const SI16 xPos, const SI16 yPos, const SI16 fx1, const SI16 fy1, const SI08 fz1,
|
|
const SI16 fx2, const SI16 fy2, const UI08 worldNumber, const UI16 instanceId )
|
|
{
|
|
if( xPos >= (( fx1 < fx2 ) ? fx1 : fx2 ) && xPos <= (( fx1 < fx2 ) ? fx2 : fx1 ))
|
|
{
|
|
if( yPos >= (( fy1 < fy2 ) ? fy1 : fy2 ) && yPos <= (( fy1 < fy2 ) ? fy2 : fy1 ))
|
|
{
|
|
if( fz1 == -1 || abs( fz1 - Map->Height( xPos, yPos, fz1, worldNumber, instanceId )) <= 5 )
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CheckBoundingCircle()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Check bounding circle
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool CheckBoundingCircle( const SI16 xPos, const SI16 yPos, const SI16 fx1, const SI16 fy1, const SI08 fz1,
|
|
const SI16 radius, const UI08 worldNumber, const UI16 instanceId )
|
|
{
|
|
if(( xPos - fx1 ) * ( xPos - fx1 ) + ( yPos - fy1 ) * ( yPos - fy1 ) <= radius * radius )
|
|
{
|
|
if( fz1 == -1 || abs( fz1 - Map->Height( xPos, yPos, fz1, worldNumber, instanceId )) <= 5 )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - InitializeWanderArea()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Setup the wander area if the npcwander is rect or circle
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void InitializeWanderArea( CChar *c, SI16 xAway, SI16 yAway )
|
|
{
|
|
// compute the rectangular bounding area
|
|
if( WT_BOX == c->GetNpcWander() )
|
|
{
|
|
// if they provided a legal rectangle and
|
|
// ensure the bounding rect contains the current location
|
|
// if it doesn't the monster will never move!
|
|
if( c->GetFx( 0 ) >= 0 && c->GetFy( 0 ) >= 0 && c->GetFy( 1 ) >= 0 && c->GetFx( 1 ) >= 0 &&
|
|
CheckBoundingBox( c->GetX(), c->GetY(), c->GetFx( 0 ), c->GetFy( 0 ), c->GetFz(), c->GetFx( 1 ), c->GetFy( 1 ), c->WorldNumber(), c->GetInstanceId() ))
|
|
{
|
|
return;// don't do anything to use what they specified in the NPC DFNs
|
|
}
|
|
else
|
|
{
|
|
// if they provided a 'radius' in the NPC DFNs, use that
|
|
if( c->GetFx( 1 ) > 0 )
|
|
{
|
|
xAway = yAway = GetRadius( c );
|
|
}
|
|
// setup info for rectangular areas
|
|
c->SetFx( c->GetX() - xAway, 0 );
|
|
c->SetFy( c->GetY() - yAway, 0 );
|
|
c->SetFx( c->GetX() + xAway, 1 );
|
|
c->SetFy( c->GetY() + yAway, 1 );
|
|
}
|
|
}
|
|
else if( WT_CIRCLE == c->GetNpcWander() )
|
|
{
|
|
// if they provided a legal circle and
|
|
// ensure the bounding circle contains the current location
|
|
// if it doesn't the monster will never move!
|
|
if( c->GetFx( 0 ) >= 0 && c->GetFy( 0 ) >= 0 && c->GetFx( 1 ) >= 0 &&
|
|
CheckBoundingCircle( c->GetX(), c->GetY(), c->GetFx( 0 ), c->GetFy( 0 ), c->GetFz(), c->GetFx( 1 ), c->WorldNumber(), c->GetInstanceId() ))
|
|
{
|
|
return;// don't do anything to use what they specified in NPC DFNs
|
|
}
|
|
else
|
|
{
|
|
// if they provided a 'radius' in the NPC DFNs use that
|
|
if( c->GetFx( 1 ) > 0 )
|
|
{
|
|
xAway = yAway = GetRadius( c );
|
|
}
|
|
// setup info for circular areas
|
|
c->SetFx( c->GetX(), 0 );
|
|
c->SetFy( c->GetY(), 0 );
|
|
c->SetFx( xAway, 1 );
|
|
c->SetFy( -1, 1 );
|
|
}
|
|
}
|
|
// setting fz1 actually makes it check against the height and slows the system down a lot
|
|
// does anyone really need to constrain the height at which a monster can move??
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::FindSpotForNPC()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find a valid spot to drop an NPC near the spawners location
|
|
//|
|
|
//| Changes - 06/26/2020 - Added support for instanceId
|
|
//|
|
|
//| Changes - 04/20/2002 - (Type 69) xos and yos (X OffSet, Y OffSet)
|
|
//| are used to find a random number that is then added to the spawner's
|
|
//| x and y (Using the spawner's z) and then place the NPC anywhere in
|
|
//| a square around the spawner. This square is random anywhere from -10
|
|
//| to +10 from the spawner's location (for x and y) If the place chosen
|
|
//| is not a valid position (the NPC can't walk there) then a new place
|
|
//| will be chosen, if a valid place cannot be found in a certain # of
|
|
//| tries (50),the NPC will be placed directly on the spawner and the
|
|
//| server op will be warned.
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CCharStuff::FindSpotForNPC( CChar *cCreated, const SI16 originX, const SI16 originY, const SI16 xAway,
|
|
const SI16 yAway, const SI08 z, const UI08 worldNumber, const UI16 instanceId )
|
|
{
|
|
|
|
#ifdef DEBUG_SPAWN
|
|
Console.Print( oldstrutil::format( "Going to spawn at (%d,%d) within %d by %d\n", originX, originY, xAway, yAway ));
|
|
#endif
|
|
|
|
if( !ValidateObject( cCreated ))
|
|
return;
|
|
|
|
SI32 k = xAway * yAway / 2;
|
|
SI16 xos = 0, yos = 0;
|
|
SI08 targZ = 0;
|
|
bool foundSpot = false;
|
|
if( k > 50 )
|
|
{
|
|
k = 50;
|
|
}
|
|
|
|
while( !foundSpot )
|
|
{
|
|
targZ = z;
|
|
if( --k < 0 ) //this CAN be a bit laggy. adjust as nessicary
|
|
{
|
|
if( xAway > 0 && yAway > 0 )
|
|
{
|
|
Console << "Problem area spawner found, NPC placed at default location" << myendl;
|
|
}
|
|
xos = originX;
|
|
yos = originY;
|
|
foundSpot = true;
|
|
break;
|
|
}
|
|
|
|
xos = originX + RandomNum( static_cast<SI16>( -xAway ), xAway );
|
|
yos = originY + RandomNum( static_cast<SI16>( -yAway ), yAway );
|
|
|
|
if( xos >= 1 && yos >= 1 )
|
|
{
|
|
targZ = Map->Height( xos, yos, z, worldNumber, instanceId );
|
|
if( !cwmWorldState->creatures[cCreated->GetId()].IsWater() )
|
|
{
|
|
foundSpot = Map->ValidSpawnLocation( xos, yos, targZ, worldNumber, instanceId );
|
|
}
|
|
else if( cwmWorldState->creatures[cCreated->GetId()].IsWater() || ( !foundSpot && cwmWorldState->creatures[cCreated->GetId()].IsAmphibian() ))
|
|
{
|
|
foundSpot = Map->ValidSpawnLocation( xos, yos, targZ, worldNumber, instanceId, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
cCreated->SetLocation( xos, yos, targZ, worldNumber, instanceId );
|
|
InitializeWanderArea( cCreated, xAway, yAway );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::LoadShopList()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Loads the shopping list pointed to by data in ITEM DFNs
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::LoadShopList( const std::string& list, CChar *c ) -> void
|
|
{
|
|
auto buyLayer = c->GetItemAtLayer( IL_BUYCONTAINER ); //Contains items the NPC is willing to buy
|
|
auto boughtLayer = c->GetItemAtLayer( IL_BOUGHTCONTAINER ); //Contains items the NPC has already bought
|
|
auto sellLayer = c->GetItemAtLayer( IL_SELLCONTAINER ); //Contains items the NPC will sell
|
|
|
|
auto sect = "SHOPLIST "s + list;
|
|
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
|
|
auto ShoppingList = FileLookup->FindEntry( sect, items_def );
|
|
if( ShoppingList == nullptr )
|
|
return;
|
|
|
|
bool shouldSave = c->ShouldSave();
|
|
std::string cdata;
|
|
SI32 ndata = -1;
|
|
[[maybe_unused]] SI32 odata = -1;
|
|
CItem *retItem = nullptr;
|
|
for( const auto &sec : ShoppingList->collection2() )
|
|
{
|
|
auto tag = sec->tag;
|
|
cdata = sec->cdata;
|
|
ndata = sec->ndata;
|
|
odata = sec->odata;
|
|
switch( tag )
|
|
{
|
|
case DFNTAG_RSHOPITEM:
|
|
if( ValidateObject( buyLayer ))
|
|
{
|
|
retItem = Items->CreateBaseScriptItem( nullptr, cdata, c->WorldNumber(), 1, c->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retItem )
|
|
{
|
|
retItem->SetCont( buyLayer );
|
|
retItem->PlaceInPack();
|
|
if( !retItem->GetName2().empty() && ( retItem->GetName2() != "#" ))
|
|
{
|
|
retItem->SetName( retItem->GetName2() ); // Item identified!
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console << "Warning: Bad Shopping List " << list << " with no Vendor Buy Pack for NPC " << c << " (serial: " << c->GetSerial() << myendl;
|
|
}
|
|
break;
|
|
case DFNTAG_SELLITEM:
|
|
if( ValidateObject( sellLayer ))
|
|
{
|
|
retItem = Items->CreateBaseScriptItem( nullptr, cdata, c->WorldNumber(), 1, c->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retItem )
|
|
{
|
|
retItem->SetCont( sellLayer );
|
|
//retitem->SetSellValue( retitem->GetBuyValue() / 2 );
|
|
retItem->PlaceInPack();
|
|
if( !retItem->GetName2().empty() && ( retItem->GetName2() != "#" ))
|
|
{
|
|
retItem->SetName( retItem->GetName2() );
|
|
}
|
|
|
|
// Let's start out with the shops fully restocked after spawning
|
|
retItem->IncAmount( retItem->GetRestock() );
|
|
retItem->SetRestock( 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console << "Warning: Bad Shopping List " << list << " with no Vendor Sell Pack for NPC " << c << " (serial: " << c->GetSerial() << myendl;
|
|
}
|
|
break;
|
|
case DFNTAG_SHOPITEM:
|
|
if( ValidateObject( boughtLayer ))
|
|
{
|
|
retItem = Items->CreateBaseScriptItem( nullptr, cdata, c->WorldNumber(), 1, c->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retItem )
|
|
{
|
|
retItem->SetCont( boughtLayer );
|
|
retItem->PlaceInPack();
|
|
if( !retItem->GetName2().empty() && ( retItem->GetName2() != "#" ))
|
|
{
|
|
retItem->SetName( retItem->GetName2() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console << "Warning: Bad Shopping List " << list << " with no Vendor Bought Pack for NPC " << c << " (serial: " << c->GetSerial() << myendl;
|
|
}
|
|
break;
|
|
case DFNTAG_VALUE:
|
|
if( retItem )
|
|
{
|
|
if( !cdata.empty() )
|
|
{
|
|
auto ssecs = oldstrutil::sections( cdata, " " );
|
|
if( ssecs.size() > 1 )
|
|
{
|
|
retItem->SetBuyValue( static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
|
|
retItem->SetSellValue( static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
|
|
break;
|
|
}
|
|
}
|
|
retItem->SetBuyValue( ndata );
|
|
retItem->SetSellValue(( ndata / 2 ));
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - SetRandomName()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Sets a character with a random name from NPC namelists in namelists.dfn
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void SetRandomName( CBaseObject *s, const std::string& namelist )
|
|
{
|
|
std::string sect = std::string( "RANDOMNAME " ) + namelist;
|
|
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
|
|
std::string tempName;
|
|
auto cat = s->CanBeObjType(OT_CHAR) ? npc_def : items_def;
|
|
|
|
CScriptSection *RandomName = FileLookup->FindEntry( sect, cat );
|
|
if( RandomName == nullptr )
|
|
{
|
|
tempName = std::string( "Error Namelist " ) + namelist + std::string( " Not Found" );
|
|
}
|
|
else
|
|
{
|
|
size_t i = RandomName->NumEntries();
|
|
if( i > 0 )
|
|
{
|
|
i = RandomNum( static_cast<size_t>( 0 ), i - 1 );
|
|
tempName = RandomName->MoveTo( static_cast<SI16>( i ));
|
|
}
|
|
}
|
|
s->SetName( tempName );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - AddRandomColor()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Randomly colors character
|
|
//o------------------------------------------------------------------------------------------------o
|
|
UI16 AddRandomColor( const std::string& colorlist )
|
|
{
|
|
std::string sect = std::string( "RANDOMCOLOR " ) + colorlist;
|
|
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
|
|
CScriptSection *RandomColours = FileLookup->FindEntry( sect, colors_def );
|
|
if( RandomColours == nullptr )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Error Colorlist %s Not Found", colorlist.c_str() ));
|
|
return 0;
|
|
}
|
|
size_t i = RandomColours->NumEntries();
|
|
if( i > 0 )
|
|
{
|
|
i = RandomNum( static_cast<size_t>( 0 ), i - 1 );
|
|
std::string tag = RandomColours->MoveTo( static_cast<SI16>( i ));
|
|
return static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( tag, "//" )), nullptr, 0 ));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - MakeShop()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Turn an NPC into a shopkeeper
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void MakeShop( CChar *c )
|
|
{
|
|
if( !ValidateObject( c ))
|
|
return;
|
|
|
|
c->SetShop( true );
|
|
CItem *tPack = nullptr;
|
|
for( UI08 i = IL_SELLCONTAINER; i <= IL_BUYCONTAINER; ++i )
|
|
{
|
|
tPack = c->GetItemAtLayer( static_cast<ItemLayers>( i ));
|
|
if( !ValidateObject( tPack ))
|
|
{
|
|
bool shouldSave = c->ShouldSave();
|
|
tPack = Items->CreateItem( nullptr, c, 0x2AF8, 1, 0, OT_ITEM, false, shouldSave );
|
|
if( ValidateObject( tPack ))
|
|
{
|
|
tPack->SetDecayable( false );
|
|
tPack->SetLayer( static_cast<ItemLayers>( i ));
|
|
if( !tPack->SetCont( c ))
|
|
{
|
|
tPack->Delete();
|
|
}
|
|
else
|
|
{
|
|
tPack->SetType( IT_CONTAINER );
|
|
tPack->SetNewbie( true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
c->Update();
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::ApplyNpcSection()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Apply NPC DFN sections to an NPC
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::ApplyNpcSection( CChar *applyTo, CScriptSection *NpcCreation, std::string sectionId, bool isGate ) -> bool
|
|
{
|
|
if( NpcCreation == nullptr || !ValidateObject( applyTo ))
|
|
return false;
|
|
|
|
UI16 haircolor = INVALIDCOLOUR;
|
|
CItem *buyPack = nullptr, *boughtPack = nullptr, *sellPack = nullptr;
|
|
CItem *retitem = nullptr, *mypack = nullptr;
|
|
|
|
std::string cdata;
|
|
SI32 ndata = -1, odata = -1;
|
|
UI08 skillToSet = 0xFF;
|
|
|
|
TAGMAPOBJECT customTag;
|
|
std::string customTagName;
|
|
std::string customTagStringValue;
|
|
for( const auto &sec : NpcCreation->collection2() )
|
|
{
|
|
DFNTAGS tag = sec->tag;
|
|
cdata = sec->cdata;
|
|
ndata = sec->ndata;
|
|
odata = sec->odata;
|
|
cdata = oldstrutil::trim( oldstrutil::removeTrailing( cdata, "//" ));
|
|
auto ssects = oldstrutil::sections( cdata, " " );
|
|
auto csects = oldstrutil::sections( cdata, "," );
|
|
switch( tag )
|
|
{
|
|
case DFNTAG_ALCHEMY: skillToSet = ALCHEMY; break;
|
|
case DFNTAG_ANATOMY: skillToSet = ANATOMY; break;
|
|
case DFNTAG_ANIMALLORE: skillToSet = ANIMALLORE; break;
|
|
case DFNTAG_ARMSLORE: skillToSet = ARMSLORE; break;
|
|
case DFNTAG_ARCHERY: skillToSet = ARCHERY; break;
|
|
case DFNTAG_DAMAGE:
|
|
case DFNTAG_ATT:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetLoDamage( static_cast<SI16>( ndata ));
|
|
applyTo->SetHiDamage( static_cast<SI16>( odata ));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetLoDamage( static_cast<SI16>( ndata ));
|
|
applyTo->SetHiDamage( static_cast<SI16>( ndata ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in ATT/DAMAGE tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_DAMAGEINCREASE: applyTo->SetDamageIncrease( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_AWAKE:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetAwake( ndata != 0 );
|
|
}
|
|
break;
|
|
case DFNTAG_BACKPACK:
|
|
if( !isGate )
|
|
{
|
|
if( mypack == nullptr )
|
|
{
|
|
mypack = applyTo->GetPackItem();
|
|
}
|
|
if( mypack == nullptr )
|
|
{
|
|
bool shouldSave = applyTo->ShouldSave();
|
|
mypack = Items->CreateItem( nullptr, applyTo, 0x0E75, 1, 0, OT_ITEM, false, shouldSave );
|
|
if( ValidateObject( mypack ))
|
|
{
|
|
mypack->SetDecayable( false );
|
|
applyTo->SetPackItem( mypack );
|
|
mypack->SetName( "Backpack" );
|
|
mypack->SetLayer( IL_PACKITEM );
|
|
if( !mypack->SetCont( applyTo ))
|
|
{
|
|
mypack = nullptr;
|
|
}
|
|
else
|
|
{
|
|
mypack->SetX( 0 );
|
|
mypack->SetY( 0 );
|
|
mypack->SetZ( 0 );
|
|
mypack->SetType( IT_CONTAINER );
|
|
mypack->SetDye( true );
|
|
mypack->SetMaxItems( cwmWorldState->ServerData()->MaxPlayerPackItems() );
|
|
mypack->SetWeightMax( cwmWorldState->ServerData()->MaxPlayerPackWeight() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_BEGGING: skillToSet = BEGGING; break;
|
|
case DFNTAG_BLACKSMITHING: skillToSet = BLACKSMITHING; break;
|
|
case DFNTAG_BOWCRAFT: skillToSet = BOWCRAFT; break;
|
|
case DFNTAG_BUSHIDO: skillToSet = BUSHIDO; break;
|
|
case DFNTAG_CAMPING: skillToSet = CAMPING; break;
|
|
case DFNTAG_CARPENTRY: skillToSet = CARPENTRY; break;
|
|
case DFNTAG_CARTOGRAPHY: skillToSet = CARTOGRAPHY; break;
|
|
case DFNTAG_CARVE:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetCarve( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_CHIVALRY: skillToSet = CHIVALRY; break;
|
|
case DFNTAG_COOKING: skillToSet = COOKING; break;
|
|
case DFNTAG_COLOUR:
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetColour( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_COLOURLIST:
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetColour( AddRandomColor( cdata ));
|
|
}
|
|
break;
|
|
case DFNTAG_COLOURMATCHHAIR:
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetColour( static_cast<UI16>( haircolor ));
|
|
}
|
|
break;
|
|
case DFNTAG_CONTROLSLOTS:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetControlSlots( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_DEX:
|
|
if( ndata > 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetDexterity( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetDexterity( ndata );
|
|
}
|
|
applyTo->SetStamina( applyTo->GetMaxStam() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in DEX tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_DETECTINGHIDDEN: skillToSet = DETECTINGHIDDEN; break;
|
|
case DFNTAG_DEF:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), PHYSICAL );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( ndata ), PHYSICAL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in DEF tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_DEFBONUS:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( applyTo->GetResist( PHYSICAL ) + static_cast<UI16>( RandomNum( ndata, odata )), PHYSICAL );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( applyTo->GetResist( PHYSICAL ) + static_cast<UI16>( ndata ), PHYSICAL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in DEFBONUS tag inside item script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_DIR:
|
|
if( !isGate )
|
|
{
|
|
std::string cupper = oldstrutil::upper( cdata );
|
|
if( cupper == "NE" )
|
|
{
|
|
applyTo->SetDir( NORTHEAST );
|
|
}
|
|
else if( cupper == "E" )
|
|
{
|
|
applyTo->SetDir( EAST );
|
|
}
|
|
else if( cupper == "SE" )
|
|
{
|
|
applyTo->SetDir( SOUTHEAST );
|
|
}
|
|
else if( cupper == "S" )
|
|
{
|
|
applyTo->SetDir( SOUTH );
|
|
}
|
|
else if( cupper == "SW" )
|
|
{
|
|
applyTo->SetDir( SOUTHWEST );
|
|
}
|
|
else if( cupper == "W" )
|
|
{
|
|
applyTo->SetDir( WEST );
|
|
}
|
|
else if( cupper == "NW" )
|
|
{
|
|
applyTo->SetDir( NORTHWEST );
|
|
}
|
|
else if( cupper == "N" )
|
|
{
|
|
applyTo->SetDir( NORTH );
|
|
}
|
|
else if( cupper == "RND" )
|
|
{
|
|
UI08 rndDir = RandomNum( 0, 7 );
|
|
switch( rndDir )
|
|
{
|
|
case 0:
|
|
applyTo->SetDir( NORTHEAST );
|
|
break;
|
|
case 1:
|
|
applyTo->SetDir( EAST );
|
|
break;
|
|
case 2:
|
|
applyTo->SetDir( SOUTHEAST );
|
|
break;
|
|
case 3:
|
|
applyTo->SetDir( SOUTH );
|
|
break;
|
|
case 4:
|
|
applyTo->SetDir( SOUTHWEST );
|
|
break;
|
|
case 5:
|
|
applyTo->SetDir( WEST );
|
|
break;
|
|
case 6:
|
|
applyTo->SetDir( NORTHWEST );
|
|
break;
|
|
case 7:
|
|
applyTo->SetDir( NORTH );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_ELEMENTRESIST:
|
|
if( ssects.size() >= 4 )
|
|
{
|
|
applyTo->SetResist(( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 ))), HEAT );
|
|
applyTo->SetResist(( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[1], "//" )), nullptr, 0 ))), COLD );
|
|
applyTo->SetResist(( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[2], "//" )), nullptr, 0 ))), LIGHTNING );
|
|
applyTo->SetResist(( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[3], "//" )), nullptr, 0 ))), POISON );
|
|
}
|
|
break;
|
|
case DFNTAG_ERBONUS:
|
|
if( ssects.size() >= 4 )
|
|
{
|
|
applyTo->SetResist( applyTo->GetResist( HEAT ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 )), HEAT );
|
|
applyTo->SetResist( applyTo->GetResist( COLD ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[1], "//" )), nullptr, 0 )), COLD );
|
|
applyTo->SetResist( applyTo->GetResist( LIGHTNING ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[2], "//" )), nullptr, 0 )), LIGHTNING );
|
|
applyTo->SetResist( applyTo->GetResist( POISON ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[3], "//" )), nullptr, 0 )), POISON );
|
|
}
|
|
break;
|
|
case DFNTAG_EMOTECOLOUR:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetEmoteColour( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_ENTICEMENT: skillToSet = ENTICEMENT; break;
|
|
case DFNTAG_EQUIPITEM:
|
|
if( !isGate )
|
|
{
|
|
bool shouldSave = applyTo->ShouldSave();
|
|
retitem = Items->CreateBaseScriptItem( nullptr, cdata, applyTo->WorldNumber(), 1, applyTo->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retitem != nullptr )
|
|
{
|
|
if( retitem->GetLayer() == IL_NONE )
|
|
{
|
|
Console << "Warning: Bad NPC Script ([" << sectionId.c_str() << "]) with problem item " << cdata << " executed!" << myendl;
|
|
}
|
|
else if( !retitem->SetCont( applyTo ))
|
|
{
|
|
if( !retitem->SetCont( applyTo->GetPackItem() ))
|
|
{
|
|
retitem = nullptr;
|
|
}
|
|
}
|
|
|
|
if( retitem->GetLayer() == 25 )
|
|
{
|
|
// Item equipped on mount layer. Set NPC as mounted!
|
|
applyTo->SetMounted( true );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_EVALUATINGINTEL: skillToSet = EVALUATINGINTEL; break;
|
|
case DFNTAG_FAME: applyTo->SetFame( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_FENCING: skillToSet = FENCING; break;
|
|
case DFNTAG_FISHING: skillToSet = FISHING; break;
|
|
case DFNTAG_FLEEAT:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFleeAt( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_FLEEINGSPEED:
|
|
applyTo->SetFleeingSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_FLEEINGSPEEDMOUNTED:
|
|
applyTo->SetMountedFleeingSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_FLAG:
|
|
if( !isGate )
|
|
{
|
|
if( !cdata.empty() )
|
|
{
|
|
std::string upperData = oldstrutil::upper( cdata );
|
|
if( upperData == "NEUTRAL" )
|
|
{
|
|
applyTo->SetNPCFlag( fNPC_NEUTRAL );
|
|
}
|
|
else if( upperData == "INNOCENT" )
|
|
{
|
|
applyTo->SetNPCFlag( fNPC_INNOCENT );
|
|
}
|
|
else if( upperData == "EVIL" )
|
|
{
|
|
applyTo->SetNPCFlag( fNPC_EVIL );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_FORENSICS: skillToSet = FORENSICS; break;
|
|
case DFNTAG_FOCUS: skillToSet = FOCUS; break;
|
|
case DFNTAG_FX1:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFx( static_cast<SI16>( ndata ), 0 );
|
|
}
|
|
break;
|
|
case DFNTAG_FX2:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFx( static_cast<SI16>( ndata ), 1 );
|
|
}
|
|
break;
|
|
case DFNTAG_FY1:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFy( static_cast<SI16>( ndata ), 0 );
|
|
}
|
|
break;
|
|
case DFNTAG_FY2:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFy( static_cast<SI16>( ndata ), 1 );
|
|
}
|
|
break;
|
|
case DFNTAG_FZ1:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetFz( static_cast<SI08>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_FOOD: applyTo->SetFood( cdata ); break;
|
|
case DFNTAG_GET:
|
|
{
|
|
std::string scriptEntry = "";
|
|
if( ssects.size() == 1 )
|
|
{
|
|
scriptEntry = cdata;
|
|
}
|
|
else
|
|
{
|
|
SI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssects.size() - 1 ));
|
|
scriptEntry = oldstrutil::trim( oldstrutil::removeTrailing( ssects[rndEntry], "//" ));
|
|
}
|
|
|
|
CScriptSection *toFind = FileLookup->FindEntry( scriptEntry, npc_def );
|
|
if( toFind == nullptr )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid script entry ([%s]) called with GET tag, NPC serial 0x%X", scriptEntry.c_str(), applyTo->GetSerial() ));
|
|
}
|
|
else if( toFind == NpcCreation )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Infinite loop avoided with GET tag inside NPC script [%s]", scriptEntry.c_str() ));
|
|
}
|
|
else
|
|
{
|
|
ApplyNpcSection( applyTo, toFind, scriptEntry, isGate );
|
|
}
|
|
break;
|
|
}
|
|
case DFNTAG_GETUO:
|
|
case DFNTAG_GETT2A:
|
|
case DFNTAG_GETUOR:
|
|
case DFNTAG_GETTD:
|
|
case DFNTAG_GETLBR:
|
|
case DFNTAG_GETAOS:
|
|
case DFNTAG_GETSE:
|
|
case DFNTAG_GETML:
|
|
case DFNTAG_GETSA:
|
|
case DFNTAG_GETHS:
|
|
case DFNTAG_GETTOL:
|
|
{
|
|
// Inherit stats for object based on active ruleset
|
|
bool getParent = false;
|
|
std::string tagName = "";
|
|
switch( cwmWorldState->ServerData()->ExpansionCoreShardEra() )
|
|
{
|
|
case ER_UO: if( tag == DFNTAG_GETUO ) { getParent = true; tagName = "GETUO"; } break;
|
|
case ER_T2A: if( tag == DFNTAG_GETT2A ) { getParent = true; tagName = "GETT2A"; } break;
|
|
case ER_UOR: if( tag == DFNTAG_GETUOR ) { getParent = true; tagName = "GETUOR"; } break;
|
|
case ER_TD: if( tag == DFNTAG_GETTD ) { getParent = true; tagName = "GETTD"; } break;
|
|
case ER_LBR: if( tag == DFNTAG_GETLBR ) { getParent = true; tagName = "GETLBR"; } break;
|
|
case ER_AOS: if( tag == DFNTAG_GETAOS ) { getParent = true; tagName = "GETAOS"; } break;
|
|
case ER_SE: if( tag == DFNTAG_GETSE ) { getParent = true; tagName = "GETSE"; } break;
|
|
case ER_ML: if( tag == DFNTAG_GETML ) { getParent = true; tagName = "GETML"; } break;
|
|
case ER_SA: if( tag == DFNTAG_GETSA ) { getParent = true; tagName = "GETSA"; } break;
|
|
case ER_HS: if( tag == DFNTAG_GETHS ) { getParent = true; tagName = "GETHS"; } break;
|
|
case ER_TOL: if( tag == DFNTAG_GETTOL ) { getParent = true; tagName = "GETTOL"; } break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if( getParent )
|
|
{
|
|
std::string scriptEntry = "";
|
|
if( ssects.size() == 1 )
|
|
{
|
|
scriptEntry = cdata;
|
|
}
|
|
else
|
|
{
|
|
SI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssects.size() - 1 ));
|
|
scriptEntry = oldstrutil::trim( oldstrutil::removeTrailing( ssects[rndEntry], "//" ));
|
|
}
|
|
|
|
CScriptSection *toFind = FileLookup->FindEntry( scriptEntry, npc_def );
|
|
if( toFind == NULL )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid script entry ([%s]) called with %s tag, NPC serial 0x%X", scriptEntry.c_str(), tagName.c_str(), applyTo->GetSerial() ));
|
|
}
|
|
else if( toFind == NpcCreation )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Infinite loop avoided with %s tag inside NPC script [%s]", tagName.c_str(), scriptEntry.c_str() ));
|
|
}
|
|
else
|
|
{
|
|
ApplyNpcSection( applyTo, toFind, scriptEntry, isGate );
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case DFNTAG_GOLD:
|
|
if( !isGate )
|
|
{
|
|
if( !ValidateObject( mypack ))
|
|
{
|
|
mypack = applyTo->GetPackItem();
|
|
}
|
|
if( ValidateObject( mypack ))
|
|
{
|
|
if( ndata >= 0 )
|
|
{
|
|
bool shouldSave = applyTo->ShouldSave();
|
|
if( odata && odata > ndata )
|
|
{
|
|
retitem = Items->CreateScriptItem( nullptr, applyTo, "0x0EED", static_cast<UI16>( RandomNum( ndata, odata )), OT_ITEM, true, 0xFFFF, shouldSave );
|
|
}
|
|
else
|
|
{
|
|
retitem = Items->CreateScriptItem( nullptr, applyTo, "0x0EED", ndata, OT_ITEM, true, 0xFFFF, shouldSave );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in GOLD tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Bad NPC Script ([%s]) with problem no backpack for gold", sectionId.c_str() ));
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_HAIRCOLOUR:
|
|
haircolor = AddRandomColor( cdata );
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetColour( haircolor );
|
|
}
|
|
break;
|
|
case DFNTAG_HEALING: skillToSet = HEALING; break;
|
|
case DFNTAG_HERDING: skillToSet = HERDING; break;
|
|
case DFNTAG_HIDING: skillToSet = HIDING; break;
|
|
case DFNTAG_HIDAMAGE: applyTo->SetHiDamage( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_HIRELING:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetCanHire( true );
|
|
}
|
|
break;
|
|
case DFNTAG_HP:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetHP( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetHP( ndata );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in HP tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_HPMAX:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetFixedMaxHP( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetFixedMaxHP( ndata );
|
|
}
|
|
|
|
// Update current HP
|
|
applyTo->SetHP( applyTo->GetMaxHP() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in HPMAX tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_ID:
|
|
/*applyTo->SetId( static_cast<UI16>( ndata ));
|
|
applyTo->SetOrgId( static_cast<UI16>( ndata ));
|
|
break;*/
|
|
|
|
if( ssects.size() == 1 )
|
|
{
|
|
applyTo->SetId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 )));
|
|
applyTo->SetOrgId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 )));
|
|
}
|
|
else
|
|
{
|
|
SI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssects.size() - 1 ));
|
|
applyTo->SetId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[rndEntry], "//" )), nullptr, 0 )));
|
|
applyTo->SetOrgId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[rndEntry], "//" )), nullptr, 0 )));
|
|
}
|
|
break;
|
|
case DFNTAG_IMBUING: skillToSet = IMBUING; break;
|
|
case DFNTAG_INSCRIPTION: skillToSet = INSCRIPTION; break;
|
|
case DFNTAG_INTELLIGENCE:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetIntelligence( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetIntelligence( ndata );
|
|
}
|
|
applyTo->SetMana( applyTo->GetMaxMana() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in INTELLIGENCE tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_ITEMID: skillToSet = ITEMID; break;
|
|
case DFNTAG_KARMA: applyTo->SetKarma( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_PACKITEM:
|
|
[[fallthrough]];
|
|
case DFNTAG_LOOT:
|
|
if( !isGate )
|
|
{
|
|
if( !ValidateObject( mypack ))
|
|
{
|
|
mypack = applyTo->GetPackItem();
|
|
}
|
|
if( ValidateObject( mypack ))
|
|
{
|
|
if( !cdata.empty() )
|
|
{
|
|
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( cdata, "//" )), "," );
|
|
if( csecs.size() > 1 )
|
|
{
|
|
UI16 iAmount = 0;
|
|
std::string amountData = oldstrutil::trim( oldstrutil::removeTrailing( csecs[1], "//" ));
|
|
auto tsects = oldstrutil::sections( amountData, " " );
|
|
if( tsects.size() > 1 ) // check if the second part of the tag-data contains two sections separated by a space
|
|
{
|
|
auto first = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( tsects[0], "//" )), nullptr, 0 ));
|
|
auto second = static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( tsects[1], "//" )), nullptr, 0 ));
|
|
|
|
// Tag contained a minimum and maximum value for amount! Let's randomize!
|
|
iAmount = static_cast<UI16>( RandomNum( first, second ));
|
|
}
|
|
else
|
|
{
|
|
iAmount = static_cast<UI16>( std::stoul(amountData, nullptr, 0 ));
|
|
}
|
|
auto tdata = oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" ));
|
|
|
|
if( tag == DFNTAG_LOOT )
|
|
{
|
|
Items->AddRespawnItem( mypack, tdata, true, true, iAmount, true );
|
|
}
|
|
else
|
|
{
|
|
Items->AddRespawnItem( mypack, tdata, true, false, iAmount, false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( tag == DFNTAG_LOOT )
|
|
{
|
|
Items->AddRespawnItem( mypack, cdata, true, true, 1, true );
|
|
}
|
|
else
|
|
{
|
|
Items->AddRespawnItem( mypack, cdata, true, false, 1, false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Bad NPC Script ([%s]) with problem no backpack for loot", sectionId.c_str() ));
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_LOCKPICKING: skillToSet = LOCKPICKING; break;
|
|
case DFNTAG_LODAMAGE: applyTo->SetLoDamage( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_LUCK: applyTo->SetLuck( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_LUMBERJACKING: skillToSet = LUMBERJACKING; break;
|
|
case DFNTAG_LOYALTY:
|
|
applyTo->SetLoyalty( static_cast<UI16>( ndata ));
|
|
break;
|
|
case DFNTAG_TITHING: applyTo->SetTithing( static_cast<UI32>( ndata )); break;
|
|
case DFNTAG_MACEFIGHTING: skillToSet = MACEFIGHTING; break;
|
|
case DFNTAG_MAGERY: skillToSet = MAGERY; break;
|
|
case DFNTAG_MAGICRESISTANCE: skillToSet = MAGICRESISTANCE; break;
|
|
case DFNTAG_MANA:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetMana( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetMana( ndata );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in MANA tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_MANAMAX:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetFixedMaxMana( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetFixedMaxMana( ndata );
|
|
}
|
|
|
|
// Update current Mana
|
|
applyTo->SetMana( applyTo->GetMaxMana() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in MANAMAX tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_MAXLOYALTY:
|
|
applyTo->SetMaxLoyalty( static_cast<UI16>( ndata ));
|
|
break;
|
|
case DFNTAG_MEDITATION: skillToSet = MEDITATION; break;
|
|
case DFNTAG_MINING: skillToSet = MINING; break;
|
|
case DFNTAG_MUSICIANSHIP: skillToSet = MUSICIANSHIP; break;
|
|
case DFNTAG_MYSTICISM: skillToSet = MYSTICISM; break;
|
|
case DFNTAG_NAME: applyTo->SetName( cdata ); break;
|
|
case DFNTAG_NAMELIST: SetRandomName( applyTo, cdata ); break;
|
|
case DFNTAG_NECROMANCY: skillToSet = NECROMANCY; break;
|
|
case DFNTAG_NINJITSU: skillToSet = NINJITSU; break;
|
|
case DFNTAG_HITCHANCE: applyTo->SetHitChance( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_DEFENSECHANCE: applyTo->SetDefenseChance( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_NPCWANDER:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetNpcWander( static_cast<SI08>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_NPCAI:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetNPCAiType( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_NPCGUILD:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetNPCGuild( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_NOHIRELING:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetCanHire( false );
|
|
}
|
|
break;
|
|
case DFNTAG_NOTRAIN:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetCanTrain( false );
|
|
}
|
|
break;
|
|
case DFNTAG_ORIGIN: applyTo->SetOrigin( static_cast<ExpansionRuleset>( cwmWorldState->ServerData()->EraStringToEnum( cdata ))); break;
|
|
case DFNTAG_POISONSTRENGTH: applyTo->SetPoisonStrength( static_cast<UI08>( ndata )); break;
|
|
case DFNTAG_PRIV:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetPriv( static_cast<UI32>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_PARRYING: skillToSet = PARRYING; break;
|
|
case DFNTAG_PEACEMAKING: skillToSet = PEACEMAKING; break;
|
|
case DFNTAG_PROVOCATION: skillToSet = PROVOCATION; break;
|
|
case DFNTAG_POISONING: skillToSet = POISONING; break;
|
|
case DFNTAG_SWINGSPEEDINCREASE: applyTo->SetSwingSpeedIncrease( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_RESISTFIRE:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), HEAT );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( ndata, HEAT );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in RESISTFIRE tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_RESISTCOLD:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), COLD );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( ndata, COLD );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in RESISTCOLD tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_RESISTLIGHTNING:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), LIGHTNING );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( ndata, LIGHTNING );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in RESISTLIGHTNING tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_RESISTPOISON:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), POISON );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetResist( ndata, POISON );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in RESISTPOISON tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_RSHOPITEM:
|
|
if( !isGate )
|
|
{
|
|
if( !ValidateObject( buyPack ))
|
|
{
|
|
buyPack = applyTo->GetItemAtLayer( IL_BUYCONTAINER );
|
|
}
|
|
if( ValidateObject( buyPack ))
|
|
{
|
|
retitem = Items->CreateBaseScriptItem( nullptr, cdata, applyTo->WorldNumber(), 1 );
|
|
if( retitem )
|
|
{
|
|
retitem->SetCont( buyPack );
|
|
retitem->PlaceInPack();
|
|
if( !retitem->GetName2().empty() && ( retitem->GetName2() != "#" ))
|
|
{
|
|
retitem->SetName( retitem->GetName2() ); // Item identified!
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Bad NPC Script ([%s]) with no Vendor Buy Pack for item", sectionId.c_str() ));
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_REATTACKAT:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetReattackAt( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_REMOVETRAP: skillToSet = REMOVETRAP; break;
|
|
case DFNTAG_HEALTHREGENBONUS: applyTo->SetHealthRegenBonus( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_STAMINAREGENBONUS: applyTo->SetStaminaRegenBonus( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_MANAREGENBONUS: applyTo->SetManaRegenBonus( static_cast<SI16>( ndata )); break;
|
|
case DFNTAG_RACE: applyTo->SetRace( static_cast<UI16>( ndata )); break;
|
|
case DFNTAG_RUNS:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetRun( true );
|
|
}
|
|
break;
|
|
case DFNTAG_RUNNINGSPEED:
|
|
applyTo->SetRunningSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_RUNNINGSPEEDMOUNTED:
|
|
applyTo->SetMountedRunningSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_SECTIONID: applyTo->SetSectionId( cdata ); break;
|
|
case DFNTAG_SKIN: applyTo->SetSkin( static_cast<UI16>( ndata )); break;
|
|
case DFNTAG_SHOPKEEPER:
|
|
if( !isGate )
|
|
{
|
|
MakeShop( applyTo );
|
|
}
|
|
break;
|
|
case DFNTAG_SHOPLIST:
|
|
if( !isGate )
|
|
{
|
|
LoadShopList( cdata, applyTo );
|
|
}
|
|
break;
|
|
case DFNTAG_SELLITEM:
|
|
if( !isGate )
|
|
{
|
|
if( !ValidateObject( sellPack ))
|
|
{
|
|
sellPack = applyTo->GetItemAtLayer( IL_SELLCONTAINER );
|
|
}
|
|
if( ValidateObject( sellPack ))
|
|
{
|
|
bool shouldSave = applyTo->ShouldSave();
|
|
retitem = Items->CreateBaseScriptItem( nullptr, cdata, applyTo->WorldNumber(), 1, applyTo->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetCont( sellPack );
|
|
retitem->PlaceInPack();
|
|
if( !retitem->GetName2().empty() && ( retitem->GetName2() != "#" ))
|
|
{
|
|
retitem->SetName( retitem->GetName2() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Bad NPC Script ([%s]) with no Vendor SellPack for item", sectionId.c_str() ));
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_SHOPITEM:
|
|
if( !isGate )
|
|
{
|
|
if( !ValidateObject( boughtPack ))
|
|
{
|
|
boughtPack = applyTo->GetItemAtLayer( IL_BOUGHTCONTAINER );
|
|
}
|
|
if( ValidateObject( boughtPack ))
|
|
{
|
|
bool shouldSave = applyTo->ShouldSave();
|
|
retitem = Items->CreateBaseScriptItem( nullptr, cdata, applyTo->WorldNumber(), 1, applyTo->GetInstanceId(), OT_ITEM, 0xFFFF, shouldSave );
|
|
if( retitem != nullptr )
|
|
{
|
|
retitem->SetCont( boughtPack );
|
|
retitem->PlaceInPack();
|
|
if( !retitem->GetName2().empty() && ( retitem->GetName2() != "#" ))
|
|
{
|
|
retitem->SetName( retitem->GetName2() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning(oldstrutil::format( "Bad NPC Script ([%s]) with no Vendor Bought Pack for item", sectionId.c_str() ));
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_SAYCOLOUR:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetSayColour( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_SPATTACK:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetSpAttack( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_SPADELAY:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetSpDelay( static_cast<SI08>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_SPELLWEAVING: skillToSet = SPELLWEAVING; break;
|
|
case DFNTAG_SPLIT:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetSplit( static_cast<UI08>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_SPLITCHANCE:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetSplitChance( static_cast<UI08>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_SNOOPING: skillToSet = SNOOPING; break;
|
|
case DFNTAG_SPIRITSPEAK: skillToSet = SPIRITSPEAK; break;
|
|
case DFNTAG_STEALING: skillToSet = STEALING; break;
|
|
case DFNTAG_STRENGTH:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetStrength( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetStrength( ndata );
|
|
}
|
|
applyTo->SetHP( applyTo->GetMaxHP() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in STRENGTH tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_SWORDSMANSHIP: skillToSet = SWORDSMANSHIP; break;
|
|
case DFNTAG_STAMINA:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetStamina( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetStamina( ndata );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in STAMINA tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_STAMINAMAX:
|
|
if( ndata >= 0 )
|
|
{
|
|
if( odata && odata > ndata )
|
|
{
|
|
applyTo->SetFixedMaxStam( static_cast<SI16>( RandomNum( ndata, odata )));
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetFixedMaxStam( ndata );
|
|
}
|
|
|
|
// Update current Stamina
|
|
applyTo->SetStamina( applyTo->GetMaxStam() );
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in STAMINA tag inside NPC script [%s]", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
case DFNTAG_STEALTH: skillToSet = STEALTH; break;
|
|
case DFNTAG_SKINLIST: applyTo->SetSkin( AddRandomColor( cdata )); break;
|
|
case DFNTAG_SKILL: applyTo->SetBaseSkill( static_cast<UI16>( odata ), static_cast<UI08>( ndata )); break;
|
|
case DFNTAG_SCRIPT:
|
|
if( !isGate )
|
|
{
|
|
applyTo->AddScriptTrigger( static_cast<UI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_THROWING: skillToSet = THROWING; break;
|
|
case DFNTAG_TITLE: applyTo->SetTitle( cdata ); break;
|
|
case DFNTAG_TOTAME:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetTaming( static_cast<SI16>( ndata ));
|
|
applyTo->SetOrneriness( static_cast<UI16>( ndata )); // Set default difficulty to control equal to skill required to tame
|
|
}
|
|
break;
|
|
case DFNTAG_TOPROV:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetProvoing( static_cast<SI16>( ndata ));
|
|
}
|
|
break;
|
|
case DFNTAG_TOPEACE:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetPeaceing( static_cast<SI16>( ndata ));
|
|
}
|
|
applyTo->SetBrkPeaceChanceGain( static_cast<SI16>( odata ));
|
|
break;
|
|
case DFNTAG_TAMEDHUNGER:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetTamedHungerRate( static_cast<UI16>( ndata ));
|
|
applyTo->SetTamedHungerWildChance( static_cast<UI08>( odata ));
|
|
}
|
|
break;
|
|
case DFNTAG_TAMEDTHIRST:
|
|
if( !isGate )
|
|
{
|
|
applyTo->SetTamedThirstRate( static_cast<UI16>( ndata ));
|
|
applyTo->SetTamedThirstWildChance( static_cast<UI08>( odata ));
|
|
}
|
|
break;
|
|
case DFNTAG_WILLHUNGER:
|
|
if( !isGate )
|
|
{
|
|
if( ndata > 0 )
|
|
{
|
|
applyTo->SetHungerStatus( true );
|
|
}
|
|
else
|
|
{
|
|
applyTo->SetHungerStatus( false );
|
|
}
|
|
}
|
|
break;
|
|
case DFNTAG_WALKINGSPEED:
|
|
applyTo->SetWalkingSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_WALKINGSPEEDMOUNTED:
|
|
applyTo->SetMountedWalkingSpeed( std::stod( cdata ));
|
|
break;
|
|
case DFNTAG_TACTICS: skillToSet = TACTICS; break;
|
|
case DFNTAG_TAILORING: skillToSet = TAILORING; break;
|
|
case DFNTAG_TAMING: skillToSet = TAMING; break;
|
|
case DFNTAG_TASTEID: skillToSet = TASTEID; break;
|
|
case DFNTAG_TINKERING: skillToSet = TINKERING; break;
|
|
case DFNTAG_TRACKING: skillToSet = TRACKING; break;
|
|
case DFNTAG_VETERINARY: skillToSet = VETERINARY; break;
|
|
case DFNTAG_WRESTLING: skillToSet = WRESTLING; break;
|
|
case DFNTAG_CUSTOMSTRINGTAG:
|
|
{
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssects )
|
|
{
|
|
if( count > 0 )
|
|
{
|
|
if( count == 1 )
|
|
{
|
|
result = oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
else
|
|
{
|
|
result = result + " " + oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" ));
|
|
customTagStringValue = result;
|
|
|
|
if( !customTagName.empty() && !customTagStringValue.empty() )
|
|
{
|
|
customTag.m_Destroy = false;
|
|
customTag.m_StringValue = customTagStringValue;
|
|
customTag.m_IntValue = static_cast<SI32>( customTag.m_StringValue.size() );
|
|
customTag.m_ObjectType = TAGMAP_TYPE_STRING;
|
|
applyTo->SetTag( customTagName, customTag);
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in CUSTOMSTRINGTAG tag inside NPC script [%s] - Supported data format: <tagName> <text>", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
}
|
|
case DFNTAG_CUSTOMINTTAG:
|
|
{
|
|
auto count = 0;
|
|
std::string result;
|
|
for( auto &sec : ssects )
|
|
{
|
|
if( count > 0 )
|
|
{
|
|
if( count == 1 )
|
|
{
|
|
result = oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
|
|
}
|
|
}
|
|
count++;
|
|
}
|
|
customTagName = oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" ));
|
|
customTagStringValue = result;
|
|
if( !customTagName.empty() && !customTagStringValue.empty() )
|
|
{
|
|
customTag.m_Destroy = false;
|
|
customTag.m_IntValue = std::stoi( customTagStringValue );
|
|
customTag.m_ObjectType = TAGMAP_TYPE_INT;
|
|
customTag.m_StringValue = "";
|
|
applyTo->SetTag( customTagName, customTag );
|
|
if( count > 2 )
|
|
{
|
|
Console.Warning( oldstrutil::format( "Multiple values detected for CUSTOMINTTAG in NPC script [%s] - only first value will be used! Supported data format: <tagName> <value>", sectionId.c_str() ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.Warning( oldstrutil::format( "Invalid data found in CUSTOMINTTAG tag in NPC script [%s] - Supported data format: <tagName> <value>", sectionId.c_str() ));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
Console << "Unknown tag in ApplyNpcSection(): " << static_cast<SI32>( tag ) << myendl; break;
|
|
}
|
|
if( skillToSet != 0xFF )
|
|
{
|
|
applyTo->SetBaseSkill( static_cast<UI16>( RandomNum( ndata, odata )), skillToSet );
|
|
skillToSet = 0xFF;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CCharStuff::CanControlPet( CChar *mChar, CChar *Npc, bool isRestricted, bool checkDifficulty, bool ignoreOwnerCheck, bool ignoreLoyaltyChanges )
|
|
{
|
|
if( ValidateObject( Npc->GetOwnerObj() ) && Npc->GetNpcAiType() != AI_PLAYERVENDOR && Npc->GetQuestType() == 0 )
|
|
{
|
|
if( !ignoreOwnerCheck && Npc->GetOwnerObj() != mChar && ( isRestricted || !Npcs->CheckPetFriend( mChar, Npc )))
|
|
return false;
|
|
|
|
if( checkDifficulty && Npc->IsTamed() && cwmWorldState->ServerData()->CheckPetControlDifficulty() )
|
|
{
|
|
// Let's base this on how difficult it is to control the pet, as well
|
|
UI16 chanceToControl = Skills->CalculatePetControlChance( mChar, Npc );
|
|
UI16 loyaltyGainOnSuccess = cwmWorldState->ServerData()->GetPetLoyaltyGainOnSuccess();
|
|
UI16 loyaltyLossOnFailure = cwmWorldState->ServerData()->GetPetLoyaltyLossOnFailure();
|
|
|
|
// Easy to control pets/summoned creatures don't gain/lose loyalty from commands
|
|
if( chanceToControl == 1000 )
|
|
return true;
|
|
|
|
if( chanceToControl >= RandomNum( 1, 1000 ))
|
|
{
|
|
// Succeeded in controlling pet
|
|
if( !ignoreLoyaltyChanges )
|
|
{
|
|
Npc->SetLoyalty( std::min( 100, Npc->GetLoyalty() + loyaltyGainOnSuccess ));
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Failed to control pet
|
|
if( !ignoreLoyaltyChanges )
|
|
{
|
|
Npc->SetLoyalty( std::max( 0, Npc->GetLoyalty() - loyaltyLossOnFailure ));
|
|
UI16 soundToPlay = cwmWorldState->creatures[Npc->GetId()].GetSound( SND_STARTATTACK );
|
|
Effects->PlaySound( Npc, soundToPlay );
|
|
|
|
if( Npc->GetLoyalty() == 0 )
|
|
{
|
|
// Reduce player's control slot usage by the amount of control slots taken up by the pet
|
|
mChar->SetControlSlotsUsed( std::max( 0, mChar->GetControlSlotsUsed() - Npc->GetControlSlots() ));
|
|
|
|
// Pet goes wild for lack of loyalty
|
|
Npcs->ReleasePet( Npc );
|
|
return false;
|
|
}
|
|
|
|
CSocket *mSock = mChar->GetSocket();
|
|
if( mSock != nullptr )
|
|
{
|
|
std::string npcName = GetNpcDictName( Npc, mSock, NRS_SPEECH );
|
|
mSock->SysMessage( 2412, npcName.c_str() ); // %s disobeys your command
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// No difficulty checking needed, just allow it
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::FinalizeTransfer()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Finalize transfer of pet to new owner
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CCharStuff::FinalizeTransfer( CChar *petChar, CChar *srcChar, CChar *targChar )
|
|
{
|
|
// If the pet is guarding something, stop guarding that something
|
|
Npcs->StopPetGuarding( petChar );
|
|
|
|
// Clear pet's existing friend-list
|
|
petChar->ClearFriendList();
|
|
|
|
std::string petName = GetNpcDictName( petChar, nullptr, NRS_SPEECH );
|
|
petChar->TextMessage( nullptr, 1074, TALK, 0, petName.c_str(), targChar->GetName().c_str() ); // * %s will now take %s as his master *
|
|
|
|
// Transfer ownership
|
|
petChar->SetOwner( targChar );
|
|
|
|
// Make pet follow their new owner
|
|
petChar->SetFTarg( static_cast<CChar*>( targChar ));
|
|
petChar->FlushPath();
|
|
petChar->SetNpcWander( WT_FOLLOW );
|
|
|
|
// Update control slots used for both old and new owners
|
|
srcChar->SetControlSlotsUsed( std::max(0, srcChar->GetControlSlotsUsed() - petChar->GetControlSlots() ));
|
|
targChar->SetControlSlotsUsed( std::clamp( targChar->GetControlSlotsUsed() + petChar->GetControlSlots(), 0, 255 ));
|
|
|
|
// Remove Pet Bonding if its Bonded
|
|
TAGMAPOBJECT petBond = petChar->GetTag( "isBondedPet" );
|
|
if( petBond.m_IntValue == 1 )
|
|
{
|
|
petBond.m_IntValue = 0;
|
|
petChar->SetTag( "isBondedPet", petBond );
|
|
petChar->RemoveScriptTrigger( 3107 );// petbonding.js
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::ReleasePet()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Release the pet from its owner
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CCharStuff::ReleasePet( CChar *pet )
|
|
{
|
|
// Stop guarding
|
|
Npcs->StopPetGuarding( pet );
|
|
|
|
// Stop following
|
|
pet->SetFTarg( nullptr );
|
|
pet->SetNpcWander( WT_FREE );
|
|
|
|
// Remove owner
|
|
pet->SetOwner( nullptr );
|
|
|
|
std::string petName = GetNpcDictName( pet, nullptr, NRS_SPEECH );
|
|
pet->TextMessage( nullptr, 1325, TALK, 0, petName.c_str() ); // *%s appears to have decided that it is better off without a master *
|
|
|
|
// If a summoned creature, unsummon it
|
|
if( pet->GetTimer( tNPC_SUMMONTIME ))
|
|
{
|
|
Effects->PlaySound( pet, 0x01FE );
|
|
pet->Delete();
|
|
}
|
|
|
|
// Reset hunger
|
|
pet->SetHunger( 6 );
|
|
|
|
// Increase pet orneriness, to make it more difficult to control for its next owner
|
|
auto ownerCount = pet->GetOwnerCount();
|
|
switch( ownerCount )
|
|
{
|
|
case 1:
|
|
pet->SetOrneriness( pet->GetTaming() + 48 );
|
|
break;
|
|
case 2:
|
|
pet->SetOrneriness( pet->GetTaming() + 192 );
|
|
break;
|
|
case 3:
|
|
pet->SetOrneriness( pet->GetTaming() + 432 );
|
|
break;
|
|
case 4:
|
|
pet->SetOrneriness( pet->GetTaming() + 768 );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Remove Bonding if its Bonded
|
|
TAGMAPOBJECT petBond = pet->GetTag( "isBondedPet" );
|
|
if( petBond.m_IntValue == 1 )
|
|
{
|
|
petBond.m_IntValue = 0;
|
|
pet->SetTag( "isBondedPet", petBond );
|
|
pet->RemoveScriptTrigger( 3107 );// petbonding.js
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::GetGuardingFollower()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Get the active follower guarding an item / character
|
|
//o------------------------------------------------------------------------------------------------o
|
|
auto CCharStuff::GetGuardingFollower( CChar *mChar, CBaseObject *guarded ) ->CChar *
|
|
{
|
|
if( ValidateObject( mChar ) && ValidateObject( guarded ))
|
|
{
|
|
auto myFollowers = mChar->GetFollowerList();
|
|
for( const auto &follower : myFollowers->collection() )
|
|
{
|
|
if( ValidateObject( follower ))
|
|
{
|
|
//if( !follower->GetMounted() && follower->GetNpcAiType() == AI_PET_GUARD &&
|
|
if( !follower->GetMounted() && follower->GetGuarding() == guarded && follower->GetOwnerObj() == mChar )
|
|
{
|
|
return follower;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::CheckPetFriend()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Search a pets friends list to check if a character is a friend
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool CCharStuff::CheckPetFriend( CChar *mChar, CChar *pet )
|
|
{
|
|
auto petFriends = pet->GetFriendList();
|
|
if( petFriends != nullptr )
|
|
{
|
|
for( auto &getFriend : *petFriends )
|
|
{
|
|
if( ValidateObject( getFriend ) && getFriend == mChar )
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CCharStuff::StopPetGuarding()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find the item/char pet is guarding and set it to not guarded
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void CCharStuff::StopPetGuarding( CChar *pet )
|
|
{
|
|
CBaseObject *petGuarding = pet->GetGuarding();
|
|
if( !ValidateObject( petGuarding ))
|
|
return;
|
|
|
|
if( petGuarding->GetObjType() == OT_ITEM )
|
|
{
|
|
CItem *itemGuard = static_cast<CItem *>( petGuarding );
|
|
if( ValidateObject( itemGuard ))
|
|
{
|
|
itemGuard->SetGuarded( false );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CChar *charGuard = static_cast<CChar *>( petGuarding );
|
|
if( ValidateObject( charGuard ))
|
|
{
|
|
charGuard->SetGuarded( false );
|
|
}
|
|
}
|
|
pet->SetGuarding( nullptr );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - MonsterGate()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle monster gates (polymorphs players into monster bodies)
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void MonsterGate( CChar *s, const std::string& scriptEntry )
|
|
{
|
|
CItem *mypack = nullptr;
|
|
if( s->IsNpc() )
|
|
return;
|
|
|
|
auto entry = scriptEntry;
|
|
entry = oldstrutil::trim( oldstrutil::removeTrailing( entry, "//" ));
|
|
CScriptSection *Monster = FileLookup->FindEntry( entry, npc_def );
|
|
if( Monster == nullptr )
|
|
return;
|
|
|
|
s->SetTitle( "\0" );
|
|
|
|
CItem *n;
|
|
for( CItem *z = s->FirstItem(); !s->FinishedItems(); z = s->NextItem() )
|
|
{
|
|
if( ValidateObject( z ))
|
|
{
|
|
if( z->IsFree() )
|
|
continue;
|
|
|
|
if( z->GetLayer() == IL_HAIR || z->GetLayer() == IL_FACIALHAIR )
|
|
{
|
|
z->Delete();
|
|
}
|
|
else if( z->GetLayer() != IL_PACKITEM && z->GetLayer() != IL_BANKBOX )
|
|
{
|
|
if( mypack == nullptr )
|
|
{
|
|
mypack = s->GetPackItem();
|
|
}
|
|
if( mypack == nullptr )
|
|
{
|
|
n = Items->CreateItem( nullptr, s, 0x0E75, 1, 0, OT_ITEM );
|
|
if( !ValidateObject( n ))
|
|
return;
|
|
|
|
s->SetPackItem( n );
|
|
n->SetDecayable( false );
|
|
n->SetLayer( IL_PACKITEM );
|
|
if( n->SetCont( s ))
|
|
{
|
|
n->SetType( IT_CONTAINER );
|
|
n->SetDye( true );
|
|
mypack = n;
|
|
}
|
|
}
|
|
z->SetCont( mypack );
|
|
z->PlaceInPack();
|
|
}
|
|
}
|
|
}
|
|
|
|
Npcs->ApplyNpcSection( s, Monster, scriptEntry, true );
|
|
//Now find real 'skill' based on 'baseskill' (stat modifiers)
|
|
for( UI08 j = 0; j < ALLSKILLS; ++j )
|
|
{
|
|
Skills->UpdateSkillLevel( s, j );
|
|
}
|
|
s->Update();
|
|
Effects->PlayStaticAnimation( s, 0x373A, 0, 15 );
|
|
Effects->PlaySound( s, 0x01E9 );
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - Karma()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle karma addition/subtraction when character kills
|
|
//| another Character / NPC
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void Karma( CChar *nCharId, CChar *nKilledId, const SI16 nKarma )
|
|
{
|
|
SI16 nChange = 0;
|
|
bool nEffect = false;
|
|
SI16 nCurKarma = nCharId->GetKarma();
|
|
|
|
if( nCurKarma > 10000 )
|
|
{
|
|
nCharId->SetKarma( 10000 );
|
|
nCurKarma = 10000;
|
|
}
|
|
else if( nCurKarma < -10000 )
|
|
{
|
|
nCharId->SetKarma( -10000 );
|
|
nCurKarma = -10000;
|
|
}
|
|
|
|
// Block positive karma gain if karma is locked and the player is not young
|
|
if( nCharId->GetKarmaLock() && nKarma > 0 && cwmWorldState->ServerData()->KarmaLocking() && ( !cwmWorldState->ServerData()->YoungPlayerSystem() || !nCharId->GetAccount().wFlags.test( AB_FLAGS_YOUNG )))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( nCurKarma < nKarma && nKarma > 0 )
|
|
{
|
|
nChange = (( nKarma - nCurKarma ) / 75 );
|
|
nCharId->SetKarma( static_cast<SI16>( nCurKarma + nChange ));
|
|
nEffect = true;
|
|
}
|
|
|
|
if( nCurKarma > nKarma && ( !ValidateObject( nKilledId ) || nKilledId->GetKarma() > 0 ))
|
|
{
|
|
nChange = (( nCurKarma - nKarma ) / 50 );
|
|
nCharId->SetKarma( static_cast<SI16>( nCurKarma - nChange ));
|
|
nEffect = false;
|
|
}
|
|
|
|
// If karma goes negative, lock it (do not lock young if the young system is active)
|
|
if( nCharId->GetKarma() < 0 && !nCharId->GetKarmaLock() && cwmWorldState->ServerData()->KarmaLocking() && ( !cwmWorldState->ServerData()->YoungPlayerSystem() || !nCharId->GetAccount().wFlags.test( AB_FLAGS_YOUNG )))
|
|
{
|
|
nCharId->SetKarmaLock(true);
|
|
CSocket *mSock = nCharId->GetSocket();
|
|
if( mSock )
|
|
{
|
|
mSock->SysMessage( 5751 ); // Karma is locked. A mantra spoken at a shrine will unlock it again.
|
|
}
|
|
}
|
|
|
|
if( nChange == 0 ) // NPCs CAN gain/lose karma
|
|
{
|
|
return;
|
|
}
|
|
|
|
CSocket *mSock = nCharId->GetSocket();
|
|
if( nCharId->IsNpc() || mSock == nullptr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( nChange <= 25 )
|
|
{
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1367 ); // You have gained a little karma.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1368 ); // You have lost a little karma.
|
|
}
|
|
return;
|
|
}
|
|
if( nChange <= 50 )
|
|
{
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1369 ); // You have gained some karma.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1370 ); // You have lost some karma.
|
|
}
|
|
return;
|
|
}
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1371 ); // You have gained a lot of karma.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1372 ); // You have lost a lot of karma.
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - Fame()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Handle fame addition when character kills another Character / NPC
|
|
//o------------------------------------------------------------------------------------------------o
|
|
void Fame( CChar *nCharId, const SI16 nFame )
|
|
{
|
|
SI16 nChange = 0;
|
|
bool nEffect = false;
|
|
const SI16 nCurFame = nCharId->GetFame();
|
|
if( nCharId->IsDead() )
|
|
{
|
|
if( nCurFame <= 0 )
|
|
{
|
|
nCharId->SetFame( 0 );
|
|
}
|
|
else
|
|
{
|
|
nChange = ( nCurFame - 0 ) / 25;
|
|
nCharId->SetFame( static_cast<SI16>( nCurFame - nChange ));
|
|
}
|
|
nCharId->SetDeaths( static_cast<UI16>( nCharId->GetDeaths() + 1 ));
|
|
nEffect = false;
|
|
}
|
|
else if( nCurFame <= nFame )
|
|
{
|
|
nChange = ( nFame - nCurFame ) / 75;
|
|
nCharId->SetFame( static_cast<SI16>( nCurFame + nChange ));
|
|
nEffect = true;
|
|
if( nCharId->GetFame() > 10000 )
|
|
{
|
|
nCharId->SetFame( 10000 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return; // current fame is greater than target fame, and we're not dead
|
|
}
|
|
|
|
if( nChange == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CSocket *mSock = nCharId->GetSocket();
|
|
if( mSock == nullptr || nCharId->IsNpc() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( nChange <= 25 )
|
|
{
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1373 ); // You have gained a little fame.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1374 ); // You have lost a little fame.
|
|
}
|
|
}
|
|
else if( nChange <= 50 )
|
|
{
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1375 ); // You have gained some fame.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1376 ); // You have lost some fame.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( nEffect )
|
|
{
|
|
mSock->SysMessage( 1377 ); // You have gained a lot of fame.
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1378 ); // You have lost a lot of fame.
|
|
}
|
|
}
|
|
}
|
|
|
|
|