uox3/source/items.cpp
Xoduz a48d9ee299 UOX3 0.99.6-RC6
Removed arbitrary and hidden restrictions on refreshing list of online players shown with 'wholist command
Fixed an issue where player ghosts that get teleported would not see themselves get updated to new location
Fixed a server crash from clients disconnecting while running server in debug mode
Improved how container updates are sent to players; now only sends to clients that have actually opened the container, and who are still within range
Added more information about UO data files being loaded during server startup
Enhanced the GM 'add menu to allow clicking directly on images of items/npcs to add them, and adjusted size of menus to accommodate this
Fixed an issue with fleeing NPCs that could cause them to attempt to flee across time and space, regardless of distance to target/attacker in combat
Added NPC AI that runs away from players if they get too close (even outside combat). Applied to hind, rabbits, squirrels, ferrets and various birds by default:
	AI_ANIMAL_SCARED (aitype 12)
Fixed issues with NPCs fleeing forever, or getting stuck in flee/don't flee loop, by introducing max fleeing distance (50 tiles) and cooldown (30 sec) on fleeing
Updated onNameRequest JS Event to include third parameter with origin of name request, so script responses can be tailored appropriately:
	onNameRequest( myObj, requestedBy, requestSource )
		Potential values for requestSource:
			0 - Speech/System Messages
			1 - Guild Menus
			2 - Stat Window (self)
			3 - Stat Window (other)
			4 - Tooltip
			5 - Paperdoll Journal
			6 - Paperdoll
			7 - Single Click / All-Names
			8 - System
			9 - Secure Trade Window
Added Adaptive Performance System (APS) that dynamically adjust how often NPC AI/movement is checked based on overall shard performance. If performance drops below defined threshold, UOX3 gradually slows down checks for NPC AI/movement to prioritize player movement/speech/command responsiveness. If performance climbs back up above threshold, slowdowns are gradually removed. The following UOX.INI options have been added under [system] category to support this system:
	APSPERFTHRESHOLD=50 	// Performance threshold (measured in simulation cycles/sec) below which the APS kicks in
	APSINTERVAL=100 		// How often (in milliseconds) the APS checks performance and makes adjustments (if needed) to balance out shard performance
	APSDELAYSTEP=50 		// How much the delay timer is modified by (in milliseconds) each time APS makes adjustments
	APSDELAYMAXCAP=2000 	// Max amount of of delay APS can introduce for NPC AI/movement handling when attempting to restore shard performance
Added new JS Methods for Region objects:
	.GetOrePrefs( oreType )	// Get ore preference data for ore type found in town region. Returned as an array containing the following data:
		orePrefData -> [
			oreName,	// name of ore
			color,		// color of ore
			minskill,	// min skill to mine ore
			ingotName,	// name of ingot created from ore
			makemenu,	// makemenu entry for crafting something from ingot
			oreChance,	// default global chance of finding this ore type
			scriptID 	// script attached to mined ore
		],
		orePrefChance 	// Chance of finding this ore type in given town region
	.GetOreChance() 	// Get base chance of finding any ore in town region
Moved Mining skill plus gravedigging feature from hard code to scripts (js/skill/mining.js) to make it easier to maintain and/or customize, and removed hard coded variants
Gravedigging now relies on the ore resource system behind the scenes to restrict how often graves in a given area can be dug up
Fixed some incorrect references to .worldNumber Character property in misc scripts (should be .worldnumber)
Made some "hard-scripted" system messages in misc scripts use dictionary system instead
Added spawn region for banker NPCs in Serpent's Hold (dfndata/spawn/felucca/spawn_town_serpents_hold.dfn, dfndata/spawn/trammel/spawn_town_serpents_hold.dfn)
Fixed region definition of Ocllo to actually cover the entire town (dfndata/regions/regions.dfn)
Added numerous additional locations accessible with 'goplace # command, for key locations in Ilshenar, Malas, Tokuno Islands and Ter Mur (dfndata/location/location.dfn)
Revised travel-menu portion of GM menu (shortcut: 'travel) to include more travel options based on new locations (dfndata/items/travelmenu.dfn and travelmenu.bulk.dfn)
Fixed an issue where NPCs could attempt to attack targets in other worlds/instances
Added new JS Event that triggers for characters who are about to deal damage in combat. Complimentary to onDamage, which triggers for chars receiving damage:
	onDamageDeal( dmgDealer, dmgReceiver, damageValue, damageType )
Added new JS Event that can trigger in global script upon creation of new player chars. Note that this will trigger in place of onCreateDFN event for player characters:
	onCreatePlayer( pChar )
Added new JS Event that triggers for characters selecting a target with a spell. Complimentary to onSpellTarget, which triggers for targets selected with a spell:
	onSpellTargetSelect( caster, target, spellNum )
Updated onCombatStart and onCombatEnd JS Events to also trigger for the other party in combat
Updated onPickup JS Event to include a third parameter - the potential container item was picked up from. Event now also triggers event in scripts attached to said containers:
	onPickup( iPickedUp, pChar, iCont )
Added tracking of total playtime per individual character, and per account across all characters, and exposed these properties to JS engine:
	.totalPlayTime		// Account property, total playtime across all chars
	.playTime 			// Character property, total playtime on given character only
Added new player-accessible command ('playtime) to spit out the playtime of current character/account as a whole (js/commands/playtime.js)
Implemented first version of Young Player System:
	Replaced the UNUSED9 account flag with YOUNG, to be used by Young/New Player System
	Added new JS Account property that gets/sets whether player account is considered Young:
		.isYoung
	Added new Char timers:
		TIMER_YOUNGHEAL		// Restricts how often Young players are healed by NPC healers
		TIMER_YOUNGMESSAGE	// Restricts how often Young players are warned about dangerous looking monsters in overworld
	Updated GM commands 'get and 'set to get/set .isYoung property of player's account (js/commands/targeting/get.js and set.js)
	Added new UOX.INI setting to enable/disable Young Player System (enabled by default):
		YOUNGPLAYERSYSTEM=1
	If Young Player System is enabled, all newly created player accounts are automatically marked with Young flag
	Added new script to handle various restrictions and functions related to Young characters (js/player/young_player.js):
		Young characters will have [Young] displayed over their head
		Young characters will have their Young status checked and verified on every login + every stat/skill gain, to revoke the Young status if any of the following is true:
			Account has a total playtime of more than 40 hours
			Any character has more than 350 total (base) skill points
			Any character has more than 70 skill points in a single skill
			Any character has more than 150 total stat points
			Any character has more than 80 stat points in a single stat
		Young characters can renounce their Young status manually by saying the words "I renounce my young player status"
		Young characters get two additional items upon creation:
			a new player ticket (can be combined with any other players new player ticket for both players to get a reward)
			a sextant (shows Young players directions to nearest moongate/bank regardless of where they are, as long as outdoors)
		Young characters cannot target other player characters with hostile spells or skills
		Young characters cannot be the target of other player characters' hostile spells or skills
		Young characters can only cast beneficial spells upon themselves or other Young characters
		Young characters can only be the target of beneficial spells from other Young players
		Young players (and their pets/followers) cannot attack or harm any other players (or their pets/followers), nor themselves be attacked or harmed by any other players (or their pets/followers), whether from combat, spells, skills or items
		Young players don't lose any of their items on death, and are teleported to the nearest healer upon death with all their items intact
		Young players receive a warning upon entering dungeons that monsters there will be hostile
	Updated global script (js/server/global.js) to:
		Check/Verify Young status of players upon login
		Attach young player script on login/creation for players on Young accounts
		Give specific items to Young players upon creation
	Added new script (js/item/corpse.js) that is assigned to all freshly created corpses. This script handles restrictions related to Young players interactions with corpses, and the interactions of other players with corpses of Young players
	Updated JS Method .Carve() to return true/false depending on whether carving a corpse was successful
	Updated Healer AI in code to heal nearby injured Young players
	Updated Evil AI and Evil Caster AI in code to avoid selecting Young players as targets in combat outside of dungeons
	Added a new section to UOX.INI - [young player starting locations] - that can be used to define starting locations for Young players. If only one such location is provided, all players start there. Otherwise, it matches the same starting location setup as the regular one - with one entry per town in Britannia.
	Restricted transferring and/or friending of pets (code) and hirelings (hirelings.js) between Young and non-Young players
	Restricted Young players from recalling or gating to Felucca facet
	Young players can instantly logout from anywhere, at any time
2023-07-01 20:02:40 +08:00

1910 lines
66 KiB
C++

#include "uox3.h"
#include "townregion.h"
#include "cServerDefinitions.h"
#include "cSpawnRegion.h"
#include "ssection.h"
#include "CJSMapping.h"
#include "scriptc.h"
#include "cScript.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "ObjectFactory.h"
#include "StringUtility.hpp"
using namespace std::string_literals;
cItem *Items = nullptr;
ItemTypes FindItemTypeFromTag( const std::string& strToFind );
//o------------------------------------------------------------------------------------------------o
//| Function - bool ApplySpawnItemSection( CSpawnItem *applyTo, const DFNTAGS tag, const SI32 ndata, const SI32 odata, const std::string &cdata )
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load item data from script sections and apply to spawner objects
//o------------------------------------------------------------------------------------------------o
bool ApplySpawnItemSection( CSpawnItem *applyTo, const DFNTAGS tag, const SI32 ndata, const SI32 odata, const std::string& cdata )
{
if( !ValidateObject( applyTo ))
return false;
switch( tag )
{
case DFNTAG_SPAWNOBJLIST:
applyTo->IsSectionAList( true );
[[fallthrough]];
case DFNTAG_SPAWNOBJ:
applyTo->SetSpawnSection( cdata );
return true;
case DFNTAG_INTERVAL:
applyTo->SetInterval( 0, static_cast<UI08>( ndata ));
applyTo->SetInterval( 1, static_cast<UI08>( odata ));
return true;
default:
break;
}
return false;
}
UI16 AddRandomColor( const std::string& colorlist );
//o------------------------------------------------------------------------------------------------o
//| Function - ApplyItemSection()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Load items from the script sections to the array
//o------------------------------------------------------------------------------------------------o
auto ApplyItemSection( CItem *applyTo, CScriptSection *toApply, std::string sectionId ) -> bool
{
if( toApply == nullptr || !ValidateObject( applyTo ))
return false;
std::string cdata;
SI32 ndata = -1, odata = -1;
bool isSpawner = ( applyTo->GetObjType() == OT_SPAWNER );
TAGMAPOBJECT customTag;
std::string customTagName;
std::string customTagStringValue;
for( const auto &sec : toApply->collection2() )
{
auto tag = sec->tag;
cdata = sec->cdata;
odata = sec->odata;
ndata = sec->ndata;
if( isSpawner && ApplySpawnItemSection( static_cast<CSpawnItem *>( applyTo ), tag, ndata, odata, cdata ))
{
continue;
}
auto ssecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( cdata, "//" )), " " );
switch( tag )
{
case DFNTAG_AMMO:
applyTo->SetAmmoId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
if( ssecs.size() > 1 )
{
applyTo->SetAmmoHue( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_AMMOFX:
applyTo->SetAmmoFX( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
if( ssecs.size() > 1 )
{
applyTo->SetAmmoFXHue( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
if( ssecs.size() > 2 )
{
applyTo->SetAmmoFXRender( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
}
}
break;
case DFNTAG_AMOUNT:
if( ndata > 0 )
{
if( odata && odata > ndata )
{
UI16 rndAmount = static_cast<UI16>( RandomNum( ndata, odata ));
applyTo->SetAmount( rndAmount );
}
else
{
applyTo->SetAmount( ndata );
}
}
else
{
Console.Warning( oldstrutil::format( "Invalid data found in AMOUNT tag inside item script [%s]", sectionId.c_str() ));
}
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 item script [%s]", sectionId.c_str() ));
}
break;
case DFNTAG_AC: applyTo->SetArmourClass( static_cast<UI08>( ndata )); break;
case DFNTAG_BASERANGE: applyTo->SetBaseRange( static_cast<UI08>( ndata )); break;
case DFNTAG_CREATOR: applyTo->SetCreator( ndata ); break;
case DFNTAG_COLOUR: applyTo->SetColour( static_cast<UI16>( ndata )); break;
case DFNTAG_COLOURLIST: applyTo->SetColour( AddRandomColor( cdata )); break;
case DFNTAG_CORPSE: applyTo->SetCorpse( ndata != 0 ) ; break;
case DFNTAG_COLD: applyTo->SetWeatherDamage( COLD, ndata != 0 ); break;
case DFNTAG_ELEMENTRESIST:
if( ssecs.size() >= 4 )
{
applyTo->SetResist( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )), HEAT );
applyTo->SetResist( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )), COLD );
applyTo->SetResist( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )), LIGHTNING );
applyTo->SetResist( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )), POISON );
}
break;
case DFNTAG_ERBONUS:
if( ssecs.size() >= 4 )
{
applyTo->SetResist( applyTo->GetResist( HEAT ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )), HEAT );
applyTo->SetResist( applyTo->GetResist( COLD ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )), COLD );
applyTo->SetResist( applyTo->GetResist( LIGHTNING ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )), LIGHTNING );
applyTo->SetResist( applyTo->GetResist( POISON ) + static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )), POISON );
}
break;
case DFNTAG_EVENT: applyTo->SetEvent( cdata ); break;
case DFNTAG_DAMAGEABLE: applyTo->SetDamageable(ndata != 0 ); break;
case DFNTAG_DEF:
if( ndata >= 0 )
{
if( odata && odata > ndata )
{
applyTo->SetResist( static_cast<UI16>( RandomNum( ndata, odata )), PHYSICAL );
}
else
{
applyTo->SetResist( ndata, PHYSICAL );
}
}
else
Console.Warning( oldstrutil::format( "Invalid data found in DEF tag inside item 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_DEX:
if( ndata > 0 )
{
if( odata && odata > ndata )
{
applyTo->SetDexterity( static_cast<SI16>( RandomNum( ndata, odata )));
}
else
{
applyTo->SetDexterity( ndata );
}
}
else
{
Console.Warning( oldstrutil::format( "Invalid data found in DEX tag inside item script [%s]", sectionId.c_str() ));
}
break;
case DFNTAG_DEXADD: applyTo->SetDexterity2( static_cast<SI16>( ndata )); break;
case DFNTAG_DIR: applyTo->SetDir( static_cast<UI08>( std::stoi( oldstrutil::trim( oldstrutil::removeTrailing( cdata, "//" )), nullptr, 0 ))); break;
case DFNTAG_DYE: applyTo->SetDye( ndata != 0 ); break;
case DFNTAG_DECAY:
if( ndata == 1 )
{
applyTo->SetDecayable( true );
}
else
{
applyTo->SetDecayable( false );
}
break;
case DFNTAG_DISPELLABLE: applyTo->SetDispellable( true ); break;
case DFNTAG_DISABLED: applyTo->SetDisabled( ndata != 0 ); break;
case DFNTAG_DOORFLAG: break;
case DFNTAG_GOOD: applyTo->SetGood( static_cast<SI16>( ndata )); break;
case DFNTAG_GLOW: applyTo->SetGlow( ndata ); break;
case DFNTAG_GLOWBC: applyTo->SetGlowColour( static_cast<UI16>( ndata )); break;
case DFNTAG_GLOWTYPE: applyTo->SetGlowEffect( static_cast<UI08>( ndata )); break;
case DFNTAG_GET:
{
std::string scriptEntry = "";
if( ssecs.size() == 1 )
{
scriptEntry = cdata;
}
else
{
UI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssecs.size() - 1 ));
scriptEntry = oldstrutil::trim( oldstrutil::removeTrailing( ssecs[rndEntry], "//" ));
}
auto toFind = FileLookup->FindEntry( scriptEntry, items_def );
if( toFind == nullptr )
{
Console.Warning( oldstrutil::format( "Invalid script entry (%s) called with GET tag, item serial 0x%X", scriptEntry.c_str(), applyTo->GetSerial() ));
}
else if( toFind == toApply )
{
Console.Warning( oldstrutil::format( "Infinite loop avoided with GET tag inside item script [%s]", scriptEntry.c_str() ));
}
else
{
ApplyItemSection( applyTo, toFind, scriptEntry );
}
break;
}
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_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( ssecs.size() == 1 )
{
scriptEntry = cdata;
}
else
{
UI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssecs.size() - 1 ));
scriptEntry = oldstrutil::trim( oldstrutil::removeTrailing( ssecs[rndEntry], "//" ));
}
CScriptSection *toFind = FileLookup->FindEntry( scriptEntry, items_def );
if( toFind == NULL )
{
Console.Warning( oldstrutil::format( "Invalid script entry (%s) called with %s tag, item serial 0x%X", scriptEntry.c_str(), tagName.c_str(), applyTo->GetSerial() ));
}
else if( toFind == toApply )
{
Console.Warning( oldstrutil::format( "Infinite loop avoided with %s tag inside item script [%s]", tagName.c_str(), scriptEntry.c_str() ));
}
else
{
ApplyItemSection( applyTo, toFind, scriptEntry );
}
}
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 item script [%s]", sectionId.c_str() ));
}
break;
case DFNTAG_HIDAMAGE: applyTo->SetHiDamage( static_cast<SI16>( ndata )); break;
case DFNTAG_HEAT: applyTo->SetWeatherDamage( HEAT, ndata != 0 ); break;
case DFNTAG_ID: // applyTo->SetId( static_cast<UI16>( ndata )); break;
if( ssecs.size() == 1 )
{
applyTo->SetId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
else
{
SI32 rndEntry = RandomNum( 0, static_cast<SI32>( ssecs.size() - 1 ));
applyTo->SetId( static_cast<UI16>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[rndEntry], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_INTELLIGENCE: applyTo->SetIntelligence( static_cast<SI16>( ndata )); break;
case DFNTAG_INTADD: applyTo->SetIntelligence2( static_cast<SI16>( ndata )); break;
case DFNTAG_LODAMAGE: applyTo->SetLoDamage( static_cast<SI16>( ndata )); break;
case DFNTAG_LAYER: applyTo->SetLayer( static_cast<ItemLayers>( ndata )); break;
case DFNTAG_LIGHT: applyTo->SetWeatherDamage( LIGHT, ndata != 0 ); break;
case DFNTAG_LIGHTNING: applyTo->SetWeatherDamage( LIGHTNING, ndata != 0 ); break;
case DFNTAG_MAXHP: applyTo->SetMaxHP( static_cast<UI16>( ndata )); break;
case DFNTAG_MAXITEMS: applyTo->SetMaxItems( static_cast<UI16>( ndata )); break;
case DFNTAG_MAXRANGE: applyTo->SetMaxRange( static_cast<UI08>( ndata )); break;
case DFNTAG_MAXUSES: applyTo->SetMaxUses( static_cast<UI16>( ndata )); break;
case DFNTAG_MOVABLE: applyTo->SetMovable( static_cast<SI08>( ndata )); break;
case DFNTAG_MORE:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MORE, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MORE, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MORE0:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MORE0, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE0, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE0, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE0, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MORE0, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MORE1:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MORE1, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE1, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE1, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE1, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MORE1, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MORE2:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MORE2, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE2, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE2, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MORE2, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MORE2, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MOREX:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MOREX, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREX, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREX, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREX, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MOREX, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MOREY:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MOREY, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREY, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREY, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREY, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MOREY, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_MOREZ:
if( ssecs.size() >= 4 )
{
applyTo->SetTempVar( CITV_MOREZ, 1, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREZ, 2, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[1], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREZ, 3, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[2], "//" )), nullptr, 0 )));
applyTo->SetTempVar( CITV_MOREZ, 4, static_cast<UI08>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[3], "//" )), nullptr, 0 )));
}
else
{
applyTo->SetTempVar( CITV_MOREZ, static_cast<UI32>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" )), nullptr, 0 )));
}
break;
case DFNTAG_NAME: applyTo->SetName( cdata ); break;
case DFNTAG_NAME2: applyTo->SetName2( cdata ); break;
case DFNTAG_NEWBIE: applyTo->SetNewbie( true ); break;
case DFNTAG_OFFSPELL: applyTo->SetOffSpell( static_cast<SI08>( ndata )); break;
case DFNTAG_ORIGIN: applyTo->SetOrigin( static_cast<ExpansionRuleset>( cwmWorldState->ServerData()->EraStringToEnum( cdata ))); break;
case DFNTAG_POISONDAMAGE: applyTo->SetWeatherDamage( POISON, ndata != 0 ); break;
case DFNTAG_POISONED: applyTo->SetPoisoned( static_cast<UI08>( ndata )); break;
case DFNTAG_PILEABLE: applyTo->SetPileable( ndata != 0 ); break;
case DFNTAG_PRIV: applyTo->SetPriv( static_cast<UI08>( ndata )); break;
case DFNTAG_RANK:
applyTo->SetRank( static_cast<SI08>( ndata ));
if( applyTo->GetRank() <= 0 )
{
applyTo->SetRank( 10 );
}
break;
case DFNTAG_RACE: applyTo->SetRace( static_cast<UI16>( 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 Item 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 Item 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 Item 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 Item script [%s]", sectionId.c_str() ));
}
break;
case DFNTAG_RESTOCK:
{
// Apply global restock multiplier from INI to item's restock property
auto restockMultiplier = cwmWorldState->ServerData()->GlobalRestockMultiplier();
applyTo->SetRestock( static_cast<UI16>( floor( static_cast<UI16>( ndata ) * restockMultiplier )));
break;
}
case DFNTAG_RAIN: applyTo->SetWeatherDamage( RAIN, ndata != 0 ); break;
case DFNTAG_SECTIONID: applyTo->SetSectionId( cdata ); break;
case DFNTAG_SK_MADE: applyTo->SetMadeWith( static_cast<SI08>( ndata )); break;
case DFNTAG_SPD: applyTo->SetSpeed( static_cast<UI08>( ndata )); break;
case DFNTAG_STRENGTH: applyTo->SetStrength( static_cast<SI16>( ndata )); break;
case DFNTAG_STRADD: applyTo->SetStrength2( static_cast<SI16>( ndata )); break;
case DFNTAG_STEALABLE: applyTo->SetStealable( static_cast<UI08>( ndata )); break;
case DFNTAG_SNOW: applyTo->SetWeatherDamage( SNOW, ndata != 0 ); break;
case DFNTAG_SCRIPT: applyTo->AddScriptTrigger( static_cast<UI16>( ndata )); break;
case DFNTAG_TYPE:
ItemTypes iType;
iType = FindItemTypeFromTag( cdata );
if( iType == IT_COUNT )
{
iType = static_cast<ItemTypes>( ndata );
}
if( iType < IT_COUNT )
{
applyTo->SetType( iType );
}
break;
case DFNTAG_USESLEFT: applyTo->SetUsesLeft( static_cast<UI16>( ndata )); break;
case DFNTAG_VISIBLE: applyTo->SetVisible( static_cast<VisibleTypes>( ndata )); break;
case DFNTAG_VALUE:
if( ndata >= 0 )
{
if( odata && odata >= 0 )
{
applyTo->SetBuyValue( ndata );
applyTo->SetSellValue( odata );
}
else
{
// Only one value was specified in DFN tag - use it for both buy and sell value
applyTo->SetBuyValue( ndata );
applyTo->SetSellValue( ndata );
}
}
else
{
Console.Warning( oldstrutil::format( "Invalid data found in VALUE tag inside item script [%s]", sectionId.c_str() ));
}
break;
case DFNTAG_WEIGHT:
applyTo->SetWeight( ndata );
applyTo->SetBaseWeight( ndata ); // Let's set the base-weight as well. Primarily used for containers.
break;
case DFNTAG_WEIGHTMAX: applyTo->SetWeightMax( ndata ); break;
case DFNTAG_WIPE: applyTo->SetWipeable( ndata != 0 ); break;
case DFNTAG_ADDMENUITEM:
Console.Print( cdata );
break;
case DFNTAG_CUSTOMSTRINGTAG:
{
auto count = 0;
std::string result;
for( auto &sec : ssecs )
{
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( ssecs[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 Item script [%s] - Supported data format: <tagName> <text>", sectionId.c_str() ));
}
break;
}
case DFNTAG_CUSTOMINTTAG:
{
auto count = 0;
std::string result;
for( auto &sec : ssecs )
{
if( count > 0 )
{
if( count == 1 )
{
result = oldstrutil::trim( oldstrutil::removeTrailing( sec, "//" ));
}
}
count++;
}
customTagName = oldstrutil::trim( oldstrutil::removeTrailing( ssecs[0], "//" ));
customTagStringValue = result;
if( !customTagName.empty() && !customTagStringValue.empty() )
{
customTag.m_Destroy = false;
customTag.m_IntValue = static_cast<SI32>( std::stoi( customTagStringValue, nullptr, 0 ));
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 Item 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 Item script [%s] - Supported data format: <tagName> <value>", sectionId.c_str() ));
}
break;
}
case DFNTAG_SPAWNOBJ:
case DFNTAG_SPAWNOBJLIST:
break;
case DFNTAG_LOOT:
[[fallthrough]];
case DFNTAG_PACKITEM:
{
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( applyTo, tdata, true, true, iAmount, true );
}
else
{
Items->AddRespawnItem( applyTo, tdata, true, false, iAmount, false );
}
}
else
{
if( tag == DFNTAG_LOOT )
{
Items->AddRespawnItem( applyTo, cdata, true, true, 1, true );
}
else
{
Items->AddRespawnItem( applyTo, cdata, true, false, 1, false );
}
}
}
break;
}
default:
{
Console.Warning( oldstrutil::format( "Unknown items dfn tag %i %s %i %i ", tag, cdata.c_str(), ndata, odata ));
break;
}
}
}
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CreateItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a basic item and gives it an ID, Colour, and amount, also will
//| automatically look for an entry in harditems.dfn and set its location (be it in
//| a pack or on the ground).
//o------------------------------------------------------------------------------------------------o
CItem * cItem::CreateItem( CSocket *mSock, CChar *mChar, const UI16 itemId, const UI16 iAmount, const UI16 iColour,
const ObjectType itemType, bool inPack, bool shouldSave, UI08 worldNumber, UI16 instanceId, SI16 xLoc, SI16 yLoc, SI08 zLoc )
{
if( ValidateObject( mChar ))
{
worldNumber = mChar->WorldNumber();
instanceId = mChar->GetInstanceId();
if( inPack && !ValidateObject( mChar->GetPackItem() ))
{
std::string charName = GetNpcDictName( mChar, nullptr, NRS_SYSTEM );
Console.Warning( oldstrutil::format( "CreateItem(): Character %s(0x%X) has no pack, item creation aborted.", charName.c_str(), mChar->GetSerial() ));
return nullptr;
}
if( inPack )
{
// Check if character's backpack can hold more items before creating any item
CItem *playerPack = mChar->GetPackItem();
if( ValidateObject( playerPack ))
{
if( playerPack->GetContainsList()->Num() >= playerPack->GetMaxItems() )
{
if( mSock != nullptr )
{
mSock->SysMessage( 1819 ); // Your backpack cannot hold any more items!
}
inPack = false; // Spawn item at character's feet instead
}
}
}
}
CItem *iCreated = CreateBaseItem( worldNumber, itemType, instanceId, shouldSave );
if( iCreated == nullptr )
return nullptr;
if( itemId != 0x0000 )
{
if( Map->IsValidTile( itemId ))
{
iCreated->SetId( itemId );
}
}
if( iColour != 0x0000 )
{
iCreated->SetColour( iColour );
}
// Only set item to decayable by default if ini setting is enabled
if( cwmWorldState->ServerData()->BaseItemsDecayable() )
{
iCreated->SetDecayable( true );
}
GetScriptItemSettings( iCreated );
if( iAmount > 1 )
{
iCreated->SetPileable( true );
iCreated->SetAmount( iAmount );
}
std::vector<UI16> scriptTriggers = iCreated->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnCreate( iCreated, false, false );
}
}
return PlaceItem( mSock, mChar, iCreated, inPack, worldNumber, instanceId, xLoc, yLoc, zLoc );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CreateScriptItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a script item, gives it an amount, and sets
//| its location (be it in a pack or on the ground).
//o------------------------------------------------------------------------------------------------o
CItem * cItem::CreateScriptItem( CSocket *mSock, CChar *mChar, const std::string &item, const UI16 iAmount,
const ObjectType itemType, bool inPack, const UI16 iColor, bool shouldSave )
{
if( inPack && !ValidateObject( mChar->GetPackItem() ))
{
std::string charName = GetNpcDictName( mChar, nullptr, NRS_SYSTEM );
Console.Warning( oldstrutil::format( "CreateScriptItem(): Character %s(0x%X) has no pack, item creation aborted.", charName.c_str(), mChar->GetSerial() ));
return nullptr;
}
if( inPack )
{
// Check if character's backpack can hold more items before creating any item
CItem *playerPack = mChar->GetPackItem();
if( ValidateObject( playerPack ))
{
if( playerPack->GetContainsList()->Num() >= playerPack->GetMaxItems() )
{
if( mSock != nullptr )
{
mSock->SysMessage( 1819 ); // Your backpack cannot hold any more items!
}
inPack = false; // Spawn item at character's feet instead
}
}
}
CItem *iCreated = CreateBaseScriptItem( nullptr, item, mChar->WorldNumber(), iAmount, mChar->GetInstanceId(), itemType, 0xFFFF, shouldSave );
if( iCreated == nullptr )
return nullptr;
if( iColor != 0xFFFF )
{
iCreated->SetColour( iColor );
}
if( iAmount > 1 && !iCreated->IsPileable() )
{
// Turns out we need to spawn more than one item, let's do that here:
CItem *iCreated2 = nullptr;
for( UI16 i = 1; i < iAmount; i++ )
{
if( inPack )
{
CItem *mPack = mChar->GetPackItem();
iCreated2 = CreateBaseScriptItem( mPack, item, mChar->WorldNumber(), 1, mChar->GetInstanceId(), itemType );
}
else
{
iCreated2 = CreateBaseScriptItem( nullptr, item, mChar->WorldNumber(), 1, mChar->GetInstanceId(), itemType );
}
if( iCreated2 )
{
if( iColor != 0xFFFF )
{
iCreated2->SetColour( iColor );
}
PlaceItem( mSock, mChar, iCreated2, inPack );
}
}
}
// Return the original item created in the function.
return PlaceItem( mSock, mChar, iCreated, inPack );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::*CreateRandomItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a random item from an itemlist in specified dfn file,
//| gives it a random buy/sell value, and places it
//o------------------------------------------------------------------------------------------------o
CItem *cItem::CreateRandomItem( CSocket *mSock, const std::string& itemList )
{
CChar *mChar = mSock->CurrcharObj();
if( !ValidateObject( mChar ))
return nullptr;
CItem *iCreated = CreateRandomItem( nullptr, itemList, mChar->WorldNumber(), mChar->GetInstanceId() );
if( iCreated == nullptr )
return nullptr;
if( iCreated->GetBuyValue() != 0 )
{
iCreated->SetBuyValue( RandomNum( static_cast<UI32>( 1 ), iCreated->GetBuyValue() ));
iCreated->SetSellValue( static_cast<UI32>( iCreated->GetBuyValue() / 2 ));
}
if( iCreated->GetHP() != 0 )
{
iCreated->SetHP( static_cast<SI16>( RandomNum( static_cast<SI16>( 1 ), iCreated->GetHP() )));
}
return PlaceItem( mSock, mChar, iCreated, true );
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CreateRandomItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a random item from an itemlist in specified dfn file
//o------------------------------------------------------------------------------------------------o
auto cItem::CreateRandomItem( CItem *mCont, const std::string& sItemList, const UI08 worldNum, const UI16 instanceId, bool shouldSave, bool useLootlist ) -> CItem *
{
CItem *iCreated = nullptr;
std::string sect;
// Are we looking for a LOOTLIST, or an ITEMLIST?
if( useLootlist )
{
sect = "LOOTLIST "s + sItemList;
}
else
{
sect = "ITEMLIST "s + sItemList;
}
sect = oldstrutil::trim( oldstrutil::removeTrailing( sect, "//" ));
// This is never "blank", since we assign "LOOTLIST" or "ITEMLIST" in the previous step!!
if( sect == "blank" ) // The itemlist-entry is just a blank filler item
return nullptr;
// Look up the relevant LOOTLIST or ITEMLIST from DFNs
auto ItemList = FileLookup->FindEntry( sect, items_def );
if( ItemList )
{
// Count the number of entries in the list
const size_t itemListSize = ItemList->NumEntries();
if( itemListSize > 0 )
{
int itemEntryToSpawn = -1;
int sum_of_weight = 0;
// Let's see if the entries in the list are setup with randomization weights,
// and sum them all up if that's the case. Format of string:
// weight|sectionName,amount
// weight|sectionName,amountMin amountMax
// weight|LOOTLIST=sectionName,amount
// weight|LOOTLIST=sectionName,amountMin amountMax
// weight|ITEMLIST=sectionName,amount
// weight|ITEMLIST=sectionName,amountMin amountMax
for( size_t j = 0; j < itemListSize; j++ )
{
// Split string for entry into a stringlist, based on | as a separator
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( ItemList->MoveTo( j ), "//" )), "|" );
if( csecs.size() == 2 )
{
// The entry has a weight assigned to it - let's add the weight to the total
sum_of_weight += static_cast<int>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 ));
}
else
{
// The entry has no weight assigned to it - add a default weight of 1
sum_of_weight += 1;
}
}
// Choose a random number between 0 and the total sum of all weights
int rndChoice = RandomNum( 0, sum_of_weight - 1 );
int itemWeight = 0;
// Prepare a vector to hold multiple entries, if more than one qualifies based on weighting
std::vector<int> matchingEntries;
// Loop through the items in the itemlist/lootlist
int weightOfChosenItem = 0;
for( auto j = 0; j < static_cast<int>( itemListSize ); j++ )
{
auto csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( ItemList->MoveTo( j ), "//" )), "|" );
if( csecs.size() == 2 )
{
// Ok, item entry has a weight, let's compare that weight to our chosen random number
itemWeight = static_cast<int>( std::stoul( oldstrutil::trim( oldstrutil::removeTrailing( csecs[0], "//" )), nullptr, 0 ));
if( rndChoice < itemWeight )
{
// If we find another entry with same weight as the first one found, or if none have been found yet, add to list
if( weightOfChosenItem == 0 || weightOfChosenItem == itemWeight )
{
itemEntryToSpawn = j;
weightOfChosenItem = itemWeight;
// Add the entry index to a temporary vector of all entries with same weight, then continue looking for more!
matchingEntries.push_back( j );
continue;
}
}
rndChoice -= itemWeight;
}
}
// Did we find more than one entry that matched our random weight criteria?
if( matchingEntries.size() > 0 )
{
// Choose a random one of these!
itemEntryToSpawn = matchingEntries[static_cast<int>( RandomNum( static_cast<size_t>( 0 ), matchingEntries.size() - 1 ))];
}
matchingEntries.clear();
std::string k = "";
std::vector<std::string> csecs;
if( itemEntryToSpawn != -1 )
{
// If an entry has been selected based on weights, use that
csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( ItemList->MoveTo( itemEntryToSpawn ), "//" )), "," );
}
else
{
// Otherwise choose a random entry
csecs = oldstrutil::sections( oldstrutil::trim( oldstrutil::removeTrailing( ItemList->MoveTo( RandomNum( static_cast<size_t>( 0 ), itemListSize - 1 )), "//" )), "," );
}
auto csecs2 = oldstrutil::sections( oldstrutil::trim( csecs[0] ), "|" );
k = csecs2.size() > 1 ? csecs2[1] : csecs2[0];
// Also fetch amount to spawn, if specified
int amountToSpawn = 1;
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!
amountToSpawn = static_cast<UI16>( RandomNum( first, second ));
}
else
{
amountToSpawn = static_cast<UI16>( std::stoul(amountData, nullptr, 0 ));
}
}
if( !k.empty() )
{
if( oldstrutil::upper( k ) == "ITEMLIST" || oldstrutil::upper( k ) == "LOOTLIST" )
{
// The chosen entry contained another ITEMLIST or LOOTLIST reference! Let's dive back into it...
iCreated = CreateRandomItem( mCont, ItemList->GrabData(), worldNum, instanceId, shouldSave );
}
else
{
// Finally, we have an item! Spawn it!
iCreated = CreateBaseScriptItem( nullptr, k, worldNum, amountToSpawn, instanceId, OT_ITEM, 0xFFFF, shouldSave );
// However, we have a valid container, and the amount is larger than 1, and the item is not stackable, let's spawn some more items!
if( ValidateObject( mCont ) && amountToSpawn > 1 && !iCreated->IsPileable() )
{
for( int i = 1; i < amountToSpawn; i++ )
{
CItem *iCreated2 = CreateBaseScriptItem( mCont, k, worldNum, 1, instanceId, OT_ITEM, 0xFFFF, shouldSave );
if( ValidateObject( iCreated2 ))
{
// Place item in container and randomize location
iCreated2->SetCont( mCont );
iCreated2->PlaceInPack();
}
}
}
}
}
}
}
return iCreated;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CreateMulti()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a multi, and looks for an entry in harditems.dfn
//o------------------------------------------------------------------------------------------------o
CMultiObj * cItem::CreateMulti( const std::string& cName, const UI16 itemId, const bool isBoat, const UI16 worldNum, const UI16 instanceId, const bool isBaseMulti )
{
CMultiObj *mCreated = static_cast<CMultiObj *>( ObjectFactory::GetSingleton().CreateObject(( isBoat ) ? OT_BOAT : OT_MULTI ));
if( mCreated == nullptr )
return nullptr;
mCreated->SetId( itemId );
if( !isBaseMulti )
{
GetScriptItemSettings( mCreated );
}
mCreated->WorldNumber( worldNum );
mCreated->SetInstanceId( instanceId );
mCreated->SetDecayable( false );
if( !cName.empty() )
{
mCreated->SetName( cName );
}
return mCreated;
}
//o------------------------------------------------------------------------------------------------o
//| Function - CItem *CreateBaseItem( const UI08 worldNum, const ObjectType itemType, const UI16 instanceId )
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a basic item
//o------------------------------------------------------------------------------------------------o
CItem * cItem::CreateBaseItem( const UI08 worldNum, const ObjectType itemType, const UI16 instanceId, bool shouldSave )
{
if( itemType != OT_ITEM && itemType != OT_SPAWNER )
return nullptr;
CItem *iCreated = static_cast<CItem *>( ObjectFactory::GetSingleton().CreateObject( itemType ));
if( iCreated == nullptr )
return nullptr;
iCreated->ShouldSave( shouldSave );
iCreated->SetWipeable( true );
iCreated->WorldNumber( worldNum );
iCreated->SetInstanceId( instanceId );
return iCreated;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CreateBaseScriptItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Creates a basic item from the scripts
//o------------------------------------------------------------------------------------------------o
CItem * cItem::CreateBaseScriptItem( CItem *mCont, std::string ourItem, const UI08 worldNum, const UI16 iAmount, const UI16 instanceId, const ObjectType itemType, const UI16 iColor, bool shouldSave )
{
ourItem = oldstrutil::trim( oldstrutil::removeTrailing( ourItem, "//" ));
if( ourItem == "blank" ) // The lootlist-entry is just a blank filler item
return nullptr;
CScriptSection *itemCreate = FileLookup->FindEntry( ourItem, items_def );
if( itemCreate == nullptr )
{
Console.Error( oldstrutil::format( "CreateBaseScriptItem(): Bad script item %s (Item Not Found).", ourItem.c_str() ));
return nullptr;
}
CItem *iCreated = nullptr;
if( itemCreate->ItemListExist() )
{
iCreated = CreateRandomItem( mCont, itemCreate->ItemListData(), worldNum, instanceId, shouldSave );
}
else
{
iCreated = CreateBaseItem( worldNum, itemType, instanceId, shouldSave );
if( iCreated == nullptr )
return nullptr;
// Only set item to decayable by default if ini setting is enabled
if( cwmWorldState->ServerData()->ScriptItemsDecayable() )
{
iCreated->SetDecayable( true );
}
if( !ApplyItemSection( iCreated, itemCreate, ourItem ))
{
Console.Error( "Trying to apply an item section failed" );
}
// If maxHP has not been defined for a new item, set it to the same value as HP
if( !iCreated->GetMaxHP() && iCreated->GetHP() )
{
iCreated->SetMaxHP( iCreated->GetHP() );
}
// If maxUses is higher than usesLeft for a new item, randomize the amount of usesLeft the item should have!
if( iCreated->GetMaxUses() > iCreated->GetUsesLeft() )
{
iCreated->SetUsesLeft( static_cast<UI16>( RandomNum( iCreated->GetUsesLeft(), iCreated->GetMaxUses() )));
}
else if( !iCreated->GetMaxUses() && iCreated->GetUsesLeft() )
{
// Also, if maxUses has not been defined, but usesLeft HAS, set maxUses to equal usesLeft
iCreated->SetMaxUses( iCreated->GetUsesLeft() );
}
// Check for (and run) onCreateDFN() event for newly created item
std::vector<UI16> scriptTriggers = iCreated->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnCreate( iCreated, true, false );
}
}
}
if( ValidateObject( iCreated ))
{
if( iColor != 0xFFFF )
{
iCreated->SetColour( iColor );
}
if( iAmount > 1 && iCreated->IsPileable() )
{
iCreated->SetAmount( iAmount );
}
// Keep reference to DFN sectionId item was created from (if it has not been set already via special DFN tag SECTIONID)
if( iCreated->GetSectionId() == "UNKNOWN" )
{
iCreated->SetSectionId( ourItem );
}
}
return iCreated;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::GetScriptItemSettings()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Grabs item entries from harditems.dfn
//o------------------------------------------------------------------------------------------------o
void cItem::GetScriptItemSettings( CItem *iCreated )
{
std::string hexId = oldstrutil::number( iCreated->GetId(), 16 );
while( hexId.size() < 4 )
{
hexId = "0" + hexId;
}
const std::string item = "x" + hexId;
CScriptSection *toFind = FileLookup->FindEntrySubStr( item, hard_items_def );
if( toFind != nullptr )
{
ApplyItemSection( iCreated, toFind, item );
}
}
CItem *AutoStack( CSocket *mSock, CItem *iToStack, CItem *iPack );
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::PlaceItem()
//| Date - 10/12/2003
//o------------------------------------------------------------------------------------------------o
//| Purpose - Places an item that was just created
//o------------------------------------------------------------------------------------------------o
CItem * cItem::PlaceItem( CSocket *mSock, CChar *mChar, CItem *iCreated, const bool inPack, UI08 worldNumber, UI16 instanceId, SI16 xLoc, SI16 yLoc, SI08 zLoc )
{
if( ValidateObject( mChar ) && inPack )
{
if( iCreated->IsPileable() )
{
iCreated = AutoStack( mSock, iCreated, mChar->GetPackItem() ); // if it didn't stack, it's iCreated... if it did, then it's the stack!
}
else
{
iCreated->SetCont( mChar->GetPackItem() );
iCreated->PlaceInPack();
}
// Only send tooltip if server feature for tooltips is enabled
if( cwmWorldState->ServerData()->GetServerFeature( SF_BIT_AOS ))
{
if( mSock != nullptr )
{
// Refresh container tooltip
CPToolTip pSend( iCreated->GetContSerial(), mSock );
mSock->Send(&pSend);
}
}
}
else if( ValidateObject( mChar ))
{
iCreated->SetLocation( mChar );
}
else
{
iCreated->SetLocation( xLoc, yLoc, zLoc, worldNumber, instanceId );
}
return iCreated;
}
//o------------------------------------------------------------------------------------------------o
//| Function - DecayItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Cause items to decay when left on the ground
//o------------------------------------------------------------------------------------------------o
auto DecayItem( CItem& toDecay, const UI32 nextDecayItems, UI32 nextDecayItemsInHouses ) -> bool
{
if( toDecay.GetDecayTime() == 0 || !cwmWorldState->ServerData()->GlobalItemDecay() )
{
if( toDecay.GetMulti() == INVALIDSERIAL )
{
toDecay.SetDecayTime( nextDecayItems );
}
else
{
toDecay.SetDecayTime( nextDecayItemsInHouses );
}
return false;
}
const auto isCorpse = toDecay.IsCorpse();
// Multis
if( !toDecay.IsFieldSpell() && !isCorpse ) // Gives fieldspells a chance to decay in multis
{
if( toDecay.IsLockedDown() || toDecay.IsDoorOpen() ||
( ValidateObject( toDecay.GetMultiObj() ) &&
( toDecay.GetMovable() >= 2 || !cwmWorldState->ServerData()->ItemDecayInHouses() )))
{
toDecay.SetDecayTime( nextDecayItemsInHouses );
return false;
}
}
if( toDecay.IsContType() )
{
if( !isCorpse || ValidateObject(toDecay.GetOwnerObj() ) || !cwmWorldState->ServerData()->CorpseLootDecay() )
{
std::vector<CItem *> corpseItems;
auto iCont = toDecay.GetContainsList();
for( const auto &io : iCont->collection() )
{
if( ValidateObject( io ))
{
if( io->GetLayer() != IL_HAIR && io->GetLayer() != IL_FACIALHAIR )
{
// Store a reference to the item we want to move out of corpse...
corpseItems.push_back( io );
}
}
}
// Loop through the items we want to move out of corpse
std::for_each( corpseItems.begin(), corpseItems.end(), [toDecay, nextDecayItems]( CItem *corpseItem )
{
corpseItem->SetCont( nullptr );
corpseItem->SetLocation(( &toDecay ));
corpseItem->SetDecayTime( nextDecayItems );
});
}
}
if( toDecay.CanBeObjType( OT_MULTI ))
{
toDecay.SetDecayTime( nextDecayItems );
Console.Warning( oldstrutil::format( "Warning: Prevented multi (serial: 0x%X) from decaying!", toDecay.GetSerial() ));
}
toDecay.Delete(); // This is a problem, if done in a ierator loop
return true;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::GetPackType()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Get the pack type based on ID
//o------------------------------------------------------------------------------------------------o
PackTypes cItem::GetPackType( CItem *i )
{
PackTypes packType = PT_UNKNOWN;
switch( i->GetId() )
{
case 0x2006: // coffin
case 0x0ECA: // bones
case 0x0ECB: // bones
case 0x0ECC: // bones
case 0x0ECD: // bones
case 0x0ECE: // bones
case 0x0ECF: // bones
case 0x0ED0: // bones
case 0x0ED1: // bones
case 0x0ED2: // bones
packType = PT_COFFIN;
break;
case 0x0E75: // backpack
case 0x0E79: // pouch
case 0x09B0: // pouch
packType = PT_PACK;
break;
case 0x0E76: // leather bag
case 0x2256: // bagball
case 0x2257: // bagball
packType = PT_BAG;
break;
case 0x0E77: // barrel
case 0x0E7F: // keg
case 0x0E83: // empty tub
case 0x0FAE: // barrel with lids
case 0x1AD7: // potion kegs
case 0x1940: // barrel with lids
packType = PT_BARREL;
break;
case 0x0E7A: // square basket
case 0x24D5: // SE basket
case 0x24D6: // SE basket
case 0x24D9: // SE basket
case 0x24DA: // SE basket
packType = PT_SQBASKET;
break;
case 0x0990: // round basket
case 0x09AC: // round bushel
case 0x09B1: // round basket
case 0x24D7: // SE basket
case 0x24D8: // SE basket
case 0x24DD: // SE basket
case 0x1882: // winnoning basket
packType = PT_RBASKET;
break;
case 0x0E40: // gold chest
case 0x0E41: // gold chest
case 0x4025: // gargoyle chest
case 0x4026: // gargoyle chest
case 0xA304: // metal chest
case 0xA305: // metal chest
case 0xA306: // rusty metal chest
case 0xA307: // rusty metal chest
case 0xA308: // gold chest
case 0xA309: // gold chest
case 0xA30A: // barnacle metal chest
case 0xA30B: // barnacle metal chest
packType = PT_GCHEST;
break;
case 0x0E7D: // wooden box
case 0x09AA: // wooden box
packType = PT_WBOX;
break;
case 0x0E3C: // large wooden crate
case 0x0E3D: // large wooden crate
case 0x0E3E: // small wooden create
case 0x0E3F: // small wooden crate
case 0x0E7E: // wooden crate
case 0x09A9: // small wooden crate
packType = PT_CRATE;
break;
case 0x2AF8: // Shopkeeper buy, sell and sold layers
packType = PT_SHOPPACK;
break;
case 0x0A30: // chest of drawers (fancy)
case 0x0A38: // chest of drawers (fancy)
packType = PT_DRAWER;
break;
case 0x0E42: // wooden chest
case 0x0E43: // wooden chest
packType = PT_WCHEST;
break;
case 0x0E80: // brass box
case 0x09A8: // metal box
packType = PT_MBOX;
break;
case 0x0E7C: // silver chest
case 0x09AB: // silver chest
packType = PT_SCHEST;
break;
case 0x0A97: // bookcase
case 0x0A98: // bookcase
case 0x0A99: // bookcase
case 0x0A9A: // bookcase
case 0x0A9B: // bookcase
case 0x0A9C: // bookcase
case 0x0A9D: // bookcase (empty)
case 0x0A9E: // bookcase (empty)
packType = PT_BOOKCASE;
break;
case 0x0A4C: // fancy armoire (open)
case 0x0A4D: // fancy armoire
case 0x0A50: // fancy armoire (open)
case 0x0A51: // fancy armoire
packType = PT_FARMOIRE;
break;
case 0x0A4E: // wooden armoire (open)
case 0x0A4F: // wooden armoire
case 0x0A52: // wooden armoire (open)
case 0x0A53: // wooden armoire
packType = PT_WARMOIRE;
break;
case 0x0A2C: // chest of drawers (wood)
case 0x0A34: // chest of drawers (wood)
case 0x0A35: // dresser
case 0x0A3C: // dresser
case 0x0A3D: // dresser
case 0x0A44: // dresser
packType = PT_DRESSER;
break;
case 0x09B2: // bank box ..OR.. backpack 2
if( i->GetTempVar( CITV_MOREX ) == 1 )
{
packType = PT_BANK;
}
else
{
packType = PT_PACK2;
}
break;
case 0x232A: // giftbox
case 0x232B: // giftbox
packType = PT_GIFTBOX1;
break;
case 0x2857: // SE armoire
case 0x2858: // SE armoire
packType = PT_SEARMOIRE1;
break;
case 0x285B: // SE armoire
case 0x285C: // SE armoire
packType = PT_SEARMOIRE2;
break;
case 0x285D: // SE armoire
case 0x285E: // SE armoire
case 0x2859: // SE armoire
case 0x285A: // SE armoire
packType = PT_SEARMOIRE3;
break;
case 0x24DB: // SE basket
case 0x24DC: // SE basket
packType = PT_SEBASKET;
break;
case 0x280B: // SE chest
case 0x280C: // SE chest
packType = PT_SECHEST1;
break;
case 0x280D: // SE chest
case 0x280E: // SE chest
packType = PT_SECHEST2;
break;
case 0x280F: // SE chest
case 0x2810: // SE chest
packType = PT_SECHEST3;
break;
case 0x2811: // SE chest
case 0x2812: // SE chest
case 0x2815: // SE cabinet
case 0x2816: // SE cabinet
case 0x2817: // SE chest
case 0x2818: // SE chest
packType = PT_SECHEST4;
break;
case 0x2813: // SE chest
case 0x2814: // SE chest
packType = PT_SECHEST5;
break;
case 0x46A5: // SA giftbox
case 0x46A6: // SA giftbox
packType = PT_GIFTBOX2;
break;
case 0x46A2: // SA giftbox
packType = PT_GIFTBOX3;
break;
case 0x46A3: // SA giftbox
packType = PT_GIFTBOX4;
break;
case 0x46A4: // SA giftbox
packType = PT_GIFTBOX5;
break;
case 0x46A7: // SA giftbox
packType = PT_GIFTBOX6;
break;
case 0x0E1C: // Backgammon board
case 0x0FAD: // Backgammon board
packType = PT_GAME_BACKGAMMON;
break;
case 0x0FA6: // Chess board
packType = PT_GAME_CHESS;
break;
case 0xA202: // Dolphin mailbox
case 0xA203: // Dolphin mailbox
case 0xA204: // Dolphin mailbox
case 0xA205: // Dolphin mailbox
packType = PT_MAILBOX1;
break;
case 0xA206: // Squirrel mailbox
case 0xA207: // Squirrel mailbox
case 0xA208: // Squirrel mailbox
case 0xA209: // Squirrel mailbox
packType = PT_MAILBOX2;
break;
case 0xA1F5: // Wooden barrel mailbox
case 0xA1F7: // Wooden barrel mailbox
case 0xA1F8: // Wooden barrel mailbox
case 0xA1F9: // Wooden barrel mailbox
packType = PT_MAILBOX3;
break;
case 0xA268: // Light mailbox
case 0xA269: // Light mailbox
case 0xA26A: // Light mailbox
case 0xA26B: // Light mailbox
case 0xA26C: // Light mailbox
case 0xA26D: // Light mailbox
case 0xA26E: // Light mailbox
case 0xA26F: // Light mailbox
packType = PT_MAILBOX4;
break;
case 0xA3EB: // Sitting kitten mailbox
case 0xA3EC: // Sitting kitten mailbox
case 0xA3ED: // Sitting kitten mailbox
case 0xA3EE: // Sitting kitten mailbox
packType = PT_MAILBOX5;
break;
case 0xA3EF: // Standing kitten mailbox
case 0xA3F0: // Standing kitten mailbox
case 0xA3F1: // Standing kitten mailbox
case 0xA3F2: // Standing kitten mailbox
packType = PT_MAILBOX6;
break;
case 0xA3F3: // Scarecrow mailbox
case 0xA3F4: // Scarecrow mailbox
case 0xA3F5: // Scarecrow mailbox
case 0xA3F6: // Scarecrow mailbox
packType = PT_MAILBOX7;
break;
case 0xA3F7: // Lion mailbox
case 0xA3F8: // Lion mailbox
case 0xA3F9: // Lion mailbox
case 0xA3FA: // Lion mailbox
packType = PT_MAILBOX8;
break;
case 0x4141: // Square gray mailbox
case 0x4142: // Square gray mailbox
case 0x4143: // Square gray mailbox
case 0x4144: // Square gray mailbox
packType = PT_MAILBOX9;
break;
default:
packType = PT_UNKNOWN;
break;
}
return packType;
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::AddRespawnItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handles spawning of items from spawn objects/containers
//o------------------------------------------------------------------------------------------------o
void cItem::AddRespawnItem( CItem *mCont, const std::string& iString, const bool inCont, const bool randomItem, const UI16 itemAmount, bool useLootlist )
{
if( !ValidateObject( mCont ) || iString.empty() )
return;
CItem *iCreated = nullptr;
if( randomItem )
{
// LOOT tag was used
iCreated = CreateRandomItem( mCont, iString, mCont->WorldNumber(), mCont->GetInstanceId(), true, useLootlist );
}
else
{
// PACKITEM tag was used
iCreated = CreateBaseScriptItem( mCont, iString, mCont->WorldNumber(), itemAmount, mCont->GetInstanceId() );
}
if( iCreated == nullptr )
return;
// If item is stackable and amount is > 1, amount should have been set already
// However, if item is not stackable, spawn each item individually
if(( itemAmount > 1 ) && (( !randomItem && !iCreated->IsPileable() ) || randomItem ))
{
CItem *iCreated2 = nullptr;
for( UI08 i = 1; i < itemAmount; ++i )
{
if( randomItem )
{
// If amount was specified for a LOOT tag, spawn a random item each time
iCreated2 = CreateRandomItem( mCont, iString, mCont->WorldNumber(), mCont->GetInstanceId(), true, useLootlist );
}
else
{
// If amount was specified for a PACKITEM tag, spawn more copies of the same item
iCreated2 = CreateBaseScriptItem( mCont, iString, mCont->WorldNumber(), 1, mCont->GetInstanceId() );
}
if( iCreated2 )
{
if( inCont )
{
// Place item randomly in container
iCreated2->SetCont( mCont );
iCreated2->PlaceInPack();
}
else
{
iCreated2->SetLocation( mCont ); // Set item'mCont location in the world
}
iCreated2->SetSpawn( mCont->GetSerial() ); // Set source item was spawned from
// Try to stack the new item with existing items in the container, if any
if( iCreated2->IsPileable() )
{
AutoStack( nullptr, iCreated2, mCont );
}
}
}
}
// Ok, time to finalize setup of original item that was spawned
if( inCont )
{
iCreated->SetCont( mCont );
}
else
{
iCreated->SetLocation( mCont );
}
iCreated->SetSpawn( mCont->GetSerial() );
if( inCont )
{
if( ValidateObject( mCont ))
{
// Determine random location within container to place item
iCreated->PlaceInPack();
// Try to stack the new item with existing items in the container, if any
AutoStack( nullptr, iCreated, mCont );
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::GlowItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Handle glowing items
//o------------------------------------------------------------------------------------------------o
void cItem::GlowItem( CItem *i )
{
if( i->GetGlow() != INVALIDSERIAL )
{
CItem *j = CalcItemObjFromSer( i->GetGlow() );
if( !ValidateObject( j ))
return;
CBaseObject *getCont = i->GetCont();
if( getCont == nullptr ) // On the ground
{
j->SetCont( nullptr );
j->SetDir( 29 );
j->SetLocation( i );
}
else if( getCont->GetObjType() == OT_ITEM ) // In a pack
{
j->SetCont( getCont );
j->SetDir( 99 ); // gives no light in backpacks
j->SetX( i->GetX() );
j->SetY( i->GetY() );
j->SetZ( i->GetZ() );
}
else // Equipped
{
CChar *s = static_cast<CChar *>( getCont );
if( ValidateObject( s ))
{
j->SetCont( getCont );
j->SetX( s->GetX() );
j->SetY( s->GetY() );
j->SetZ( s->GetZ()+4 );
if( IsOnline(( *s )))
{
j->SetDir( 29 );
}
else
{
j->SetDir( 99 );
}
j->SetLayer( IL_TALISMAN );
}
}
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::CheckEquipment()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Checks equipment of character and validates that they have enough strength
//| to have each item equipped
//o------------------------------------------------------------------------------------------------o
void cItem::CheckEquipment( CChar *p )
{
if( ValidateObject( p ))
{
CSocket *pSock = p->GetSocket();
if( pSock == nullptr )
return;
std::vector<CItem *> itemsToUnequip;
const SI16 StrengthToCompare = p->GetStrength();
for( CItem *i = p->FirstItem(); !p->FinishedItems(); i = p->NextItem() )
{
if( ValidateObject( i ))
{
if( i->GetStrength() > StrengthToCompare )//if strength required > character's strength
{
itemsToUnequip.push_back( i );
}
}
}
// Loop through the items we want to move out of corpse
std::for_each( itemsToUnequip.begin(), itemsToUnequip.end(), [p, pSock]( CItem *iUnequip )
{
auto pPack = p->GetPackItem();
if( ValidateObject( pPack ) )
{
// Pack detected, drop to pack
iUnequip->SetCont( pPack );
iUnequip->PlaceInPack();
}
else
{
// No pack detected - drop on ground
iUnequip->SetCont( nullptr );
iUnequip->SetLocation( p );
}
for( auto &item : FindNearbyPlayers( p ))
{
p->SendWornItems( item );
}
pSock->SysMessage( 2782 ); // You are not strong enough to keep some of your items equipped!
Effects->ItemSound( pSock, iUnequip );
});
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::StoreItemRandomValue()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Stores an item's random "good" value (used by trade system)
//o------------------------------------------------------------------------------------------------o
void cItem::StoreItemRandomValue( CItem *i, CTownRegion *tReg )
{
if( i->GetGood() < 0 )
return;
if( tReg == nullptr )
{
CBaseObject *getLastCont = i->GetCont();
if( getLastCont != nullptr )
{
tReg = CalcRegionFromXY( getLastCont->GetX(), getLastCont->GetY(), getLastCont->WorldNumber(), getLastCont->GetInstanceId() );
}
if( tReg == nullptr )
{
return;
}
}
const SI32 min = tReg->GetGoodRnd1( static_cast<UI08>( i->GetGood() ));
const SI32 max = tReg->GetGoodRnd2( static_cast<UI08>( i->GetGood() ));
if( max != 0 || min != 0 )
{
i->SetRndValueRate( RandomNum( min, max ));
}
}
//o------------------------------------------------------------------------------------------------o
//| Function - cItem::DupeItem()
//o------------------------------------------------------------------------------------------------o
//| Purpose - Duplicates selected item
//o------------------------------------------------------------------------------------------------o
CItem *cItem::DupeItem( CSocket *s, CItem *i, UI32 amount )
{
CChar *mChar = s->CurrcharObj();
CBaseObject *iCont = i->GetCont();
CItem *charPack = mChar->GetPackItem();
if( !ValidateObject( mChar ) || i->IsCorpse() || ( !ValidateObject( iCont ) && !ValidateObject( charPack )))
return nullptr;
CItem *c = i->Dupe();
if( c == nullptr )
return nullptr;
c->IncLocation( 2, 2 );
if(( !ValidateObject( iCont ) || iCont->GetObjType() != OT_ITEM ) && ValidateObject( charPack ))
{
c->SetCont( charPack );
}
if( amount > MAX_STACK )
{
amount -= MAX_STACK;
DupeItem( s, i, MAX_STACK );
}
c->SetAmount( amount );
std::vector<UI16> scriptTriggers = c->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
toExecute->OnCreate( c, false, false );
}
}
return c;
}