/** @file * * @par History * - 2005/05/28 Shinigami: now u can call open_trade_window without item and splitted (for use with * uo::SecureTradeWin) * place_item_in_secure_trade_container splitted (for use with * uo::MoveItemToSecureTradeWin) * - 2005/06/01 Shinigami: return_traded_items - added realm support * - 2005/11/28 MuadDib: Added 2 more send_trade_statuses() for freshing of chr and droped_on * in sending of secure trade windows. This was per packet checks on * OSI, and works in newer clients. Unable to test in 2.0.0 * - 2009/07/20 MuadDib: Slot checks added where can_add() is called. * - 2009/07/23 MuadDib: updates for new Enum::Packet Out ID * - 2009/08/06 MuadDib: Added gotten_by code for items. * - 2009/08/09 MuadDib: Refactor of Packet 0x25 for naming convention * - 2009/08/16 MuadDib: find_giveitem_container(), removed passert, made it return nullptr to * reject move instead of a crash. Added slot support to find_giveitem_container() * - 2009/09/03 MuadDib: Changes for account related source file relocation * Changes for multi related source file relocation * - 2009/09/06 Turley: Changed Version checks to bitfield client->ClientType * - 2009/11/19 Turley: removed sysmsg after CanInsert call (let scripter handle it) - Tomi * * @note FIXME: Does STW use slots with KR or newest 2d? If so, we must do slot checks there too. */ #include #include #include "../bscript/berror.h" #include "../clib/clib_endian.h" #include "../clib/logfacility.h" #include "../clib/passert.h" #include "../clib/random.h" #include "../clib/rawtypes.h" #include "../plib/systemstate.h" #include "containr.h" #include "eventid.h" #include "fnsearch.h" #include "getitem.h" #include "globals/uvars.h" #include "item/item.h" #include "los.h" #include "mobile/charactr.h" #include "mobile/npc.h" #include "multi/multi.h" #include "network/client.h" #include "network/clientio.h" #include "network/packetdefs.h" #include "network/packethelper.h" #include "network/packets.h" #include "network/pktboth.h" #include "network/pktdef.h" #include "network/pktin.h" #include "objtype.h" #include "polcfg.h" #include "realms/realm.h" #include "reftypes.h" #include "statmsg.h" #include "storage.h" #include "syshook.h" #include "systems/suspiciousacts.h" #include "ufunc.h" #include "uobject.h" #include "uoscrobj.h" #include "uworld.h" namespace Pol { namespace Core { void send_trade_statuses( Mobile::Character* chr ); bool place_item_in_container( Network::Client* client, Items::Item* item, UContainer* cont, const Pos2d& newpos, u8 slotIndex ) { ItemRef itemref( item ); if ( ( cont->serial == item->serial ) || is_a_parent( cont, item->serial ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( !cont->can_add( *item ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); send_sysmessage( client, "That item is too heavy for the container or the container is full." ); return false; } if ( !cont->can_insert_add_item( client->chr, UContainer::MT_PLAYER, item ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( item->orphan() ) { return true; } if ( !cont->can_add_to_slot( slotIndex ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); send_sysmessage( client, "The container has no free slots available!" ); return false; } if ( !item->slot_index( slotIndex ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); send_sysmessage( client, "The container has no free slots available!" ); return false; } item->set_dirty(); client->pause(); send_remove_object_to_inrange( item ); item->setposition( Pos4d( newpos, 0, nullptr ) ); cont->add( item ); cont->restart_decay_timer(); if ( !item->orphan() ) { send_put_in_container_to_inrange( item ); cont->on_insert_add_item( client->chr, UContainer::MT_PLAYER, item ); } client->restart(); return true; } bool do_place_item_in_secure_trade_container( Network::Client* client, Items::Item* item, UContainer* cont, Mobile::Character* dropon, const Pos2d& newpos, u8 move_type ); bool place_item_in_secure_trade_container( Network::Client* client, Items::Item* item, const Pos2d& newpos ) { UContainer* cont = client->chr->trade_container(); Mobile::Character* dropon = client->chr->trading_with.get(); if ( dropon == nullptr || dropon->client == nullptr ) { send_sysmessage( client, "Unable to complete trade" ); return false; } if ( gamestate.system_hooks.can_trade ) { if ( !gamestate.system_hooks.can_trade->call( new Module::ECharacterRefObjImp( client->chr ), new Module::ECharacterRefObjImp( dropon ), new Module::EItemRefObjImp( item ) ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } } if ( !cont->can_add( *item ) ) { send_sysmessage( client, "That's too heavy to trade." ); return false; } if ( !cont->can_insert_add_item( client->chr, UContainer::MT_PLAYER, item ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } // FIXME : Add Grid Index Default Location Checks here. // Remember, if index fails, move to the ground. That is, IF secure trade uses // grid index. return do_place_item_in_secure_trade_container( client, item, cont, dropon, newpos, 0 ); } Bscript::BObjectImp* place_item_in_secure_trade_container( Network::Client* client, Items::Item* item ) { UContainer* cont = client->chr->trade_container(); Mobile::Character* dropon = client->chr->trading_with.get(); if ( dropon == nullptr || dropon->client == nullptr ) { return new Bscript::BError( "Unable to complete trade" ); } if ( gamestate.system_hooks.can_trade ) { if ( !gamestate.system_hooks.can_trade->call( new Module::ECharacterRefObjImp( client->chr ), new Module::ECharacterRefObjImp( dropon ), new Module::EItemRefObjImp( item ) ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return new Bscript::BError( "Could not insert item into container." ); } } if ( !cont->can_add( *item ) ) { return new Bscript::BError( "That's too heavy to trade." ); } if ( !cont->can_insert_add_item( client->chr, UContainer::MT_CORE_MOVED, item ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return new Bscript::BError( "Could not insert item into container." ); } // FIXME : Add Grid Index Default Location Checks here. // Remember, if index fails, move to the ground. Pos2d newpos( 5 + static_cast( Clib::random_int( 44 ) ), 5 + static_cast( Clib::random_int( 44 ) ) ); if ( do_place_item_in_secure_trade_container( client, item, cont, dropon, newpos, 1 ) ) return new Bscript::BLong( 1 ); else return new Bscript::BError( "Something went wrong with trade window." ); } bool do_place_item_in_secure_trade_container( Network::Client* client, Items::Item* item, UContainer* cont, Mobile::Character* dropon, const Pos2d& newpos, u8 move_type ) { client->pause(); client->chr->trade_accepted( false ); dropon->trade_accepted( false ); send_trade_statuses( client->chr ); send_remove_object_to_inrange( item ); item->setposition( Pos4d( newpos, 9, nullptr ) ); // TODO: erm lol z==9??? cont->add( item ); send_put_in_container( client, item ); send_put_in_container( dropon->client, item ); if ( move_type == 1 ) cont->on_insert_add_item( client->chr, UContainer::MT_CORE_MOVED, item ); else cont->on_insert_add_item( client->chr, UContainer::MT_PLAYER, item ); send_trade_statuses( client->chr ); send_trade_statuses( dropon->client->chr ); client->restart(); return true; } bool add_item_to_stack( Network::Client* client, Items::Item* item, Items::Item* target_item ) { // TJ 3/18/3: Only called (so far) from place_item(), so it's only ever from // a player; this means that there's no need to worry about passing // UContainer::MT_CORE_* constants to the can_insert function (yet). :) ItemRef itemref( item ); if ( ( !target_item->stackable() ) || ( !target_item->can_add_to_self( *item, false ) ) || ( target_item->container && !target_item->container->can_insert_increase_stack( client->chr, UContainer::MT_PLAYER, target_item, item->getamount(), item ) ) ) { send_sysmessage( client, "Could not add item to stack." ); send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( item->orphan() ) return false; /* At this point, we know: the object types match the combined stack isn't 'too large' We don't know: (FIXME) if a container that the target_item is in will overfill from this */ send_remove_object_to_inrange( item ); u16 amtadded = item->getamount(); target_item->add_to_self( item ); update_item_to_inrange( target_item ); if ( target_item->container ) { target_item->container->on_insert_increase_stack( client->chr, UContainer::MT_PLAYER, target_item, amtadded ); target_item->container->restart_decay_timer(); } return true; } bool place_item( Network::Client* client, Items::Item* item, u32 target_serial, const Pos2d& newpos, u8 slotIndex ) { Items::Item* target_item = find_legal_item( client->chr, target_serial ); if ( target_item == nullptr && client->chr->is_trading() ) { UContainer* cont = client->chr->trade_container(); if ( target_serial == cont->serial ) { if ( item->no_drop() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } return place_item_in_secure_trade_container( client, item, newpos ); } } if ( target_item == nullptr ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( !client->chr->can_moveanydist() && !client->chr->in_pickplace_range( target_item ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_TOO_FAR_AWAY ); return false; } if ( !client->chr->realm()->has_los( *client->chr, *target_item->toplevel_owner() ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_OUT_OF_SIGHT ); return false; } if ( target_item->isa( UOBJ_CLASS::CLASS_ITEM ) ) { if ( item->no_drop() ) { if ( target_item->container == nullptr || !target_item->container->no_drop_exception() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } } return add_item_to_stack( client, item, target_item ); } else if ( target_item->isa( UOBJ_CLASS::CLASS_CONTAINER ) ) { if ( item->no_drop() && !( static_cast( target_item )->no_drop_exception() ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } return place_item_in_container( client, item, static_cast( target_item ), newpos, slotIndex ); } else { // UNTESTED CLIENT_HOLE? send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } } bool drop_item_on_ground( Network::Client* client, Items::Item* item, const Pos4d& newpos ) { if ( item->no_drop() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } Mobile::Character* chr = client->chr; Multi::UMulti* multi; s8 newz; if ( !client->chr->can_moveanydist() && !chr->in_pickplace_range( newpos ) ) { POLLOG_ERROR.Format( "Client (Character {}) tried to drop an item out of range.\n" ) << client->chr->name(); send_item_move_failure( client, MOVE_ITEM_FAILURE_TOO_FAR_AWAY ); return false; } if ( !chr->realm()->dropheight( newpos, chr->z(), &newz, &multi ) ) { POLLOG_ERROR.Format( "Client (Character {}) tried to drop an item at ({},{},{}), which is a blocked " "location.\n" ) << client->chr->name() << newpos.x() << newpos.y() << (int)newpos.z(); return false; } LosObj tgt( newpos ); if ( !chr->realm()->has_los( *chr, tgt ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_OUT_OF_SIGHT ); return false; } item->set_dirty(); item->restart_decay_timer(); item->setposition( newpos ); item->container = nullptr; item->reset_slot(); add_item_to_world( item ); if ( multi != nullptr ) { multi->register_object( item ); } send_item_to_inrange( item ); return true; } UContainer* find_giveitem_container( Items::Item* item_to_add, u8 slotIndex ) { StorageArea* area = gamestate.storage.create_area( "GivenItems" ); passert( area != nullptr ); for ( unsigned short i = 0; i < 500; ++i ) { std::string name = "Cont" + Clib::tostring( i ); Items::Item* item = nullptr; item = area->find_root_item( name ); if ( item == nullptr ) { item = Items::Item::create( UOBJ_BACKPACK ); item->setname( name ); area->insert_root_item( item ); } // Changed this from a passert to return null. if ( !( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) ) ) return nullptr; UContainer* cont = static_cast( item ); if ( !cont->can_add_to_slot( slotIndex ) ) { return nullptr; } if ( !item_to_add->slot_index( slotIndex ) ) { return nullptr; } if ( cont->can_add( *item_to_add ) ) return cont; } return nullptr; } void send_trade_container( Network::Client* client, Mobile::Character* whos, UContainer* cont ) { auto msg = Network::AddItemContainerMsg( cont->serial_ext, cont->graphic, 1 /*amount*/, Pos2d( 0, 0 ), cont->slot_index(), whos->serial_ext, cont->color ); msg.Send( client ); } bool do_open_trade_window( Network::Client* client, Items::Item* item, Mobile::Character* dropon ); bool open_trade_window( Network::Client* client, Items::Item* item, Mobile::Character* dropon ) { if ( !Plib::systemstate.config.enable_secure_trading ) { send_sysmessage( client, "Secure trading is unavailable." ); return false; } if ( !settingsManager.ssopt.allow_secure_trading_in_warmode ) { if ( dropon->warmode() ) { send_sysmessage( client, "You cannot trade with someone in war mode." ); return false; } if ( client->chr->warmode() ) { send_sysmessage( client, "You cannot trade while in war mode." ); return false; } } if ( dropon->is_trading() ) { send_sysmessage( client, "That person is already involved in a trade." ); return false; } if ( client->chr->is_trading() ) { send_sysmessage( client, "You are already involved in a trade." ); return false; } if ( !dropon->client ) { send_sysmessage( client, "That person is already involved in a trade." ); return false; } if ( client->chr->dead() || dropon->dead() ) { send_sysmessage( client, "Ghosts cannot trade items." ); return false; } return do_open_trade_window( client, item, dropon ); } Bscript::BObjectImp* open_trade_window( Network::Client* client, Mobile::Character* dropon ) { if ( !Plib::systemstate.config.enable_secure_trading ) { return new Bscript::BError( "Secure trading is unavailable." ); } if ( !settingsManager.ssopt.allow_secure_trading_in_warmode ) { if ( dropon->warmode() ) { return new Bscript::BError( "You cannot trade with someone in war mode." ); } if ( client->chr->warmode() ) { return new Bscript::BError( "You cannot trade while in war mode." ); } } if ( dropon->is_trading() ) { return new Bscript::BError( "That person is already involved in a trade." ); } if ( client->chr->is_trading() ) { return new Bscript::BError( "You are already involved in a trade." ); } if ( !dropon->client ) { return new Bscript::BError( "That person is already involved in a trade." ); } if ( client->chr->dead() || dropon->dead() ) { return new Bscript::BError( "Ghosts cannot trade items." ); } if ( do_open_trade_window( client, nullptr, dropon ) ) return new Bscript::BLong( 1 ); else return new Bscript::BError( "Something goes wrong." ); } bool do_open_trade_window( Network::Client* client, Items::Item* item, Mobile::Character* dropon ) { dropon->create_trade_container(); client->chr->create_trade_container(); dropon->trading_with.set( client->chr ); dropon->trade_accepted( false ); client->chr->trading_with.set( dropon ); client->chr->trade_accepted( false ); send_trade_container( client, dropon, dropon->trade_container() ); send_trade_container( dropon->client, dropon, dropon->trade_container() ); send_trade_container( client, client->chr, client->chr->trade_container() ); send_trade_container( dropon->client, client->chr, client->chr->trade_container() ); Network::PktHelper::PacketOut msg; msg->WriteFlipped( sizeof msg->buffer ); msg->Write( PKTBI_6F::ACTION_INIT ); msg->Write( dropon->serial_ext ); msg->Write( client->chr->trade_container()->serial_ext ); msg->Write( dropon->trade_container()->serial_ext ); msg->Write( 1u ); msg->Write( dropon->name().c_str(), 30, false ); msg.Send( client ); msg->offset = 4; msg->Write( client->chr->serial_ext ); msg->Write( dropon->trade_container()->serial_ext ); msg->Write( client->chr->trade_container()->serial_ext ); msg->offset++; // u8 havename same as above msg->Write( client->chr->name().c_str(), 30, false ); msg.Send( dropon->client ); if ( item != nullptr ) return place_item_in_secure_trade_container( client, item, Pos2d( 20, 20 ) ); else return true; } bool drop_item_on_mobile( Network::Client* client, Items::Item* item, u32 target_serial, u8 slotIndex ) { Mobile::Character* dropon = find_character( target_serial ); if ( dropon == nullptr ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( !client->chr->can_moveanydist() && !client->chr->in_pickplace_range( dropon ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_TOO_FAR_AWAY ); return false; } if ( !client->chr->realm()->has_los( *client->chr, *dropon ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_OUT_OF_SIGHT ); return false; } if ( !dropon->isa( UOBJ_CLASS::CLASS_NPC ) ) { if ( item->no_drop() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( gamestate.system_hooks.can_trade ) { if ( !gamestate.system_hooks.can_trade->call( new Module::ECharacterRefObjImp( client->chr ), new Module::ECharacterRefObjImp( dropon ), new Module::EItemRefObjImp( item ) ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } } bool res = open_trade_window( client, item, dropon ); if ( !res ) send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return res; } Mobile::NPC* npc = static_cast( dropon ); if ( !npc->can_accept_event( EVID_ITEM_GIVEN ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( item->no_drop() && !npc->no_drop_exception() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } UContainer* cont = find_giveitem_container( item, slotIndex ); if ( cont == nullptr ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( !cont->can_add_to_slot( slotIndex ) || !item->slot_index( slotIndex ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } client->pause(); send_remove_object_to_inrange( item ); cont->add_at_random_location( item ); npc->send_event( new Module::ItemGivenEvent( client->chr, item, npc ) ); client->restart(); return true; } // target_serial should indicate a character, or a container, but not a pile. bool drop_item_on_object( Network::Client* client, Items::Item* item, u32 target_serial, u8 slotIndex ) { ItemRef itemref( item ); UContainer* cont = nullptr; if ( IsCharacter( target_serial ) ) { if ( target_serial == client->chr->serial ) { cont = client->chr->backpack(); } else { return drop_item_on_mobile( client, item, target_serial, slotIndex ); } } if ( cont == nullptr ) { cont = find_legal_container( client->chr, target_serial ); } if ( cont == nullptr ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( item->no_drop() && !cont->no_drop_exception() ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_UNKNOWN ); return false; } if ( !client->chr->can_moveanydist() && !client->chr->in_pickplace_range( cont ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_TOO_FAR_AWAY ); return false; } if ( !client->chr->realm()->has_los( *client->chr, *cont->toplevel_owner() ) ) { send_item_move_failure( client, MOVE_ITEM_FAILURE_OUT_OF_SIGHT ); return false; } if ( item->stackable() ) { for ( UContainer::const_iterator itr = cont->begin(); itr != cont->end(); ++itr ) { Items::Item* exitem = GET_ITEM_PTR( itr ); if ( exitem->can_add_to_self( *item, false ) ) { if ( cont->can_add( *item ) ) { if ( cont->can_insert_increase_stack( client->chr, UContainer::MT_PLAYER, exitem, item->getamount(), item ) ) { if ( item->orphan() ) { return true; } u16 amtadded = item->getamount(); exitem->add_to_self( item ); update_item_to_inrange( exitem ); cont->on_insert_increase_stack( client->chr, UContainer::MT_PLAYER, exitem, amtadded ); return true; } } return false; } } } Pos2d newpos = cont->get_random_location(); return place_item_in_container( client, item, cont, newpos, slotIndex ); } /* DROP_ITEM messages come in a couple varieties: 1) Dropping an item on another object, or a person: item_serial: serial number of item to drop x: 0xFFFF y: 0xFFFF z: 0 target_serial: serial number of item or character to drop on. 2) Dropping an item on the ground: item_serial: serial number of item to drop x,y,z: position target_serial: 0xFFFFFFFF 3) Placing an item in a container, or in an existing pile: item_serial: serial number of item to drop x: x-position y: y-position z: 0 target_serial: serial number of item or character or pile to drop on. */ /* Name: drop_item Details: Original version of packet is supported by this function. Access: public Qualifier: Parameter: Client * client Parameter: PKTIN_08_V1 * msg */ void drop_item( Network::Client* client, PKTIN_08_V1* msg ) { u32 item_serial = cfBEu32( msg->item_serial ); Pos3d newpos( cfBEu16( msg->x ), cfBEu16( msg->y ), msg->z ); u32 target_serial = cfBEu32( msg->target_serial ); Items::Item* item = client->chr->gotten_item(); if ( item == nullptr ) { SuspiciousActs::DropItemButNoneGotten( client, item_serial ); return; } if ( item->serial != item_serial ) { SuspiciousActs::DropItemOtherThanGotten( client, item_serial, item->serial ); item->gotten_by( nullptr ); // TODO: shouldn't we clear_gotten_item() here? return; } item->inuse( false ); item->gotten_by( nullptr ); client->chr->gotten_item( nullptr ); bool res; if ( target_serial == 0xFFffFFffLu ) { res = drop_item_on_ground( client, item, Pos4d( newpos, client->chr->realm() ) ); } else if ( newpos.x() == 0xFFFF ) { res = drop_item_on_object( client, item, target_serial, 0 ); } else { Multi::UMulti* multi = system_find_multi( target_serial ); if ( multi != nullptr ) { Pos4d ground = multi->pos() + Vec2d( newpos.x(), newpos.y() ); ground.z( newpos.z() ); res = drop_item_on_ground( client, item, ground ); } else res = place_item( client, item, target_serial, newpos.xy(), 0 ); } if ( !item->orphan() ) { if ( !res ) { undo_get_item( client->chr, item ); } item->inuse( false ); item->gotten_by( nullptr ); } send_full_statmsg( client, client->chr ); } /* Name: drop_item_v2 Details: This is used for the version of the packet introduced in clients 6.0.1.7 2D and UO:KR+ to support Slots Access: public Qualifier: Parameter: Client * client Parameter: PKTIN_08_V2 * msg */ void drop_item_v2( Network::Client* client, PKTIN_08_V2* msg ) { u32 item_serial = cfBEu32( msg->item_serial ); Pos3d newpos( cfBEu16( msg->x ), cfBEu16( msg->y ), msg->z ); u8 slotIndex = msg->slotindex; u32 target_serial = cfBEu32( msg->target_serial ); Items::Item* item = client->chr->gotten_item(); if ( item == nullptr ) { SuspiciousActs::DropItemButNoneGotten( client, item_serial ); return; } if ( item->serial != item_serial ) { SuspiciousActs::DropItemOtherThanGotten( client, item_serial, item->serial ); item->gotten_by( nullptr ); return; } item->inuse( false ); item->gotten_by( nullptr ); client->chr->gotten_item( nullptr ); bool res; if ( target_serial == 0xFFffFFffLu ) { res = drop_item_on_ground( client, item, Pos4d( newpos, client->chr->realm() ) ); } else if ( newpos.x() == 0xFFFF ) { res = drop_item_on_object( client, item, target_serial, slotIndex ); } else { Multi::UMulti* multi = system_find_multi( target_serial ); if ( multi != nullptr ) { Pos4d ground = multi->pos() + Vec2d( newpos.x(), newpos.y() ); ground.z( newpos.z() ); res = drop_item_on_ground( client, item, ground ); } else res = place_item( client, item, target_serial, newpos.xy(), 0 ); } if ( !item->orphan() ) { if ( !res ) { undo_get_item( client->chr, item ); } item->inuse( false ); item->gotten_by( nullptr ); } Network::PktHelper::PacketOut drop_msg; drop_msg.Send( client ); send_full_statmsg( client, client->chr ); } void return_traded_items( Mobile::Character* chr ) { UContainer* cont = chr->trade_container(); UContainer* bp = chr->backpack(); if ( cont == nullptr || bp == nullptr ) return; UContainer::Contents tmp; cont->extract( tmp ); while ( !tmp.empty() ) { Items::Item* item = ITEM_ELEM_PTR( tmp.back() ); tmp.pop_back(); Pos4d oldpos = item->toplevel_pos(); item->container = nullptr; item->layer = 0; ItemRef itemref( item ); if ( bp->can_add( *item ) && bp->can_insert_add_item( chr, UContainer::MT_CORE_MOVED, item ) ) { if ( item->orphan() ) { continue; } u8 newSlot = 1; if ( !bp->can_add_to_slot( newSlot ) || !item->slot_index( newSlot ) ) { item->set_dirty(); item->setposition( chr->pos() ); add_item_to_world( item ); register_with_supporting_multi( item ); move_item( item, item->pos(), oldpos ); return; } bp->add_at_random_location( item ); if ( chr->client ) send_put_in_container( chr->client, item ); bp->on_insert_add_item( chr, UContainer::MT_CORE_MOVED, item ); } else { item->set_dirty(); item->setposition( chr->pos() ); add_item_to_world( item ); register_with_supporting_multi( item ); move_item( item, item->pos(), oldpos ); } } } void cancel_trade( Mobile::Character* chr1 ) { Mobile::Character* chr2 = chr1->trading_with.get(); return_traded_items( chr1 ); chr1->trading_with.clear(); if ( chr1->client ) { Network::PktHelper::PacketOut msg; msg->WriteFlipped( 17u ); // no name msg->Write( PKTBI_6F::ACTION_CANCEL ); msg->Write( chr1->trade_container()->serial_ext ); msg->offset += 9; // u32 cont1_serial, cont2_serial, u8 havename msg.Send( chr1->client ); send_full_statmsg( chr1->client, chr1 ); } if ( chr2 ) { return_traded_items( chr2 ); chr2->trading_with.clear(); if ( chr2->client ) { Network::PktHelper::PacketOut msg; msg->WriteFlipped( 17u ); // no name msg->Write( PKTBI_6F::ACTION_CANCEL ); msg->Write( chr2->trade_container()->serial_ext ); msg->offset += 9; // u32 cont1_serial, cont2_serial, u8 havename msg.Send( chr2->client ); send_full_statmsg( chr2->client, chr2 ); } } } void send_trade_statuses( Mobile::Character* chr ) { unsigned int stat1 = chr->trade_accepted() ? 1 : 0; unsigned int stat2 = chr->trading_with->trade_accepted() ? 1 : 0; Network::PktHelper::PacketOut msg; msg->WriteFlipped( 17u ); // no name msg->Write( PKTBI_6F::ACTION_STATUS ); msg->Write( chr->trade_container()->serial_ext ); msg->WriteFlipped( stat1 ); msg->WriteFlipped( stat2 ); msg->offset++; // u8 havename Network::transmit( chr->client, &msg->buffer, msg->offset ); msg->offset = 4; msg->Write( chr->trading_with->trade_container()->serial_ext ); msg->WriteFlipped( stat2 ); msg->WriteFlipped( stat1 ); msg->offset++; msg.Send( chr->trading_with->client ); } void change_trade_status( Mobile::Character* chr, bool set ) { chr->trade_accepted( set ); send_trade_statuses( chr ); if ( chr->trade_accepted() && chr->trading_with->trade_accepted() ) { UContainer* cont0 = chr->trade_container(); UContainer* cont1 = chr->trading_with->trade_container(); if ( cont0->can_swap( *cont1 ) ) { cont0->swap( *cont1 ); cancel_trade( chr ); } else { POLLOG_ERROR.Format( "Can't swap trade containers: ic0={},w0={}, ic1={},w1={}\n" ) << cont0->item_count() << cont0->weight() << cont1->item_count() << cont1->weight(); } } } void handle_secure_trade_msg( Network::Client* client, PKTBI_6F* msg ) { if ( !client->chr->is_trading() ) return; switch ( msg->action ) { case PKTBI_6F::ACTION_CANCEL: INFO_PRINT_TRACE( 5 ) << "Cancel secure trade\n"; cancel_trade( client->chr ); break; case PKTBI_6F::ACTION_STATUS: bool set; set = msg->cont1_serial ? true : false; if ( set ) { INFO_PRINT_TRACE( 5 ) << "Set secure trade indicator\n"; } else { INFO_PRINT_TRACE( 5 ) << "Clear secure trade indicator\n"; } change_trade_status( client->chr, set ); break; default: INFO_PRINT_TRACE( 5 ) << "Unknown secure trade action: " << (int)msg->action << "\n"; break; } } void cancel_all_trades() { for ( auto& client : networkManager.clients ) { if ( client->ready && client->chr ) { Mobile::Character* chr = client->chr; if ( chr->is_trading() ) cancel_trade( chr ); } } } } // namespace Core } // namespace Pol