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