/** @file * * @par History * - 2005/03/05 Shinigami: format_description -> ServSpecOpt UseAAnTileFlags to enable/disable * "a"/"an" via Tiles.cfg in formatted Item Names * - 2005/04/03 Shinigami: send_feature_enable - added UOExpansionFlag for Samurai Empire * - 2005/08/29 Shinigami: ServSpecOpt UseAAnTileFlags renamed to UseTileFlagPrefix * - 2005/09/17 Shinigami: send_nametext - smaller bugfix in passert-check * - 2006/05/07 Shinigami: SendAOSTooltip - will now send merchant_description() if needed * - 2006/05/16 Shinigami: send_feature_enable - added UOExpansionFlag for Mondain's Legacy * - 2007/04/08 MuadDib: Updated send_worn_item_to_inrange to create the message only * once and use the Transmit_to_inrange instead. Then uses * send_object_cache_to_inrange. Required a tooltips.* update. * - 2008/07/08 Turley: get_flag1() changed to show WarMode of other player again * get_flag1_aos() removed * - 2009/07/23 MuadDib: updates for new Enum::Packet Out ID * - 2009/07/31 Turley: added send_fight_occuring() for packet 0x2F * - 2009/08/01 MuadDib: Removed send_tech_stuff(), unused and obsolete. * - 2009/08/09 MuadDib: UpdateCharacterWeight() Rewritten to send stat msg intead of refreshar(). * Refactor of Packet 0x25 for naming convention * - 2009/08/14 Turley: PKTOUT_B9_V2 removed unk u16 and changed flag to u32 * - 2009/09/03 MuadDib: Relocation of account related cpp/h * Relocation of multi related cpp/h * - 2009/09/13 MuadDib: Optimized send_remove_character_to_nearby_cansee, * send_remove_character_to_nearby_cantsee, send_remove_character_to_nearby * to build packet and handle iter internally. Packet built just once this * way, and sent to those who need it. * - 2009/09/06 Turley: Changed Version checks to bitfield client->ClientType * - 2009/09/22 MuadDib: Adding resending of light level if override not in effect, to sending of * season packet. Fixes EA client bug. * - 2009/09/22 Turley: Added DamagePacket support * - 2009/10/07 Turley: Fixed DestroyItem while in hand * - 2009/10/12 Turley: whisper/yell/say-range ssopt definition * - 2009/12/02 Turley: 0xf3 packet support - Tomi * face support * - 2009/12/03 Turley: added 0x17 packet everywhere only send if poisoned, fixed get_flag1 * (problem with poisoned & flying) */ #include "ufunc.h" #include #include #include #include #include #include #include "../bscript/impstr.h" #include "../clib/clib_endian.h" #include "../clib/logfacility.h" #include "../clib/passert.h" #include "../plib/mapcell.h" #include "../plib/systemstate.h" #include "accounts/account.h" #include "containr.h" #include "fnsearch.h" #include "globals/network.h" #include "globals/object_storage.h" #include "globals/state.h" #include "globals/uvars.h" #include "item/item.h" #include "layers.h" #include "lightlvl.h" #include "mobile/charactr.h" #include "mobile/corpse.h" #include "multi/multi.h" #include "multi/multidef.h" #include "network/cgdata.h" #include "network/client.h" #include "network/packetdefs.h" #include "network/packethelper.h" #include "network/pktdef.h" #include "objecthash.h" #include "objtype.h" #include "polcfg.h" #include "polclass.h" #include "realms/realm.h" #include "regions/miscrgn.h" #include "statmsg.h" #include "tooltips.h" #include "uobject.h" #include "uoclient.h" #include "uworld.h" namespace Pol { namespace Core { using namespace Network; using namespace Mobile; using namespace Items; // Dave added 3/9/3 void SetCurrentItemSerialNumber( u32 serial ) { stateManager.itemserialnumber = serial; } // Dave added 3/9/3 void SetCurrentCharSerialNumber( u32 serial ) { stateManager.charserialnumber = serial; } // Dave added 3/8/3 u32 GetCurrentItemSerialNumber( void ) { return stateManager.itemserialnumber; } // Dave added 3/8/3 u32 GetCurrentCharSerialNumber( void ) { return stateManager.charserialnumber; } // Dave changed 3/8/3 to use objecthash u32 GetNextSerialNumber( void ) { u32 nextserial = objStorageManager.objecthash.GetNextUnusedCharSerial(); stateManager.charserialnumber = nextserial; return stateManager.charserialnumber; } u32 UseCharSerialNumber( u32 serial ) { if ( serial > stateManager.charserialnumber ) stateManager.charserialnumber = serial + 1; return serial; } // Dave changed 3/8/3 u32 UseItemSerialNumber( u32 serial ) { if ( serial > stateManager.itemserialnumber ) stateManager.itemserialnumber = serial + 1; return serial; } // Dave changed 3/8/3 to use objecthash u32 GetNewItemSerialNumber( void ) { u32 nextserial = objStorageManager.objecthash.GetNextUnusedItemSerial(); stateManager.itemserialnumber = nextserial; return stateManager.itemserialnumber; } void send_goxyz( Client* client, const Character* chr ) { PktHelper::PacketOut msg; msg->Write( chr->serial_ext ); msg->WriteFlipped( chr->graphic ); msg->offset++; // unk7 msg->WriteFlipped( chr->color ); msg->Write( chr->get_flag1( client ) ); msg->WriteFlipped( chr->x() ); msg->WriteFlipped( chr->y() ); msg->offset += 2; // unk15,16 msg->Write( 0x80u | chr->facing ); // is it always right to set this flag? msg->Write( chr->z() ); msg.Send( client ); if ( ( client->ClientType & CLIENTTYPE_UOKR ) && ( chr->poisoned() ) ) // if poisoned send 0x17 for newer clients send_poisonhealthbar( client, chr ); if ( ( client->ClientType & CLIENTTYPE_UOKR ) && ( chr->invul() ) ) // if invul send 0x17 for newer clients send_invulhealthbar( client, chr ); } // Character chr has moved. Tell a client about it. void send_move( Client* client, const Character* chr ) { MoveChrPkt msgmove( chr ); msgmove.Send( client ); if ( chr->poisoned() ) // if poisoned send 0x17 for newer clients send_poisonhealthbar( client, chr ); if ( chr->invul() ) // if invul send 0x17 for newer clients send_invulhealthbar( client, chr ); } void send_poisonhealthbar( Client* client, const Character* chr ) { if ( client->ClientType & Network::CLIENTTYPE_UOKR ) { Network::HealthBarStatusUpdate msg( chr->serial_ext, Network::HealthBarStatusUpdate::Color::GREEN, chr->poisoned() ); msg.Send( client ); } } void send_invulhealthbar( Client* client, const Character* chr ) { if ( client->ClientType & Network::CLIENTTYPE_UOKR ) { Network::HealthBarStatusUpdate msg( chr->serial_ext, Network::HealthBarStatusUpdate::Color::YELLOW, chr->invul() ); msg.Send( client ); } } void send_owncreate( Client* client, const Character* chr ) { PktHelper::PacketOut owncreate; owncreate->offset += 2; owncreate->Write( chr->serial_ext ); owncreate->WriteFlipped( chr->graphic ); owncreate->WriteFlipped( chr->x() ); owncreate->WriteFlipped( chr->y() ); owncreate->Write( chr->z() ); owncreate->Write( chr->facing ); owncreate->WriteFlipped( chr->color ); owncreate->Write( chr->get_flag1( client ) ); owncreate->Write( chr->hilite_color_idx( client->chr ) ); for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer ) { const Item* item = chr->wornitem( layer ); if ( item == nullptr ) continue; // Dont send faces if older client or ssopt if ( ( layer == LAYER_FACE ) && ( ( settingsManager.ssopt.support_faces == 0 ) || ( ~client->ClientType & CLIENTTYPE_UOKR ) ) ) continue; if ( client->ClientType & CLIENTTYPE_70331 ) { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( item->graphic ); owncreate->Write( static_cast( layer ) ); owncreate->WriteFlipped( item->color ); } else if ( item->color ) { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( 0x8000u | item->graphic ); owncreate->Write( static_cast( layer ) ); owncreate->WriteFlipped( item->color ); } else { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( item->graphic ); owncreate->Write( static_cast( layer ) ); } } owncreate->offset += 4; // items nullterm u16 len = owncreate->offset; owncreate->offset = 1; owncreate->WriteFlipped( len ); owncreate.Send( client, len ); if ( client->UOExpansionFlag & AOS ) { send_object_cache( client, chr ); // 07/11/09 Turley: moved to bottom first the client needs to know the item then we can send // revision for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer ) { const Item* item = chr->wornitem( layer ); if ( item == nullptr ) continue; if ( layer == LAYER_FACE ) continue; send_object_cache( client, item ); } } if ( chr->poisoned() ) // if poisoned send 0x17 for newer clients send_poisonhealthbar( client, chr ); if ( chr->invul() ) // if invul send 0x17 for newer clients send_invulhealthbar( client, chr ); } void build_owncreate( const Character* chr, PktOut_78* owncreate ) { owncreate->offset += 2; owncreate->Write( chr->serial_ext ); owncreate->WriteFlipped( chr->graphic ); owncreate->WriteFlipped( chr->x() ); owncreate->WriteFlipped( chr->y() ); owncreate->Write( chr->z() ); owncreate->Write( chr->facing ); owncreate->WriteFlipped( chr->color ); // 17 } void send_owncreate( Client* client, const Character* chr, PktOut_78* owncreate ) { owncreate->offset = 17; owncreate->Write( chr->get_flag1( client ) ); owncreate->Write( chr->hilite_color_idx( client->chr ) ); for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer ) { const Item* item = chr->wornitem( layer ); if ( item == nullptr ) continue; // Dont send faces if older client or ssopt if ( ( layer == LAYER_FACE ) && ( ( settingsManager.ssopt.support_faces == 0 ) || ( ~client->ClientType & CLIENTTYPE_UOKR ) ) ) continue; if ( client->ClientType & CLIENTTYPE_70331 ) { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( item->graphic ); owncreate->Write( static_cast( layer ) ); owncreate->WriteFlipped( item->color ); } else if ( item->color ) { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( 0x8000u | item->graphic ); owncreate->Write( static_cast( layer ) ); owncreate->WriteFlipped( item->color ); } else { owncreate->Write( item->serial_ext ); owncreate->WriteFlipped( item->graphic ); owncreate->Write( static_cast( layer ) ); } } owncreate->offset += 4; // items nullterm u16 len = owncreate->offset; owncreate->offset = 1; owncreate->WriteFlipped( len ); networkManager.clientTransmit->AddToQueue( client, &owncreate->buffer, len ); if ( client->UOExpansionFlag & AOS ) { send_object_cache( client, chr ); // 07/11/09 Turley: moved to bottom first the client needs to know the item then we can send // revision for ( int layer = LAYER_EQUIP__LOWEST; layer <= LAYER_EQUIP__HIGHEST; ++layer ) { const Item* item = chr->wornitem( layer ); if ( item == nullptr ) continue; if ( layer == LAYER_FACE ) continue; send_object_cache( client, item ); } } } void send_remove_character( Client* client, const Character* chr ) { if ( !client->ready ) /* if a client is just connecting, don't bother him. */ return; /* Don't remove myself */ if ( client->chr == chr ) return; Network::RemoveObjectPkt msgremove( chr->serial_ext ); msgremove.Send( client ); } void send_remove_character( Network::Client* client, const Mobile::Character* chr, Network::RemoveObjectPkt& pkt ) { if ( !client->ready ) /* if a client is just connecting, don't bother him. */ return; /* Don't remove myself */ if ( client->chr == chr ) return; pkt.update( chr->serial_ext ); pkt.Send( client ); } void send_remove_character_to_nearby( const Character* chr ) { Network::RemoveObjectPkt msgremove( chr->serial_ext ); WorldIterator::InVisualRange( chr, [&]( Character* zonechr ) { if ( zonechr == chr ) return; msgremove.Send( zonechr->client ); } ); } void send_remove_character_to_nearby_cantsee( const Character* chr ) { Network::RemoveObjectPkt msgremove( chr->serial_ext ); WorldIterator::InVisualRange( chr, [&]( Character* zonechr ) { if ( zonechr == chr ) return; if ( !zonechr->is_visible_to_me( chr ) ) msgremove.Send( zonechr->client ); } ); } void send_remove_character_to_nearby_cansee( const Character* chr ) { Network::RemoveObjectPkt msgremove( chr->serial_ext ); WorldIterator::InVisualRange( chr, [&]( Character* _chr ) { if ( _chr != chr && _chr->is_visible_to_me( chr ) ) msgremove.Send( _chr->client ); } ); } void send_remove_object_if_inrange( Client* client, const Item* item ) { if ( !client->ready ) /* if a client is just connecting, don't bother him. */ return; if ( !inrange( client->chr, item ) ) return; Network::RemoveObjectPkt msgremove( item->serial_ext ); msgremove.Send( client ); } void send_remove_object( Client* client, const UObject* object ) { if ( client == nullptr || !client->ready ) return; Network::RemoveObjectPkt msgremove( object->serial_ext ); msgremove.Send( client ); } void send_remove_object_to_inrange( const UObject* centerObject ) { Network::RemoveObjectPkt msgremove( centerObject->serial_ext ); WorldIterator::InVisualRange( centerObject, [&]( Character* chr ) { msgremove.Send( chr->client ); } ); } void send_remove_object( Client* client, const UObject* item, RemoveObjectPkt& pkt ) { if ( !client->ready ) /* if a client is just connecting, don't bother him. */ return; pkt.update( item->serial_ext ); pkt.Send( client ); } bool inrangex( const Character* c1, const Character* c2, int maxdist ) { return c1->toplevel_pos().inRange( c2->pos(), maxdist ); } // TODO: Change all those inrangex functions to pol_distance() or something similar bool inrangex( const UObject* c1, const Pos2d& p2, int maxdist ) { return c1->pos().inRange( p2, maxdist ); } bool inrange( const UObject* c1, const Pos2d& p2 ) { return c1->toplevel_pos().inRange( p2, RANGE_VISUAL ); } bool inrange( const Mobile::Character* c1, const Mobile::Character* c2 ) { return c1->pos().inRange( c2->pos(), RANGE_VISUAL ); } bool inrange( const Mobile::Character* c1, const UObject* obj ) { return c1->pos().inRange( obj->toplevel_pos(), RANGE_VISUAL ); } bool multi_inrange( const Mobile::Character* c1, const Multi::UMulti* obj ) { return c1->pos().inRange( obj->pos(), RANGE_VISUAL_LARGE_BUILDINGS ); } bool in_say_range( const Character* c1, const Character* c2 ) { return inrangex( c1, c2, settingsManager.ssopt.speech_range ); } bool in_yell_range( const Character* c1, const Character* c2 ) { return inrangex( c1, c2, settingsManager.ssopt.yell_range ); } bool in_whisper_range( const Character* c1, const Character* c2 ) { return inrangex( c1, c2, settingsManager.ssopt.whisper_range ); } bool inrange( const Pos2d& p1, const Pos2d& p2 ) { return p1.inRange( p2, RANGE_VISUAL ); } bool multi_inrange( const Pos2d& p1, const Pos2d& p2 ) { return p1.inRange( p2, RANGE_VISUAL_LARGE_BUILDINGS ); } void send_put_in_container( Client* client, const Item* item ) { auto msg = Network::AddItemContainerMsg( item->serial_ext, item->graphic, item->get_senditem_amount(), item->pos().xy(), item->slot_index(), item->container->serial_ext, item->color ); msg.Send( client ); if ( client->UOExpansionFlag & AOS ) send_object_cache( client, item ); } void send_put_in_container_to_inrange( const Item* item ) { auto msg = Network::AddItemContainerMsg( item->serial_ext, item->graphic, item->get_senditem_amount(), item->pos().xy(), item->slot_index(), item->container->serial_ext, item->color ); auto pkt_rev = Network::ObjRevisionPkt( item->serial_ext, item->rev() ); // FIXME mightsee also checks remote containers thus the ForEachPlayer functions cannot be used for ( auto& client2 : networkManager.clients ) { if ( !client2->ready ) continue; // FIXME need to check character's additional_legal_items. // looks like inrange should be a Character member function. if ( client2->chr->mightsee( item->container ) ) { // FIXME if the container has an owner, and I'm not it, don't tell me? msg.Send( client2 ); pkt_rev.Send( client2 ); } } } // An item is visible on a corpse if: // - it's visible // - or the chr has seeinvisitems() privilege // - it's hair or beard bool can_see_on_corpse( const Client* client, const Item* item ) { bool invisible = ( item->invisible() && !client->chr->can_seeinvisitems() && item->layer != LAYER_HAIR && item->layer != LAYER_BEARD && item->layer != LAYER_FACE ); return !invisible; } // Helper function for send_corpse_items(). Sends packet 0x89 containing information // of equipped items on the corpse. void send_corpse_equip( Client* client, const UCorpse* corpse ) { PktHelper::PacketOut msg; msg->offset += 2; msg->Write( corpse->serial_ext ); for ( unsigned layer = LOWEST_LAYER; layer <= HIGHEST_LAYER; ++layer ) { Item* item2 = corpse->GetItemOnLayer( layer ); if ( !item2 ) continue; if ( !can_see_on_corpse( client, item2 ) ) continue; msg->Write( item2->layer ); msg->Write( item2->serial_ext ); } msg->offset += 1; // nullterm byte u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } // Helper function for send_corpse_items(). No need to send the full corpse contents, // just the equipped items. Uses packet 0x3C. void send_corpse_contents( Client* client, const UCorpse* corpse ) { PktHelper::PacketOut msg; msg->offset += 4; // msglen+count u16 count = 0; for ( unsigned layer = LOWEST_LAYER; layer <= HIGHEST_LAYER; ++layer ) { const Items::Item* item = corpse->GetItemOnLayer( layer ); if ( !item ) continue; if ( !can_see_on_corpse( client, item ) ) continue; msg->Write( item->serial_ext ); msg->WriteFlipped( item->graphic ); msg->offset++; // unk6 msg->WriteFlipped( item->get_senditem_amount() ); msg->WriteFlipped( item->x() ); msg->WriteFlipped( item->y() ); if ( client->ClientType & CLIENTTYPE_6017 ) msg->Write( item->slot_index() ); msg->Write( corpse->serial_ext ); msg->WriteFlipped( item->color ); // color ++count; } u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg->WriteFlipped( count ); msg.Send( client, len ); } // FIXME it would be better to compose this message once and // send to multiple clients. // // The corpse requires a packet (0x89) to say which items are equipped and another (0x3C) // to describe the equipped items (similar packet as in the send_container_contents(), but // just the outside items). void send_corpse( Client* client, const Item* item ) { const UCorpse* corpse = static_cast( item ); send_corpse_equip( client, corpse ); send_corpse_contents( client, corpse ); } // This function sends every item in the corpse, not only the equipped items. It's mainly // used to tell the player that he's now dead and his items are in the corpse. void send_full_corpse( Client* client, const Item* item ) { const UCorpse* corpse = static_cast( item ); send_corpse_equip( client, corpse ); send_container_contents( client, *corpse ); } void send_corpse_equip_inrange( const Item* item ) { const UCorpse* corpse = static_cast( item ); WorldIterator::InVisualRange( corpse, [&]( Character* chr ) { send_corpse_equip( chr->client, corpse ); } ); } // Item::sendto( Client* ) ?? void send_item( Client* client, const Item* item ) { if ( item->invisible() && !client->chr->can_seeinvisitems() ) { send_remove_object( client, item ); return; } u8 flags = 0; if ( client->chr->can_move( item ) ) flags |= ITEM_FLAG_FORCE_MOVABLE; auto pkt = SendWorldItem( item->serial, item->graphic, item->get_senditem_amount(), item->pos().xyz(), item->facing, item->color, flags ); pkt.Send( client ); // if the item is a corpse, transmit items contained by it if ( item->objtype_ == UOBJ_CORPSE ) { send_corpse( client, item ); } if ( client->UOExpansionFlag & AOS ) { send_object_cache( client, item ); return; } } /* Tell all clients new information about an item */ void send_item_to_inrange( const Item* item ) { auto pkt = SendWorldItem( item->serial, item->graphic, item->get_senditem_amount(), item->pos().xyz(), item->facing, item->color, 0 ); auto pkt_remove = RemoveObjectPkt( item->serial_ext ); auto pkt_rev = ObjRevisionPkt( item->serial_ext, item->rev() ); WorldIterator::InVisualRange( item, [&]( Character* zonechr ) { if ( item->invisible() && !zonechr->client->chr->can_seeinvisitems() ) { pkt_remove.Send( zonechr->client ); return; } u8 flags = 0; if ( zonechr->client->chr->can_move( item ) ) flags |= ITEM_FLAG_FORCE_MOVABLE; pkt.updateFlags( flags ); pkt.Send( zonechr->client ); // if the item is a corpse, transmit items contained by it if ( item->objtype_ == UOBJ_CORPSE ) { send_corpse( zonechr->client, item ); } pkt_rev.Send( zonechr->client ); } ); } void update_item_to_inrange( const Item* item ) { if ( item->container != nullptr ) { if ( IsCharacter( item->container->serial ) ) { // this may not be the right thing in all cases. // specifically, handle_dye used to not ever do send_wornitem. // FIXME way, way inefficient, but nontrivial. Character* chr = find_character( item->container->serial ); if ( chr ) { update_wornitem_to_inrange( chr, item ); } else POLLOG_ERROR.Format( "Ack! update_item_to_inrange: character 0x{:X} doesn't exist!\n" ) << item->container->serial; } else { send_put_in_container_to_inrange( item ); } } else { send_item_to_inrange( item ); } } void send_light( Client* client, int lightlevel ) { if ( VALID_LIGHTLEVEL( lightlevel ) ) { PktHelper::PacketOut msg; msg->Write( static_cast( lightlevel ) ); msg.Send( client ); } } void send_weather( Client* client, u8 type, u8 severity, u8 aux ) { PktHelper::PacketOut msg; msg->Write( type ); msg->Write( severity ); msg->Write( aux ); msg.Send( client ); } /* send_char_data: called once for each client when a new character enters the world. */ void send_char_data( Client* client, Character* chr ) { if ( !client->ready ) return; if ( !client->chr->is_visible_to_me( chr ) ) return; if ( inrange( client->chr, chr ) ) { send_owncreate( client, chr ); } } /* send_client_char_data: called once for each character when a client logs on. If in range, tell the client about each character. */ void send_client_char_data( Character* chr, Client* client ) { // Don't tell a client about its own character. if ( client->chr == chr ) return; if ( !client->chr->is_visible_to_me( chr ) ) return; if ( inrange( client->chr, chr ) ) { send_owncreate( client, chr ); } } void send_item_move_failure( Network::Client* client, u8 reason ) { PktHelper::PacketOut msg; msg->Write( reason ); msg.Send( client ); } void send_wornitem( Client* client, const Character* chr, const Item* item ) { PktHelper::PacketOut msg; msg->Write( item->serial_ext ); msg->WriteFlipped( item->graphic ); msg->offset++; // unk7 msg->Write( item->layer ); msg->Write( chr->serial_ext ); msg->WriteFlipped( item->color ); msg.Send( client ); if ( client->UOExpansionFlag & AOS ) { send_object_cache( client, item ); } } void send_wornitem_to_inrange( const Character* chr, const Item* item ) { PktHelper::PacketOut msg; msg->Write( item->serial_ext ); msg->WriteFlipped( item->graphic ); msg->offset++; // unk7 msg->Write( item->layer ); msg->Write( chr->serial_ext ); msg->WriteFlipped( item->color ); transmit_to_inrange( item, &msg->buffer, msg->offset ); send_object_cache_to_inrange( item ); } // This used when item already worn and graphic/color changed. Deletes the item // at client and then sends the new information. void update_wornitem_to_inrange( const Character* chr, const Item* item ) { if ( chr != nullptr ) { send_remove_object_to_inrange( item ); PktHelper::PacketOut msg; msg->Write( item->serial_ext ); msg->WriteFlipped( item->graphic ); msg->offset++; // unk7 msg->Write( item->layer ); msg->Write( chr->serial_ext ); msg->WriteFlipped( item->color ); transmit_to_inrange( item, &msg->buffer, msg->offset ); send_object_cache_to_inrange( item ); } } // does 'item' have a parent with serial 'serial'? bool is_a_parent( const Item* item, u32 serial ) { while ( item->container != nullptr ) { // UNTESTED item = item->container; if ( item->serial == serial ) return true; } return false; } // search for a container that this character can legally act upon // - remove items, insert items, etc. UContainer* find_legal_container( const Character* chr, u32 serial ) { UContainer* cont; cont = chr->backpack(); if ( cont ) { if ( serial == cont->serial ) return cont; // not the main backpack, look for subpacks. cont = cont->find_container( serial ); if ( cont ) return cont; } // 4/2007 - MuadDib // Wasn't in backpack, check wornitems cont = nullptr; Item* worn_item = chr->find_wornitem( serial ); if ( worn_item != nullptr && worn_item->script_isa( POLCLASS_CONTAINER ) ) { // Ignore these layers explicitly. Backpack especially since it was // already checked above. if ( worn_item->layer != LAYER_HAIR && worn_item->layer != LAYER_FACE && worn_item->layer != LAYER_BEARD && worn_item->layer != LAYER_BACKPACK && worn_item->layer != LAYER_MOUNT ) { UContainer* worn_cont = static_cast( worn_item ); if ( worn_cont != nullptr ) return worn_cont; } } // not in the backpack, or in a subpack. check global items and subpacks. Area2d area( zone_convert( chr->pos() - Vec2d( 8, 8 ) ), zone_convert( chr->pos() + Vec2d( 8, 8 ) ), nullptr ); for ( const auto& p : area ) { for ( auto& item : chr->realm()->getzone( p ).items ) { if ( item->isa( UOBJ_CLASS::CLASS_CONTAINER ) ) { cont = (UContainer*)item; if ( serial == cont->serial ) return cont; cont = cont->find_container( serial ); if ( cont ) return cont; } } } Item* item = chr->search_remote_containers( serial, nullptr /* don't care if it's a remote container */ ); if ( item != nullptr && item->isa( UOBJ_CLASS::CLASS_CONTAINER ) ) return static_cast( item ); else return nullptr; } // assume if you pass additlegal or isRemoteContainer, you init to false Item* find_legal_item( const Character* chr, u32 serial, bool* additlegal, bool* isRemoteContainer ) { UContainer* backpack = chr->backpack(); if ( backpack != nullptr && backpack->serial == serial ) return backpack; // check worn items // 04/2007 - MuadDib Added: // find_wornitem will now check inside containers listed in layers // for normal items now also. This will allow for quivers // in wornitems, handbags, pockets, whatever people want, // to find stuff as a legal item to the character. Treats it // just like the backpack, without making it specific like // a bankbox or backpack. Item* item = chr->find_wornitem( serial ); if ( item != nullptr ) return item; if ( backpack != nullptr ) { item = backpack->find( serial ); if ( item != nullptr ) return item; } // check items on the ground Area2d area( zone_convert( chr->pos() - Vec2d( 8, 8 ) ), zone_convert( chr->pos() + Vec2d( 8, 8 ) ), nullptr ); for ( const auto& p : area ) { for ( const auto& _item : chr->realm()->getzone( p ).items ) { if ( !inrange( chr, _item ) ) continue; if ( _item->serial == serial ) { passert_always( _item->container == nullptr ); return _item; } if ( _item->isa( UOBJ_CLASS::CLASS_CONTAINER ) ) { item = ( (const UContainer*)_item )->find( serial ); if ( item != nullptr ) return item; } } } if ( additlegal != nullptr ) *additlegal = true; return chr->search_remote_containers( serial, isRemoteContainer ); } void play_sound_effect( const UObject* center, u16 effect ) { Network::PlaySoundPkt msg( PKTOUT_54_FLAG_SINGLEPLAY, effect - 1u, Pos3d( center->pos().xy(), 0 ) ); // FIXME hearing range check perhaps? WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_sound_effect_xyz( const Pos4d& pos, u16 effect ) { Network::PlaySoundPkt msg( PKTOUT_54_FLAG_SINGLEPLAY, effect - 1u, pos.xyz() ); WorldIterator::InRange( pos, RANGE_VISUAL, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_sound_effect_private( const UObject* center, u16 effect, Character* forchr ) { if ( forchr->client ) { Network::PlaySoundPkt msg( PKTOUT_54_FLAG_SINGLEPLAY, effect - 1u, Pos3d( center->pos().xy(), 0 ) ); msg.Send( forchr->client ); } } void play_moving_effect( const UObject* src, const UObject* dst, u16 effect, u8 speed, u8 loop, u8 explode ) { Network::GraphicEffectPkt msg; msg.movingEffect( src, dst, effect, speed, loop, explode ); WorldIterator::InVisualRange( src->toplevel_owner(), [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); WorldIterator::InVisualRange( dst->toplevel_owner(), [&]( Character* zonechr ) { if ( !inrange( zonechr, src ) ) // send to char only in range of dst msg.Send( zonechr->client ); } ); } void play_moving_effect2( const Pos4d& src, const Pos3d& dst, u16 effect, u8 speed, u8 loop, u8 explode ) { Network::GraphicEffectPkt msg; msg.movingEffect( src.xyz(), dst, effect, speed, loop, explode ); WorldIterator::InRange( src, RANGE_VISUAL, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); WorldIterator::InRange( Pos4d( dst, src.realm() ), RANGE_VISUAL, [&]( Character* zonechr ) { if ( !inrange( zonechr, src.xy() ) ) // send to chrs only in range of dest msg.Send( zonechr->client ); } ); } void play_lightning_bolt_effect( const UObject* center ) { Network::GraphicEffectPkt msg; msg.lightningBold( center ); WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_object_centered_effect( const UObject* center, u16 effect, u8 speed, u8 loop ) { Network::GraphicEffectPkt msg; msg.followEffect( center, effect, speed, loop ); WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_stationary_effect( const Pos4d& pos, u16 effect, u8 speed, u8 loop, u8 explode ) { Network::GraphicEffectPkt msg; msg.stationaryEffect( pos.xyz(), effect, speed, loop, explode ); WorldIterator::InRange( pos, RANGE_VISUAL, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_stationary_effect_ex( const Pos4d& pos, u16 effect, u8 speed, u8 duration, u32 hue, u32 render, u16 effect3d ) { Network::GraphicEffectExPkt msg; msg.stationaryEffect( pos.xyz(), effect, speed, duration, hue, render, effect3d ); WorldIterator::InRange( pos, RANGE_VISUAL, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_object_centered_effect_ex( const UObject* center, u16 effect, u8 speed, u8 duration, u32 hue, u32 render, u8 layer, u16 effect3d ) { Network::GraphicEffectExPkt msg; msg.followEffect( center, effect, speed, duration, hue, render, layer, effect3d ); WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); } void play_moving_effect_ex( const UObject* src, const UObject* dst, u16 effect, u8 speed, u8 duration, u32 hue, u32 render, u8 direction, u8 explode, u16 effect3d, u16 effect3dexplode, u16 effect3dsound ) { Network::GraphicEffectExPkt msg; msg.movingEffect( src, dst, effect, speed, duration, hue, render, direction, explode, effect3d, effect3dexplode, effect3dsound ); WorldIterator::InVisualRange( src, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); WorldIterator::InVisualRange( dst, [&]( Character* zonechr ) { if ( !inrange( zonechr, src ) ) // send to chrs only in range of dst msg.Send( zonechr->client ); } ); } void play_moving_effect2_ex( const Pos4d& src, const Pos3d& dst, u16 effect, u8 speed, u8 duration, u32 hue, u32 render, u8 direction, u8 explode, u16 effect3d, u16 effect3dexplode, u16 effect3dsound ) { Network::GraphicEffectExPkt msg; msg.movingEffect( src.xyz(), dst, effect, speed, duration, hue, render, direction, explode, effect3d, effect3dexplode, effect3dsound ); WorldIterator::InRange( src, RANGE_VISUAL, [&]( Character* zonechr ) { msg.Send( zonechr->client ); } ); WorldIterator::InRange( Pos4d( dst, src.realm() ), RANGE_VISUAL, [&]( Character* zonechr ) { if ( !inrange( zonechr, src.xy() ) ) // send to chrs only in range of dst msg.Send( zonechr->client ); } ); } // System message -- message in lower left corner void send_sysmessage( Network::Client* client, const char* text, unsigned short font, unsigned short color ) { PktHelper::PacketOut msg; u16 textlen = static_cast( strlen( text ) + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) // FIXME need to handle this better second msg? textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( 0x01010101u ); msg->Write( 0x0101u ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); msg->Write( "System", 30 ); msg->Write( text, textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } // Unicode System message -- message in lower left corner void send_sysmessage_unicode( Network::Client* client, const std::string& text, const std::string& lang, unsigned short font, unsigned short color ) { std::vector utf16text = Bscript::String::toUTF16( text ); if ( utf16text.size() > SPEECH_MAX_LEN ) utf16text.resize( SPEECH_MAX_LEN ); PktHelper::PacketOut msg; msg->offset += 2; msg->Write( 0x01010101u ); msg->Write( 0x0101u ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); msg->Write( lang.c_str(), 4 ); msg->Write( "System", 30 ); msg->WriteFlipped( utf16text ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } void send_sysmessage( Network::Client* client, const std::string& text, unsigned short font, unsigned short color ) { send_sysmessage( client, text.c_str(), font, color ); } void broadcast( const char* text, unsigned short font, unsigned short color, unsigned short requiredCmdLevel ) { for ( auto& client : networkManager.clients ) { if ( !client->ready || client->chr->cmdlevel_ < requiredCmdLevel ) continue; send_sysmessage( client, text, font, color ); } } void broadcast_unicode( const std::string& text, const std::string& lang, unsigned short font, unsigned short color, unsigned short requiredCmdLevel ) { for ( auto& client : networkManager.clients ) { if ( !client->ready || client->chr->cmdlevel_ < requiredCmdLevel ) continue; send_sysmessage_unicode( client, text, lang, font, color ); } } void send_nametext( Client* client, const Character* chr, const std::string& str ) { PktHelper::PacketOut msg; u16 textlen = static_cast( str.length() + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( chr->serial_ext ); msg->Write( 0x0101u ); msg->Write( Plib::TEXTTYPE_YOU_SEE ); msg->WriteFlipped( chr->name_color( client->chr ) ); // 0x03B2 msg->WriteFlipped( 3u ); msg->Write( str.c_str(), 30 ); msg->Write( str.c_str(), textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } bool say_above( const UObject* obj, const char* text, unsigned short font, unsigned short color, unsigned int journal_print ) { PktHelper::PacketOut msg; u16 textlen = static_cast( strlen( text ) + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) // FIXME need to handle this better second msg? textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( obj->serial_ext ); msg->WriteFlipped( obj->graphic ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); switch ( journal_print ) { case JOURNAL_PRINT_YOU_SEE: msg->Write( "You see", 30 ); break; case JOURNAL_PRINT_NAME: default: msg->Write( obj->description().c_str(), 30 ); break; } msg->Write( text, textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); // todo: only send to those that I'm visible to. transmit_to_inrange( obj, &msg->buffer, len ); return true; } bool say_above_unicode( const UObject* obj, const std::string& text, const std::string& lang, unsigned short font, unsigned short color, unsigned int journal_print ) { std::vector utf16text = Bscript::String::toUTF16( text ); if ( utf16text.size() > SPEECH_MAX_LEN ) utf16text.resize( SPEECH_MAX_LEN ); PktHelper::PacketOut msg; msg->offset += 2; msg->Write( obj->serial_ext ); msg->WriteFlipped( obj->graphic ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); msg->Write( lang.c_str(), 4 ); switch ( journal_print ) { case JOURNAL_PRINT_YOU_SEE: msg->Write( "You see", 30 ); break; case JOURNAL_PRINT_NAME: default: msg->Write( obj->description().c_str(), 30 ); break; } msg->WriteFlipped( utf16text ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); // todo: only send to those that I'm visible to. transmit_to_inrange( obj, &msg->buffer, len ); return true; } bool private_say_above( Character* chr, const UObject* obj, const char* text, unsigned short font, unsigned short color, unsigned int journal_print ) { if ( chr->client == nullptr ) return false; PktHelper::PacketOut msg; u16 textlen = static_cast( strlen( text ) + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) // FIXME need to handle this better second msg? textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( obj->serial_ext ); msg->WriteFlipped( obj->graphic ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); switch ( journal_print ) { case JOURNAL_PRINT_YOU_SEE: msg->Write( "You see", 30 ); break; case JOURNAL_PRINT_NAME: default: msg->Write( obj->description().c_str(), 30 ); break; } msg->Write( text, textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( chr->client, len ); return true; } bool private_say_above_unicode( Character* chr, const UObject* obj, const std::string& text, const std::string& lang, unsigned short font, unsigned short color, unsigned int journal_print ) { std::vector utf16text = Bscript::String::toUTF16( text ); if ( utf16text.size() > SPEECH_MAX_LEN ) utf16text.resize( SPEECH_MAX_LEN ); if ( chr->client == nullptr ) return false; PktHelper::PacketOut msg; msg->offset += 2; msg->Write( obj->serial_ext ); msg->WriteFlipped( obj->graphic ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( font ); msg->Write( lang.c_str(), 4 ); switch ( journal_print ) { case JOURNAL_PRINT_YOU_SEE: msg->Write( "You see", 30 ); break; case JOURNAL_PRINT_NAME: default: msg->Write( obj->description().c_str(), 30 ); break; } msg->WriteFlipped( utf16text ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( chr->client, len ); return true; } bool private_say_above_ex( Character* chr, const UObject* obj, const char* text, unsigned short color ) { if ( chr->client == nullptr ) return false; PktHelper::PacketOut msg; u16 textlen = static_cast( strlen( text ) + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) // FIXME need to handle this better second msg? textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( obj->serial_ext ); msg->WriteFlipped( obj->graphic ); msg->Write( Plib::TEXTTYPE_NORMAL ); msg->WriteFlipped( color ); msg->WriteFlipped( 3u ); msg->Write( obj->description().c_str(), 30 ); msg->Write( text, textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( chr->client, len ); return true; } void send_objdesc( Client* client, Item* item ) { PktHelper::PacketOut msg; u16 textlen = static_cast( item->description().length() + 1 ); if ( textlen > SPEECH_MAX_LEN + 1 ) // FIXME need to handle this better second msg? textlen = SPEECH_MAX_LEN + 1; msg->offset += 2; msg->Write( item->serial_ext ); msg->WriteFlipped( item->graphic ); msg->Write( Plib::TEXTTYPE_YOU_SEE ); msg->WriteFlipped( 0x03B2u ); msg->WriteFlipped( 3u ); msg->Write( "System", 30 ); msg->Write( item->description().c_str(), textlen ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } void send_stamina_level( Client* client ) { Character* chr = client->chr; PktHelper::PacketOut msg; msg->Write( chr->serial_ext ); if ( networkManager.uoclient_general.stamina.any ) { int v = chr->vital( networkManager.uoclient_general.mana.id ).maximum_ones(); if ( v > 0xFFFF ) v = 0xFFFF; msg->WriteFlipped( static_cast( v ) ); v = chr->vital( networkManager.uoclient_general.mana.id ).current_ones(); if ( v > 0xFFFF ) v = 0xFFFF; msg->WriteFlipped( static_cast( v ) ); } else { msg->offset += 4; } msg.Send( client ); } void send_mana_level( Client* client ) { Character* chr = client->chr; PktHelper::PacketOut msg; msg->Write( chr->serial_ext ); if ( networkManager.uoclient_general.mana.any ) { int v = chr->vital( networkManager.uoclient_general.mana.id ).maximum_ones(); if ( v > 0xFFFF ) v = 0xFFFF; msg->WriteFlipped( static_cast( v ) ); v = chr->vital( networkManager.uoclient_general.mana.id ).current_ones(); if ( v > 0xFFFF ) v = 0xFFFF; msg->WriteFlipped( static_cast( v ) ); } else { msg->offset += 4; } msg.Send( client ); } void send_death_message( Character* chr_died, Item* corpse ) { PktHelper::PacketOut msg; msg->Write( chr_died->serial_ext ); msg->Write( corpse->serial_ext ); msg->offset += 4; // u32 unk4_zero WorldIterator::InVisualRange( corpse, [&]( Character* zonechr ) { if ( zonechr == chr_died ) return; msg.Send( zonechr->client ); } ); } void transmit_to_inrange( const UObject* center, const void* msg, unsigned msglen ) { WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { networkManager.clientTransmit->AddToQueue( zonechr->client, msg, msglen ); } ); } void transmit_to_others_inrange( Character* center, const void* msg, unsigned msglen ) { WorldIterator::InVisualRange( center, [&]( Character* zonechr ) { Client* client = zonechr->client; if ( zonechr == center ) return; networkManager.clientTransmit->AddToQueue( client, msg, msglen ); } ); } void destroy_item( Item* item ) { if ( item->serial == 0 ) { POLLOG_ERROR.Format( "destroy {}: {}, orphan! (old serial: 0x{:X})\n" ) << item->description() << item->classname() << ( cfBEu32( item->serial_ext ) ); } if ( item->serial != 0 ) { /* cout << "destroy " << item->description() << ": " << item->classname() << " " << item << ", serial=" << hexint(item->serial) << endl; */ item->set_dirty(); send_remove_object_to_inrange( item ); if ( item->container == nullptr ) // on ground, easy. { if ( !item->has_gotten_by() ) // and not in hand remove_item_from_world( item ); } else { item->extricate(); } item->destroy(); } } void subtract_amount_from_item( Item* item, unsigned short amount ) { if ( amount >= item->getamount() ) { destroy_item( item ); return; // destroy_item will update character weight if item is carried. } else { item->subamount( amount ); update_item_to_inrange( item ); } // DAVE added this 11/17: if in a Character's pack, update weight. UpdateCharacterWeight( item ); } // FIXME: this is called from some places where the item didn't used // to be on the ground - in a container, say. // FIXME OPTIMIZE: Core is building the packet in send_item for every single client // that needs to get it. There should be a better method for this. Such as, a function // to run all the checks after building the packet here, then send as it needs to. void move_item( Item* item, Pos4d newpos, Pos4d oldpos ) { item->set_dirty(); // Pos4d oldpos = item->pos(); // item->setposition( std::move( newpos ) ); item->restart_decay_timer(); MoveItemWorldPosition( oldpos, item ); WorldIterator::InVisualRange( item, [&]( Character* zonechr ) { send_item( zonechr->client, item ); } ); Network::RemoveObjectPkt msgremove( item->serial_ext ); WorldIterator::InRange( oldpos, RANGE_VISUAL, [&]( Character* zonechr ) { if ( !inrange( zonechr, item ) ) // not in range. If old loc was in range, send a delete. msgremove.Send( zonechr->client ); } ); } void send_multi( Client* client, const Multi::UMulti* multi ) { auto pkt = SendWorldMulti( multi->serial_ext, multi->multidef().multiid, multi->pos().xyz(), multi->color ); pkt.Send( client ); } void send_multi_to_inrange( const Multi::UMulti* multi ) { auto pkt = SendWorldMulti( multi->serial_ext, multi->multidef().multiid, multi->pos().xyz(), multi->color ); WorldIterator::InVisualRange( multi, [&]( Character* zonechr ) { pkt.Send( zonechr->client ); } ); } void update_lightregion( Client* client, LightRegion* /*lightregion*/ ) { if ( !client->ready ) return; client->chr->check_light_region_change(); } void SetRegionLightLevel( LightRegion* lightregion, int lightlevel ) { lightregion->lightlevel = lightlevel; PktHelper::PacketOut msg; msg->Write( static_cast( lightlevel ) ); for ( Clients::iterator itr = networkManager.clients.begin(), end = networkManager.clients.end(); itr != end; ++itr ) { Client* client = *itr; if ( !client->ready ) continue; auto light_until = client->chr->lightoverride_until(); if ( light_until < read_gameclock() && light_until != ~0u ) { client->chr->lightoverride_until( 0 ); client->chr->lightoverride( -1 ); } if ( client->gd->weather_region && client->gd->weather_region->lightoverride != -1 && !client->chr->has_lightoverride() ) continue; int newlightlevel; if ( client->chr->has_lightoverride() ) newlightlevel = client->chr->lightoverride(); else { // dave 12-22 check for no regions LightRegion* light_region = gamestate.lightdef->getregion( client->chr->pos() ); if ( light_region != nullptr ) newlightlevel = light_region->lightlevel; else newlightlevel = settingsManager.ssopt.default_light_level; } if ( newlightlevel != client->gd->lightlevel ) { if ( VALID_LIGHTLEVEL( newlightlevel ) ) { if ( newlightlevel != lightlevel ) { msg->offset = 1; msg->Write( static_cast( newlightlevel ) ); msg.Send( client ); msg->offset = 1; msg->Write( static_cast( lightlevel ) ); } else msg.Send( client ); } client->gd->lightlevel = newlightlevel; } } } void update_weatherregion( Client* client, WeatherRegion* weatherregion ) { if ( !client->ready ) return; if ( client->gd->weather_region == weatherregion ) { // client->gd->weather_region = nullptr; //dave commented this out 5/26/03, causing no // processing to happen in following function, added force bool instead. client->chr->check_weather_region_change( true ); client->chr->check_light_region_change(); } } void SetRegionWeatherLevel( WeatherRegion* weatherregion, unsigned type, unsigned severity, unsigned aux, int lightoverride ) { weatherregion->weathertype = static_cast( type ); weatherregion->severity = static_cast( severity ); weatherregion->aux = static_cast( aux ); weatherregion->lightoverride = lightoverride; for ( auto& client : networkManager.clients ) { update_weatherregion( client, weatherregion ); } } void update_all_weatherregions() { for ( auto& client : networkManager.clients ) { if ( !client->ready ) return; client->chr->check_weather_region_change(); client->chr->check_light_region_change(); } } /* there are four forms of 'name' in objinfo: name (normal) name%s (percent followed by plural-part, then null-term) name%s% (percent followed by plural-part, then percent, then more) wheat shea%ves/f% ( '%', plural part, '/', single part, '%', rest ) Some examples: pil%es/e% of hides banana%s% feather%s Known bugs: 1 gold coin displays as "gold coin". There must be a bit somewhere that I just don't understand yet. */ std::string format_description( unsigned int polflags, const std::string& descdef, unsigned short amount, const std::string suffix ) { std::string desc; if ( amount != 1 ) { char s[15]; sprintf( s, "%hu ", amount ); desc = s; } else if ( settingsManager.ssopt.use_tile_flag_prefix ) { if ( polflags & Plib::FLAG::DESC_PREPEND_AN ) { desc = "an "; } else if ( polflags & Plib::FLAG::DESC_PREPEND_A ) { desc = "a "; } } // might want to consider strchr'ing here, // if not found, strcpy/return // if found, memcpy up to strchr result, then continue as below. const char* src = descdef.c_str(); int singular = ( amount == 1 ); int plural_handled = 0; int phase = 0; /* 0= first part, 1=plural part, 2=singular part, 3=rest */ char ch; while ( '\0' != ( ch = *src ) ) { if ( phase == 0 ) { if ( ch == '%' ) { plural_handled = 1; phase = 1; } else { desc += ch; } } else if ( phase == 1 ) { if ( ch == '%' ) phase = 3; else if ( ch == '/' ) phase = 2; else if ( !singular ) desc += ch; } else if ( phase == 2 ) { if ( ch == '%' ) phase = 3; else if ( singular ) desc += ch; } // if phase == 3 that means there are more words to come, // lets loop through them to support singular/plural stuff in more than just the first word of // the desc. else { desc += ch; phase = 0; } ++src; } if ( !singular && !plural_handled ) desc += 's'; if ( !suffix.empty() ) desc += " " + suffix; return desc; } void send_midi( Client* client, u16 midi ) { PktHelper::PacketOut msg; msg->WriteFlipped( midi ); msg.Send( client ); // cout << "Setting midi for " << client->chr->name() << " to " << midi << endl; } void register_with_supporting_multi( Item* item ) { if ( item->container == nullptr ) { Multi::UMulti* multi = item->supporting_multi(); if ( multi ) multi->register_object( item ); } } void send_create_mobile_if_nearby_cansee( Client* client, const Character* chr ) { if ( client->ready && // must be logged into game inrange( client->chr, chr ) && client->chr != chr && client->chr->is_visible_to_me( chr ) ) { send_owncreate( client, chr ); } } void send_create_mobile_to_nearby_cansee( const Character* chr ) { WorldIterator::InVisualRange( chr, [&]( Character* zonechr ) { if ( zonechr == chr ) return; if ( zonechr->is_visible_to_me( chr ) ) send_owncreate( zonechr->client, chr ); } ); } void send_move_mobile_to_nearby_cansee( const Character* chr ) { MoveChrPkt msgmove( chr ); std::unique_ptr msgpoisoned; std::unique_ptr msginvul; if ( chr->poisoned() ) msgpoisoned.reset( new HealthBarStatusUpdate( chr->serial_ext, HealthBarStatusUpdate::Color::GREEN, chr->poisoned() ) ); if ( chr->invul() ) msginvul.reset( new HealthBarStatusUpdate( chr->serial_ext, HealthBarStatusUpdate::Color::YELLOW, chr->invul() ) ); WorldIterator::InVisualRange( chr, [&]( Character* zonechr ) { if ( zonechr == chr ) return; if ( zonechr->is_visible_to_me( chr ) ) { msgmove.Send( zonechr->client ); if ( msgpoisoned ) msgpoisoned->Send( zonechr->client ); if ( msginvul ) msginvul->Send( zonechr->client ); } } ); } Character* UpdateCharacterWeight( Item* item ) { Character* chr_owner = item->toplevel_chr(); if ( chr_owner != nullptr && chr_owner->client != nullptr ) { send_full_statmsg( chr_owner->client, chr_owner ); return chr_owner; } return nullptr; } void UpdateCharacterOnDestroyItem( Item* item ) { Character* chr_owner = item->toplevel_chr(); if ( chr_owner != nullptr ) { if ( item->layer && chr_owner->is_equipped( item ) ) { item->check_unequiptest_scripts(); item->check_unequip_script(); send_remove_object_to_inrange( item ); } } } // Dave added this 12/1/02 bool clientHasCharacter( Client* c ) { return ( c->chr != nullptr ); } void login_complete( Client* c ) { PktHelper::PacketOut msg; msg.Send( c ); } void send_feature_enable( Client* client ) { u32 clientflag = 0; switch ( client->acct->uo_expansion_flag() ) { case TOL: clientflag = 0x7387DF; client->UOExpansionFlag = TOL | HSA | SA | KR | ML | SE | AOS; // TOL needs HSA- SA- KR- ML- SE- and AOS- features (and used checks) too break; case HSA: clientflag = 0x387DF; client->UOExpansionFlag = HSA | SA | KR | ML | SE | AOS; // HSA needs SA- KR- ML- SE- and AOS- features (and used checks) too break; case SA: clientflag = 0x187DF; client->UOExpansionFlag = SA | KR | ML | SE | AOS; // SA needs KR- ML- SE- and AOS- features (and used checks) too break; case KR: clientflag = 0x86DB; client->UOExpansionFlag = KR | ML | SE | AOS; // KR needs ML- SE- and AOS-features (and used checks) too break; case ML: clientflag = 0x80DB; client->UOExpansionFlag = ML | SE | AOS; // ML needs SE- and AOS-features (and used checks) too break; case SE: clientflag = 0x805B; client->UOExpansionFlag = SE | AOS; // SE needs AOS-features (and used checks) too break; case AOS: clientflag = 0x801B; client->UOExpansionFlag = AOS; break; case LBR: clientflag = 0x0002; client->UOExpansionFlag = LBR; break; case T2A: clientflag = 0x0001; client->UOExpansionFlag = T2A; break; default: break; } // Change flag according to the number of CharacterSlots if ( client->UOExpansionFlag & AOS ) { if ( Plib::systemstate.config.character_slots == 7 ) { clientflag |= 0x1000; // 7th & 6th character flag (B9 Packet) clientflag &= ~0x0004; // Disable Third Dawn? } else if ( Plib::systemstate.config.character_slots == 6 ) { clientflag |= 0x0020; // 6th character flag (B9 Packet) clientflag &= ~0x0004; } } // Roleplay faces? if ( client->UOExpansionFlag & KR ) { if ( settingsManager.ssopt.support_faces == 2 ) clientflag |= 0x2000; } PktHelper::PacketOut msg; if ( client->ClientType & CLIENTTYPE_60142 ) msg->WriteFlipped( clientflag & 0xFFFFFFFFu ); else msg->WriteFlipped( clientflag & 0xFFFFu ); msg.Send( client ); } void send_realm_change( Client* client, Realms::Realm* realm ) { PktHelper::PacketOut msg; msg->WriteFlipped( 6u ); msg->offset += 2; // sub msg->Write( realm->getUOMapID() ); msg.Send( client ); } /// Sends number of maps used and number of map/static patches for each map void send_map_difs( Client* client ) { // Prepare the data by reading map id used by every realm // When a map is used multiple times in different realms, only // take into account first realm found. No support for using the // same map with different diff files for different realms. struct mapdiff { u32 static_patches; u32 map_patches; }; std::map mapinfo; for ( auto it = gamestate.Realms.begin(); it != gamestate.Realms.end(); ++it ) { mapdiff md = {( *it )->getNumStaticPatches(), ( *it )->getNumMapPatches()}; mapinfo.insert( std::pair( ( *it )->getUOMapID(), md ) ); } u32 max_map_id = mapinfo.rbegin()->first; PktHelper::PacketOut msg; msg->offset += 4; // len+sub msg->WriteFlipped( max_map_id + 1 ); // Number of maps for ( u32 i = 0; i <= max_map_id; i++ ) { auto it = mapinfo.find( i ); if ( it == mapinfo.end() ) { // Filling hole (map not used) msg->WriteFlipped( 0u ); msg->WriteFlipped( 0u ); } else { msg->WriteFlipped( mapinfo.at( i ).static_patches ); msg->WriteFlipped( mapinfo.at( i ).map_patches ); } } u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( client, len ); } // FIXME : Works, except for Login. Added length check as to not mess with 1.x clients // during login. Added send_season_info to handle_client_version to make up. void send_season_info( Client* client ) { if ( client->getversiondetail().major >= 1 ) { PktHelper::PacketOut msg; msg->Write( client->chr->realm()->season() ); msg->Write( PKTOUT_BC::PLAYSOUND_YES ); msg.Send( client ); // Sending Season info resets light level in client, this fixes it during login if ( client->gd->weather_region != nullptr && client->gd->weather_region->lightoverride != -1 && !client->chr->has_lightoverride() ) { send_light( client, client->gd->weather_region->lightoverride ); } } } // assumes new realm has been set on client->chr void send_new_subserver( Client* client ) { PktHelper::PacketOut msg; msg->WriteFlipped( client->chr->x() ); msg->WriteFlipped( client->chr->y() ); msg->WriteFlipped( static_cast( client->chr->z() ) ); msg->offset += 5; // unk0,x1,y2 msg->WriteFlipped( client->chr->realm()->width() ); msg->WriteFlipped( client->chr->realm()->height() ); msg.Send( client ); } void send_fight_occuring( Client* client, Character* opponent ) { PktHelper::PacketOut msg; msg->offset++; // zero1 msg->Write( client->chr->serial_ext ); msg->Write( opponent->serial_ext ); msg.Send( client ); } void send_damage( Character* attacker, Character* defender, u16 damage ) { SendDamagePkt pkt( defender->serial_ext, damage ); if ( attacker->client != nullptr ) pkt.Send( attacker->client ); if ( ( defender->client != nullptr ) && ( attacker != defender ) ) pkt.Send( defender->client ); } void sendCharProfile( Character* chr, Character* of_who, const std::string& title, const std::string& utext, const std::string& etext ) { PktHelper::PacketOut msg; std::vector uwtext = Bscript::String::toUTF16( utext ); std::vector ewtext = Bscript::String::toUTF16( etext ); size_t titlelen = title.size(); // Check Lengths if ( titlelen > SPEECH_MAX_LEN ) titlelen = SPEECH_MAX_LEN; if ( uwtext.size() > SPEECH_MAX_LEN ) uwtext.resize( SPEECH_MAX_LEN ); if ( ewtext.size() > SPEECH_MAX_LEN ) ewtext.resize( SPEECH_MAX_LEN ); // Build Packet msg->offset += 2; msg->Write( of_who->serial_ext ); msg->Write( title.c_str(), static_cast( titlelen + 1 ) ); msg->WriteFlipped( uwtext ); msg->WriteFlipped( ewtext ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( chr->client, len ); } /** * Sends the packet for the buff bar * @author Bodom, 2015-12-02 * * @param chr the Character to send the packet to * @param icon the ID of the icon to show/remove * @param show if true, shows/updates the icon; if false, removes the icon * @param duration duration in seconds, only for displaying [ignored if show is false] * @param cl_name name of the buff, cliloc id [ignored if show is false] * @param cl_descr description of the buff, cliloc id [ignored if show is false] * @param arguments arguments for cl_descr as string, separated by spaces */ void send_buff_message( Character* chr, u16 icon, bool show, u16 duration, u32 cl_name, u32 cl_descr, const std::string& arguments ) { PktHelper::PacketOut msg; msg->offset += 2; // length will be written later msg->Write( chr->serial_ext ); msg->WriteFlipped( icon ); msg->Write( 0u ); // unknown, always 0 msg->Write( show ? 1u : 0u ); if ( show ) { msg->Write( 0u ); // unknown, always 0 msg->WriteFlipped( icon ); msg->Write( 0u ); // unknown, always 0 msg->Write( 1u ); // unknown, always 1 msg->Write( 0u ); // unknown, always 0 msg->WriteFlipped( duration ); msg->Write( 0u ); // unknown, always 0 msg->Write( 0u ); // unknown, always 0 msg->WriteFlipped( cl_name ); msg->WriteFlipped( cl_descr ); msg->Write( 0u ); // unknown, always 0 msg->Write( 0u ); // unknown, always 0 msg->Write( 1u ); // unknown, always 1 msg->Write( 20u ); // a space character msg->Write( 20u ); // a space character msg->Write( Bscript::String::toUTF16( arguments ) ); } u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( chr->client, len ); } } // namespace Core } // namespace Pol