uox3/source/cPlayerAction.cpp
Xoduz 68e57b0756 UOX3 0.99.5b-6
Increased world load speed by almost 20% by reducing how often loading progress is written to the console
Increased world save speed by almost 50% through the use of binary mode and buffer tweaks for ofstream combined with a cached newline char and std::to_string instead of static_cast
Fixed a server crash involving killing NPCs of a specific spawn region using the 'spawnkill # command
Fixed a server crash involving moving items from decaying corpses to the ground
Fixed a server crash involving carving up human corpses
Fixed a server crash related to AreaCharacterFunction JS function, which could trigger for any script that potentially kills any NPC this function iterates over (like explosion potions)
Fixed a server crash related to GMs attempting to add items from add-menu while having no backpack
Fixed a client crash issue caused by attempting to sell house deeds back to architect NPCs
Fixed an issue where players would get incorrect amounts of gold for selling house deeds back to architect NPCs
Fixed an issue with characters dying onboard boats which prevented players from packing up the boats later
Added new item property in code (via bools bitset) that can be used to enable/disable maker's mark on crafted items. This property can be set to true for items crafted at exceptional quality by GM craftsmen
Exposed the new item property to JS engine:
	.isMarkedByMaker	 	// If true, maker's mark will be displayed
Updated default item tooltip to display " of exceptional quality" for items crafted with exceptional quality
Updated default item tooltip to display "[Crafted] by [Crafter's Name]" for exceptional quality items crafted by GM craftsmen. The [Crafted] text comes from the MADEWORD defined in dfndata/skills/skills.dfn for the primary skill used to craft the item
Updated old-school single-click names for items (when AoS bit is disabled in client/server features) to display exceptional status and/or maker's mark for appropriate items
Fixed an issue with the display of titles for NPCs that used dictionary lookups for their title
Added new UOX.INI settings under the [settings] section:
	DISPLAYMAKERSMARK=1/0			// Controls if maker's marks on crafted items are shown on a global level
	SHOWNPCTITLESOVERHEAD=1/0 		// Controls whether NPC titles are shown over their heads
	SHOWINVULNERABLETAGOVERHEAD=0/1 // Controls whether invulnerable tags are shown overhead
	GLOBALRESTOCKMULTIPLIER=1.0 	// Global multiplier applied to RESTOCK property of items when loaded from DFNs
Added new UOX.INI settings under the [combat] section:
	PETCOMBATTRAINING=1/0 			// Controls whether pets can gain skills/stats from combat
	HIRELINGCOMBATTRAINING=1/0 		// Controls whether hirelings can gain skills/stats from combat
	NPCCOMBATTRAINING=0/1 			// Controls whether NPCs in general can gain skills/stats from combat
Fixed an issue where a player meeting the exact minimum requirements for crafting an item would always fail
Fixed a bug with teleporting off boats to valid nearby locations when double-clicking boat planks while standing on it
Updated behaviour of planks on boats:
	Boat key can unlock plank, but won't automatically open it
	Boat key used on an open, unlocked plank will lock and automatically close the plank
	Boat key used on an open, locked plank will unlock the plank and leave it open
	Player using an unlocked plank now opens it
	Player using an open plank will disembark (if onboard) or embark (if not) the boat
	Player can always open a locked plank if they're on the boat, even with no key, to allow disembarking the boat.
	Planks that are open, but locked cannot be accessed from outside the boat, and will close automatically after five of seconds
Updated behaviour of Magic Lock/Unlock spells:
	Magic Lock cannot be used on objects inside a multi
	Magic Lock applies difficulty to lock based on caster's Magery skill
	Magic Lock effect will last between 7 to 50 seconds (depending on caster's Magery skill)
	Any object that can be locked via Magic Lock spell can also be unlocked via Magic Unlock spell, if caster has high enough Magery skill
Fixed a bug where certain JS Functions, Methods and Properties would occasionally pass incorrect values to scripts that called upon them. Scripts that rely on the GetCurrentClock() Function, the .GetTimer() Method, .decayTime, .logTime, .oreTime and .fishTime Properties might need to be updated to account for the fix, which causes larger values to be passed in than before.
Fixed a bug related to GetCurrentClock() that caused decay and respawning of dungeon chests from working properly
Fixed a bug related to GetCurrentClock() and .logTime/.fishTime that prevented consumption and regeneration of log/fish resources from working properly
Added a new JS Function:
	GetStartTime()	// Returns a timestamp for when server started up
Fixed a bug related to GetCurrentClock() where server uptime stats in help menu was showing up incorrectly
Fixed a bug related to GetCurrentClock() and purging of registrations of visitors in public houses so their visits can be counted again later
Fixed a bug related to GetCurrentClock() that caused monster speech to not work properly
Fixed a Z-related issue that sometimes prevented players from fishing when standing on the shoreline next to the sea
Fixed an issue with Animal Lore skill that would always return a pet's loyalty level as "Wild"
Increased max range pets can be from their owner in order to still be included in moongate/teleporter travel from 12 to 24 tiles
Fixed an issue where the alchemy bonus damage for explosion potions was off by an order of magnitude
Fixed an issue where food IDs from foodlists referenced by other foodlists was not detected as valid food for tamed pets or NPCs
Fixed an issue where Alchemy crafting skill did not properly check for the existence of the crafting tool before proceeding with the crafting process
Fixed an issue that prevented the Teleport spell from working as intended
Fixed an issue where players would still get 1 gold even if STARTGOLD in uox.ini was set to 0
Fixed an issue with mount restrictions script that prevented players from mounting Unicorns, Ki-rins and Cu Sidhes
Fixed an issue where aggressive creatures wouldn't stop attacking player after being tamed (js/skill/taming.js)
Added a system message to inform the player when they've dropped out of stealth due to taking too many steps
Fixed an issue where hunger level of NPCs instantly dropped upon spawning because hunger timer was not active yet
Fixed a bug where character corpses would appear with a [unidentified] tag attached
Fixed an issue where the [unidentified] tag did not show up for unidentified magic items
Added ANIMAL tag to a small bunch of creature definitions (dfndata/creatures/creatures.dfn)
Fixed an issue with Bowcraft skill where player could not gain enough skill from crafting bows to reach min requirement for crossbows, and similiarly could not reach min requirement for heavy crossbows from crossbows
Updated default value of MURDERDECAYTIMER INI setting from 60 seconds to 28800 seconds (8 hours), to match the amount of time it would take for one (short term) murder count to decay on LBR-era OSI shards.
2022-08-25 02:13:00 +08:00

3381 lines
112 KiB
C++

#include "uox3.h"
#include "weight.h"
#include "combat.h"
#include "cRaces.h"
#include "skills.h"
#include "cMagic.h"
#include "CJSMapping.h"
#include "mapstuff.h"
#include "cScript.h"
#include "cEffects.h"
#include "CPacketSend.h"
#include "classes.h"
#include "regions.h"
#include "books.h"
#include "magic.h"
#include "townregion.h"
#include "gump.h"
#include "cGuild.h"
#include "ssection.h"
#include "cServerDefinitions.h"
#include "cSkillClass.h"
#include "Dictionary.h"
using namespace std::string_literals;
void sendTradeStatus( CItem *cont1, CItem *cont2 );
CItem * startTrade( CSocket *mSock, CChar *i );
bool checkItemRange( CChar *mChar, CItem *i );
void DoHouseTarget( CSocket *mSock, UI08 houseEntry );
void SocketMapChange( CSocket *sock, CChar *charMoving, CItem *gate );
void BuildGumpFromScripts( CSocket *s, UI16 m );
void PlankStuff( CSocket *s, CItem *p );
CBoatObj * GetBoat( CSocket *s );
void ModelBoat( CSocket *s, CBoatObj *i );
//o-----------------------------------------------------------------------------------------------o
//| Function - void Bounce( CSocket *bouncer, CItem *bouncing, UI08 mode )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Bounce items back from where they came
//o-----------------------------------------------------------------------------------------------o
void Bounce( CSocket *bouncer, CItem *bouncing, UI08 mode = 5 )
{
if( bouncer == nullptr || !ValidateObject( bouncing ) )
return;
CPBounce bounce( mode );
PickupLocations from = bouncer->PickupSpot();
SERIAL spot = bouncer->PickupSerial();
switch( from )
{
default:
case PL_NOWHERE: break;
case PL_GROUND:
{
SI16 x = bouncer->PickupX();
SI16 y = bouncer->PickupY();
SI08 z = bouncer->PickupZ();
bouncing->SetLocation( x, y, z );
//If item bounces out of range, bounce it to character's feet instead
if( !objInRange( bouncer, bouncing, DIST_INRANGE ))
{
x = bouncer->CurrcharObj()->GetX();
y = bouncer->CurrcharObj()->GetY();
z = bouncer->CurrcharObj()->GetZ();
bouncing->SetLocation( x, y, z );
}
}
break;
case PL_OWNPACK:
case PL_OTHERPACK:
case PL_PAPERDOLL:
bouncing->SetContSerial( spot );
break;
}
bouncing->SetHeldOnCursor( false );
bouncing->Dirty( UT_UPDATE );
bouncer->Send( &bounce );
bouncer->PickupSpot( PL_NOWHERE );
bouncer->SetCursorItem( nullptr );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void PickupBounce( CSocket *bouncer )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Bounce items back if pickup is illegal. Doesn't require updating item.
//o-----------------------------------------------------------------------------------------------o
void PickupBounce( CSocket *bouncer, UI08 mode = 0 )
{
if( bouncer == nullptr )
return;
CPBounce bounce( mode );
bouncer->Send( &bounce );
bouncer->PickupSpot( PL_NOWHERE );
bouncer->SetCursorItem( nullptr );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - CItem *doStacking( CSocket *mSock, CChar *mChar, CItem *i, CItem *stack )
//| Date - 8/14/01
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Searches pack for pileable items that match the item being
//| dropped into said pack (only if it's pileable), if found
//| ensures the amount won't go over 65535 (the limit how large
//| an item can stack) then stacks it. If the item is not stackable
//| or it cannot stack the item with a pile and have an amount that
//| is <= 65355 then it creates a new pile.
//|
//| Changes - 09/09/2001 - returns true if item deleted
//| 09/25/2002 - Weight fixes
//o-----------------------------------------------------------------------------------------------o
CItem *doStacking( CSocket *mSock, CChar *mChar, CItem *i, CItem *stack )
{
UI32 newAmt = stack->GetAmount() + i->GetAmount();
if( newAmt > MAX_STACK )
{
i->SetAmount( ( newAmt - MAX_STACK ) );
stack->SetAmount( MAX_STACK );
}
else
{
stack->SetAmount( newAmt );
i->Delete();
if( mSock != nullptr )
Effects->itemSound( mSock, stack, false );
return stack;
}
return i;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - CItem *autoStack( CSocket *mSock, CItem *iToStack, CItem *iPack )
//| Status - Modified to v2
//| v2 - accepts a possible nullptr socket to deal with the JSE
//| v3 - returns a CItem * (stack if stacked, item otherwise)
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Searches through the pack to see if an item can be stacked
//| stacking them automatically
//o-----------------------------------------------------------------------------------------------o
auto autoStack( CSocket *mSock, CItem *iToStack, CItem *iPack ) ->CItem *{
CChar *mChar = nullptr;
if( mSock){
mChar = mSock->CurrcharObj();
}
if( ValidateObject( iToStack )&& ValidateObject( iPack ) ){
iToStack->SetCont( iPack );
if( iToStack->isPileable() ) {
if( mSock != nullptr && ( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND ) ){
Weight->subtractItemWeight( mChar, iToStack );
}
const UI16 itID = iToStack->GetID();
const SERIAL itSer = iToStack->GetSerial();
const UI16 itCol = iToStack->GetColour();
const UI32 itMore = iToStack->GetTempVar( CITV_MORE );
const UI32 itMoreX = iToStack->GetTempVar( CITV_MOREX );
const UI32 itMoreY = iToStack->GetTempVar( CITV_MOREY );
const UI32 itMoreZ = iToStack->GetTempVar( CITV_MOREZ );
const UI32 itBuyValue = iToStack->GetBuyValue();
const UI32 itSellValue = iToStack->GetSellValue();
auto ipCont = iPack->GetContainsList();
for (const auto &stack :ipCont->collection()){
if( ValidateObject( stack ) ){
if( stack->isPileable() && stack->GetAmount() < MAX_STACK &&
stack->GetSerial() != itSer && stack->GetID() == itID && stack->GetColour() == itCol && stack->GetTempVar( CITV_MORE ) == itMore && stack->GetTempVar( CITV_MOREX ) == itMoreX
&& stack->GetTempVar( CITV_MOREY ) == itMoreY && stack->GetTempVar( CITV_MOREZ ) == itMoreZ && stack->GetBuyValue() == itBuyValue && stack->GetSellValue() == itSellValue ) {
// Autostack
if( doStacking( mSock, mChar, iToStack, stack ) == stack ){ // compare to stack, if doStacking returned the stack, then the raw object was deleted
return stack; // return the stack
}
}
}
}
if( mSock != nullptr && ( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND ) ){
Weight->addItemWeight( mChar, iToStack );
}
}
iToStack->PlaceInPack();
if( mSock){
Effects->itemSound( mSock, iToStack, false );
}
return iToStack;
}
return nullptr ;
}
auto findNearbyChars( SI16 x, SI16 y, UI08 worldNumber, UI16 instanceID, UI16 distance ) ->std::vector< CChar* >;
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPIGetItem::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when a player picks up an item
//o-----------------------------------------------------------------------------------------------o
bool CPIGetItem::Handle( void )
{
CChar *ourChar = tSock->CurrcharObj();
SERIAL serial = tSock->GetDWord( 1 );
if( serial == INVALIDSERIAL )
return true;
// Break their meditation/concentration
ourChar->BreakConcentration( tSock );
// In case player is casting a spell, cancel their targeting cursor (if enabled in INI)
if(( ourChar->IsCasting() || ourChar->GetSpellCast() != -1 ) && cwmWorldState->ServerData()->ItemsInterruptCasting() )
{
tSock->target( 0, 0x00000000, Dictionary->GetEntry( 2862, tSock->Language() ), 3 ); // Your hands must be free to cast spells or meditate.
ourChar->StopSpell();
if( ourChar->IsFrozen() )
ourChar->SetFrozen( false );
}
CItem *i = calcItemObjFromSer( serial );
if( !ValidateObject( i ) )
return true;
CPBounce bounce( 0 );
ObjectType oType = OT_CBO;
CBaseObject *iOwner = nullptr;
CItem *x = i;
CBaseObject *iCont = i->GetCont();
if( iCont != nullptr ) //Find character owning item
{
iOwner = FindItemOwner( i, oType );
tSock->PickupSerial( i->GetContSerial() );
if( oType == OT_CHAR )
{
if( iCont->GetObjType() == OT_CHAR )
tSock->PickupSpot( PL_PAPERDOLL );
else
{
if( iOwner != ourChar )
tSock->PickupSpot( PL_OTHERPACK );
else
tSock->PickupSpot( PL_OWNPACK );
}
bool otherPackCheck = (iOwner != ourChar);
if( otherPackCheck ) // okay, not our owner!
{
otherPackCheck = !ourChar->IsGM();
if( otherPackCheck ) // not a GM either
{
if( iOwner->GetOwnerObj() == ourChar )
otherPackCheck = false;
else if( Npcs->checkPetFriend( ourChar, static_cast< CChar * >(iOwner) ) )
otherPackCheck = false;
}
}
if( otherPackCheck || !objInRange( ourChar, iOwner, DIST_NEARBY ) )
{
tSock->sysmessage( 9065 ); // That item does not belong to you. You'll have to steal it.
Bounce( tSock, i );
return true;
}
if( iCont->CanBeObjType( OT_ITEM ) )
{
CItem *contItem = static_cast<CItem *>(iCont);
if( contItem->GetType() == IT_TRADEWINDOW ) // Trade Window
{
tSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( tSock, i );
return true;
}
CItem *recurseItem = FindRootContainer( contItem );
if( recurseItem->GetType() == IT_TRADEWINDOW )
{
CItem *z = calcItemObjFromSer( recurseItem->GetTempVar( CITV_MOREX ) );
if( ValidateObject( z ) )
{
if( z->GetTempVar( CITV_MOREZ ) || recurseItem->GetTempVar( CITV_MOREZ ) )
{
z->SetTempVar( CITV_MOREZ, 0 );
recurseItem->SetTempVar( CITV_MOREZ, 0 );
sendTradeStatus( z, recurseItem );
}
CChar *zChar = FindItemOwner( z );
if( ValidateObject( zChar ) )
{
CSocket *zSock = zChar->GetSocket();
if( zSock != nullptr )
Effects->PlaySound( zSock, 0x0057, true );
}
}
}
else if( recurseItem->GetLayer() == IL_BANKBOX )
tSock->PickupSpot( PL_OTHERPACK );
}
}
else if( oType == OT_ITEM )
{
// Picking up item from non-character container
tSock->PickupSpot( PL_OTHERPACK );
CItem *x = static_cast<CItem *>(iOwner);
if( !objInRange( ourChar, x, DIST_NEARBY ) )
{
Bounce( tSock, i );
tSock->sysmessage( 393 ); // That is too far away.
// Resend items in container after bounce, or the bounced item will vanish from player's screen
CPItemsInContainer itemsIn( tSock, static_cast<CItem *>(iCont), 0x01 );
tSock->Send( &itemsIn );
return true;
}
if( x->isCorpse() )
{
CChar *corpseTargChar = x->GetOwnerObj();
if( ValidateObject( corpseTargChar ) )
{
if( corpseTargChar->IsGuarded() ) // Is the corpse being guarded?
Combat->petGuardAttack( ourChar, corpseTargChar, corpseTargChar );
else if( x->isGuarded() )
{
CTownRegion *itemTownRegion = x->GetRegion();
if( !itemTownRegion->IsGuarded() && !itemTownRegion->IsSafeZone() )
{
Combat->petGuardAttack( ourChar, corpseTargChar, x );
}
}
}
}
}
}
else
{
// Picking up item from ground - check that character is in range and can see the item!
tSock->PickupSpot( PL_GROUND );
tSock->PickupLocation( i->GetX(), i->GetY(), i->GetZ() );
if( !ourChar->IsGM() && ( !objInRange( ourChar, i, DIST_NEARBY ) || !LineOfSight( tSock, ourChar, i->GetX(), i->GetY(), i->GetZ(), WALLS_CHIMNEYS + DOORS, true )))
{
tSock->sysmessage( 683 ); // There seems to be something in the way
Bounce( tSock, i );
return true;
}
}
if( i->isCorpse() || !checkItemRange( ourChar, x ) )
{
tSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( tSock, i );
return true;
}
if( x->GetMultiObj() != nullptr )
{
if( ( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_GROUND ) && ( x->GetMultiObj() != ourChar->GetMultiObj() || x->IsLockedDown() ))
{
tSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( tSock, i );
return true;
}
i->SetMulti( INVALIDSERIAL );
}
if( i->isDecayable() )
i->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_DECAY ) );
if( i->isGuarded() )
{
// Only care about guarded state if outside of a guarded/safezone region
CTownRegion *itemTownRegion = x->GetRegion();
if( !itemTownRegion->IsGuarded() && !itemTownRegion->IsSafeZone() )
{
// Let's loop through a list of nearby characters to see if anyone is guarding the object
for (auto &nearbyChar : findNearbyChars(i->GetX(), i->GetY(), i->WorldNumber(), i->GetInstanceID(), DIST_INRANGE)) {
// Only proceed if the character is guarding the object
if( nearbyChar->GetGuarding() == i )
{
CChar *nearbyCharOwner = nearbyChar->GetOwnerObj();
if( ValidateObject( nearbyCharOwner ) )
{
// Turn the player who picked up the guarded item criminal
criminal( ourChar );
// Have pet/follower attack the player who picked up the item
Combat->petGuardAttack( ourChar, nearbyCharOwner, i, nearbyChar );
// Stop guarding item, it is now in someone else's possession
nearbyChar->SetGuarding( nullptr );
i->SetGuarded( false );
}
}
}
}
}
CTile& tile = Map->SeekTile( i->GetID() );
if( !ourChar->AllMove() && ( i->GetMovable() == 2 || i->IsLockedDown() ||
( tile.Weight() == 255 && i->GetMovable() != 1 ) ) )
{
if( ourChar->GetCommandLevel() < 2 || tSock->PickupSpot() != PL_OWNPACK )
{
tSock->sysmessage( 9064 ); // You cannot pick that up.
if( ourChar->GetCommandLevel() >= 2 )
tSock->sysmessage( 9099 ); // Tip: Try 'ALLMOVE ON command or modify item's movable property with 'TWEAK command!
Bounce( tSock, i );
return true;
}
else
{
tSock->sysmessage( 9098 ); // Item immovable to normal players, but can be dragged out of backpack by GM characters.
}
}
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
SI08 retStatus = toExecute->OnPickup( i, ourChar );
// -1 == script doesn't exist, or returned -1
// 0 == script returned false, 0, or nothing - don't execute hard code
// 1 == script returned true or 1
if( retStatus == 0 )
{
Bounce( tSock, i );
return true;
}
}
}
if( i->GetAmount() > 1 && i->GetObjType() != OT_SPAWNER )
{
UI16 amount = tSock->GetWord( 5 );
if( amount > i->GetAmount() )
amount = i->GetAmount();
if( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_GROUND )
{
if( !Weight->checkCharWeight( nullptr, ourChar, i, amount ))
{
tSock->sysmessage( 1743 ); // That person can not possibly hold more weight
Bounce( tSock, i );
return true;
}
}
if( amount < i->GetAmount() )
{
CItem *c = i->Dupe();
if( c != nullptr )
{
c->IncAmount( -amount );
c->SetCont( i->GetCont() );
c->SetLocation( c->GetX(), c->GetY(), c->GetZ(), 0, c->WorldNumber(), c->GetInstanceID() );
}
i->SetAmount( amount );
}
if( i->GetID() == 0x0EED )
{
if( tSock->PickupSpot() == PL_OWNPACK )
ourChar->Dirty( UT_STATWINDOW );
}
}
Effects->PlaySound( tSock, 0x0057, true );
if( iCont == nullptr )
MapRegion->RemoveItem( i );
i->RemoveFromSight();
tSock->SetCursorItem( i );
i->SetHeldOnCursor( true );
// Update region of item to match that of the character holding it
i->SetRegion( ourChar->GetRegionNum() );
if( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_GROUND )
Weight->addItemWeight( ourChar, i );
if( ValidateObject( iCont ) && iCont->CanBeObjType( OT_ITEM ))
{
std::vector<UI16> contScriptTriggers = iCont->GetScriptTriggers();
for( auto scriptTrig : contScriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
CItem *contItem = static_cast<CItem *>(iCont);
toExecute->OnContRemoveItem( contItem, i, ourChar );
}
}
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPIEquipItem::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped on a players paperdoll
//o-----------------------------------------------------------------------------------------------o
bool CPIEquipItem::Handle( void )
{
CChar *ourChar = tSock->CurrcharObj();
SERIAL cserial = tSock->GetDWord( 6 );
SERIAL iserial = tSock->GetDWord( 1 );
if( cserial == INVALIDSERIAL || iserial == INVALIDSERIAL )
return true;
ourChar->BreakConcentration( tSock );
CItem *i = calcItemObjFromSer( iserial );
CChar *k = calcCharObjFromSer( cserial );
if( !ValidateObject( i ) )
return true;
if( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_PAPERDOLL )
{
ObjectType pOType;
CBaseObject *pOwner = FindItemOwner( i, pOType );
if( pOType == OT_CHAR )
{
CChar *pOChar = static_cast< CChar * >( pOwner );
if( ( ( pOChar != ourChar ) && ( ( !ourChar->IsGM() && ( pOChar->GetOwnerObj() != ourChar ) ) ) ) || !objInRange( ourChar, pOwner, DIST_NEARBY ) )
{
tSock->sysmessage( 9066 ); // That belongs to someone else.
Bounce( tSock, i );
return true;
}
}
else if( pOType == OT_ITEM )
{
if( !objInRange( ourChar, pOwner, DIST_NEARBY ) )
{
tSock->sysmessage( 393 ); // That is too far away
Bounce( tSock, i );
return true;
}
}
}
else if( tSock->PickupSpot() == PL_GROUND )
{
if( !objInRange( ourChar, i, DIST_NEARBY ) )
{
tSock->sysmessage( 393 ); // That is too far away
Bounce( tSock, i );
return true;
}
}
if( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( ourChar, i ); // SetCont() adds the weight for us (But we also had to add the weight in grabItem() since it sets cont as INVALIDSERIAL
if( !ourChar->IsGM() && k != ourChar ) // players cant equip items on other players or npc`s paperdolls. // GM PRIVS
{
Bounce( tSock, i );
tSock->sysmessage( 1186 ); // You can't put items on other players!
return true;
}
if( ourChar->IsDead() )
{
Bounce( tSock, i );
tSock->sysmessage( 1185 ); // You can't do much in your current state.
return true;
}
if( !ValidateObject( k ) )
return true;
RACEID raceID = k->GetRace();
CRace *race = Races->Race( raceID );
if( !race->CanEquipItem( i->GetID() ))
{
tSock->sysmessage( 1981, race->Name().c_str() ); // Members of the %s race cannot wear this
Bounce( tSock, i );
return true;
}
ARMORCLASS ac1 = Races->ArmorRestrict( raceID );
ARMORCLASS ac2 = i->GetArmourClass();
if( ac1 != 0 && ( (ac1&ac2) == 0 ) ) // bit comparison, if they have ANYTHING in common, they can wear it
{
tSock->sysmessage( 1187 ); // You are unable to equip that due to your race.
Bounce( tSock, i );
return true;
}
if( k == ourChar )
{
bool canWear = false;
if( i->GetStrength() > k->GetStrength() )
tSock->sysmessage( 1188 ); // You are not strong enough to use that. (NOTE: Should these messages use color 0x096a to stand out and replicate hard coded client message?)
else if( i->GetDexterity() > k->GetDexterity() )
tSock->sysmessage( 1189 ); // You are not agile enough to use that.
else if( i->GetIntelligence() > k->GetIntelligence() )
tSock->sysmessage( 1190 ); // You are not smart enough to use that.
else
canWear = true;
if( !canWear )
{
Bounce( tSock, i );
if( tSock->PickupSpot() == PL_OTHERPACK || tSock->PickupSpot() == PL_GROUND )
Effects->itemSound( tSock, i, true );
else
Effects->itemSound( tSock, i, false );
return true;
}
}
CTile& tile = Map->SeekTile( i->GetID() );
if( !ourChar->AllMove() && ( i->GetMovable() == 2 || ( i->IsLockedDown() && i->GetOwnerObj() != ourChar ) ||
( tile.Weight() == 255 && i->GetMovable() != 1 ) ) )
{
tSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( tSock, i );
return true;
}
if( i->GetLayer() == IL_NONE )
i->SetLayer( static_cast<ItemLayers>(tSock->GetByte( 5 )) );
// 1/13/2003 - Fix for equipping an item to more than one hand, or multiple equipping.
if( i->GetCont() != k )
{
bool conflictItem = true;
CItem *j = k->GetItemAtLayer( i->GetLayer() );
if( !ValidateObject( j ))
{
if( i->GetLayer() == IL_RIGHTHAND )
j = k->GetItemAtLayer( IL_LEFTHAND );
else if( i->GetLayer() == IL_LEFTHAND )
j = k->GetItemAtLayer( IL_RIGHTHAND );
// GetDir-check is to allow for torches and lanterns,
// which use left-hand layer but are not 2-handers or shields
if( ValidateObject( j ) && !i->IsShieldType() && i->GetDir() == 0 )
{
if( j->IsShieldType() || j->GetDir() != 0 )
conflictItem = false;
}
else
conflictItem = false;
}
if( conflictItem == true )
{
tSock->sysmessage( 1744 ); // You can't equip two items in the same slot
Bounce( tSock, i );
return true;
}
}
if( !Weight->checkCharWeight( ourChar, k, i ) )
{
tSock->sysmessage( 1743 ); // That person can not possibly hold more weight
Bounce( tSock, i );
return true;
}
i->SetCont( k );
i->SetHeldOnCursor( false );
//Reset PickupSpot after dropping the item
tSock->PickupSpot( PL_NOWHERE );
tSock->SetCursorItem( nullptr );
// In case player is casting a spell, cancel their targeting cursor (if enabled in INI)
if(( ourChar->IsCasting() || ourChar->GetSpellCast() != -1 ) && cwmWorldState->ServerData()->ItemsInterruptCasting() )
{
tSock->target( 0, 0x00000000, Dictionary->GetEntry( 2862, tSock->Language() ), 3 ); // Your hands must be free to cast spells or meditate.
ourChar->StopSpell();
if( ourChar->IsFrozen() )
ourChar->SetFrozen( false );
}
CPDropItemApproved lc;
tSock->Send( &lc );
auto weaponType = Combat->getCombatSkill( i );
if( weaponType == SWORDSMANSHIP || weaponType == MACEFIGHTING || weaponType == FENCING )
Effects->PlaySound( tSock, 0x0056, false ); // Play unsheath sound if
else
Effects->PlaySound( tSock, 0x0057, false );
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool DropOnPC( CSocket *mSock, CChar *mChar, CChar *targPlayer, CItem *i )
//| Changes - September 14th, 2001, returns true if item deleted
//| - September 25, 2002, Weight fixes
//| - September 21st, 2003, moved into seperate file and few other minor tweaks
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped on a character
//o-----------------------------------------------------------------------------------------------o
bool DropOnPC( CSocket *mSock, CChar *mChar, CChar *targPlayer, CItem *i )
{
bool stackDeleted = false;
if( targPlayer == mChar )
{
CItem *pack = mChar->GetPackItem();
if( !ValidateObject( pack ) ) // if player has no pack, put it at its feet
{
i->SetCont( nullptr );
i->SetLocation( mChar );
}
else
stackDeleted = ( autoStack( mSock, i, pack ) != i );
}
else // Trade stuff
{
if( isOnline( (*targPlayer) ) )
{
CItem *j = startTrade( mSock, targPlayer );
if( ValidateObject( j ) )
{
i->SetCont( j );
i->SetX( 30 );
i->SetY( 30 );
i->SetZ( 9 );
}
}
else
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
mSock->sysmessage( 1754 ); // That character is not online.
Bounce( mSock, i );
return true;
}
}
return stackDeleted;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool IsOnFoodList( const std::string& sFoodList, const UI16 sItemID )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if a particular item is on an NPC's list of accepted foods
//o-----------------------------------------------------------------------------------------------o
auto IsOnFoodList( const std::string& sFoodList, const UI16 sItemID ) ->bool{
auto doesEat = false;
const std::string sect = "FOODLIST "s + sFoodList;
auto FoodList = FileLookup->FindEntry( sect, items_def );
if(FoodList) {
for (const auto &sec : FoodList->collection()){
auto tag = sec->tag ;
if( !tag.empty() ) {
if( oldstrutil::upper( tag ) == "FOODLIST" ) {
doesEat = IsOnFoodList( sec->data, sItemID );
}
else if( sItemID == static_cast<UI16>(std::stoul(tag, nullptr, 0)) ) {
doesEat = true;
}
}
if( doesEat ) // We found a valid food, let's stop looking
break;
}
}
return doesEat;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool DropOnNPC( CSocket *mSock, CChar *mChar, CChar *targNPC, CItem *i )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped on an NPC
//o-----------------------------------------------------------------------------------------------o
bool DropOnNPC( CSocket *mSock, CChar *mChar, CChar *targNPC, CItem *i )
{
UI08 dropResult = 0;
const bool isGM = (mChar->GetCommandLevel() >= CL_CNS);
bool stackDeleted = false;
bool executeNpc = true;
SI08 rVal = 0;
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
rVal = toExecute->OnDropItemOnNpc( mChar, targNPC, i ); // returns 1 if we should bounce it
switch( rVal )
{
case -1: // no such event
default:
executeNpc = true;
break;
case 0: // bounce, no code, error message handled in script
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
Bounce( mSock, i );
return false; // stack not deleted, as we're bouncing
case 1: // don't bounce, use code
executeNpc = false;
break;
case 2: // don't bounce, don't use code
executeNpc = true;
break;
}
}
}
if( executeNpc )
{
std::vector<UI16> targScriptTriggers = targNPC->GetScriptTriggers();
for( auto scriptTrig : targScriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
rVal = toExecute->OnDropItemOnNpc( mChar, targNPC, i ); // returns 1 if we should bounce it
switch( rVal )
{
case -1: // no such event
case 1: // we don't want to bounce, use code
default:
break;
case 0: // we want to bounce and return. Handle error message in script
// If we want to bounce, then it's safe to assume the item still exists!
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
Bounce( mSock, i );
return false; // bouncing, so item exists
case 2: // we don't want to bounce, don't use code
return true; // don't let the code assume the item exists, so we never reference it
}
}
}
}
if( targNPC->IsTamed() && ( isGM || targNPC->GetOwnerObj() == mChar || Npcs->checkPetFriend( mChar, targNPC ) ) ) // do food stuff
{
if( targNPC->WillHunger() && IsOnFoodList( targNPC->GetFood(), i->GetID() ) )
{
if( targNPC->GetHunger() < 6 )
{
Effects->PlaySound( mSock, static_cast<UI16>(0x003A + RandomNum( 0, 2 )), true );
if( cwmWorldState->creatures[targNPC->GetID()].IsAnimal() )
{
Effects->PlayCharacterAnimation( targNPC, 3, 0, 5 );
// Restore loyalty to max upon feeding pet
if( targNPC->GetLoyalty() < targNPC->GetMaxLoyalty() )
{
targNPC->SetLoyalty( targNPC->GetMaxLoyalty() );
mSock->sysmessage( 2416 ); // Your pet looks happier.
}
}
UI08 poisonStrength = i->GetPoisoned();
if( poisonStrength && targNPC->GetPoisoned() < poisonStrength )
{
Effects->PlaySound( mSock, 0x0246, true ); // poison sound - SpaceDog
// Apply poison on target
targNPC->SetPoisoned( poisonStrength );
// Set time until next time poison "ticks"
targNPC->SetTimer( tCHAR_POISONTIME, BuildTimeValue(static_cast<R32>(getPoisonTickTime( poisonStrength ))));
// Set time until poison wears off completely
targNPC->SetTimer( tCHAR_POISONWEAROFF, targNPC->GetTimer( tCHAR_POISONTIME ) + ( 1000 * getPoisonDuration( poisonStrength ))); //wear off starts after poison takes effect
// Cut loyalty in half if pet was fed poisoned food
if( targNPC->GetLoyalty() > 0 )
{
targNPC->SetLoyalty( std::clamp( static_cast<UI16>(targNPC->GetLoyalty() / 2), static_cast<UI16>(0), static_cast<UI16>(100)) );
}
}
//Remove a food item
bool iDeleted = i->IncAmount( -1 );
targNPC->SetHunger( static_cast<SI08>(targNPC->GetHunger() + 1) );
mSock->sysmessage( 1781 ); // That pet accepts your food and happily eats it.
if( iDeleted )
return true; //stackdeleted
}
else
mSock->sysmessage( 1780 );
}
else if( isGM || targNPC->GetID() == 0x0123 || targNPC->GetID() == 0x0124 || targNPC->GetID() == 0x0317 ) // It's a pack animal
dropResult = 2;
}
else if( cwmWorldState->creatures[targNPC->GetID()].IsHuman() )
{
if( static_cast<CChar *>(mSock->TempObj()) != targNPC )
{
if( isGM )
dropResult = 2;
else
{
dropResult = 1;
targNPC->TextMessage( mSock, 1197, TALK, false );
}
}
else if( i->GetID() == 0x0EED ) // They gave the NPC gold
{
UI08 trainedIn = targNPC->GetTrainingPlayerIn();
targNPC->TextMessage( mSock, 1198, TALK, false );
UI16 oldskill = mChar->GetBaseSkill( trainedIn );
mChar->SetBaseSkill( (UI16)( mChar->GetBaseSkill( trainedIn ) + i->GetAmount() ), trainedIn );
if( mChar->GetBaseSkill( trainedIn ) > 250 )
mChar->SetBaseSkill( 250, trainedIn );
Skills->updateSkillLevel( mChar, trainedIn );
mSock->updateskill( trainedIn );
UI16 getAmount = i->GetAmount();
if( i->GetAmount() > 250 ) // Paid too much
{
i->IncAmount( -(250 - oldskill) );
dropResult = 1;
}
else // Gave exact change
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
i->Delete();
stackDeleted = true;
}
mSock->TempObj( nullptr );
targNPC->SetTrainingPlayerIn( 0xFF );
Effects->goldSound( mSock, getAmount, false );
}
else // Did not give gold
{
dropResult = 1;
targNPC->TextMessage( mSock, 1199, TALK, false );
}
}
else
dropResult = ( isGM ? 2 : 1 );
switch( dropResult )
{
default:
case 0: // Do nothing;
break;
case 1: // Bounce
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
Bounce( mSock, i );
break;
case 2: // Stack
CItem *pack;
pack = targNPC->GetPackItem();
if( ValidateObject( pack ) )
stackDeleted = ( autoStack( mSock, i, pack ) != i );
break;
}
return stackDeleted;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool DropOnChar( CSocket *mSock, CChar *targChar, CItem *i )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped on an a character
//o-----------------------------------------------------------------------------------------------o
bool DropOnChar( CSocket *mSock, CChar *targChar, CItem *i )
{
CChar *mChar = mSock->CurrcharObj();
if( !ValidateObject( mChar ) )
return false;
CItem *packItem = targChar->GetPackItem();
if( ValidateObject( packItem ) )
{
// Can character's backpack hold any more items?
if( mSock->PickupSpot() != PL_OWNPACK && ( GetTotalItemCount( packItem ) >= packItem->GetMaxItems() ||
GetTotalItemCount( packItem ) + std::max( static_cast<UI32>(1), 1 + GetTotalItemCount( i )) > packItem->GetMaxItems() ))
{
mSock->sysmessage( 1818 ); // The container is already at capacity.
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
Bounce( mSock, i );
return false;
}
// Can character's backpack hold any more weight?
if( !Weight->checkCharWeight( mChar, targChar, i ) )
{
mSock->sysmessage( 1743 );
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, i );
Bounce( mSock, i );
return false;
}
if( !targChar->IsNpc() )
return DropOnPC( mSock, mChar, targChar, i );
return DropOnNPC( mSock, mChar, targChar, i );
}
return false;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CheckForValidDropLocation( CSocket *mSock, CChar *nChar, UI16 x, UI16 y, SI08 z )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check for a valid drop location at the location where client attempts to drop an item
//o-----------------------------------------------------------------------------------------------o
bool CheckForValidDropLocation( CSocket *mSock, CChar *nChar, UI16 x, UI16 y, SI08 z )
{
bool dropLocationBlocked = false;
// Don't allow dropping item at a location far below or far above character
if( z < nChar->GetZ() - 5 || z > nChar->GetZ() + 16 )
dropLocationBlocked = true;
if( !dropLocationBlocked )
{
// Check static items for TF_BLOCKING flag, or if map tile blocks dropping item
dropLocationBlocked = ( Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_BLOCKING, false )
|| Map->DoesMapBlock( x, y, z, nChar->WorldNumber(), true, false, false, false ));
if( !dropLocationBlocked )
{
// No? Well, then check static flags for TF_ROOF flag
dropLocationBlocked = Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_ROOF, false );
if( !dropLocationBlocked )
{
// Still no? Time to check dynamic items for TF_BLOCKING flag...
dropLocationBlocked = ( Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceID(), TF_BLOCKING ));
if( !dropLocationBlocked )
{
// ...and then for TF_ROOF flag
dropLocationBlocked = Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceID(), TF_ROOF );
}
else
{
// Location blocked! But wait, there might be a valid dynamic surface to place the item on...
dropLocationBlocked = !Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceID(), TF_SURFACE );
}
}
}
else
{
// Location blocked! But wait, there might be a valid static surface to place the item on...
dropLocationBlocked = !Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_SURFACE, false );
}
}
// Let's check line of sight as well, for good measure
if( !nChar->IsGM() && !dropLocationBlocked && !LineOfSight( mSock, nChar, x, y, z, WALLS_CHIMNEYS + DOORS + ROOFING_SLANTED, true ) )
{
dropLocationBlocked = true;
}
return !dropLocationBlocked;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void Drop( CSocket *mSock, SERIAL item, SERIAL dest, SI16 x, SI16 y, SI08 z, SI08 gridLoc )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Item is dropped on the ground or on a character
//o-----------------------------------------------------------------------------------------------o
void Drop( CSocket *mSock, SERIAL item, SERIAL dest, SI16 x, SI16 y, SI08 z, SI08 gridLoc )
{
CChar *nChar = mSock->CurrcharObj();
CItem *i = calcItemObjFromSer( item );
bool stackDeleted = false;
if( !ValidateObject( i ) )
{
nChar->Teleport();
return;
}
//Bounce if no valid pickupspot exists
if( mSock->PickupSpot() == PL_NOWHERE )
{
mSock->sysmessage( 9067 ); // Unable to drop item... where was it picked up from?
Bounce( mSock, i );
return;
}
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
SI08 rVal = toExecute->OnDrop( i, nChar ); // returns 1 if we should bounce it
switch( rVal )
{
case 1: // don't bounce, use code
case -1: // no such event
default:
break;
case 0: // bounce, no code, handle error message in client
Bounce( mSock, i );
return; // stack not deleted, as we're bouncing
case 2: // don't bounce, don't use code
return;
}
}
}
CTile& tile = Map->SeekTile( i->GetID() );
if( !nChar->AllMove() && ( i->GetMovable() == 2 || ( i->IsLockedDown() && i->GetOwnerObj() != nChar ) ||
( tile.Weight() == 255 && i->GetMovable() != 1 ) ) )
{
if( nChar->GetCommandLevel() < 2 || mSock->PickupSpot() != PL_OWNPACK )
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( nChar, i );
mSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( mSock, i );
return;
}
}
if( mSock->GetByte( 5 ) != 0xFF ) // Dropped in a specific location or on an item
{
// Let's check for a valid drop location for the item, and bounce it if none was found
if( !CheckForValidDropLocation( mSock, nChar, x, y, z ))
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( nChar, i );
Bounce( mSock, i );
mSock->sysmessage( 683 ); // There seems to be something in the way
return;
}
// New location either is not blocking, or has a surface we can put the item on, so let's find the exact Z of where to put it
// auto nCharZ = nChar->GetZ();
auto newZ = Map->StaticTop( x, y, z, nChar->WorldNumber(), 16 );
if( newZ == ILLEGAL_Z || newZ < z || newZ > z + 16 )
{
// No valid static elevation found, use dynamic elevation instead
newZ = Map->DynamicElevation( x, y, z, nChar->WorldNumber(), 16, nChar->GetInstanceID() );
if( newZ < z || newZ > z + 16 )
{
// No valid dynamic elevation found. Use map elevation instead (don't implicitly trust Z from client)
newZ = Map->MapElevation( x, y, nChar->WorldNumber() );
}
}
else
{
auto dynZ = Map->DynamicElevation( x, y, z, nChar->WorldNumber(), 16, nChar->GetInstanceID() );
newZ = (( dynZ >= z && dynZ <= z + 16 ) ? dynZ : newZ );
}
i->SetCont( nullptr );
i->SetLocation( x, y, newZ, gridLoc, nChar->WorldNumber(), nChar->GetInstanceID() );
// If item was picked up from a container and dropped on ground, update old location to match new!
if( mSock->PickupSpot() != PL_GROUND )
i->SetOldLocation( x, y, z );
}
else
{
// Dropped on a character
CChar *t = calcCharObjFromSer( dest );
if( ValidateObject( t ) )
stackDeleted = DropOnChar( mSock, t, i );
else
{
//Bounces items dropped in illegal locations in 3D UO client!!!
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( nChar, i );
mSock->sysmessage( 683 ); // There seems to be something in the way
Bounce( mSock, i );
return;
}
}
if( !stackDeleted )
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( nChar, i );
if( nChar->GetMultiObj() != nullptr )
{
CMultiObj *multi = findMulti( i );
if( ValidateObject( multi ) )
{
if( i->isDecayable() )
i->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_DECAYINHOUSE ) );
i->SetMulti( multi );
}
else if( i->isDecayable() )
i->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_DECAY ) );
}
else if( i->isDecayable() )
i->SetDecayTime( cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_DECAY ) );
i->SetHeldOnCursor( false );
Effects->itemSound( mSock, i, ( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND ) );
}
// IF client-version is above 6.0.1.7, send approval packet for dropping item. If not, don't.
CPDropItemApproved lc;
mSock->Send( &lc );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DropOnTradeWindow( CSocket& mSock, CChar& mChar, CItem& tradeWindowOne, CItem& iDropped, SI16 x, SI16 y, SI08 gridLoc )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when item is dropped in the secure trade window
//o-----------------------------------------------------------------------------------------------o
void DropOnTradeWindow( CSocket& mSock, CChar& mChar, CItem& tradeWindowOne, CItem& iDropped, SI16 x, SI16 y, SI08 gridLoc )
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
iDropped.SetCont( &tradeWindowOne );
iDropped.SetX( x );
iDropped.SetY( y );
iDropped.SetZ( 9 );
iDropped.SetGridLocation( gridLoc );
CItem *tradeWindowTwo = calcItemObjFromSer( tradeWindowOne.GetTempVar( CITV_MOREX ) );
if( ValidateObject( tradeWindowTwo ) )
{
if( tradeWindowTwo->GetTempVar( CITV_MOREZ ) || tradeWindowOne.GetTempVar( CITV_MOREZ ) )
{
tradeWindowTwo->SetTempVar( CITV_MOREZ, 0 );
tradeWindowOne.SetTempVar( CITV_MOREZ, 0 );
sendTradeStatus( tradeWindowTwo, &tradeWindowOne );
}
CChar *tw2Owner = FindItemOwner( tradeWindowTwo );
if( ValidateObject( tw2Owner ) )
{
CSocket *tw2Sock = tw2Owner->GetSocket();
if( tw2Sock != nullptr )
Effects->itemSound( tw2Sock, &tradeWindowOne, ( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND ) );
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool ValidateLockdownAccess( CChar *mChar, CSocket *mSock, CItem *itemToCheck, bool checkFriend )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Check if player has access to a locked down item as an owner, co-owner or friend
//o-----------------------------------------------------------------------------------------------o
bool ValidateLockdownAccess( CChar *mChar, CSocket *mSock, CItem *itemToCheck, bool checkFriend )
{
CMultiObj *iMulti = itemToCheck->GetMultiObj();
if( ValidateObject( iMulti ) && !iMulti->IsOwner( mChar ) && !iMulti->IsOnOwnerList( mChar )
&& ( checkFriend && !iMulti->IsOnFriendList( mChar )))
{
mSock->sysmessage( 1032 ); // This is not yours!
return false;
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DropOnSpellBook( CSocket& mSock, CChar& mChar, CItem& spellBook, CItem& iDropped )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when item is dropped on a spellbook
//o-----------------------------------------------------------------------------------------------o
void DropOnSpellBook( CSocket& mSock, CChar& mChar, CItem& spellBook, CItem& iDropped )
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
if( iDropped.GetID( 1 ) != 0x1F || iDropped.GetID( 2 ) < 0x2D || iDropped.GetID( 2 ) > 0x72 )
{
Bounce( &mSock, &iDropped );
mSock.sysmessage( 1202 ); // You can only place spell scrolls in a spellbook!
return;
}
CChar *sbOwner = FindItemOwner( &spellBook );
if( ValidateObject( sbOwner ) && sbOwner != &mChar && !mChar.CanSnoop() )
{
Bounce( &mSock, &iDropped );
mSock.sysmessage( 1203 ); // You cannot place spells in other peoples spellbooks.
return;
}
std::string name;
name.reserve( MAX_NAME );
if( iDropped.GetName()[0] == '#' )
getTileName( iDropped, name );
else
name = iDropped.GetName();
if( spellBook.GetTempVar( CITV_MORE, 1 ) == 1 ) // using more1 to "lock" a spellbook for RP purposes
{
mSock.sysmessage( 1204 ); // There are no empty pages left in your book.
Bounce( &mSock, &iDropped );
return;
}
if( name == Dictionary->GetEntry( 1605 ) ) // All-Spell Scroll
{
if( spellBook.GetSpell( 0 ) == INVALIDSERIAL && spellBook.GetSpell( 1 ) == INVALIDSERIAL && spellBook.GetSpell( 2 ) == INVALIDSERIAL )
{
mSock.sysmessage( 1205 ); // You already have a full book!
Bounce( &mSock, &iDropped );
return;
}
spellBook.SetSpell( 0, INVALIDSERIAL );
spellBook.SetSpell( 1, INVALIDSERIAL );
spellBook.SetSpell( 2, INVALIDSERIAL );
}
else
{
SI32 targSpellNum = 0;
UI16 scrollID = iDropped.GetID();
if( scrollID == 0x1F2D )
targSpellNum = 6;
else if( scrollID > 0x1F2D && scrollID < 0x1F34 )
targSpellNum = (scrollID - 0x1F2E);
else if( scrollID >= 0x1F34 && scrollID < 0x1F6D )
targSpellNum = (scrollID - 0x1F2D);
if( Magic->HasSpell( &spellBook, targSpellNum ) )
{
mSock.sysmessage( 1206 ); // You already have that spell.
Bounce( &mSock, &iDropped );
return;
}
else
Magic->AddSpell( &spellBook, targSpellNum );
}
Effects->PlaySound( &mSock, 0x0042, false );
if( iDropped.GetAmount() > 1 )
{
iDropped.IncAmount( -1 );
Bounce( &mSock, &iDropped );
}
else
iDropped.Delete();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool DropOnStack( CSocket& mSock, CChar& mChar, CItem& droppedOn, CItem& iDropped, bool &stackDeleted )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when item is dropped on a stack of items
//o-----------------------------------------------------------------------------------------------o
bool DropOnStack( CSocket& mSock, CChar& mChar, CItem& droppedOn, CItem& iDropped, bool &stackDeleted )
{
// Check if player is allowed to drop item on a locked down stack of items
if( droppedOn.IsLockedDown() && !ValidateLockdownAccess( &mChar, &mSock, &droppedOn, true ) )
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
mSock.sysmessage( 774 ); // That is locked down and you cannot use it.
Bounce( &mSock, &iDropped );
return false;
}
bool canHold = true;
if( droppedOn.GetCont() != nullptr )
{
if( droppedOn.GetContSerial() >= BASEITEMSERIAL )
canHold = Weight->checkPackWeight( &mChar, static_cast<CItem *>(droppedOn.GetCont()), &iDropped );
else
canHold = Weight->checkCharWeight( &mChar, static_cast<CChar *>(droppedOn.GetCont()), &iDropped );
}
if( !canHold )
{
if( droppedOn.GetContSerial() >= BASEITEMSERIAL )
mSock.sysmessage( 1385 ); // That pack cannot hold any more weight
else
mSock.sysmessage( 1743 ); // That person can not possibly hold more weight
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
Bounce( &mSock, &iDropped );
return false;
}
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
stackDeleted = ( doStacking( &mSock, &mChar, &iDropped, &droppedOn ) != &iDropped );
if( !stackDeleted ) // if the item didn't stack or the stack was full
Bounce( &mSock, &iDropped );
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - UI16 HandleAutoStack( CItem *mItem, CItem *mCont )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Automatically stack an item with existing items in a container
//o-----------------------------------------------------------------------------------------------o
UI16 HandleAutoStack( CItem *mItem, CItem *mCont, CSocket *mSock, CChar *mChar )
{
// Check that container can hold more weight
if( mItem != mCont->GetCont() && !Weight->checkPackWeight( mChar, mCont, mItem ))
{
if( mSock != nullptr && ValidateObject( mChar ) )
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, mItem );
mSock->sysmessage( 1385 ); // That pack cannot hold any more weight
Bounce( mSock, mItem );
return mItem->GetAmount();
}
}
// Attempt to autostack item in container
bool stackDeleted = ( autoStack( mSock, mItem, mCont ) != mItem );
if( !stackDeleted )
{
if( mSock != nullptr && ( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND ) )
{
Weight->subtractItemWeight( mChar, mItem );
}
return mItem->GetAmount();
}
return 0;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool DropOnContainer( CSocket& mSock, CChar& mChar, CItem& droppedOn, CItem& iDropped, bool &stackDeleted, SI16 x, SI16 y, SI08 gridLoc )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when item is dropped on a container
//o-----------------------------------------------------------------------------------------------o
bool DropOnContainer( CSocket& mSock, CChar& mChar, CItem& droppedOn, CItem& iDropped, bool &stackDeleted, SI16 x, SI16 y, SI08 gridLoc )
{
CChar *contOwner = FindItemOwner( &droppedOn );
if( ValidateObject( contOwner ) )
{
if( contOwner == &mChar )
{
CBaseObject *recurseCont = droppedOn.GetCont();
while( ValidateObject( recurseCont ) )
{
if( recurseCont->CanBeObjType( OT_ITEM ) )
{
CItem *recurseItem = static_cast<CItem *>(recurseCont);
if( recurseItem->GetType() == IT_TRADEWINDOW )
{
CItem *tradeWindowTwo = calcItemObjFromSer( recurseItem->GetTempVar( CITV_MOREX ) );
if( ValidateObject( tradeWindowTwo ) )
{
if( tradeWindowTwo->GetTempVar( CITV_MOREZ ) || recurseItem->GetTempVar( CITV_MOREZ ) )
{
tradeWindowTwo->SetTempVar( CITV_MOREZ, 0 );
recurseItem->SetTempVar( CITV_MOREZ, 0 );
sendTradeStatus( tradeWindowTwo, recurseItem );
}
CChar *tw2Char = FindItemOwner( tradeWindowTwo );
if( ValidateObject( tw2Char ) )
{
CSocket *tw2Sock = tw2Char->GetSocket();
if( tw2Sock != nullptr )
Effects->itemSound( tw2Sock, recurseItem, ( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND ) );
}
}
break;
}
else
recurseCont = recurseItem->GetCont();
}
else
break;
}
}
else if( contOwner->IsNpc() && contOwner->GetNPCAiType() == AI_PLAYERVENDOR )
{
if( contOwner->GetOwnerObj() == &mChar || mChar.GetCommandLevel() > CL_PLAYER )
{
mChar.SetSpeechMode( 3 );
mChar.SetSpeechItem( &iDropped );
mSock.sysmessage( 1207 );
}
else
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
mSock.sysmessage( 1630 ); // You cannot put items in other players packs!
Bounce( &mSock, &iDropped );
return false;
}
}
else if( mChar.GetCommandLevel() < CL_CNS && ( !contOwner->IsNpc() || !contOwner->IsTamed() ||
( contOwner->GetID() != 0x0123 && contOwner->GetID() != 0x0124 && contOwner->GetID() != 0x0317 ) ||
( contOwner->GetOwnerObj() != &mChar && !Npcs->checkPetFriend( &mChar, contOwner ) ) ) )
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
mSock.sysmessage( 1630 ); // You cannot put items in other players packs!
Bounce( &mSock, &iDropped );
return false;
}
}
// Check if player is allowed to drop item on container
if( droppedOn.IsLockedDown() )
{
auto iMultiObj = droppedOn.GetMultiObj();
if( ValidateObject( iMultiObj ) && iMultiObj->IsSecureContainer( &droppedOn ) )
{
if( !ValidateLockdownAccess( &mChar, &mSock, &droppedOn, true ) )
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
mSock.sysmessage( 774 ); // That is locked down and you cannot use it.
Bounce( &mSock, &iDropped );
return false;
}
}
}
// Check if container can hold more items
if( iDropped.GetContSerial() != droppedOn.GetSerial() && ( mSock.PickupSpot() != PL_OWNPACK && ( GetTotalItemCount( &droppedOn ) >= droppedOn.GetMaxItems() ||
GetTotalItemCount( &droppedOn ) + std::max(static_cast<UI32>(1), 1 + GetTotalItemCount( &iDropped )) > droppedOn.GetMaxItems() )))
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
mSock.sysmessage( 1818 ); // The container is already at capacity.
Bounce( &mSock, &iDropped );
return false;
}
if( mSock.GetByte( 5 ) != 0xFF ) // In a specific spot in a container
{
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( &mChar, &iDropped );
if( &droppedOn != iDropped.GetCont() && !Weight->checkPackWeight( &mChar, &droppedOn, &iDropped ) )
{
mSock.sysmessage( 1385 ); // That pack cannot hold any more weight
Bounce( &mSock, &iDropped );
return false;
}
iDropped.SetCont( &droppedOn );
iDropped.SetX( x );
iDropped.SetY( y );
iDropped.SetZ( 9 );
iDropped.SetGridLocation( gridLoc );
// Only send tooltip if server feature for tooltips is enabled
if( cwmWorldState->ServerData()->GetServerFeature( SF_BIT_AOS ) )
{
// Refresh target container tooltip
CPToolTip pSend( droppedOn.GetSerial(), &mSock );
mSock.Send(&pSend);
}
}
else // Drop directly on a container, placing it randomly inside
{
[[maybe_unused]] UI16 amountLeft = HandleAutoStack( &iDropped, &droppedOn, &mSock, &mChar );
// Only send tooltip if server feature for tooltips is enabled
if( cwmWorldState->ServerData()->GetServerFeature( SF_BIT_AOS ) )
{
// Refresh target container tooltip
CPToolTip pSend( droppedOn.GetSerial(), &mSock);
mSock.Send(&pSend);
}
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void DropOnItem( CSocket *mSock, SERIAL item, SERIAL dest, SI16 x, SI16 y, SI08 z, SI08 gridLoc )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped in a container or on another item
//o-----------------------------------------------------------------------------------------------o
void DropOnItem( CSocket *mSock, SERIAL item, SERIAL dest, SI16 x, SI16 y, SI08 z, SI08 gridLoc )
{
CChar *mChar = mSock->CurrcharObj();
CItem *nCont = calcItemObjFromSer( dest );
if( !ValidateObject( nCont ) || !ValidateObject( mChar ) )
return;
CItem *nItem = calcItemObjFromSer( item );
if( !ValidateObject( nItem ) )
return;
nItem->SetHeldOnCursor( false );
bool executeCont = true;
std::vector<UI16> scriptTriggers = nItem->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
SI08 rVal = toExecute->OnDropItemOnItem( nItem, mChar, nCont ); // returns 1 if we should bounce it
switch( rVal )
{
case -1: // no such event
default:
executeCont = true;
break;
case 0: // bounce, no code, handle player feedback in script
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, nItem );
Bounce( mSock, nItem );
return; // stack not deleted, as we're bouncing
case 1: // don't bounce, use code
executeCont = false;
break;
case 2: // don't bounce, don't use code
executeCont = true;
break;
}
}
}
if( executeCont )
{
std::vector<UI16> contScriptTriggers = nCont->GetScriptTriggers();
for( auto scriptTrig : contScriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
SI08 rVal = toExecute->OnDropItemOnItem( nItem, mChar, nCont ); // returns 1 if we should bounce it
switch( rVal )
{
case 1: // don't bounce, use code
case -1: // no such event
default:
break;
case 0: // bounce, no code, handle player feedback in script
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, nItem );
Bounce( mSock, nItem );
return; // stack not deleted, as we're bouncing
case 2: // don't bounce, don't use code
return;
}
}
}
}
bool stackDeleted = false;
CTile& tile = Map->SeekTile( nItem->GetID() );
if( !mChar->AllMove() && ( nItem->GetMovable() == 2 || ( nItem->IsLockedDown() && nItem->GetOwnerObj() != mChar ) ||
( tile.Weight() == 255 && nItem->GetMovable() != 1 ) ) )
{
if( mChar->GetCommandLevel() < 2 || mSock->PickupSpot() != PL_OWNPACK )
{
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, nItem );
mSock->sysmessage( 9064 ); // You cannot pick that up.
Bounce( mSock, nItem );
return;
}
}
if( nCont->GetType() == IT_TRADEWINDOW && FindItemOwner( nCont ) == mChar ) // Trade window
DropOnTradeWindow( (*mSock), (*mChar), (*nCont), (*nItem), x, y, gridLoc );
else if( nCont->GetType() == IT_TRASHCONT ) // Trash container
{
Effects->PlaySound( mSock, 0x0042, false );
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, nItem );
nItem->Delete();
mSock->sysmessage( 1201 ); // As you let go of the item it disappears.
return;
}
else if( nCont->GetType() == IT_SPELLBOOK ) // Spell Book
{
DropOnSpellBook( (*mSock), (*mChar), (*nCont), (*nItem) );
return;
}
else if( nCont->GetType() == IT_CONTAINER )
{
if( !DropOnContainer( (*mSock), (*mChar), (*nCont), (*nItem), stackDeleted, x, y, gridLoc ) )
return;
}
else if( nCont->isPileable() && nItem->isPileable() && nCont->GetID() == nItem->GetID() && nCont->GetColour() == nItem->GetColour() && nCont->GetTempVar( CITV_MORE ) == nItem->GetTempVar( CITV_MORE )
&& nCont->GetTempVar( CITV_MOREX ) == nItem->GetTempVar( CITV_MOREX ) && nCont->GetTempVar( CITV_MOREY ) == nItem->GetTempVar( CITV_MOREY ) && nCont->GetTempVar( CITV_MOREZ ) == nItem->GetTempVar( CITV_MOREZ )
&& nCont->GetBuyValue() == nItem->GetBuyValue() && nCont->GetSellValue() == nItem->GetSellValue() )
{ // Stacking
if( !DropOnStack( (*mSock), (*mChar), (*nCont), (*nItem), stackDeleted ) )
return;
}
else
{
if( nCont->GetType() == IT_SPAWNCONT || nCont->GetType() == IT_UNLOCKABLESPAWNCONT ) // - Unlocked item spawner or unlockable item spawner
nItem->SetCont( nCont );
else
nItem->SetCont( nCont->GetCont() );
nItem->SetX( x );
nItem->SetY( y );
nItem->SetZ( z );
nItem->SetGridLocation( gridLoc );
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
Weight->subtractItemWeight( mChar, nItem );
}
if( !stackDeleted )
Effects->itemSound( mSock, nItem, ( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND ) );
//Would be nice to do this to instantly update the stats & weight on the container the item is dropped on
// but unfortunately it seems like updating the container will also force-close it :/
// nCont->Dirty( UT_UPDATE );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPIDropItem::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item is dropped
//o-----------------------------------------------------------------------------------------------o
bool CPIDropItem::Handle( void )
{
CChar *ourChar = tSock->CurrcharObj();
ourChar->BreakConcentration( tSock );
if( dest >= BASEITEMSERIAL && dest != INVALIDSERIAL )
DropOnItem( tSock, item, dest, x, y, z, gridLoc );
else
Drop( tSock, item, dest, x, y, z, gridLoc );
// Display overloaded message if character is overloaded as a result of the above
if( Weight->isOverloaded( ourChar ) )
{
SI32 maxWeight = ourChar->GetStrength() * cwmWorldState->ServerData()->WeightPerStr() + 40;
SI32 currentWeight = ourChar->GetWeight() / 100;
tSock->sysmessage( 1784, currentWeight, maxWeight ); //You are overloaded. Current / Max: %i / %i
}
// Only send tooltip if server feature for tooltips is enabled
if( cwmWorldState->ServerData()->GetServerFeature( SF_BIT_AOS ) )
{
// Refresh source container tooltip
CPToolTip pSend( tSock->PickupSerial(), tSock );
tSock->Send(&pSend);
}
//Reset PickupSpot after dropping the item
tSock->PickupSpot( PL_NOWHERE );
tSock->SetCursorItem( nullptr );
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - UI08 BestSkill( CChar *mChar, SKILLVAL &skillLevel )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Return characters highest skill
//o-----------------------------------------------------------------------------------------------o
UI08 BestSkill( CChar *mChar, SKILLVAL &skillLevel )
{
UI08 retSkill = 0;
std::vector< cSkillClass > vecSkills;
for( UI08 sCtr = 0 ; sCtr < ALLSKILLS; ++sCtr )
vecSkills.push_back( cSkillClass( sCtr, mChar->GetBaseSkill( sCtr ) ) );
std::sort( vecSkills.rbegin(), vecSkills.rend() );
if( vecSkills[0].value > 0 )
{
retSkill = vecSkills[0].skill;
skillLevel = vecSkills[0].value;
}
return retSkill;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void getSkillProwessTitle( CChar *mChar, std::string &SkillProwessTitle )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Fetches the characters "prowess" and "skill" title based upon titles.dfn
//| entries and characters best skill
//o-----------------------------------------------------------------------------------------------o
void getSkillProwessTitle( CChar *mChar, std::string &SkillProwessTitle )
{
if( cwmWorldState->prowessTitles.empty() )
return;
SKILLVAL skillLevel = 0;
UI08 bestSkill = BestSkill( mChar, skillLevel );
if( skillLevel <= cwmWorldState->prowessTitles[0].lowBound )
SkillProwessTitle = cwmWorldState->prowessTitles[0].toDisplay;
else
{
size_t pEntry = 0;
for( pEntry = 0; pEntry < cwmWorldState->prowessTitles.size() - 1; ++pEntry )
{
if( skillLevel >= cwmWorldState->prowessTitles[pEntry].lowBound && skillLevel < cwmWorldState->prowessTitles[pEntry+1].lowBound )
break;
}
SkillProwessTitle = cwmWorldState->prowessTitles[pEntry].toDisplay;
}
SkillProwessTitle += " " + cwmWorldState->title[static_cast<UI08>(bestSkill + 1)].skill;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void getFameTitle( CChar *p, std::string& FameTitle )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Returns players reputation title based on their Fame and Karma
//o-----------------------------------------------------------------------------------------------o
void getFameTitle( CChar *p, std::string& FameTitle )
{
if( ValidateObject( p ) )
{
std::string thetitle;
UI08 titlenum = 0xFF;
SI16 k = p->GetKarma();
SI16 f = p->GetFame();
if( k >= 10000 )
{
if( f >= 5000 )
titlenum = 0;
else if( f >= 2500 )
titlenum = 1;
else if( f >= 1250 )
titlenum = 2;
else
titlenum = 3;
}
else if( k >= 5000 )
{
if( f >= 5000 )
titlenum = 4;
else if( f >= 2500 )
titlenum = 5;
else if( f >= 1250 )
titlenum = 6;
else
titlenum = 7;
}
else if( k >= 2500 )
{
if( f >= 5000 )
titlenum = 8;
else if( f >= 2500 )
titlenum = 9;
else if( f >= 1250 )
titlenum = 10;
else
titlenum = 11;
}
else if( k >= 1250 )
{
if( f >= 5000 )
titlenum = 12;
else if( f >= 2500 )
titlenum = 13;
else if( f >= 1250 )
titlenum = 14;
else
titlenum = 15;
}
else if( k >= 625 )
{
if( f >= 5000 )
titlenum = 16;
else if( f >= 1000 )
titlenum = 17;
else if( f >= 500 )
titlenum = 18;
else
titlenum = 19;
}
else if( k > -625 )
{
if( f >= 5000 )
titlenum = 20;
else if( f >= 2500 )
titlenum = 21;
else if( f >= 1250 )
titlenum = 22;
else
titlenum = 23;
}
else if( k > -1250 )
{
if( f >= 10000 )
titlenum = 28;
else if( f >= 5000 )
titlenum = 27;
else if( f >= 2500 )
titlenum = 26;
else if( f >= 1250 )
titlenum = 25;
else
titlenum = 24;
}
else if( k > -2500 )
{
if( f >= 5000 )
titlenum = 32;
else if( f >= 2500 )
titlenum = 31;
else if( f >= 1250 )
titlenum = 30;
else
titlenum = 29;
}
else if( k > -5000 )
{
if( f >= 10000 )
titlenum = 37;
else if( f >= 5000 )
titlenum = 36;
else if( f >= 2500 )
titlenum = 35;
else if( f >= 1250 )
titlenum = 34;
else
titlenum = 33;
}
else if( k > -10000 )
{
if( f >= 5000 )
titlenum = 41;
else if( f >= 2500 )
titlenum = 40;
else if( f >= 1250 )
titlenum = 39;
else
titlenum = 38;
}
else if( k <= -10000 )
{
if( f >= 5000 )
titlenum = 45;
else if( f >= 2500 )
titlenum = 44;
else if( f >= 1250 )
titlenum = 43;
else
titlenum = 42;
}
if( !cwmWorldState->title[titlenum].fame.empty() )
thetitle = cwmWorldState->title[titlenum].fame + " ";
if( p->GetRace() != 0 && p->GetRace() != 65535 )
thetitle = thetitle + Races->Name( p->GetRace() ) + " ";
if( f >= 10000 ) // bugfix for repsys
{
if( p->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
{
if( p->GetID( 2 ) == 0x91 )
{
FameTitle = oldstrutil::format( Dictionary->GetEntry( 1177 ), Races->Name( p->GetRace() ).c_str() ) + std::string(" ");
}
else
{
FameTitle = oldstrutil::format( Dictionary->GetEntry( 1178 ), Races->Name( p->GetRace() ).c_str() ) + std::string(" ");
}
}
else if( p->GetID( 2 ) == 0x91 )
{
FameTitle = oldstrutil::format( Dictionary->GetEntry( 1179 ), thetitle.c_str() ) + std::string(" ");
}
else
{
FameTitle = oldstrutil::format( Dictionary->GetEntry( 1180 ), thetitle.c_str() ) + std::string(" ");
}
}
else
{
if( p->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
{
FameTitle = Dictionary->GetEntry( 1181 ) + std::string(" ");
}
else if( !( thetitle = oldstrutil::removeTrailing( thetitle, "//" )).empty() )
{
FameTitle = oldstrutil::format( Dictionary->GetEntry( 1182 ), thetitle.c_str() );
}
}
}
}
//o-----------------------------------------------------------------------------------------------o
//| Function - void PaperDoll( CSocket *s, CChar *pdoll )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Open a characters paperdoll and show titles based on skill,
//| reputation, murder counts, race, ect.
//o-----------------------------------------------------------------------------------------------o
void PaperDoll( CSocket *s, CChar *pdoll )
{
CChar *myChar = s->CurrcharObj();
std::string tempstr;
CPPaperdoll pd = (*pdoll);
UnicodeTypes sLang = s->Language();
cScript *toExecute;
std::vector<UI16> scriptTriggers = pdoll->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// -1 == script doesn't exist, or returned -1
// 0 == script returned false, 0, or nothing - don't execute hard code
// 1 == script returned true or 1
if( toExecute->OnCharDoubleClick( myChar, pdoll ) == 0 )
{
// if it exists and we don't want hard code, return
return;
}
}
}
// Also check global script!
toExecute = JSMapping->GetScript( static_cast<UI16>(0) );
if( toExecute != nullptr )
{
if( toExecute->OnCharDoubleClick( myChar, pdoll ) == 0 )
{
// if it exists and we don't want hard code, return
return;
}
}
std::string SkillProwessTitle;
std::string FameTitle;
getSkillProwessTitle( pdoll, SkillProwessTitle );
getFameTitle( pdoll, FameTitle );
bool bContinue = false;
if( pdoll->IsGM() )
tempstr = pdoll->GetName() + " " + pdoll->GetTitle();
else if( pdoll->IsNpc() )
{
// Prefix name with fame title, if it exists
if( FameTitle != "" )
tempstr = FameTitle;
// Then add actual name
tempstr += pdoll->GetNameRequest( myChar );
// Append title, if one exists
if( pdoll->GetTitle() != "" )
{
std::string tempTitle = getNpcDictTitle( pdoll, s );
tempstr += " " + tempTitle;
}
}
else if( pdoll->IsDead() )
tempstr = pdoll->GetNameRequest( myChar );
// Murder tags now scriptable in SECTION MURDERER - Titles.dfn
else if( pdoll->GetKills() > cwmWorldState->ServerData()->RepMaxKills() )
{
if( cwmWorldState->murdererTags.empty() )
tempstr = oldstrutil::format( Dictionary->GetEntry( 374, sLang ), pdoll->GetNameRequest( myChar ).c_str(), pdoll->GetTitle().c_str(), SkillProwessTitle.c_str() );
else if( pdoll->GetKills() < cwmWorldState->murdererTags[0].lowBound ) // not a real murderer
bContinue = true;
else
{
SI16 mKills = pdoll->GetKills();
size_t kCtr;
for( kCtr = 0; kCtr < cwmWorldState->murdererTags.size() - 1; ++kCtr )
{
if( mKills >= cwmWorldState->murdererTags[kCtr].lowBound && mKills < cwmWorldState->murdererTags[kCtr+1].lowBound )
break;
}
if( kCtr >= cwmWorldState->murdererTags.size() )
bContinue = true;
else
tempstr = cwmWorldState->murdererTags[kCtr].toDisplay + " " + pdoll->GetNameRequest( myChar ) + ", " + pdoll->GetTitle() + SkillProwessTitle;
}
}
else if( pdoll->IsCriminal() )
tempstr = oldstrutil::format( Dictionary->GetEntry( 373, sLang ), pdoll->GetNameRequest( myChar ).c_str(), pdoll->GetTitle().c_str(), SkillProwessTitle.c_str() );
else
bContinue = true;
if( bContinue )
{
tempstr = FameTitle + pdoll->GetNameRequest( myChar ); //Repuation + Name
if( pdoll->GetTownTitle() || pdoll->GetTownPriv() == 2 ) // TownTitle
{
if( pdoll->GetTownPriv() == 2 ) // is Mayor
tempstr = oldstrutil::format( Dictionary->GetEntry( 379, sLang ), pdoll->GetNameRequest( myChar ).c_str(), cwmWorldState->townRegions[pdoll->GetTown()]->GetName().c_str(), SkillProwessTitle.c_str() );
else // is Resident
tempstr = pdoll->GetNameRequest( myChar ) + " of " + cwmWorldState->townRegions[pdoll->GetTown()]->GetName() + ", " + SkillProwessTitle;
}
else // No Town Title
{
if( !pdoll->IsIncognito() && !( pdoll->GetTitle().empty() ) ) // Titled & Skill
tempstr += " " + pdoll->GetTitle() + ", " + SkillProwessTitle;
else // Just skilled
tempstr += ", " + SkillProwessTitle;
}
}
pd.Text( tempstr );
CChar *mChar = s->CurrcharObj();
if( ValidateObject( mChar ) && ( mChar == pdoll ))
pd.FlagByte( 0x02 | ( mChar->IsAtWar() ? 0x01 : 0x00 ));
else if( ValidateObject( mChar ) && mChar->IsGM() )
pd.FlagByte( 0x02 );
s->Send( &pd );
// Only send tooltip if server feature for tooltips is enabled
if( cwmWorldState->ServerData()->GetServerFeature( SF_BIT_AOS ) )
{
for( CItem *wearItem = pdoll->FirstItem(); !pdoll->FinishedItems(); wearItem = pdoll->NextItem() )
{
if( ValidateObject( wearItem ) )
{
CPToolTip pSend( wearItem->GetSerial(), s );
s->Send( &pSend );
}
}
}
}
void MountCreature( CSocket *mSock, CChar *s, CChar *x );
//o-----------------------------------------------------------------------------------------------o
//| Function - void handleCharDoubleClick( CSocket *mSock, SERIAL serial, bool keyboard )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Handles double-clicks on a character
//o-----------------------------------------------------------------------------------------------o
void handleCharDoubleClick( CSocket *mSock, SERIAL serial, bool keyboard )
{
CChar *mChar = mSock->CurrcharObj();
CChar *c = calcCharObjFromSer( serial );
if( !ValidateObject( c ) )
return;
std::vector<UI16> scriptTriggers = c->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// -1 == script doesn't exist, or returned -1
// 0 == script returned false, 0, or nothing - don't execute hard code
// 1 == script returned true or 1
if( toExecute->OnCharDoubleClick( mChar, c ) == 0 ) // if it exists and we don't want hard code, return
return;
}
}
if( c->IsNpc() )
{
CItem *pack = nullptr;
if( cwmWorldState->creatures[c->GetID()].MountID() != 0 ) // Is a mount
{
if( ( c->IsTamed() && ( c->GetOwnerObj() == mChar || Npcs->checkPetFriend( mChar, c ) ) ) || mChar->GetCommandLevel() >= CL_GM )
{
if( objInRange( mChar, c, DIST_NEXTTILE ) )
{
if( mChar->IsDead() )
mSock->sysmessage( 381 );
else if( mChar->GetID() != mChar->GetOrgID() )
mSock->sysmessage( 380 );
else
MountCreature( mSock, mChar, c );
}
else
mSock->sysmessage( 382 );
}
else
mSock->sysmessage( 1214 );
return;
}
else if( !cwmWorldState->creatures[c->GetID()].IsHuman() && !c->IsDead() )
{
if( c->GetID() == 0x0123 || c->GetID() == 0x0124 || c->GetID() == 0x0317 ) // Is a pack animal
{
if( mChar->IsDead() )
mSock->sysmessage( 392 );
else if( !objInRange( mChar, c, DIST_NEARBY ) )
mSock->sysmessage( 382 );
else
{
pack = c->GetPackItem();
if( mChar->GetCommandLevel() >= CL_CNS || c->GetOwnerObj() == mChar || Npcs->checkPetFriend( mChar, c ) )
{
if( ValidateObject( pack ) )
mSock->openPack( pack );
else
Console << "Pack animal " << c->GetSerial() << " has no backpack!" << myendl;
}
else
{
Skills->Snooping( mSock, c, pack );
mSock->sysmessage( 383 );
}
}
return;
}
return;
}
else if( c->GetNPCAiType() == AI_PLAYERVENDOR ) // PlayerVendors
{
c->TextMessage( mSock, 385, TALK, false );
pack = c->GetPackItem();
if( ValidateObject( pack ) )
mSock->openPack( pack, true );
return;
}
}
else
{
if( mChar->GetSerial() == serial ) // Double-clicked yourself
{
if( mChar->IsOnHorse() && !keyboard )
{
DismountCreature( mChar ); // If on horse, dismount
return;
}
}
}
PaperDoll( mSock, c );
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool handleDoubleClickTypes( CSocket *mSock, CChar *mChar, CItem *iUsed, ItemTypes iType )
//| Date - 2/11/2003
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Runs a switch to match an item type to a function
//o-----------------------------------------------------------------------------------------------o
bool handleDoubleClickTypes( CSocket *mSock, CChar *mChar, CItem *iUsed, ItemTypes iType )
{
CChar *iChar = nullptr;
CItem *i = nullptr;
UI16 itemID = iUsed->GetID();
bool canTrap = false;
// Begin Check items by type
switch( iType )
{
case IT_NOTYPE:
case IT_COUNT:
case IT_HOUSEADDON:
return false;
case IT_CONTAINER: // Container, Backpack
case IT_SPAWNCONT: // Item spawn container
canTrap = true;
[[fallthrough]]; // Indicate to compiler that fallthrough is valid to suppress warning
case IT_UNLOCKABLESPAWNCONT: // Unlockable item spawn container
case IT_TRASHCONT: // Trash container
bool packOpened;
packOpened = false;
CBaseObject *baseCont;
ObjectType objType;
baseCont = FindItemOwner( iUsed, objType );
if( !ValidateObject( baseCont ) )
baseCont = iUsed;
if( ValidateObject( baseCont ) )
{
if( baseCont->CanBeObjType( OT_ITEM ) )
{
CMultiObj * baseContMultiObj = baseCont->GetMultiObj();
if( baseContMultiObj == nullptr || mChar->GetMultiObj() == baseContMultiObj )
{
if( baseContMultiObj && baseContMultiObj->IsSecureContainer( static_cast<CItem *>( baseCont ) ) && !mChar->GetMultiObj()->IsOnOwnerList( mChar ) )
{
mSock->sysmessage( 9011 ); // That container is secure. You cannot use this unless you are the owner.
return true;
}
else
{
mSock->openPack( iUsed );
packOpened = true;
}
}
}
else
{
iChar = static_cast<CChar *>(baseCont);
if( ValidateObject( iChar ) )
{
if( mChar == iChar || mChar == iChar->GetOwnerObj() )
{
if( iChar->GetNPCAiType() == AI_PLAYERVENDOR ) // PlayerVendors
mSock->openPack( iUsed, true );
else
{
if( mChar->GetPackItem() == iUsed )
mChar->SetWeight( Weight->calcCharWeight( mChar ));
mSock->openPack( iUsed );
}
}
else if( iChar->GetNPCAiType() == AI_PLAYERVENDOR ) // PlayerVendors
mSock->openPack( iUsed, true );
else
{
CItem *rootItem = FindRootContainer( iUsed );
if( ValidateObject( rootItem ) && rootItem->GetType() == IT_TRADEWINDOW )
mSock->openPack( iUsed, true );
else
Skills->Snooping( mSock, iChar, iUsed );
}
packOpened = true;
}
}
}
if( packOpened )
{
if( canTrap && iUsed->GetTempVar( CITV_MOREZ, 1 ))// Is trapped
Magic->MagicTrap( mChar, iUsed );
}
else
mSock->sysmessage( 400 );
return true;
case IT_CASTLEGATEOPENER: // Order gate opener
i = calcItemObjFromSer( iUsed->GetTempVar( CITV_MORE ) );
if( ValidateObject( i ) )
{
if( i->GetType() == IT_CASTLEGATE )
{
if( i->GetTempVar( CITV_MOREZ ) == 1 )
{
i->SetTempVar( CITV_MOREZ, 2 );
i->SetZ( i->GetZ() + 17 );
}
else if( i->GetTempVar( CITV_MOREZ ) == 2 )
{
i->SetTempVar( CITV_MOREZ, 1 );
i->SetZ( i->GetZ() - 17 );
}
}
}
return true;
case IT_LOCKEDCONTAINER: // Locked container
case IT_LOCKEDSPAWNCONT: // locked spawn container
mSock->sysmessage( 1428 );
return true;
case IT_SPELLBOOK: // Spellbook
i = mChar->GetPackItem();
if( ValidateObject( i ) )
{
if( ( iUsed->GetCont() == i || iUsed->GetCont() == mChar ) || iUsed->GetLayer() == IL_RIGHTHAND )
Magic->SpellBook( mSock );
else
mSock->sysmessage( 403 );
}
return true;
case IT_MAP: // Map
{
CPMapMessage m1;
CPMapRelated m2;
m1.KeyUsed( iUsed->GetSerial() );
m1.GumpArt( 0x139D );
m1.UpperLeft( 0, 0 );
m1.LowerRight( 0x13FF, 0x0FA0 );
m1.Dimensions( 0x0190, 0x0190 );
mSock->Send( &m1 );
m2.ID( iUsed->GetSerial() );
m2.Command( 5 );
m2.Location( 0, 0 );
m2.PlotState( 0 );
mSock->Send( &m2 );
return true;
}
case IT_BOOK: // Readable book
if( iUsed->GetTempVar( CITV_MOREX ) != 666 && iUsed->GetTempVar( CITV_MOREX ) != 999 )
Books->OpenPreDefBook( mSock, iUsed );
else
Books->OpenBook( mSock, iUsed, ( iUsed->GetTempVar( CITV_MOREX ) == 666 ) );
return true;
case IT_MAGICWAND: // Magic Wands
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
if( iUsed->GetTempVar( CITV_MOREZ ) != 0 )
{
mSock->CurrentSpellType( 2 );
if( Magic->SelectSpell( mSock, iUsed->GetTempVar( CITV_MOREY )))
{
iUsed->SetTempVar( CITV_MOREZ, iUsed->GetTempVar( CITV_MOREZ ) - 1 );
if( iUsed->GetTempVar( CITV_MOREZ ) == 0 ) // Number of charges have dropped to zero
{
iUsed->SetType( IT_NOTYPE );
iUsed->SetTempVar( CITV_MOREX, 0 );
iUsed->SetTempVar( CITV_MOREY, 0 );
iUsed->SetOffSpell( 0 );
}
//Update item so player will see how many charges remain (if any)
iUsed->Dirty( UT_UPDATE );
}
}
return true;
case IT_TOWNSTONE: // Townstone and Townstone Deed
if( itemID == 0x14F0 ) // Check for Deed
{
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
Items->CreateScriptItem( nullptr, mChar, "townstone", 1, OT_ITEM );
iUsed->Delete();
}
else // Display Townstone gump
{
CTownRegion *useRegion = iUsed->GetRegion();
if( useRegion != nullptr )
useRegion->DisplayTownMenu( iUsed, mSock );
}
return true;
case IT_RECALLRUNE: // Recall Rune
if( iUsed->GetTempVar( CITV_MOREX ) == 0 && iUsed->GetTempVar( CITV_MOREY ) == 0 && iUsed->GetTempVar( CITV_MOREZ ) == 0 ) // changed, to fix, Lord Vader
mSock->sysmessage( 431 );
else
{
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
mSock->sysmessage( 432 );
mChar->SetSpeechItem( iUsed );
mChar->SetSpeechMode( 7 );
}
return true;
case IT_OBJTELEPORTER: // Object Teleporter
if( objInRange( mChar, iUsed, DIST_NEARBY ) )
mChar->SetLocation( static_cast<SI16>(iUsed->GetTempVar( CITV_MOREX )), static_cast<SI16>(iUsed->GetTempVar( CITV_MOREY )), static_cast<SI08>(iUsed->GetTempVar( CITV_MOREZ ) ));
return true;
case IT_MAPCHANGEOBJECT:
SocketMapChange( mSock, mChar, iUsed );
return true;
case IT_MORPHOBJECT: // Morph Object
mChar->SetID( static_cast<UI16>(iUsed->GetTempVar( CITV_MOREX )) );
iUsed->SetType( IT_UNMORPHOBJECT );
return true;
case IT_UNMORPHOBJECT: // Unmorph Object
mChar->SetID( mChar->GetOrgID() );
iUsed->SetType( IT_MORPHOBJECT );
return true;
case IT_PLANK: // Planks
if( objInRange( mChar, iUsed, DIST_INRANGE ))
{
auto plankStatus = iUsed->GetTag( "plankLocked" );
if( plankStatus.m_IntValue == 1 ) // Is plank locked?
{
auto iMulti = iUsed->GetMultiObj();
auto mMulti = mChar->GetMultiObj();
if( ValidateObject( iMulti ) && ValidateObject( mMulti ) && iMulti == mMulti )
{
// Allow opening plank for disembarking purposes if player is onboard boat
switch( iUsed->GetID() )
{
// Open the plank visually
case 0x3EE9: iUsed->SetID( 0x3E84 ); break;
case 0x3EB1: iUsed->SetID( 0x3ED5 ); break;
case 0x3EB2: iUsed->SetID( 0x3ED4 ); break;
case 0x3E8A: iUsed->SetID( 0x3E89 ); break;
case 0x3E85: iUsed->SetID( 0x3E84 ); break;
// Exit from the open (but locked) plank
case 0x3E84: [[fallthrough]];
case 0x3ED5: [[fallthrough]];
case 0x3ED4: [[fallthrough]];
case 0x3E89: PlankStuff( mSock, iUsed ); break;
default: return true;
}
// Start a timer to auto-close the plank
mSock->sysmessage( 9144 ); // You open the plank, though it's still locked.
iUsed->SetTempTimer( BuildTimeValue( static_cast<R32>( 5 )));
}
else
{
// Plank is locked! Tough luck
mSock->sysmessage( 398 ); // Locked
}
}
else
{
// Plank is unlocked! Open the plank, or use it to disembark
switch( iUsed->GetID() )
{
case 0x3EE9: iUsed->SetID( 0x3E84 ); break;
case 0x3EB1: iUsed->SetID( 0x3ED5 ); break;
case 0x3EB2: iUsed->SetID( 0x3ED4 ); break;
case 0x3E8A: iUsed->SetID( 0x3E89 ); break;
case 0x3E85: iUsed->SetID( 0x3E84 ); break;
default: PlankStuff( mSock, iUsed ); return true;;
}
mSock->sysmessage( 2031 ); // You open the plank
}
}
else
{
mSock->sysmessage( 399 ); // You can't reach that!
}
return true;
case IT_FIREWORKSWAND: //Fireworks wands
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
if( iUsed->GetTempVar( CITV_MOREX ) == 0 )
{
mSock->sysmessage( 396 );
return true;
}
iUsed->SetTempVar( CITV_MOREX, iUsed->GetTempVar( CITV_MOREX ) - 1 );
mSock->sysmessage( 397, iUsed->GetTempVar( CITV_MOREX ) );
UI08 j;
for( j = 0; j < static_cast< UI08 >( RandomNum( 0, 3 ) + 2 ); ++j )
{
SI16 wx = ( mChar->GetX() + RandomNum( 0, 5 ) - 5 );
SI16 wy = ( mChar->GetY() - RandomNum( 0, 7 ) );
Effects->PlayMovingAnimation( mChar, wx, wy, mChar->GetZ() + 10, 0x36E4, 17, 0, ( RandomNum( 0, 1 ) == 1 ) );
UI16 animID;
switch( RandomNum( 0, 4 ) )
{
default:
case 0: animID = 0x373A; break;
case 1: animID = 0x374A; break;
case 2: animID = 0x375A; break;
case 3: animID = 0x376A; break;
case 4: animID = 0x377A; break;
}
Effects->PlayStaticAnimation( wx, wy, mChar->GetZ() + 10, animID, RandomNum( 0x04, 0x09 ), 30, 0 );
}
return true;
case IT_RENAMEDEED: // Rename Deed
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
mChar->SetSpeechItem( iUsed );
mChar->SetSpeechMode( 6 );
mSock->sysmessage( 434 );
return true;
case IT_TILLER: // Tillerman
{
CBoatObj *boat = static_cast<CBoatObj *>( iUsed->GetMultiObj() );
if( ValidateObject( boat ))
{
if( boat->GetMoveType() != BOAT_ANCHORED )
{
mSock->sysmessage( 2029 ); // You must lower the anchor to dock the boat.
}
else
{
if( objInRange( mChar, boat, DIST_INRANGE ) )
{
ModelBoat( mSock, boat );
}
else
{
mSock->sysmessage( 461 ); // You are too far away.
}
}
}
return true;
}
case IT_GUILDSTONE: // Guildstone Deed
if( itemID == 0x14F0 || itemID == 0x1869 ) // Check for Deed/Teleporter + Guild Type
{
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
mChar->SetSpeechItem( iUsed ); // we're using this as a generic item
GuildSys->PlaceStone( mSock, iUsed );
return true;
}
else if( itemID == 0x0ED5 ) // Check for Guildstone + Guild Type
{
mSock->TempInt( iUsed->GetTempVar( CITV_MORE ) ); // track things properly
if( mChar->GetGuildNumber() == -1 || mChar->GetGuildNumber() == static_cast<SI16>(iUsed->GetTempVar( CITV_MORE )) )
GuildSys->Menu( mSock, BasePage + 1, static_cast<GUILDID>(iUsed->GetTempVar( CITV_MORE )) ); // more of the stone is the guild number
else
mSock->sysmessage( 438 );
return true;
}
else
Console << "Unhandled guild item type named: " << iUsed->GetName() << " with ID of: " << itemID << myendl;
return true;
case IT_METALREPAIRTOOL:
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
mSock->TempObj( iUsed );
mSock->target( 0, TARGET_REPAIRMETAL, 0, 485 ); // What do we wish to repair?
return true;
case IT_FORGE: // Forges
mSock->TempObj( iUsed );
mSock->target( 0, TARGET_SMELT, 0, 440 );
return true;
case IT_DYE: // Dye
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
mSock->TempObj( iUsed );
mSock->DyeAll( 0 );
mSock->target( 0, TARGET_DYEALL, 0, 441 );
return true;
case IT_DYEVAT: // Dye vat
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
mSock->TempObj( iUsed );
mSock->AddID1( iUsed->GetColour( 1 ) );
mSock->AddID2( iUsed->GetColour( 2 ) );
mSock->target( 0, TARGET_DVAT, 0, 442 );
return true;
case IT_MODELMULTI: // Model boat/Houses
if( iType != IT_TOWNSTONE && iType != IT_GUILDSTONE )
{
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
if( iUsed->GetTempVar( CITV_MOREX ) == 0 )
break;
mChar->SetSpeechItem( iUsed );
DoHouseTarget( mSock, static_cast<UI08>(iUsed->GetTempVar( CITV_MOREX )) );
}
return true;
case IT_SMITHYTOOL:
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
mSock->TempObj( iUsed );
mSock->target( 0, TARGET_SMITH, 0, 444 );
return true;
case IT_MININGTOOL: // Mining
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
// Don't allow using tools with no usage left, if usage limits are enabled in ini
if( cwmWorldState->ServerData()->ToolUseLimit() && iUsed->GetUsesLeft() == 0 )
{
mSock->sysmessage( 9262 ); // This has no more charges
return true;
}
mSock->TempObj( iUsed );
mSock->target( 0, TARGET_MINE, 0, 446 );
return true;
case IT_EMPTYVIAL: // empty vial
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, true ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
i = mChar->GetPackItem();
if( ValidateObject( i ) )
{
if( iUsed->GetCont() == i )
{
mSock->TempObj( iUsed );
mSock->target( 0, TARGET_VIAL, 0, 447 );
}
else
mSock->sysmessage( 448 );
}
return true;
case IT_ORE: // smelt ore
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
if( iUsed->GetID() == 0x19B7 && iUsed->GetAmount() < 2 )
{
mSock->sysmessage( 1814 ); // Too little ore to smelt!
}
else
{
mSock->TempObj( iUsed );
if( iUsed->GetID() == 0x19B7 )
mSock->target( 0, TARGET_SMELTORE, 0, 460 ); // Select the forge on which to smelt the ore.
else
mSock->target( 0, TARGET_SMELTORE, 0, 1815 ); // Select the forge on which to smelt the ore, or another pile of ore with which to combine it.
}
return true;
case IT_MESSAGEBOARD: // Message board opening
if( objInRange( mChar, iUsed, DIST_NEARBY ) )
{
CPIMsgBoardEvent *mbEvent = new CPIMsgBoardEvent( mSock, false );
mbEvent->Handle();
}
else
mSock->sysmessage( 461 );
return true;
case IT_MAGICSTATUE:
if( Skills->CheckSkill( mChar, ITEMID, 0, 10 ) )
{
if( itemID == 0x1508 )
iUsed->SetID( 0x1509 );
else
iUsed->SetID( 0x1508 );
}
else
mSock->sysmessage( 465 );
return true;
case IT_GUILLOTINE:
if( Skills->CheckSkill( mChar, ITEMID, 0, 10 ) )
{
if( itemID == 0x1245 )
iUsed->SetID( 0x1230 );
else
iUsed->SetID( 0x1245 );
}
else
mSock->sysmessage( 466 );
return true;
case IT_HAIRDYE: // Hair Dye
// If item is locked down, check if player has access to use it
if( iUsed->IsLockedDown() && !ValidateLockdownAccess( mChar, mSock, iUsed, false ))
return true;
// Don't allow using GM-locked down tools
if( iUsed->GetMovable() == 2 )
{
mSock->sysmessage( 1032 ); // This is not yours!
return true;
}
mSock->TempObj( iUsed );
BuildGumpFromScripts( mSock, 6 );
return true;
default:
if( iType )
Console << "Unhandled item type for item: " << iUsed->GetName() << "[" << iUsed->GetSerial() << "] of type: " << static_cast<UI16>(iType) << myendl;
break;
}
return false;
}
std::map< std::string, ItemTypes > tagToItemType;
void InitTagToItemType( void )
{
tagToItemType["CONTAINER"] = IT_CONTAINER;
tagToItemType["CASTLEGATEOPENER"] = IT_CASTLEGATEOPENER;
tagToItemType["CASTLEGATE"] = IT_CASTLEGATE;
tagToItemType["TELEPORTITEM"] = IT_TELEPORTITEM;
tagToItemType["KEY"] = IT_KEY;
tagToItemType["LOCKEDCONTAINER"] = IT_LOCKEDCONTAINER;
tagToItemType["SPELLBOOK"] = IT_SPELLBOOK;
tagToItemType["MAP"] = IT_MAP;
tagToItemType["BOOK"] = IT_BOOK;
tagToItemType["DOOR"] = IT_DOOR;
tagToItemType["LOCKEDDOOR"] = IT_LOCKEDDOOR;
tagToItemType["FOOD"] = IT_FOOD;
tagToItemType["MAGICWAND"] = IT_MAGICWAND;
tagToItemType["RESURRECTOBJECT"] = IT_RESURRECTOBJECT;
tagToItemType["CRYSTALBALL"] = IT_CRYSTALBALL;
tagToItemType["POTION"] = IT_POTION;
tagToItemType["TOWNSTONE"] = IT_TOWNSTONE;
tagToItemType["RECALLRUNE"] = IT_RECALLRUNE;
tagToItemType["GATE"] = IT_GATE;
tagToItemType["OBJTELEPORTER"] = IT_OBJTELEPORTER;
tagToItemType["ITEMSPAWNER"] = IT_ITEMSPAWNER;
tagToItemType["NPCSPAWNER"] = IT_NPCSPAWNER;
tagToItemType["SPAWNCONT"] = IT_SPAWNCONT;
tagToItemType["LOCKEDSPAWNCONT"] = IT_LOCKEDSPAWNCONT;
tagToItemType["UNLOCKABLESPAWNCONT"] = IT_UNLOCKABLESPAWNCONT;
tagToItemType["AREASPAWNER"] = IT_AREASPAWNER;
tagToItemType["ADVANCEGATE"] = IT_ADVANCEGATE;
tagToItemType["MULTIADVANCEGATE"] = IT_MULTIADVANCEGATE;
tagToItemType["MONSTERGATE"] = IT_MONSTERGATE;
tagToItemType["RACEGATE"] = IT_RACEGATE;
tagToItemType["DAMAGEOBJECT"] = IT_DAMAGEOBJECT;
tagToItemType["TRASHCONT"] = IT_TRASHCONT;
tagToItemType["SOUNDOBJECT"] = IT_SOUNDOBJECT;
tagToItemType["MAPCHANGEOBJECT"] = IT_MAPCHANGEOBJECT;
tagToItemType["WORLDCHANGEGATE"] = IT_WORLDCHANGEGATE;
tagToItemType["MORPHOBJECT"] = IT_MORPHOBJECT;
tagToItemType["UNMORPHOBJECT"] = IT_UNMORPHOBJECT;
tagToItemType["ZEROKILLSGATE"] = IT_ZEROKILLSGATE;
tagToItemType["PLANK"] = IT_PLANK;
tagToItemType["FIREWORKSWAND"] = IT_FIREWORKSWAND;
tagToItemType["ESCORTNPCSPAWNER"] = IT_ESCORTNPCSPAWNER;
tagToItemType["RENAMEDEED"] = IT_RENAMEDEED;
tagToItemType["TILLER"] = IT_TILLER;
tagToItemType["GUILDSTONE"] = IT_GUILDSTONE;
tagToItemType["HOUSESIGN"] = IT_HOUSESIGN;
tagToItemType["METALREPAIRTOOL"] = IT_METALREPAIRTOOL;
tagToItemType["FORGE"] = IT_FORGE;
tagToItemType["DYE"] = IT_DYE;
tagToItemType["DYEVAT"] = IT_DYEVAT;
tagToItemType["MODELMULTI"] = IT_MODELMULTI;
tagToItemType["PLAYERVENDORDEED"] = IT_PLAYERVENDORDEED;
tagToItemType["SMITHYTOOL"] = IT_SMITHYTOOL;
tagToItemType["MININGTOOL"] = IT_MININGTOOL;
tagToItemType["EMPTYVIAL"] = IT_EMPTYVIAL;
tagToItemType["CANNONBALL"] = IT_CANNONBALL;
tagToItemType["ORE"] = IT_ORE;
tagToItemType["MESSAGEBOARD"] = IT_MESSAGEBOARD;
tagToItemType["MAGICSTATUE"] = IT_MAGICSTATUE;
tagToItemType["GUILLOTINE"] = IT_GUILLOTINE;
tagToItemType["FISHINGPOLE"] = IT_FISHINGPOLE;
tagToItemType["CLOCK"] = IT_CLOCK;
tagToItemType["SEXTANT"] = IT_SEXTANT;
tagToItemType["HAIRDYE"] = IT_HAIRDYE;
}
ItemTypes FindItemTypeFromTag( const std::string &strToFind )
{
if( tagToItemType.empty() ) // if we haven't built our array yet
InitTagToItemType();
std::map< std::string, ItemTypes >::const_iterator toFind = tagToItemType.find( oldstrutil::upper(strToFind) );
if( toFind != tagToItemType.end() )
return toFind->second;
return IT_COUNT;
}
std::map< UI16, ItemTypes > idToItemType;
//======================================================================================
auto InitIDToItemType() ->void {
auto Itemtypes = FileLookup->FindEntry( "ITEMTYPES", items_def );
if( Itemtypes){
SI32 sectionCount;
ItemTypes iType = IT_COUNT;
for (const auto &sec : Itemtypes->collection()){
auto tag = sec->tag ;
auto data = sec->data ;
auto comma_secs = oldstrutil::sections( data, "," );
iType = FindItemTypeFromTag( tag );
if( iType != IT_COUNT ) {
sectionCount = static_cast<SI32>(comma_secs.size() - 1);
if( sectionCount != 0 ) {
for( SI32 i = 0; i <= sectionCount; i++ ) {
idToItemType[ oldstrutil::value<std::uint16_t>(oldstrutil::extractSection(data, ",", i, i ), 16 )] = iType;
}
}
else {
idToItemType[static_cast<UI16>(std::stoul(data, nullptr, 16))] = iType;
}
}
}
}
}
ItemTypes FindItemTypeFromID( UI16 idToFind )
{
if( idToItemType.empty() ) // if we haven't built our array yet
InitIDToItemType();
std::map< UI16, ItemTypes >::const_iterator toFind = idToItemType.find( idToFind );
if( toFind != idToItemType.end() )
return toFind->second;
return IT_COUNT;
}
ItemTypes findItemType( CItem *i )
{
ItemTypes iType = i->GetType();
switch( iType )
{
case IT_NOTYPE:
case IT_COUNT:
iType = FindItemTypeFromID( i->GetID() ); break;
default: break;
}
return iType;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool ItemIsUsable( CSocket *tSock, CChar *ourChar, CItem *iUsed, ItemTypes iType )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Checks if item is usable and if player can actually use it
//o-----------------------------------------------------------------------------------------------o
bool ItemIsUsable( CSocket *tSock, CChar *ourChar, CItem *iUsed, ItemTypes iType )
{
if( ourChar->IsDead() && iType != IT_PLANK && iType != IT_HOUSESIGN )
{
if( iType == IT_RESURRECTOBJECT ) // Check for a resurrect item type
{
NpcResurrectTarget( ourChar );
tSock->sysmessage( 390 ); // You have been resurrected.
}
else // If it's not a ressurect item, and you're dead, forget it
tSock->sysmessage( 392 ); // You are dead and cannot do that.
return false;
}
// Range check for double clicking on items
if( iType != IT_PLANK && iType != IT_TILLER && iType != IT_HOUSESIGN )
{
bool canUse = checkItemRange( ourChar, iUsed );
if( canUse )
canUse = (iType == IT_DOOR || iType == IT_LOCKEDDOOR || checkItemLineOfSight( ourChar, iUsed ) );
if( !canUse )
{
tSock->sysmessage( 389 ); // That is too far away and you cannot reach it.
return false;
}
}
if( ourChar->GetCommandLevel() < CL_CNS )
{
if( ( tSock->GetTimer( tPC_OBJDELAY ) >= cwmWorldState->GetUICurrentTime() || cwmWorldState->GetOverflow() ) )
{
if( !tSock->ObjDelayMsgShown() )
{
tSock->sysmessage( 386 ); // You must wait to perform another action.
tSock->ObjDelayMsgShown( true );
}
return false;
}
tSock->SetTimer( tPC_OBJDELAY, cwmWorldState->ServerData()->BuildSystemTimeValue( tSERVER_OBJECTUSAGE ) );
tSock->ObjDelayMsgShown( false );
CChar *iChar = nullptr;
if( iUsed->GetCont() != nullptr )
{
iChar = FindItemOwner( iUsed );
if( ValidateObject( iChar ) && iChar != ourChar &&
!( iUsed->IsContType() && cwmWorldState->ServerData()->RogueStatus() ) )
{
tSock->sysmessage( 387 ); // You cannot use items equipped by other players.
return false;
}
}
// Check if item is in a house?
else if( iType != IT_DOOR && iType != IT_LOCKEDDOOR && iType != IT_PLANK && iType != IT_HOUSESIGN && iType != IT_TILLER )
{
if( iUsed->GetMultiObj() != nullptr && iUsed->GetMultiObj() != ourChar->GetMultiObj() )
{
tSock->sysmessage( 389 ); // That is too far away and you cannot reach it.
return false;
}
}
iChar = iUsed->GetOwnerObj();
if( iUsed->isCorpse() )
{
if( cwmWorldState->ServerData()->LootingIsCrime() )
{
bool willCrim = false;
if( ValidateObject( iChar ) )
{
// if the corpse is from an innocent player, and is not our own corpse if( otherCheck
// and if the corpse is not from an enemy/allied guild && guildCheck
// and if the races are not allied/enemy && raceCheck )
willCrim = WillResultInCriminal( ourChar, iChar );
}
else
willCrim = ( (iUsed->GetTempVar( CITV_MOREZ )&0x04) == 0x04 );
if( willCrim )
criminal( ourChar );
}
if( ValidateObject( iChar ) )
{
CTownRegion *itemTownRegion = iUsed->GetRegion();
if( !itemTownRegion->IsGuarded() && !itemTownRegion->IsSafeZone() )
{
if( iChar->IsGuarded() ) // Is the corpse being guarded?
Combat->petGuardAttack( ourChar, iChar, iChar );
else if( iUsed->isGuarded() )
Combat->petGuardAttack( ourChar, iChar, iUsed );
}
}
}
else if( iUsed->isGuarded() )
{
CMultiObj *multi = iUsed->GetMultiObj();
if( ValidateObject( multi ) )
{
CChar *multiOwner = multi->GetOwnerObj();
if( ValidateObject( multiOwner ) && multiOwner != ourChar )
Combat->petGuardAttack( ourChar, multiOwner, iUsed );
}
else
{
CTownRegion *itemTownRegion = iUsed->GetRegion();
if( !itemTownRegion->IsGuarded() && !itemTownRegion->IsSafeZone() )
{
if( ValidateObject( iChar ))
Combat->petGuardAttack( ourChar, iChar, iUsed );
}
}
}
}
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPIDblClick::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Player double clicks on a character or item
//| Changes - 09/22/2002 - Removed piece of code which is not needed. Cooking of raw
//| fish shouldn't produce cooked ribs, people can cook the fish after filleting with a knife.
//o-----------------------------------------------------------------------------------------------o
bool CPIDblClick::Handle( void )
{
CChar *ourChar = tSock->CurrcharObj();
ourChar->BreakConcentration( tSock );
UI08 a1 = tSock->GetByte( 1 );
bool keyboard = ( (a1 & 0x80) == 0x80 );
if( keyboard )
objectID -= 0x80000000;
if( objectID < BASEITEMSERIAL )
{
handleCharDoubleClick( tSock, objectID, keyboard );
return true;
}
// Begin Items / Guildstones Section
CItem *iUsed = calcItemObjFromSer( objectID );
if( !ValidateObject( iUsed ) )
return true;
ItemTypes iType = findItemType( iUsed );
bool scriptExecuted = false;
std::vector<UI16> scriptTriggers = iUsed->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
// First loop through all scripts, checking for OnUseUnChecked events, which should run before
// item usage check is performed
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr ) // Was a script found?
{
// If retVal is -1, event doesn't exist in script
// If retVal is 0, event exists, but returned false/0, and handles item usage. Don't proceed with hard code (or other scripts!)
// If retVal is 1, event exists, proceed with hard code/other scripts
SI08 retVal = toExecute->OnUseUnChecked( ourChar, iUsed );
if( retVal == 1 )
{
// Event exists, and returned 1 - proceed with other scripts/hard code
scriptExecuted = true;
}
else if( retVal == 0 )
{
// Event exists, and returned 0 - script handles usage, don't continue!
return true;
}
}
}
bool itemUsageCheckComplete = false;
// Then loop through all scripts again, checking for OnUseChecked event - but first run item usage check
// once to make sure player can actually use item!
// If it hasn't been done at least once already, run the usual item-usage checks
if( !itemUsageCheckComplete )
{
if( !ItemIsUsable( tSock, ourChar, iUsed, iType ) )
{
return true;
}
// If item is disabled, don't continue
if( iUsed->isDisabled() )
{
tSock->sysmessage( 394 ); // You cannot do that at the moment.
return true;
}
// Item-usage check completed!
itemUsageCheckComplete = true;
}
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// Check if OnUseChecked event is present in script
SI08 retVal = toExecute->OnUseChecked( ourChar, iUsed );
if( retVal == 1 )
{
// Event exists, and returned 1 - proceed with other scripts/hard code
scriptExecuted = true;
}
else if( retVal == 0 )
{
// Script returned 0 - don't continue, usage is handled in script!
return true;
}
}
}
// If no scripts were found, or if no script contained onUse events, proceed with envoke stuff
if( scriptTriggers.size() == 0 || !scriptExecuted )
{
UI16 itemID = iUsed->GetID();
cScript *toExecute = nullptr;
UI16 envTrig = 0;
if( JSMapping->GetEnvokeByType()->Check( static_cast<UI16>(iType) ) )
{
// Get script to run by item type
envTrig = JSMapping->GetEnvokeByType()->GetScript( static_cast<UI16>(iType) );
toExecute = JSMapping->GetScript( envTrig );
}
else if( JSMapping->GetEnvokeByID()->Check( itemID ) )
{
// Get script to run by item ID
envTrig = JSMapping->GetEnvokeByID()->GetScript( itemID );
toExecute = JSMapping->GetScript( envTrig );
}
// Check for the onUse events in envoke scripts!
if( toExecute != nullptr )
{
if( toExecute->OnUseUnChecked( ourChar, iUsed ) == 0 )
return true;
if( toExecute->OnUseChecked( ourChar, iUsed ) == 0 )
return true;
}
}
if( handleDoubleClickTypes( tSock, ourChar, iUsed, iType ) )
return true;
tSock->sysmessage( 486 );
return true;
}
//o-----------------------------------------------------------------------------------------------o
//| Function - const char *AppendData( CItem *i, std::string currentName )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Add data onto the end of the string in singleclick() based on an items type
//| This is used by legacy singleclick names that don't show tooltips
//o-----------------------------------------------------------------------------------------------o
const char *AppendData( CSocket *s, CItem *i, std::string &currentName )
{
std::string dataToAdd;
switch( i->GetType() )
{
case IT_CONTAINER:
case IT_SPAWNCONT:
case IT_UNLOCKABLESPAWNCONT:
dataToAdd = std::string( " (" ) + oldstrutil::number( static_cast<SI32>( i->GetContainsList()->Num() )) + std::string( " items, " );
dataToAdd += oldstrutil::number(( i->GetWeight() / 100 )) + std::string( " stones)" );
break;
case IT_LOCKEDCONTAINER: // containers
case IT_LOCKEDSPAWNCONT: // spawn containers
dataToAdd = std::string( " (" ) + oldstrutil::number( static_cast<SI32>( i->GetContainsList()->Num() )) + std::string( " items, " );
dataToAdd += oldstrutil::number(( i->GetWeight() / 100 )) + std::string( " stones) " + Dictionary->GetEntry( 9050, s->Language() )); // [Locked]
break;
case IT_LOCKEDDOOR:
dataToAdd = " " + Dictionary->GetEntry( 9050 ); // [Locked]
break;
case IT_RECALLRUNE:
case IT_GATE:
case IT_OBJTELEPORTER:
{
CTownRegion *newRegion = calcRegionFromXY( static_cast<SI16>( i->GetTempVar( CITV_MOREX )), static_cast<SI16>( i->GetTempVar( CITV_MOREY )), i->WorldNumber(), i->GetInstanceID() );
dataToAdd = std::string( " (" ) + newRegion->GetName() + std::string( ")" );
break;
}
default:
break;
}
if( i->IsLockedDown() )
{
auto iMultiObj = i->GetMultiObj();
if( ValidateObject( iMultiObj ) && iMultiObj->IsSecureContainer( i ))
{
dataToAdd += Dictionary->GetEntry( 9052, s->Language() ); // [locked down & secure]
}
else
{
dataToAdd += Dictionary->GetEntry( 9053, s->Language() ); // [locked down]
}
}
if( i->isGuarded() )
{
CTownRegion *itemTownRegion = i->GetRegion();
if( !itemTownRegion->IsGuarded() && !itemTownRegion->IsSafeZone() )
{
dataToAdd += " " + Dictionary->GetEntry( 9051, s->Language() ); // [Guarded]
}
}
if( i->isNewbie() )
{
dataToAdd += " " + Dictionary->GetEntry( 9055, s->Language() ); // [Blessed]
}
// Is the item a magical item, and unidentified?
if( !i->isCorpse() && i->GetName2() != "#" && i->GetName2() != "" )
{
s->objMessage( Dictionary->GetEntry( 9402, s->Language() ), i ); // [Unidentified]
}
currentName += dataToAdd;
// Question: Do we put the creator thing here, saves some redundancy a bit later
return currentName.c_str();
}
//o-----------------------------------------------------------------------------------------------o
//| Function - bool CPISingleClick::Handle( void )
//o-----------------------------------------------------------------------------------------------o
//| Purpose - Called when an item or character is single-clicked (also used for AllNames macro)
//o-----------------------------------------------------------------------------------------------o
bool CPISingleClick::Handle( void )
{
if( objectID == INVALIDSERIAL ) // invalid
return true;
if( objectID < BASEITEMSERIAL )
{
// Begin chars/npcs section
CChar *c = calcCharObjFromSer( objectID );
if( ValidateObject( c ))
{
std::vector<UI16> scriptTriggers = c->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/1, don't show hard-coded name, and don't run additional onClick events
if( toExecute->OnClick( tSock, c ) == 1 )
{
return true;
}
}
}
tSock->ShowCharName( c, false );
}
//End chars/npcs section
return true;
}
std::string temp2;
std::string realname;
CChar *mChar = tSock->CurrcharObj();
//Begin items section
UI08 a1 = tSock->GetByte( 1 );
UI08 a2 = tSock->GetByte( 2 );
UI08 a3 = tSock->GetByte( 3 );
UI08 a4 = tSock->GetByte( 4 );
CItem *i = calcItemObjFromSer( objectID );
if( !ValidateObject( i )) // invalid item
return true;
// October 6, 2002 - Added support for the onClick event
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
for( auto scriptTrig : scriptTriggers )
{
cScript *toExecute = JSMapping->GetScript( scriptTrig );
if( toExecute != nullptr )
{
// If script returns true/0, don't show hard-coded name, and don't run additional onClick events
if( toExecute->OnClick( tSock, i ) == 1 )
{
return true;
}
}
}
if( mChar->GetSingClickSer() )
tSock->objMessage( 1737, i, 0.0f, 0x03B2, a1, a2, a3, a4 );
UI16 getAmount = i->GetAmount();
if( i->GetCont() != nullptr && i->GetContSerial() >= BASEITEMSERIAL )
{
CChar *w = FindItemOwner( static_cast<CItem *>( i->GetCont() ));
if( ValidateObject( w ))
{
if( w->GetNPCAiType() == AI_PLAYERVENDOR )
{
std::string temp;
temp = "Desc: " + i->GetDesc() + std::string( " at " ) + std::to_string( i->GetBuyValue() ) + std::string( "gp" );
tSock->objMessage( temp, i );
}
}
}
#if defined( _MSC_VER )
#pragma todo( "We need to update this to use getTileName almost exclusively, for plurality" )
#endif
if( i->GetNameRequest( tSock->CurrcharObj() )[0] != '#' )
{
if( i->GetID() == 0x0ED5 ) //guildstone
{
realname = oldstrutil::format( Dictionary->GetEntry( 101, tSock->Language() ).c_str(), i->GetNameRequest( tSock->CurrcharObj() ).c_str() );
}
if( !i->isPileable() || getAmount == 1 )
{
if( mChar->IsGM() && !i->isCorpse() && getAmount > 1 )
{
realname = oldstrutil::format( "%s (%u)", i->GetNameRequest( tSock->CurrcharObj() ).c_str(), getAmount );
}
else
{
realname = i->GetNameRequest( tSock->CurrcharObj() );
}
}
else
{
realname = oldstrutil::format( "%u %ss", getAmount, i->GetNameRequest( tSock->CurrcharObj() ).c_str() );
}
}
else
{
getTileName( (*i), realname );
if( i->GetAmount() > 1 )
realname = std::to_string( getAmount ) + std::string(" ") + realname;
}
if( i->GetType() == IT_MAGICWAND )
{
if( i->GetName2() == "#" || i->GetName2() == "" )
{
realname += oldstrutil::format( Dictionary->GetEntry( 9404, tSock->Language() ), i->GetTempVar( CITV_MOREZ )); // with %d charges
}
else
{
realname += " (unidentified)";
}
}
else if( i->IsContType() )
{
SI32 iCount = 0;
if( i->isCorpse() )
{
iCount = static_cast<SI32>( GetTotalItemCount( i ));
}
else
{
iCount = static_cast<SI32>( i->GetContainsList()->Num() );
}
realname += oldstrutil::format( ", (%u items, %u stones)", iCount, ( i->GetWeight() / 100 ));
}
// Use item's real name as starting point for final string
temp2 = realname;
if( i->GetCreator() != INVALIDSERIAL )
{
CChar *mCreator = calcCharObjFromSer( i->GetCreator() );
if( ValidateObject( mCreator ))
{
if( cwmWorldState->ServerData()->RankSystemStatus() && i->GetRank() == 10 )
{
std::string creatorName = getNpcDictName( mCreator, tSock );
temp2 += oldstrutil::format( " %s", Dictionary->GetEntry( 9140, tSock->Language() ).c_str() ); // of exceptional quality
if( cwmWorldState->ServerData()->DisplayMakersMark() && i->isMarkedByMaker() )
{
temp2 += oldstrutil::format( " %s by %s", cwmWorldState->skill[i->GetMadeWith()-1].madeword.c_str(), creatorName.c_str() ); // [crafted] by %s
}
}
}
}
tSock->objMessage( temp2, i );
std::string itemTags = "";
tSock->objMessage( AppendData( tSock, i, itemTags ), i );
return true;
}