mirror of
https://github.com/UOX3DevTeam/UOX3
synced 2026-08-13 12:27:04 -04:00
Fixed an issue with poison damage in combat where the poison resist of the attacker was checked instead of that of the defender Fixed an issue where the poison details in tooltip of an item would only be visible when equipped, and not visible when kept in backpack Fixed an issue where if a weapon was destroyed due to corrosion damage, it would not unset the poison strength on the character who wielded it Added new Character/Item property to track the source (serial) of poison applied to an object. This is available as a JS Char/Item property: .poisonedBy // serial of character who applied poison to an object. Set to 0xFFFFFFFF/SE_INVALIDSERIAL() to clear Updated all code and scripts related to setting/unsetting poison effect on objects to also update the .poisonedBy property Serial of character who poisoned a character who died while poisoned is now stored on the corpse Added third optional parameter to Character JS Method .SetPoisoned() that can be used to specify source char of applied poison. New syntax: .SetPoisoned( poisonLevel, wearOffTime[, poisonSourceChar ])
4036 lines
122 KiB
C++
4036 lines
122 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 "CGump.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, UI16 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 - Bounce()
|
|
//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 - PickupBounce()
|
|
//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 - DoStacking()
|
|
//| 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, [[maybe_unused]] 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 - AutoStack()
|
|
//| 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 ))
|
|
return nullptr;
|
|
|
|
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;
|
|
}
|
|
|
|
auto FindNearbyChars( SI16 x, SI16 y, UI08 worldNumber, UI16 instanceId, UI16 distance ) -> std::vector<CChar *>;
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CPIGetItem::Handle()
|
|
//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->SendTargetCursor( 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 );
|
|
ourChar->Dirty( UT_UPDATE );
|
|
}
|
|
}
|
|
|
|
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 ))
|
|
{
|
|
if( iOwner->CanBeObjType( OT_CHAR ) && static_cast<CChar *>( iOwner )->GetNpcAiType() == AI_PLAYERVENDOR )
|
|
{
|
|
tSock->SysMessage( 9175 ); // If you'd like to purchase an item, just say so.
|
|
}
|
|
else
|
|
{
|
|
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
|
|
MakeCriminal( 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() < CL_GM || tSock->PickupSpot() != PL_OWNPACK )
|
|
{
|
|
tSock->SysMessage( 9064 ); // You cannot pick that up.
|
|
if( ourChar->GetCommandLevel() >= CL_GM )
|
|
{
|
|
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.
|
|
}
|
|
}
|
|
|
|
// Trigger pickup event for item being picked up
|
|
std::vector<UI16> scriptTriggers = i->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
SI08 retStatus = toExecute->OnPickup( i, ourChar, iCont );
|
|
|
|
// -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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trigger pickup event for character picking up item
|
|
scriptTriggers.clear();
|
|
scriptTriggers = ourChar->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
SI08 retStatus = toExecute->OnPickup( i, ourChar, iCont );
|
|
|
|
// -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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Trigger pickup event for potential container item is being picked up from
|
|
if( ValidateObject( iCont ) && iCont != nullptr )
|
|
{
|
|
// Trigger pickup event for character picking up item
|
|
scriptTriggers.clear();
|
|
scriptTriggers = iCont->GetScriptTriggers();
|
|
for( auto scriptTrig : scriptTriggers )
|
|
{
|
|
cScript *toExecute = JSMapping->GetScript( scriptTrig );
|
|
if( toExecute != nullptr )
|
|
{
|
|
SI08 retStatus = toExecute->OnPickup( i, ourChar, iCont );
|
|
|
|
// -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( i->IsCorpse() )
|
|
{
|
|
// Store temp tag on corpse with serial of player who looted the corpse last
|
|
TAGMAPOBJECT tagObject;
|
|
tagObject.m_Destroy = false;
|
|
tagObject.m_StringValue = oldstrutil::number( ourChar->GetSerial() );
|
|
tagObject.m_IntValue = 0;
|
|
tagObject.m_ObjectType = TAGMAP_TYPE_INT;
|
|
i->SetTempTag( "lootedBy", tagObject );
|
|
}
|
|
|
|
if( iCont == nullptr )
|
|
{
|
|
// Remove item from mapregion it was in before being picked up
|
|
MapRegion->RemoveItem( i );
|
|
|
|
// Add item to mapregion of character who picked up item instead
|
|
MapRegion->AddItem( 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( iCont != nullptr )
|
|
{
|
|
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 - CPIEquipItem::Handle()
|
|
//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;
|
|
const SI16 scaledStrength = ( i->GetStrength() * ( 100 - i->GetLowerStatReq() )) / 100;
|
|
const SI16 scaledDexterity = ( i->GetDexterity() * ( 100 - i->GetLowerStatReq() )) / 100;
|
|
const SI16 scaledIntelligence = ( i->GetIntelligence() * ( 100 - i->GetLowerStatReq() )) / 100;
|
|
|
|
if( scaledStrength > 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( scaledDexterity > k->GetDexterity() )
|
|
{
|
|
tSock->SysMessage( 1189 ); // You are not agile enough to use that.
|
|
}
|
|
else if( scaledIntelligence > 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->SendTargetCursor( 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 );
|
|
ourChar->Dirty( UT_UPDATE );
|
|
}
|
|
}
|
|
|
|
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 - DropOnPC()
|
|
//| 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 - IsOnFoodList()
|
|
//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 - DropOnNPC()
|
|
//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, ACT_ANIMAL_EAT, 0, 5 );
|
|
}
|
|
|
|
// Restore loyalty upon feeding pet
|
|
if( targNPC->GetLoyalty() < targNPC->GetMaxLoyalty() )
|
|
{
|
|
if( cwmWorldState->ServerData()->ExpansionCoreShardEra() >= ER_AOS )
|
|
{
|
|
// Post-AoS (Pub16), restore loyalty to max upon feeding, regardless of amount
|
|
targNPC->SetLoyalty( targNPC->GetMaxLoyalty() );
|
|
}
|
|
else
|
|
{
|
|
// Pre-AoS (Pub16), restore loyalty by 10 each time pet is fed, regardless of amount
|
|
targNPC->SetLoyalty( targNPC->GetLoyalty() + 10 );
|
|
}
|
|
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 );
|
|
targNPC->SetPoisonedBy( mChar->GetSerial() );
|
|
|
|
// Set time until next time poison "ticks"
|
|
targNPC->SetTimer( tCHAR_POISONTIME, BuildTimeValue( 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 ); // That pet is already stuffed, it can't eat more.
|
|
}
|
|
}
|
|
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 ); // Thank thee kindly, but I have done nothing to warrant a gift.
|
|
}
|
|
}
|
|
else if( i->GetId() == 0x0EED ) // They gave the NPC gold
|
|
{
|
|
targNPC->TextMessage( mSock, 1198, TALK, false ); // I thank thee for thy payment. That should give thee a good start on thy way. Farewell!
|
|
UI08 trainedIn = targNPC->GetTrainingPlayerIn();
|
|
UI16 oldskill = mChar->GetBaseSkill( trainedIn );
|
|
mChar->SetBaseSkill( static_cast<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 ); // I am sorry, but I can only accept gold.
|
|
}
|
|
}
|
|
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 - DropOnChar()
|
|
//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 ); // That person can not possibly hold more weight
|
|
if( mSock->PickupSpot() == PL_OTHERPACK || mSock->PickupSpot() == PL_GROUND )
|
|
{
|
|
Weight->SubtractItemWeight( mChar, i );
|
|
}
|
|
Bounce( mSock, i );
|
|
return false;
|
|
}
|
|
|
|
if( !targChar->IsNpc() )
|
|
{
|
|
// Drop on player character
|
|
return DropOnPC( mSock, mChar, targChar, i );
|
|
}
|
|
|
|
// Drop on NPC
|
|
return DropOnNPC( mSock, mChar, targChar, i );
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - CheckForValidDropLocation()
|
|
//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() - 7 || z > nChar->GetZ() + 16 ) // 7 to allow to stand on bottom of stairs and drop items on floor/ground below
|
|
{
|
|
dropLocationBlocked = true;
|
|
}
|
|
|
|
if( !dropLocationBlocked )
|
|
{
|
|
// Check for a valid surface to drop item on first - then check for blocking tiles after
|
|
// If done the other way, the valid surface will override the blocking tiles!
|
|
|
|
// First, check for a static surface to drop item on
|
|
UI16 foundTileId1 = 0;
|
|
UI16 foundTileId2 = 0;
|
|
if( !Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_SURFACE, foundTileId1, false ))
|
|
{
|
|
// Nowhere static to put item? Check dynamic tiles for surface!
|
|
if( !Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceId(), TF_SURFACE, foundTileId1 ))
|
|
{
|
|
// No static OR dynamic surface was found to place item? Check if map itself blocks the placement
|
|
dropLocationBlocked = Map->DoesMapBlock( x, y, z, nChar->WorldNumber(), true, false, false, false );
|
|
}
|
|
}
|
|
|
|
if( !dropLocationBlocked )
|
|
{
|
|
// Some kind of valid surface was found. But is it blocked by...
|
|
if( Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_BLOCKING, foundTileId2, false ) || Map->CheckStaticFlag( x, y, z, nChar->WorldNumber(), TF_ROOF, foundTileId2, false ))
|
|
{ // ...static items?
|
|
if( foundTileId1 != foundTileId2 )
|
|
{
|
|
dropLocationBlocked = true;
|
|
}
|
|
}
|
|
else if( Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceId(), TF_BLOCKING, foundTileId2 ) || Map->CheckDynamicFlag( x, y, z, nChar->WorldNumber(), nChar->GetInstanceId(), TF_ROOF, foundTileId2 ))
|
|
{ // No? What about dynamic items?
|
|
if( foundTileId1 != foundTileId2 )
|
|
{
|
|
dropLocationBlocked = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 - Drop()
|
|
//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() < CL_GM || 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 newZ = Map->StaticTop( x, y, z, nChar->WorldNumber(), 14 - ( z - nChar->GetZ() ));
|
|
if( newZ == ILLEGAL_Z || newZ < z || newZ > z + 14 )
|
|
{
|
|
// No valid static elevation found, use dynamic elevation instead
|
|
newZ = Map->DynamicElevation( x, y, z, nChar->WorldNumber(), nChar->GetInstanceId(), 14 );
|
|
if( newZ < z || newZ > z + 14 )
|
|
{
|
|
// 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(), nChar->GetInstanceId(), 14 );
|
|
newZ = (( dynZ >= z && dynZ <= z + 14 ) ? dynZ : newZ );
|
|
}
|
|
|
|
auto iMulti = FindMulti( x, y, newZ, nChar->WorldNumber(), nChar->GetInstanceId() );
|
|
if( ValidateObject( iMulti ) )
|
|
{
|
|
// Check if new Z plus height of dropped item will make it exceed 20 Z limit per floor in houses, and deny placement if so
|
|
if( newZ > ( nChar->GetZ() + ( 20 - tile.Height() )))
|
|
{
|
|
// Item is too tall - can't drop it this high up
|
|
if( nChar->GetCommandLevel() >= CL_SEER || nChar->IsGM() )
|
|
{
|
|
// GM override
|
|
mSock->SysMessage( 91 ); // Item placement rule overruled by GM privileges - Z too high for normal players!
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
else if( newZ + tile.Height() > z + tile.Height() )
|
|
{
|
|
// Item is too tall - can't drop it this high up
|
|
if( nChar->GetCommandLevel() >= CL_SEER || nChar->IsGM() )
|
|
{
|
|
// GM override
|
|
mSock->SysMessage( 91 ); // Item placement rule overruled by GM privileges - Z too high for normal players!
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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, newZ );
|
|
}
|
|
}
|
|
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 - DropOnTradeWindow()
|
|
//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 - ValidateLockdownAccess()
|
|
//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 - DropOnSpellBook()
|
|
//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 - DropOnStack()
|
|
//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 - HandleAutoStack()
|
|
//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 - DropOnContainer()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Called when item is dropped on a container
|
|
//o------------------------------------------------------------------------------------------------o
|
|
bool DropOnContainer( CSocket& mSock, CChar& mChar, CItem& droppedOn, CItem& iDropped, [[maybe_unused]] 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_GM )
|
|
{
|
|
// Clear old values, if item have 'em
|
|
iDropped.SetDesc( "" );
|
|
iDropped.SetVendorPrice( 0 );
|
|
|
|
// Ask player to enter price for item, or 0 to mark it as 'Not for Sale'
|
|
mChar.SetSpeechMode( 3 );
|
|
mChar.SetSpeechItem( &iDropped );
|
|
mSock.SysMessage( 1207 ); // Set a price for this item.
|
|
}
|
|
else
|
|
{
|
|
if( mSock.PickupSpot() == PL_OTHERPACK || mSock.PickupSpot() == PL_GROUND )
|
|
{
|
|
Weight->SubtractItemWeight( &mChar, &iDropped );
|
|
}
|
|
contOwner->TextMessage( &mSock, 9176, TALK, false ); // I can only accept items from the shop owner.
|
|
|
|
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 - DropOnItem()
|
|
//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() < CL_GM || 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 - CPIDropItem::Handle()
|
|
//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->GetWeightMax();
|
|
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 - BestSkill()
|
|
//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 - GetSkillProwessTitle()
|
|
//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 - GetFameTitle()
|
|
//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( cwmWorldState->ServerData()->ShowRaceWithName() && 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 - PaperDoll()
|
|
//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 );
|
|
if( !pdoll->HideFameKarmaTitle() )
|
|
{
|
|
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, NRS_PAPERDOLL );
|
|
|
|
// Append race, if enabled
|
|
if( cwmWorldState->ServerData()->ShowRaceInPaperdoll() && pdoll->GetRace() != 0 && pdoll->GetRace() != 65535 )
|
|
{
|
|
tempstr += " ("s + Races->Name( pdoll->GetRace() ) + ")";
|
|
}
|
|
|
|
// Append title, if one exists
|
|
if( pdoll->GetTitle() != "" )
|
|
{
|
|
std::string tempTitle = GetNpcDictTitle( pdoll, s );
|
|
tempstr += " " + tempTitle;
|
|
}
|
|
}
|
|
else if( pdoll->IsDead() )
|
|
{
|
|
tempstr = pdoll->GetNameRequest( myChar, NRS_PAPERDOLL );
|
|
}
|
|
// 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, NRS_PAPERDOLL ).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, NRS_PAPERDOLL ) + ", " + pdoll->GetTitle() + skillProwessTitle;
|
|
}
|
|
}
|
|
}
|
|
else if( pdoll->IsCriminal() )
|
|
{
|
|
tempstr = oldstrutil::format( Dictionary->GetEntry( 373, sLang ), pdoll->GetNameRequest( myChar, NRS_PAPERDOLL ).c_str(), pdoll->GetTitle().c_str(), skillProwessTitle.c_str() );
|
|
}
|
|
else
|
|
{
|
|
bContinue = true;
|
|
}
|
|
if( bContinue )
|
|
{
|
|
tempstr = fameTitle + pdoll->GetNameRequest( myChar, NRS_PAPERDOLL ); //Repuation + Name
|
|
|
|
// Append race, if enabled
|
|
if( cwmWorldState->ServerData()->ShowRaceInPaperdoll() && pdoll->GetRace() != 0 && pdoll->GetRace() != 65535 )
|
|
{
|
|
tempstr += " ("s + Races->Name( pdoll->GetRace() ) + ")";
|
|
}
|
|
|
|
if( pdoll->GetTownTitle() || pdoll->GetTownPriv() == 2 ) // TownTitle
|
|
{
|
|
if( pdoll->GetTownPriv() == 2 ) // is Mayor
|
|
{
|
|
tempstr = oldstrutil::format( Dictionary->GetEntry( 379, sLang ), pdoll->GetNameRequest( myChar, NRS_PAPERDOLL ).c_str(), cwmWorldState->townRegions[pdoll->GetTown()]->GetName().c_str(), skillProwessTitle.c_str() );
|
|
}
|
|
else // is Resident
|
|
{
|
|
tempstr = pdoll->GetNameRequest( myChar, NRS_PAPERDOLL ) + " of " + cwmWorldState->townRegions[pdoll->GetTown()]->GetName() + ", " + skillProwessTitle;
|
|
}
|
|
}
|
|
else // No Town Title
|
|
{
|
|
if( !pdoll->IsIncognito() && !pdoll->IsDisguised() && !( pdoll->GetTitle().empty() ))
|
|
{
|
|
if( cwmWorldState->ServerData()->ExpansionCoreShardEra() >= ER_T2A )
|
|
{
|
|
// Title & Skill
|
|
tempstr += " " + pdoll->GetTitle() + ", " + skillProwessTitle;
|
|
}
|
|
else
|
|
{
|
|
// Title only
|
|
tempstr += " " + pdoll->GetTitle();
|
|
}
|
|
}
|
|
else if( cwmWorldState->ServerData()->ExpansionCoreShardEra() >= ER_T2A )
|
|
{
|
|
// Just Skill
|
|
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 - handleCharDoubleClick()
|
|
//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 ); // You are dead and cannot do that.
|
|
}
|
|
else if( mChar->GetId() != mChar->GetOrgId() )
|
|
{
|
|
mSock->SysMessage( 380 ); // You cannot ride anything under polymorph effect.
|
|
}
|
|
else
|
|
{
|
|
MountCreature( mSock, mChar, c );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 382 ); // You need to get closer.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 1214 ); // You don't own that creature.
|
|
}
|
|
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 ); // You are dead and cannot do that.
|
|
}
|
|
else if( !ObjInRange( mChar, c, DIST_NEARBY ))
|
|
{
|
|
mSock->SysMessage( 382 ); // You need to get closer.
|
|
}
|
|
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 ); // That is not your beast of burden!
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if( c->GetNpcAiType() == AI_PLAYERVENDOR ) // PlayerVendors
|
|
{
|
|
c->TextMessage( mSock, 385, TALK, false ); // Take a look at my goods.
|
|
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 - HandleDoubleClickTypes()
|
|
//| 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 ); // You are too far away!
|
|
}
|
|
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 ); // This item is locked.
|
|
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 ); // If you wish to open a spellbook, it must be equipped or in your main backpack.
|
|
}
|
|
}
|
|
return true;
|
|
case IT_SPELLCHANNELING: // Spell Channeling
|
|
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 ); // That rune is not yet marked!
|
|
}
|
|
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 ); // Enter new rune name.
|
|
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( 5.0 ));
|
|
}
|
|
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 ); // That is out of charges!
|
|
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 ); // Enter your new name.
|
|
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 )))
|
|
{
|
|
mSock->SysMessage( 1984 ); // You are not a member of this guild!
|
|
}
|
|
else if( 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 ); // You already belong to a guild.
|
|
}
|
|
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->SendTargetCursor( 0, TARGET_REPAIRMETAL, 0, 485 ); // What do we wish to repair?
|
|
return true;
|
|
case IT_FORGE: // Forges
|
|
mSock->TempObj( iUsed );
|
|
mSock->SendTargetCursor( 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->SendTargetCursor( 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->SendTargetCursor( 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<UI16>( 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->SendTargetCursor( 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->SendTargetCursor( 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->SendTargetCursor( 0, TARGET_VIAL, 0, 447 );
|
|
}
|
|
else
|
|
{
|
|
mSock->SysMessage( 448 ); // The vial is not in your pack!
|
|
}
|
|
}
|
|
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->SendTargetCursor( 0, TARGET_SMELTORE, 0, 460 ); // Select the forge on which to smelt the ore.
|
|
}
|
|
else
|
|
{
|
|
mSock->SendTargetCursor( 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 ); // You are too far away.
|
|
}
|
|
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 ); // You failed to use this statue.
|
|
}
|
|
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 ); // You failed to use this.
|
|
}
|
|
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;
|
|
tagToItemType["SPELLCHANNELING"] = IT_SPELLCHANNELING;
|
|
tagToItemType["SHIELD"] = IT_SHIELD;
|
|
}
|
|
|
|
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;
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - InitIdToItemType()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Load in and map IDs to item types based on entries in itemtypes.dfn
|
|
//o------------------------------------------------------------------------------------------------o
|
|
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<UI16>( oldstrutil::extractSection( data, ",", i, i ), 16 )] = iType;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
idToItemType[static_cast<UI16>( std::stoul( data, nullptr, 16 ))] = iType;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - FindItemTypeFromId()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find an item's type based on its ID
|
|
//o------------------------------------------------------------------------------------------------o
|
|
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;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - FindItemType()
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Purpose - Find an item's type, or try to find it's type from its ID if no type is specified
|
|
//o------------------------------------------------------------------------------------------------o
|
|
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 - ItemIsUsable()
|
|
//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() )
|
|
{
|
|
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 )
|
|
{
|
|
MakeCriminal( 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 - CPIDblClick::Handle()
|
|
//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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no scripts were found, or if no script contained onUseUnchecked 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;
|
|
}
|
|
}
|
|
|
|
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->OnUseChecked( ourChar, iUsed ) == 0 )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( HandleDoubleClickTypes( tSock, ourChar, iUsed, iType ))
|
|
return true;
|
|
|
|
tSock->SysMessage( 486 ); // You can't think of a way to use that item.
|
|
return true;
|
|
}
|
|
|
|
//o------------------------------------------------------------------------------------------------o
|
|
//| Function - AppendData()
|
|
//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 ¤tName )
|
|
{
|
|
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 - CPISingleClick::Handle()
|
|
//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(), NRS_SINGLECLICK )[0] != '#' )
|
|
{
|
|
if( i->GetId() == 0x0ED5 ) //guildstone
|
|
{
|
|
realname = oldstrutil::format( Dictionary->GetEntry( 101, tSock->Language() ).c_str(), i->GetNameRequest( tSock->CurrcharObj(), NRS_SINGLECLICK ).c_str() );
|
|
}
|
|
if( !i->IsPileable() || getAmount == 1 )
|
|
{
|
|
if( mChar->IsGM() && !i->IsCorpse() && getAmount > 1 )
|
|
{
|
|
realname = oldstrutil::format( "%s (%u)", i->GetNameRequest( tSock->CurrcharObj(), NRS_SINGLECLICK ).c_str(), getAmount );
|
|
}
|
|
else
|
|
{
|
|
realname = i->GetNameRequest( tSock->CurrcharObj(), NRS_SINGLECLICK );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
realname = oldstrutil::format( "%u %ss", getAmount, i->GetNameRequest( tSock->CurrcharObj(), NRS_SINGLECLICK ).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, NRS_SINGLECLICK );
|
|
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;
|
|
}
|