mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
* origin/master: (93 commits) Add support for dictionary creation (#277) Add do-while statement (#276) Add foreach loops (#275) New compiler: array initialization (#274) Disambiguator: figure out if label is a case statement dispatch selector, or should apply to a statement within the case dispatch group's block. (#273) New compiler: Add support for case statements (#272) Change "logical" to "bitwise". (#258) Fix divide-by-zero errors, and mask FormatError if it were thrown in a destructor. (#271) Add break and continue statements. (#270) New compiler: assignment to local and global variables. (#269) Add support for while loops (#268) Optimize if statements, and emit module functions in legacy order (#267) Optimizations: binary operators (#266) Add binary operators (#265) Add support for const declarations (#264) Handle default parameter values, pass-by-name, and return statements in functions. (#263) Add limited support for user functions (#262) New compiler: add return and exit statements (#261) Add support for if-then-else statements (#259) OG compiler: set module to Mod_Basic when a token is an identifier (#257) ...
1072 lines
32 KiB
C++
1072 lines
32 KiB
C++
/** @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 <cstdio>
|
|
#include <string>
|
|
|
|
#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<u16>( Clib::random_int( 44 ) ),
|
|
5 + static_cast<u16>( 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<UContainer*>( 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<UContainer*>( 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<UContainer*>( 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<Network::PktOut_6F> msg;
|
|
msg->WriteFlipped<u16>( sizeof msg->buffer );
|
|
msg->Write<u8>( PKTBI_6F::ACTION_INIT );
|
|
msg->Write<u32>( dropon->serial_ext );
|
|
msg->Write<u32>( client->chr->trade_container()->serial_ext );
|
|
msg->Write<u32>( dropon->trade_container()->serial_ext );
|
|
msg->Write<u8>( 1u );
|
|
msg->Write( dropon->name().c_str(), 30, false );
|
|
|
|
msg.Send( client );
|
|
|
|
msg->offset = 4;
|
|
msg->Write<u32>( client->chr->serial_ext );
|
|
msg->Write<u32>( dropon->trade_container()->serial_ext );
|
|
msg->Write<u32>( 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<Mobile::NPC*>( 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<Network::PktOut_29> 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<Network::PktOut_6F> msg;
|
|
msg->WriteFlipped<u16>( 17u ); // no name
|
|
msg->Write<u8>( PKTBI_6F::ACTION_CANCEL );
|
|
msg->Write<u32>( 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<Network::PktOut_6F> msg;
|
|
msg->WriteFlipped<u16>( 17u ); // no name
|
|
msg->Write<u8>( PKTBI_6F::ACTION_CANCEL );
|
|
msg->Write<u32>( 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<Network::PktOut_6F> msg;
|
|
msg->WriteFlipped<u16>( 17u ); // no name
|
|
msg->Write<u8>( PKTBI_6F::ACTION_STATUS );
|
|
msg->Write<u32>( chr->trade_container()->serial_ext );
|
|
msg->WriteFlipped<u32>( stat1 );
|
|
msg->WriteFlipped<u32>( stat2 );
|
|
msg->offset++; // u8 havename
|
|
Network::transmit( chr->client, &msg->buffer, msg->offset );
|
|
msg->offset = 4;
|
|
msg->Write<u32>( chr->trading_with->trade_container()->serial_ext );
|
|
msg->WriteFlipped<u32>( stat2 );
|
|
msg->WriteFlipped<u32>( 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
|