#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 alwaysAwakeNPCs; //o------------------------------------------------------------------------------------------------o //| Function - CCharStuff::GetAlwaysAwakeNPCs() //o------------------------------------------------------------------------------------------------o //| Purpose - Returns list of NPCs that are always awake //o------------------------------------------------------------------------------------------------o GenericList * 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( 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( 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( 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( 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 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> 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 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(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( RandomNum( static_cast( 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( 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> 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( 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> 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( 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( 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( -xAway ), xAway ); yos = originY + RandomNum( static_cast( -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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 ))); retItem->SetSellValue( static_cast( 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( 0 ), i - 1 ); tempName = RandomName->MoveTo( static_cast( 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( 0 ), i - 1 ); std::string tag = RandomColours->MoveTo( static_cast( i )); return static_cast( 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( 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( 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( ndata )); applyTo->SetHiDamage( static_cast( odata )); } else { applyTo->SetLoDamage( static_cast( ndata )); applyTo->SetHiDamage( static_cast( 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( 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( ndata )); } break; case DFNTAG_CHIVALRY: skillToSet = CHIVALRY; break; case DFNTAG_COOKING: skillToSet = COOKING; break; case DFNTAG_COLOUR: if( retitem != nullptr ) { retitem->SetColour( static_cast( ndata )); } break; case DFNTAG_COLOURLIST: if( retitem != nullptr ) { retitem->SetColour( AddRandomColor( cdata )); } break; case DFNTAG_COLOURMATCHHAIR: if( retitem != nullptr ) { retitem->SetColour( static_cast( haircolor )); } break; case DFNTAG_CONTROLSLOTS: if( !isGate ) { applyTo->SetControlSlots( static_cast( ndata )); } break; case DFNTAG_DEX: if( ndata > 0 ) { if( odata && odata > ndata ) { applyTo->SetDexterity( static_cast( 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( RandomNum( ndata, odata )), PHYSICAL ); } else { applyTo->SetResist( static_cast( 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( RandomNum( ndata, odata )), PHYSICAL ); } else { applyTo->SetResist( applyTo->GetResist( PHYSICAL ) + static_cast( 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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 ))), HEAT ); applyTo->SetResist(( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[1], "//" )), nullptr, 0 ))), COLD ); applyTo->SetResist(( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[2], "//" )), nullptr, 0 ))), LIGHTNING ); applyTo->SetResist(( static_cast( 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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 )), HEAT ); applyTo->SetResist( applyTo->GetResist( COLD ) + static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[1], "//" )), nullptr, 0 )), COLD ); applyTo->SetResist( applyTo->GetResist( LIGHTNING ) + static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[2], "//" )), nullptr, 0 )), LIGHTNING ); applyTo->SetResist( applyTo->GetResist( POISON ) + static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[3], "//" )), nullptr, 0 )), POISON ); } break; case DFNTAG_EMOTECOLOUR: if( !isGate ) { applyTo->SetEmoteColour( static_cast( 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( ndata )); break; case DFNTAG_FENCING: skillToSet = FENCING; break; case DFNTAG_FISHING: skillToSet = FISHING; break; case DFNTAG_FLEEAT: if( !isGate ) { applyTo->SetFleeAt( static_cast( 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( ndata ), 0 ); } break; case DFNTAG_FX2: if( !isGate ) { applyTo->SetFx( static_cast( ndata ), 1 ); } break; case DFNTAG_FY1: if( !isGate ) { applyTo->SetFy( static_cast( ndata ), 0 ); } break; case DFNTAG_FY2: if( !isGate ) { applyTo->SetFy( static_cast( ndata ), 1 ); } break; case DFNTAG_FZ1: if( !isGate ) { applyTo->SetFz( static_cast( 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( 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( 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( 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( 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( 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( 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( ndata )); applyTo->SetOrgId( static_cast( ndata )); break;*/ if( ssects.size() == 1 ) { applyTo->SetId( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 ))); applyTo->SetOrgId( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[0], "//" )), nullptr, 0 ))); } else { SI32 rndEntry = RandomNum( 0, static_cast( ssects.size() - 1 )); applyTo->SetId( static_cast( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssects[rndEntry], "//" )), nullptr, 0 ))); applyTo->SetOrgId( static_cast( 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( 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( 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( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( tsects[0], "//" )), nullptr, 0 )); auto second = static_cast( 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( RandomNum( first, second )); } else { iAmount = static_cast( 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( ndata )); break; case DFNTAG_LUCK: applyTo->SetLuck( static_cast( ndata )); break; case DFNTAG_LUMBERJACKING: skillToSet = LUMBERJACKING; break; case DFNTAG_LOYALTY: applyTo->SetLoyalty( static_cast( ndata )); break; case DFNTAG_TITHING: applyTo->SetTithing( static_cast( 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( 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( 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( 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( ndata )); break; case DFNTAG_DEFENSECHANCE: applyTo->SetDefenseChance( static_cast( ndata )); break; case DFNTAG_NPCWANDER: if( !isGate ) { applyTo->SetNpcWander( static_cast( ndata )); } break; case DFNTAG_NPCAI: if( !isGate ) { applyTo->SetNPCAiType( static_cast( ndata )); } break; case DFNTAG_NPCGUILD: if( !isGate ) { applyTo->SetNPCGuild( static_cast( 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( cwmWorldState->ServerData()->EraStringToEnum( cdata ))); break; case DFNTAG_POISONSTRENGTH: applyTo->SetPoisonStrength( static_cast( ndata )); break; case DFNTAG_PRIV: if( !isGate ) { applyTo->SetPriv( static_cast( 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( ndata )); break; case DFNTAG_RESISTFIRE: if( ndata >= 0 ) { if( odata && odata > ndata ) { applyTo->SetResist( static_cast( 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( 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( 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( 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( ndata )); } break; case DFNTAG_REMOVETRAP: skillToSet = REMOVETRAP; break; case DFNTAG_HEALTHREGENBONUS: applyTo->SetHealthRegenBonus( static_cast( ndata )); break; case DFNTAG_STAMINAREGENBONUS: applyTo->SetStaminaRegenBonus( static_cast( ndata )); break; case DFNTAG_MANAREGENBONUS: applyTo->SetManaRegenBonus( static_cast( ndata )); break; case DFNTAG_RACE: applyTo->SetRace( static_cast( 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( 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( ndata )); } break; case DFNTAG_SPATTACK: if( !isGate ) { applyTo->SetSpAttack( static_cast( ndata )); } break; case DFNTAG_SPADELAY: if( !isGate ) { applyTo->SetSpDelay( static_cast( ndata )); } break; case DFNTAG_SPELLWEAVING: skillToSet = SPELLWEAVING; break; case DFNTAG_SPLIT: if( !isGate ) { applyTo->SetSplit( static_cast( ndata )); } break; case DFNTAG_SPLITCHANCE: if( !isGate ) { applyTo->SetSplitChance( static_cast( 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( 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( 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( 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( odata ), static_cast( ndata )); break; case DFNTAG_SCRIPT: if( !isGate ) { applyTo->AddScriptTrigger( static_cast( ndata )); } break; case DFNTAG_THROWING: skillToSet = THROWING; break; case DFNTAG_TITLE: applyTo->SetTitle( cdata ); break; case DFNTAG_TOTAME: if( !isGate ) { applyTo->SetTaming( static_cast( ndata )); applyTo->SetOrneriness( static_cast( ndata )); // Set default difficulty to control equal to skill required to tame } break; case DFNTAG_TOPROV: if( !isGate ) { applyTo->SetProvoing( static_cast( ndata )); } break; case DFNTAG_TOPEACE: if( !isGate ) { applyTo->SetPeaceing( static_cast( ndata )); } applyTo->SetBrkPeaceChanceGain( static_cast( odata )); break; case DFNTAG_TAMEDHUNGER: if( !isGate ) { applyTo->SetTamedHungerRate( static_cast( ndata )); applyTo->SetTamedHungerWildChance( static_cast( odata )); } break; case DFNTAG_TAMEDTHIRST: if( !isGate ) { applyTo->SetTamedThirstRate( static_cast( ndata )); applyTo->SetTamedThirstWildChance( static_cast( 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( 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: ", 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: ", sectionId.c_str() )); } } else { Console.Warning( oldstrutil::format( "Invalid data found in CUSTOMINTTAG tag in NPC script [%s] - Supported data format: ", sectionId.c_str() )); } break; } default: Console << "Unknown tag in ApplyNpcSection(): " << static_cast( tag ) << myendl; break; } if( skillToSet != 0xFF ) { applyTo->SetBaseSkill( static_cast( 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( 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( petGuarding ); if( ValidateObject( itemGuard )) { itemGuard->SetGuarded( false ); } } else { CChar *charGuard = static_cast( 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( nCurKarma + nChange )); nEffect = true; } if( nCurKarma > nKarma && ( !ValidateObject( nKilledId ) || nKilledId->GetKarma() > 0 )) { nChange = (( nCurKarma - nKarma ) / 50 ); nCharId->SetKarma( static_cast( 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( nCurFame - nChange )); } nCharId->SetDeaths( static_cast( nCharId->GetDeaths() + 1 )); nEffect = false; } else if( nCurFame <= nFame ) { nChange = ( nFame - nCurFame ) / 75; nCharId->SetFame( static_cast( 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. } } }