/** @file * * @par History * - 2009/07/23 MuadDib: updates for new Enum::Packet Out ID * - 2009/08/25 Shinigami: STLport-5.2.1 fix: init order changed of _leaderserial * STLport-5.2.1 fix: UCconv, bytemsg and wtextoffset not used * STLport-5.2.1 fix: "wtext[ SPEECH_MAX_LEN+1 ];" has no effect * STLport-5.2.1 fix: illegal usage of nullptr instead of 0 * STLport-5.2.1 fix: initialization of member in handle_party_msg() * - 2009/08/26 Turley: changed convertUCtoArray() to if check * - 2009/11/17 Turley: fixed hang due to illegal pointers */ #include "pol_global_config.h" #include "party.h" #include #include #include #include #include #include "../bscript/impstr.h" #include "../clib/cfgelem.h" #include "../clib/cfgfile.h" #include "../clib/cfgsect.h" #include "../clib/clib_endian.h" #include "../clib/fileutil.h" #include "../clib/logfacility.h" #include "../clib/streamsaver.h" #include "../plib/systemstate.h" #include "clfunc.h" #include "fnsearch.h" #include "globals/network.h" #include "globals/settings.h" #include "globals/uvars.h" #include "mobile/charactr.h" #include "module/partymod.h" #include "network/client.h" #include "network/packethelper.h" #include "network/packets.h" #include "network/pktboth.h" #include "network/pktdef.h" #include "party_cfg.h" #include "polclock.h" #include "schedule.h" #include "statmsg.h" #include "syshook.h" #include "ufunc.h" #include "uobject.h" #include "uoclient.h" #ifdef MEMORYLEAK #include "../bscript/bobject.h" #endif namespace Pol { namespace Core { void handle_unknown_packet( Network::Client* client ); Party::Party( u32 serial ) : _member_serials(), _candidates_serials(), _offlinemember_serials(), _leaderserial( serial ), _proplist( Core::CPropProfiler::Type::PARTY ) { _member_serials.push_back( serial ); } Party::Party( Clib::ConfigElem& elem ) : _member_serials(), _candidates_serials(), _offlinemember_serials(), _leaderserial( 0 ), _proplist( Core::CPropProfiler::Type::PARTY ) { _leaderserial = elem.remove_ulong( "LEADER" ); unsigned int tmp; while ( elem.remove_prop( "MEMBER", &tmp ) ) { _member_serials.push_back( tmp ); } _proplist.readProperties( elem ); } void load_party_cfg_general( Clib::ConfigElem& elem ) { settingsManager.party_cfg.General.MaxPartyMembers = elem.remove_ushort( "MaxPartyMembers", 10 ); settingsManager.party_cfg.General.TreatNoAsPrivate = elem.remove_bool( "TreatNoAsPrivate", false ); settingsManager.party_cfg.General.DeclineTimeout = elem.remove_ushort( "DeclineTimeout", 10 ); settingsManager.party_cfg.General.RemoveMemberOnLogoff = elem.remove_bool( "RemoveMemberOnLogoff", false ); if ( settingsManager.party_cfg.General.RemoveMemberOnLogoff ) settingsManager.party_cfg.General.RejoinPartyOnLogon = elem.remove_bool( "RejoinPartyOnLogon", false ); else settingsManager.party_cfg.General.RejoinPartyOnLogon = false; std::string tmp = elem.remove_string( "PrivateMsgPrefix", "" ); if ( !tmp.empty() ) { Clib::sanitizeUnicodeWithIso( &tmp ); tmp += " "; settingsManager.party_cfg.General.PrivateMsgPrefix = tmp; } else settingsManager.party_cfg.General.PrivateMsgPrefix = ""; } void load_party_cfg_hooks( Clib::ConfigElem& elem ) { std::string temp; if ( elem.remove_prop( "CanAddToParty", &temp ) ) settingsManager.party_cfg.Hooks.CanAddToParty = FindExportedFunction( elem, nullptr, temp, 2 ); if ( elem.remove_prop( "CanRemoveMember", &temp ) ) settingsManager.party_cfg.Hooks.CanRemoveMember = FindExportedFunction( elem, nullptr, temp, 2 ); if ( elem.remove_prop( "CanLeaveParty", &temp ) ) settingsManager.party_cfg.Hooks.CanLeaveParty = FindExportedFunction( elem, nullptr, temp, 1 ); if ( elem.remove_prop( "OnPublicChat", &temp ) ) settingsManager.party_cfg.Hooks.OnPublicChat = FindExportedFunction( elem, nullptr, temp, 2 ); if ( elem.remove_prop( "OnPrivateChat", &temp ) ) settingsManager.party_cfg.Hooks.OnPrivateChat = FindExportedFunction( elem, nullptr, temp, 3 ); if ( elem.remove_prop( "OnDisband", &temp ) ) settingsManager.party_cfg.Hooks.OnDisband = FindExportedFunction( elem, nullptr, temp, 1 ); if ( elem.remove_prop( "ChangePublicChat", &temp ) ) settingsManager.party_cfg.Hooks.ChangePublicChat = FindExportedFunction( elem, nullptr, temp, 2 ); if ( elem.remove_prop( "ChangePrivateChat", &temp ) ) settingsManager.party_cfg.Hooks.ChangePrivateChat = FindExportedFunction( elem, nullptr, temp, 3 ); if ( elem.remove_prop( "OnLeaveParty", &temp ) ) settingsManager.party_cfg.Hooks.OnLeaveParty = FindExportedFunction( elem, nullptr, temp, 2 ); if ( elem.remove_prop( "OnAddToParty", &temp ) ) settingsManager.party_cfg.Hooks.OnAddToParty = FindExportedFunction( elem, nullptr, temp, 1 ); if ( elem.remove_prop( "OnPartyCreate", &temp ) ) settingsManager.party_cfg.Hooks.OnPartyCreate = FindExportedFunction( elem, nullptr, temp, 1 ); if ( elem.remove_prop( "OnDecline", &temp ) ) settingsManager.party_cfg.Hooks.OnDecline = FindExportedFunction( elem, nullptr, temp, 1 ); if ( elem.remove_prop( "OnLootPermChange", &temp ) ) settingsManager.party_cfg.Hooks.OnLootPermChange = FindExportedFunction( elem, nullptr, temp, 1 ); } void load_party_cfg( bool reload ) { if ( !Clib::FileExists( "config/party.cfg" ) ) { settingsManager.party_cfg.General.MaxPartyMembers = 10; settingsManager.party_cfg.General.TreatNoAsPrivate = false; settingsManager.party_cfg.General.DeclineTimeout = 10; settingsManager.party_cfg.General.RemoveMemberOnLogoff = false; settingsManager.party_cfg.General.RejoinPartyOnLogon = false; settingsManager.party_cfg.General.PrivateMsgPrefix = ""; } else { Clib::ConfigFile cf( "config/party.cfg" ); Clib::ConfigSection general( cf, "General" ); Clib::ConfigSection hooks( cf, "HookList", Clib::CST_NORMAL ); Clib::ConfigElem elem; while ( cf.read( elem ) ) { if ( general.matches( elem ) ) load_party_cfg_general( elem ); else if ( hooks.matches( elem ) && !reload ) load_party_cfg_hooks( elem ); } } } void unload_party_hooks() { if ( settingsManager.party_cfg.Hooks.CanAddToParty != nullptr ) { delete settingsManager.party_cfg.Hooks.CanAddToParty; settingsManager.party_cfg.Hooks.CanAddToParty = nullptr; } if ( settingsManager.party_cfg.Hooks.CanRemoveMember != nullptr ) { delete settingsManager.party_cfg.Hooks.CanRemoveMember; settingsManager.party_cfg.Hooks.CanRemoveMember = nullptr; } if ( settingsManager.party_cfg.Hooks.CanLeaveParty != nullptr ) { delete settingsManager.party_cfg.Hooks.CanLeaveParty; settingsManager.party_cfg.Hooks.CanLeaveParty = nullptr; } if ( settingsManager.party_cfg.Hooks.OnPublicChat != nullptr ) { delete settingsManager.party_cfg.Hooks.OnPublicChat; settingsManager.party_cfg.Hooks.OnPublicChat = nullptr; } if ( settingsManager.party_cfg.Hooks.OnPrivateChat != nullptr ) { delete settingsManager.party_cfg.Hooks.OnPrivateChat; settingsManager.party_cfg.Hooks.OnPrivateChat = nullptr; } if ( settingsManager.party_cfg.Hooks.OnDisband != nullptr ) { delete settingsManager.party_cfg.Hooks.OnDisband; settingsManager.party_cfg.Hooks.OnDisband = nullptr; } if ( settingsManager.party_cfg.Hooks.ChangePublicChat != nullptr ) { delete settingsManager.party_cfg.Hooks.ChangePublicChat; settingsManager.party_cfg.Hooks.ChangePublicChat = nullptr; } if ( settingsManager.party_cfg.Hooks.ChangePrivateChat != nullptr ) { delete settingsManager.party_cfg.Hooks.ChangePrivateChat; settingsManager.party_cfg.Hooks.ChangePrivateChat = nullptr; } if ( settingsManager.party_cfg.Hooks.OnLeaveParty != nullptr ) { delete settingsManager.party_cfg.Hooks.OnLeaveParty; settingsManager.party_cfg.Hooks.OnLeaveParty = nullptr; } if ( settingsManager.party_cfg.Hooks.OnAddToParty != nullptr ) { delete settingsManager.party_cfg.Hooks.OnAddToParty; settingsManager.party_cfg.Hooks.OnAddToParty = nullptr; } if ( settingsManager.party_cfg.Hooks.OnPartyCreate != nullptr ) { delete settingsManager.party_cfg.Hooks.OnPartyCreate; settingsManager.party_cfg.Hooks.OnPartyCreate = nullptr; } if ( settingsManager.party_cfg.Hooks.OnDecline != nullptr ) { delete settingsManager.party_cfg.Hooks.OnDecline; settingsManager.party_cfg.Hooks.OnDecline = nullptr; } if ( settingsManager.party_cfg.Hooks.OnLootPermChange != nullptr ) { delete settingsManager.party_cfg.Hooks.OnLootPermChange; settingsManager.party_cfg.Hooks.OnLootPermChange = nullptr; } } void unload_party() { unload_party_hooks(); for ( auto& party : gamestate.parties ) { party->cleanup(); } gamestate.parties.clear(); } void Party::cleanup() { _member_serials.clear(); _candidates_serials.clear(); _offlinemember_serials.clear(); } u32 Party::leader() const { return _leaderserial; } bool Party::is_member( u32 serial ) const { return std::find( _member_serials.begin(), _member_serials.end(), serial ) != _member_serials.end(); } bool Party::is_candidate( u32 serial ) const { return std::find( _candidates_serials.begin(), _candidates_serials.end(), serial ) != _candidates_serials.end(); } bool Party::register_with_members() { std::vector::iterator itr = _member_serials.begin(); while ( itr != _member_serials.end() ) { Mobile::Character* chr = system_find_mobile( *itr ); if ( chr != nullptr ) { chr->party( this ); ++itr; } else itr = _member_serials.erase( itr ); } if ( _member_serials.empty() ) return ( false ); if ( !test_size() ) return ( false ); if ( system_find_mobile( _leaderserial ) == nullptr ) _leaderserial = *_member_serials.begin(); return true; } bool Party::is_leader( u32 serial ) const { return _leaderserial == serial; } u32 Party::get_member_at( unsigned short pos ) const { if ( _member_serials.size() < pos ) return 0; return _member_serials[pos]; } bool Party::add_candidate( u32 serial ) { if ( ( _member_serials.size() + _candidates_serials.size() ) >= settingsManager.party_cfg.General.MaxPartyMembers ) return false; _candidates_serials.push_back( serial ); return true; } bool Party::add_member( u32 serial ) { if ( ( _member_serials.size() + _candidates_serials.size() ) >= settingsManager.party_cfg.General.MaxPartyMembers ) return false; _member_serials.push_back( serial ); return true; } void Party::add_offline_mem( u32 serial ) { _offlinemember_serials.push_back( serial ); } bool Party::remove_candidate( u32 serial ) { auto itr = std::find_if( _candidates_serials.begin(), _candidates_serials.end(), [&]( u32& i ) { return i == serial; } ); if ( itr != _candidates_serials.end() ) { _candidates_serials.erase( itr ); return true; } return false; } bool Party::remove_member( u32 serial ) { auto itr = std::find_if( _member_serials.begin(), _member_serials.end(), [&]( u32& i ) { return i == serial; } ); if ( itr != _member_serials.end() ) { _member_serials.erase( itr ); return true; } return false; } bool Party::remove_offline_mem( u32 serial ) { auto itr = std::find_if( _offlinemember_serials.begin(), _offlinemember_serials.end(), [&]( u32& i ) { return i == serial; } ); if ( itr != _offlinemember_serials.end() ) { _offlinemember_serials.erase( itr ); return true; } return false; } void Party::set_leader( u32 serial ) { auto itr = std::find_if( _member_serials.begin(), _member_serials.end(), [&]( u32& i ) { return i == serial; } ); if ( itr != _member_serials.end() ) { _member_serials.erase( itr ); } _member_serials.insert( _member_serials.begin(), serial ); _leaderserial = serial; } bool Party::test_size() const { if ( ( _candidates_serials.empty() ) && ( _member_serials.size() <= 1 ) ) return false; return true; } bool Party::can_add() const { if ( ( _member_serials.size() + _candidates_serials.size() ) >= settingsManager.party_cfg.General.MaxPartyMembers ) return false; return true; } void Party::send_member_list( Mobile::Character* to_chr ) { Network::PktHelper::PacketOut msg; msg->offset += 4; // len+sub msg->Write( PKTBI_BF_06::PARTYCMD_ADD ); msg->offset++; // nummembers // TODO: refactor the loop below to use std::remove_if() + extract a method auto itr = _member_serials.begin(); while ( itr != _member_serials.end() ) { Mobile::Character* chr = system_find_mobile( *itr ); if ( chr != nullptr ) { msg->Write( chr->serial_ext ); ++itr; } else itr = _member_serials.erase( itr ); } u16 len = msg->offset; msg->offset = 6; msg->Write( _member_serials.size() ); msg->offset = 1; msg->WriteFlipped( len ); if ( to_chr == nullptr ) { for ( const auto& serial : _member_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) { if ( chr->has_active_client() ) msg.Send( chr->client, len ); } } } else msg.Send( to_chr->client, len ); } void Party::disband() { if ( settingsManager.party_cfg.Hooks.OnDisband ) settingsManager.party_cfg.Hooks.OnDisband->call( Module::CreatePartyRefObjImp( this ) ); for ( const auto& serial : _member_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) { chr->party( nullptr ); if ( chr->has_active_client() ) { send_empty_party( chr ); send_sysmessage_cl( chr->client, CLP_Disbanded ); // Your party has disbanded. } } } for ( const auto& serial : _candidates_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) chr->candidate_of( nullptr ); } for ( const auto& serial : _offlinemember_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) chr->offline_mem_of( nullptr ); } _member_serials.clear(); _candidates_serials.clear(); _offlinemember_serials.clear(); } void Party::send_remove_member( Mobile::Character* remchr, bool* disband ) { *disband = false; if ( remchr->serial == _leaderserial ) { *disband = true; return; } else { Network::PktHelper::PacketOut msg; msg->offset += 4; // len+sub msg->Write( PKTBI_BF_06::PARTYCMD_REMOVE ); msg->offset++; // nummembers msg->Write( remchr->serial_ext ); // TODO: refactor the loop below to use std::remove_if() + extract a method auto itr = _member_serials.begin(); while ( itr != _member_serials.end() ) { Mobile::Character* chr = system_find_mobile( *itr ); if ( chr != nullptr ) { msg->Write( chr->serial_ext ); ++itr; } else itr = _member_serials.erase( itr ); } u16 len = msg->offset; msg->offset = 6; msg->Write( _member_serials.size() ); msg->offset = 1; msg->WriteFlipped( len ); for ( const auto& serial : _member_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) { if ( chr->has_active_client() ) msg.Send( chr->client, len ); } } send_msg_to_all( CLP_Player_Removed ); // A player has been removed from your party. if ( !test_size() ) { send_msg_to_all( CLP_Last_Person ); // The last person has left the party... *disband = true; } } } void Party::send_msg_to_all( unsigned int clilocnr, const std::string& affix, Mobile::Character* exeptchr ) const { Network::PktHelper::PacketOut msgc1; Network::PktHelper::PacketOut msgcc; if ( !affix.empty() ) build_sysmessage_cl_affix( msgcc.Get(), clilocnr, affix, true ); else build_sysmessage_cl( msgc1.Get(), clilocnr ); for ( const auto& serial : _member_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) { if ( chr != exeptchr ) { if ( chr->has_active_client() ) { if ( !affix.empty() ) msgcc.Send( chr->client ); else msgc1.Send( chr->client ); } } } } } void Party::send_stat_to( Mobile::Character* chr, Mobile::Character* bob ) const { if ( ( chr != bob ) && ( is_member( bob->serial ) ) && ( chr->pos().inRange( bob->pos(), 20 ) ) ) { if ( !chr->is_visible_to_me( bob ) ) send_short_statmsg( chr->client, bob ); send_attributes_normalized( chr, bob ); } } void Party::send_stats_on_add( Mobile::Character* newmember ) const { if ( newmember == nullptr ) return; for ( const auto& serial : _member_serials ) { Mobile::Character* chr = system_find_mobile( serial ); if ( chr != nullptr ) { if ( newmember != chr ) { if ( chr->has_active_client() ) { send_short_statmsg( chr->client, newmember ); send_attributes_normalized( chr, newmember ); } if ( newmember->has_active_client() ) { send_short_statmsg( newmember->client, chr ); send_attributes_normalized( newmember, chr ); } } } } } void Party::on_mana_changed( Mobile::Character* chr ) const { Network::PktHelper::PacketOut msg; msg->Write( chr->serial_ext ); const auto& vital = chr->vital( networkManager.uoclient_general.mana.id ); int h = vital.current_ones(); if ( h > 0xFFFF ) h = 0xFFFF; int mh = vital.maximum_ones(); if ( mh > 0xFFFF ) mh = 0xFFFF; msg->WriteFlipped( 1000u ); msg->WriteFlipped( static_cast( h * 1000 / mh ) ); for ( const auto& serial : _member_serials ) { Mobile::Character* mem = system_find_mobile( serial ); if ( mem != nullptr ) { if ( mem != chr ) { if ( mem->has_active_client() ) msg.Send( mem->client ); } } } } void Party::on_stam_changed( Mobile::Character* chr ) const { Network::PktHelper::PacketOut msg; msg->Write( chr->serial_ext ); const auto& vital = chr->vital( networkManager.uoclient_general.stamina.id ); int h = vital.current_ones(); if ( h > 0xFFFF ) h = 0xFFFF; int mh = vital.maximum_ones(); if ( mh > 0xFFFF ) mh = 0xFFFF; msg->WriteFlipped( 1000u ); msg->WriteFlipped( static_cast( h * 1000 / mh ) ); for ( const auto& serial : _member_serials ) { Mobile::Character* mem = system_find_mobile( serial ); if ( mem != nullptr ) { if ( mem != chr ) { if ( mem->has_active_client() ) msg.Send( mem->client ); } } } } void Party::send_member_msg_public( Mobile::Character* chr, const std::string& text ) const { Network::PktHelper::PacketOut msg; msg->offset += 4; // len+sub msg->Write( PKTBI_BF_06::PARTYCMD_PARTY_MSG ); msg->Write( chr->serial_ext ); std::vector utf16text = Bscript::String::toUTF16( text ); if ( settingsManager.party_cfg.Hooks.ChangePublicChat ) { Bscript::BObject obj = settingsManager.party_cfg.Hooks.ChangePublicChat->call_object( chr->make_ref(), new Bscript::String( text ) ); if ( obj->isa( Bscript::BObjectImp::OTString ) || obj->isa( Bscript::BObjectImp::OTArray ) ) { if ( obj->isa( Bscript::BObjectImp::OTArray ) ) utf16text = std::unique_ptr( Bscript::String::fromUCArray( static_cast( obj.impptr() ) ) ) ->toUTF16(); else utf16text = static_cast( obj.impptr() )->toUTF16(); } else if ( obj->isa( Bscript::BObjectImp::OTLong ) ) // break on return(0) { if ( !obj->isTrue() ) return; } } if ( utf16text.size() > SPEECH_MAX_LEN ) utf16text.resize( SPEECH_MAX_LEN ); msg->WriteFlipped( utf16text, true ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); for ( const auto& serial : _member_serials ) { Mobile::Character* mem = system_find_mobile( serial ); if ( mem != nullptr ) { if ( mem->has_active_client() ) msg.Send( mem->client, len ); } } } void Party::send_member_msg_private( Mobile::Character* chr, Mobile::Character* tochr, const std::string& text ) const { if ( !tochr->has_active_client() ) return; Network::PktHelper::PacketOut msg; msg->offset += 4; // len+sub msg->Write( PKTBI_BF_06::PARTYCMD_MEMBER_MSG ); msg->Write( chr->serial_ext ); std::vector utf16text = Bscript::String::toUTF16( text ); if ( settingsManager.party_cfg.Hooks.ChangePrivateChat ) { Bscript::BObject obj = settingsManager.party_cfg.Hooks.ChangePrivateChat->call_object( chr->make_ref(), tochr->make_ref(), new Bscript::String( text ) ); if ( obj->isa( Bscript::BObjectImp::OTString ) || obj->isa( Bscript::BObjectImp::OTArray ) ) { if ( obj->isa( Bscript::BObjectImp::OTArray ) ) utf16text = std::unique_ptr( Bscript::String::fromUCArray( static_cast( obj.impptr() ) ) ) ->toUTF16(); else utf16text = static_cast( obj.impptr() )->toUTF16(); } else if ( obj->isa( Bscript::BObjectImp::OTLong ) ) // break on return(0) { if ( !obj->isTrue() ) return; } } if ( !settingsManager.party_cfg.General.PrivateMsgPrefix.empty() ) { std::vector ptext = Bscript::String::toUTF16( settingsManager.party_cfg.General.PrivateMsgPrefix ); utf16text.insert( utf16text.begin(), ptext.begin(), ptext.end() ); } if ( utf16text.size() > SPEECH_MAX_LEN ) utf16text.resize( SPEECH_MAX_LEN ); msg->WriteFlipped( utf16text, true ); u16 len = msg->offset; msg->offset = 1; msg->WriteFlipped( len ); msg.Send( tochr->client, len ); } void Party::printOn( Clib::StreamWriter& sw ) const { if ( _member_serials.size() <= 1 ) return; if ( system_find_mobile( _leaderserial ) == nullptr ) return; sw() << "Party" << pf_endl << "{" << pf_endl << "\tLeader\t0x" << fmt::hex( _leaderserial ) << pf_endl; for ( const auto& mserial : _member_serials ) { Mobile::Character* mem = system_find_mobile( mserial ); if ( mem != nullptr ) sw() << "\tMember\t0x" << fmt::hex( mserial ) << pf_endl; } _proplist.printProperties( sw ); sw() << "}" << pf_endl << pf_endl; // sw.flush(); } std::vector Party::get_members( bool include_offline ) const { std::vector ret; for ( auto& serial : _member_serials ) { Mobile::Character* chr = Core::system_find_mobile( serial ); if ( chr && ( include_offline || chr->logged_in() ) ) ret.push_back( chr ); } return ret; } size_t Party::estimateSize() const { return 3 * sizeof( u32* ) + _member_serials.capacity() * sizeof( u32 ) + 3 * sizeof( u32* ) + _candidates_serials.capacity() * sizeof( u32 ) + 3 * sizeof( u32* ) + _offlinemember_serials.capacity() * sizeof( u32 ) + sizeof( u32 ) /*_leaderserial*/ + _proplist.estimatedSize(); } void send_empty_party( Mobile::Character* chr ) { if ( chr != nullptr && chr->has_active_client() ) { Network::PktHelper::PacketOut msg; msg->WriteFlipped( 11u ); msg->offset += 2; // sub msg->Write( PKTBI_BF_06::PARTYCMD_REMOVE ); msg->offset++; // nummembers msg->Write( chr->serial_ext ); msg.Send( chr->client ); } } void register_party_members() { Parties::iterator itr = gamestate.parties.begin(); while ( itr != gamestate.parties.end() ) { if ( !( *itr )->register_with_members() ) { ( *itr )->disband(); itr = gamestate.parties.erase( itr ); } else ++itr; } } void disband_party( u32 leader ) { auto itr = std::find_if( gamestate.parties.begin(), gamestate.parties.end(), [&]( PartyRef& party ) { return party->is_leader( leader ); } ); if ( itr != gamestate.parties.end() ) { ( *itr )->disband(); gamestate.parties.erase( itr ); } } void read_party_dat() { std::string partyfile = Plib::systemstate.config.world_data_path + "parties.txt"; if ( !Clib::FileExists( partyfile ) ) return; if ( settingsManager.party_cfg.General.RemoveMemberOnLogoff ) return; INFO_PRINT << " " << partyfile << ":"; Clib::ConfigFile cf( partyfile ); Clib::ConfigSection sect_party( cf, "PARTY", Clib::CST_NORMAL ); Clib::ConfigElem elem; static int num_until_dot = 1000; clock_t start = clock(); while ( cf.read( elem ) ) { if ( --num_until_dot == 0 ) { INFO_PRINT << "."; num_until_dot = 1000; } if ( sect_party.matches( elem ) ) gamestate.parties.push_back( ref_ptr( new Party( elem ) ) ); } clock_t end = clock(); int ms = static_cast( ( end - start ) * 1000.0 / CLOCKS_PER_SEC ); INFO_PRINT << " " << gamestate.parties.size() << " elements in " << ms << " ms.\n"; register_party_members(); } void write_party( Clib::StreamWriter& sw ) { if ( settingsManager.party_cfg.General.RemoveMemberOnLogoff ) return; for ( const auto& party : gamestate.parties ) { party->printOn( sw ); } } void on_loggoff_party( Mobile::Character* chr ) { if ( chr->has_party() ) { if ( settingsManager.party_cfg.General.RemoveMemberOnLogoff ) { Party* party = chr->party(); if ( party->remove_member( chr->serial ) ) { bool disband = false; if ( party->is_leader( chr->serial ) ) { if ( !party->test_size() ) { disband_party( party->leader() ); chr->party( nullptr ); return; } party->set_leader( party->get_member_at( 0 ) ); party->send_msg_to_all( CLP_Player_Removed ); // A player has been removed from your party. party->send_member_list( nullptr ); } else party->send_remove_member( chr, &disband ); chr->party( nullptr ); if ( settingsManager.party_cfg.Hooks.OnLeaveParty ) settingsManager.party_cfg.Hooks.OnLeaveParty->call( chr->make_ref(), chr->make_ref() ); if ( disband ) disband_party( party->leader() ); else { if ( settingsManager.party_cfg.General.RejoinPartyOnLogon ) { party->add_offline_mem( chr->serial ); chr->offline_mem_of( party ); } } } } } } void on_loggon_party( Mobile::Character* chr ) { if ( chr->has_offline_mem_of() ) { Party* party = chr->offline_mem_of(); if ( party->remove_offline_mem( chr->serial ) ) { if ( party->add_member( chr->serial ) ) { chr->party( party ); send_sysmessage_cl( chr->client, CLP_Rejoined ); // You have rejoined the party. party->send_member_list( nullptr ); party->send_stats_on_add( chr ); party->send_msg_to_all( CLP_Player_Rejoined, chr->name(), chr ); //: rejoined the party. } } chr->offline_mem_of( nullptr ); } else if ( !chr->has_party() ) send_empty_party( chr ); else { Party* party = chr->party(); send_sysmessage_cl( chr->client, CLP_Rejoined ); // You have rejoined the party. party->send_member_list( chr ); party->send_stats_on_add( chr ); party->send_msg_to_all( CLP_Player_Rejoined, chr->name(), chr ); //: rejoined the party. } } void party_cmd_handler( Network::Client* client, PKTBI_BF* msg ) { switch ( msg->partydata.partycmd ) { case PKTBI_BF_06::PARTYCMD_ADD: handle_add( client, msg ); break; case PKTBI_BF_06::PARTYCMD_REMOVE: handle_remove( client, msg ); break; case PKTBI_BF_06::PARTYCMD_MEMBER_MSG: handle_member_msg( client, msg ); break; case PKTBI_BF_06::PARTYCMD_PARTY_MSG: handle_party_msg( client, msg ); break; case PKTBI_BF_06::PARTYCMD_LOOT_PERMISSION: handle_loot_perm( client, msg ); break; case PKTBI_BF_06::PARTYCMD_ACCEPT_INVITE: handle_accept_invite( client, msg ); break; case PKTBI_BF_06::PARTYCMD_DECLINE_INVITE: handle_decline_invite( client, msg ); break; default: handle_unknown_packet( client ); } } void handle_add_member_cursor( Mobile::Character* chr, PKTBI_6C* msgin ) { if ( chr->client != nullptr ) { Mobile::Character* add = system_find_mobile( cfBEu32( msgin->selected_serial ) ); add_candidate( add, chr ); } } void handle_add( Network::Client* client, PKTBI_BF* msg ) { if ( msg->partydata.partyadd.memberid == 0x0 ) { send_sysmessage_cl( client, CLP_Who_To_Add ); // Who would you like to add to your party? gamestate.target_cursors.add_member_cursor.send_object_cursor( client ); } else { Mobile::Character* member = system_find_mobile( cfBEu32( msg->partydata.partyadd.memberid ) ); add_candidate( member, client->chr ); } } void handle_remove_member_cursor( Mobile::Character* chr, PKTBI_6C* msgin ) { if ( chr->client != nullptr ) { Mobile::Character* rem = system_find_mobile( cfBEu32( msgin->selected_serial ) ); if ( rem != nullptr ) { Party* party = chr->party(); if ( ( party == nullptr ) || ( !party->is_leader( chr->serial ) ) || ( !party->is_member( rem->serial ) ) ) return; if ( chr == rem ) send_sysmessage_cl( chr->client, CLP_Cannot_Remove_Self ); // You may only remove yourself // from a party if you are not // the leader. else { if ( settingsManager.party_cfg.Hooks.CanRemoveMember ) { if ( !settingsManager.party_cfg.Hooks.CanRemoveMember->call( chr->make_ref(), rem->make_ref() ) ) { party->send_member_list( chr ); // resend list return; } } if ( party->remove_member( rem->serial ) ) { bool disband; party->send_remove_member( rem, &disband ); rem->party( nullptr ); if ( settingsManager.party_cfg.Hooks.OnLeaveParty ) settingsManager.party_cfg.Hooks.OnLeaveParty->call( rem->make_ref(), chr->make_ref() ); send_sysmessage_cl( rem->client, CLP_Removed ); // You have been removed from the party. send_empty_party( rem ); if ( disband ) disband_party( party->leader() ); } } } } } void handle_remove( Network::Client* client, PKTBI_BF* msg ) { Party* party = client->chr->party(); if ( party == nullptr ) { send_sysmessage_cl( client, CLP_No_Party ); // You are not in a party. return; } Mobile::Character* member = system_find_mobile( cfBEu32( msg->partydata.partyremove.memberid ) ); // leader wants target if ( ( party->is_leader( client->chr->serial ) ) && ( member == nullptr ) ) { send_sysmessage_cl( client, CLP_Who_to_Remove ); // Who would you like to remove from your party? gamestate.target_cursors.remove_member_cursor.send_object_cursor( client ); } // leader kicks member, or member kicks himself else if ( ( ( party->is_leader( client->chr->serial ) ) || ( client->chr == member ) ) && ( party->is_member( member->serial ) ) ) { if ( client->chr == member ) { if ( settingsManager.party_cfg.Hooks.CanLeaveParty ) { if ( !settingsManager.party_cfg.Hooks.CanLeaveParty->call( client->chr->make_ref() ) ) { party->send_member_list( client->chr ); // resend list return; } } } else { if ( settingsManager.party_cfg.Hooks.CanRemoveMember ) { if ( !settingsManager.party_cfg.Hooks.CanRemoveMember->call( client->chr->make_ref(), member->make_ref() ) ) { party->send_member_list( client->chr ); // resend list return; } } } if ( party->remove_member( member->serial ) ) { bool disband; member->party( nullptr ); party->send_remove_member( member, &disband ); if ( settingsManager.party_cfg.Hooks.OnLeaveParty ) settingsManager.party_cfg.Hooks.OnLeaveParty->call( member->make_ref(), client->chr->make_ref() ); if ( member->has_active_client() ) { send_sysmessage_cl( member->client, CLP_Removed ); // You have been removed from the party. send_empty_party( member ); } if ( disband ) disband_party( party->leader() ); } } } void handle_member_msg( Network::Client* client, PKTBI_BF* msg ) { using std::wcout; Mobile::Character* member = system_find_mobile( cfBEu32( msg->partydata.partymembermsg.memberid ) ); if ( member != nullptr ) { Party* party = client->chr->party(); if ( ( party != nullptr ) && ( member->has_party() ) && ( party->is_member( member->serial ) ) ) { u16* themsg = msg->partydata.partymembermsg.wtext; int intextlen = ( cfBEu16( msg->msglen ) - 10 ) / sizeof( msg->partydata.partymembermsg.wtext[0] ) - 1; if ( intextlen < 0 ) intextlen = 0; if ( intextlen > SPEECH_MAX_LEN ) intextlen = SPEECH_MAX_LEN; std::string text = Bscript::String::fromUTF16( themsg, intextlen, true ); if ( settingsManager.party_cfg.Hooks.OnPrivateChat ) { settingsManager.party_cfg.Hooks.OnPrivateChat->call( client->chr->make_ref(), member->make_ref(), new Bscript::String( text ) ); } party->send_member_msg_private( client->chr, member, text ); } else send_sysmessage_cl( client, CLP_No_Party ); // You are not in a party. } } void handle_party_msg( Network::Client* client, PKTBI_BF* msg ) { Party* party = client->chr->party(); if ( party != nullptr ) { u16* themsg = msg->partydata.partymsg.wtext; Mobile::Character* member = nullptr; int intextlen = ( cfBEu16( msg->msglen ) - 6 ) / sizeof( msg->partydata.partymsg.wtext[0] ) - 1; // intextlen does not include the null terminator. // Preprocess the text into a sanity-checked, printable, null-terminated form in textbuf if ( intextlen < 0 ) intextlen = 0; if ( intextlen > SPEECH_MAX_LEN ) intextlen = SPEECH_MAX_LEN; std::string text = Bscript::String::fromUTF16( themsg, intextlen, true ); if ( settingsManager.party_cfg.General.TreatNoAsPrivate ) { if ( text.size() > 2 && ( isdigit( text[0] ) ) && ( text[1] == ' ' ) ) { int no = atoi( &text[0] ); if ( no == 0 ) no = 9; else no--; u32 mem = party->get_member_at( (unsigned short)no ); if ( mem != 0 ) { member = system_find_mobile( mem ); if ( ( member != nullptr ) && ( member->has_active_client() ) ) text = text.substr( 2 ); } } } if ( member != nullptr ) // private chat { if ( settingsManager.party_cfg.Hooks.OnPrivateChat ) { settingsManager.party_cfg.Hooks.OnPrivateChat->call( client->chr->make_ref(), member->make_ref(), new Bscript::String( text ) ); } party->send_member_msg_private( client->chr, member, text ); } else { if ( settingsManager.party_cfg.Hooks.OnPublicChat ) { settingsManager.party_cfg.Hooks.OnPublicChat->call( client->chr->make_ref(), new Bscript::String( text ) ); } party->send_member_msg_public( client->chr, text ); } } else send_sysmessage_cl( client, CLP_No_Party ); // You are not in a party. } void handle_loot_perm( Network::Client* client, PKTBI_BF* msg ) { if ( !client->chr->has_party() ) send_sysmessage_cl( client, CLP_No_Party ); // You are not in a party. else { bool loot = msg->partydata.partylootperm.canloot ? true : false; client->chr->set_party_can_loot( loot ); if ( settingsManager.party_cfg.Hooks.OnLootPermChange ) settingsManager.party_cfg.Hooks.OnLootPermChange->call( client->chr->make_ref() ); // You have chosen to allow your party to loot your corpse. // You have chosen to prevent your party from looting your corpse. send_sysmessage_cl( client, loot ? CLP_Allow_Loot : CLP_Prevent_Loot ); } } void handle_accept_invite( Network::Client* client, PKTBI_BF* msg ) { Mobile::Character* leader = system_find_mobile( cfBEu32( msg->partydata.partyaccinvite.leaderid ) ); if ( leader != nullptr ) { Party* party = leader->party(); if ( party != nullptr ) { if ( party->remove_candidate( client->chr->serial ) ) { client->chr->cancel_party_invite_timeout(); client->chr->candidate_of( nullptr ); if ( party->add_member( client->chr->serial ) ) { client->chr->party( party ); if ( settingsManager.party_cfg.Hooks.OnAddToParty ) settingsManager.party_cfg.Hooks.OnAddToParty->call( client->chr->make_ref() ); send_sysmessage_cl( client, CLP_Added ); // You have been added to the party. party->send_msg_to_all( CLP_Joined, client->chr->name(), client->chr ); // : joined the party. party->send_member_list( nullptr ); party->send_stats_on_add( client->chr ); } } } else send_sysmessage_cl( client, CLP_No_Invite ); // No one has invited you to be in a party. } } void handle_decline_invite( Network::Client* client, PKTBI_BF* msg ) { Mobile::Character* leader = system_find_mobile( cfBEu32( msg->partydata.partydecinvite.leaderid ) ); if ( leader != nullptr ) { Party* party = leader->party(); if ( party != nullptr ) { if ( party->remove_candidate( client->chr->serial ) ) { client->chr->cancel_party_invite_timeout(); client->chr->candidate_of( nullptr ); send_sysmessage_cl( client, CLP_Decline ); // You notify them that you do not wish to join the party. if ( leader->has_active_client() ) send_sysmessage_cl_affix( leader->client, CLP_Notify_Decline, client->chr->name(), true ); //: Does not wish to join the party. if ( settingsManager.party_cfg.Hooks.OnDecline ) settingsManager.party_cfg.Hooks.OnDecline->call( client->chr->make_ref() ); if ( !party->test_size() ) disband_party( leader->serial ); } else if ( party->is_member( client->chr->serial ) ) send_sysmessage_cl( client, CLP_Too_Late_Decline ); // Too late to decline, you are already // in the party. Try /quit. } else send_sysmessage_cl( client, CLP_No_Invite ); // No one has invited you to be in a party. } } void add_candidate( Mobile::Character* member, Mobile::Character* leader ) { if ( leader == nullptr ) return; if ( member == nullptr ) { send_sysmessage_cl( leader->client, CLP_Add_Living ); // You may only add living things to your party! return; } Party* party = leader->party(); if ( member == leader ) send_sysmessage_cl( leader->client, CLP_Add_Yourself ); // You cannot add yourself to a party. else if ( ( party != nullptr ) && ( !party->is_leader( leader->serial ) ) ) send_sysmessage_cl( leader->client, CLP_Add_No_Leader ); // You may only add members to the party if you are the leader. else if ( ( party != nullptr ) && ( !party->can_add() ) ) send_sysmessage_cl( leader->client, CLP_Max_Size ); // You may only have 10 in your party (this includes candidates). else if ( member->isa( UOBJ_CLASS::CLASS_NPC ) ) send_sysmessage_cl( leader->client, CLP_Ignore_Offer ); // The creature ignores your offer. else if ( ( party != nullptr ) && ( party->is_member( member->serial ) ) ) send_sysmessage_cl( leader->client, CLP_Already_Your_Party ); // This person is already in your party! else if ( ( party != nullptr ) && ( party->is_candidate( member->serial ) ) ) send_sysmessage_cl( leader->client, CLP_Already_Your_Party ); // This person is already in your party! else if ( member->has_party() ) send_sysmessage_cl( leader->client, CLP_Already_in_a_Party ); // This person is already in a party! else { if ( member->has_candidate_of() ) send_sysmessage_cl( leader->client, CLP_Already_in_a_Party ); // This person is already in a party! else { if ( settingsManager.party_cfg.Hooks.CanAddToParty ) { if ( !settingsManager.party_cfg.Hooks.CanAddToParty->call( leader->make_ref(), member->make_ref() ) ) return; } if ( settingsManager.party_cfg.General.DeclineTimeout > 0 ) member->set_party_invite_timeout(); if ( party == nullptr ) { party = new Party( leader->serial ); gamestate.parties.push_back( ref_ptr( party ) ); leader->party( party ); if ( settingsManager.party_cfg.Hooks.OnPartyCreate ) settingsManager.party_cfg.Hooks.OnPartyCreate->call( Module::CreatePartyRefObjImp( party ) ); } if ( party->add_candidate( member->serial ) ) { member->candidate_of( party ); send_invite( member, leader ); } else send_sysmessage_cl( leader->client, CLP_Max_Size ); // You may only have 10 in your party (this includes candidates). } } } void invite_timeout( Mobile::Character* mem ) { Party* party = mem->candidate_of(); if ( party != nullptr ) { if ( party->remove_candidate( mem->serial ) ) { if ( mem->has_active_client() ) send_sysmessage_cl( mem->client, CLP_Decline ); // You notify them that you do not wish to join the party. Mobile::Character* leader = system_find_mobile( party->leader() ); if ( settingsManager.party_cfg.Hooks.OnDecline ) settingsManager.party_cfg.Hooks.OnDecline->call( mem->make_ref() ); if ( leader != nullptr ) { if ( leader->has_active_client() ) send_sysmessage_cl_affix( leader->client, CLP_Notify_Decline, mem->name(), true ); //: Does not wish to join the party. if ( !party->test_size() ) disband_party( leader->serial ); } } mem->candidate_of( nullptr ); } } void send_invite( Mobile::Character* member, Mobile::Character* leader ) { Network::PktHelper::PacketOut msg; msg->WriteFlipped( 10u ); msg->offset += 2; // sub msg->Write( PKTBI_BF_06::PARTYCMD_INVITE_MEMBER ); msg->Write( leader->serial_ext ); msg.Send( member->client ); // : You are invited to join the party. Type /accept to join or /decline to decline the offer. send_sysmessage_cl_affix( member->client, CLP_Invite, leader->name(), true ); send_sysmessage_cl( leader->client, CLP_Invited ); // You have invited them to join the party. } void send_attributes_normalized( Mobile::Character* chr, Mobile::Character* bob ) { Network::PktHelper::PacketOut msg; msg->Write( bob->serial_ext ); const auto& hits = bob->vital( networkManager.uoclient_general.hits.id ); int h = hits.current_ones(); if ( h > 0xFFFF ) h = 0xFFFF; int mh = hits.maximum_ones(); if ( mh > 0xFFFF ) mh = 0xFFFF; msg->WriteFlipped( 1000u ); msg->WriteFlipped( static_cast( h * 1000 / mh ) ); const auto& mana = bob->vital( networkManager.uoclient_general.mana.id ); h = mana.current_ones(); if ( h > 0xFFFF ) h = 0xFFFF; mh = mana.maximum_ones(); if ( mh > 0xFFFF ) mh = 0xFFFF; msg->WriteFlipped( 1000u ); msg->WriteFlipped( static_cast( h * 1000 / mh ) ); const auto& stam = bob->vital( networkManager.uoclient_general.stamina.id ); h = stam.current_ones(); if ( h > 0xFFFF ) h = 0xFFFF; mh = stam.maximum_ones(); if ( mh > 0xFFFF ) mh = 0xFFFF; msg->WriteFlipped( 1000u ); msg->WriteFlipped( static_cast( h * 1000 / mh ) ); msg.Send( chr->client ); } } // namespace Core namespace Mobile { bool Character::party_can_loot() const { return mob_flags_.get( MOB_FLAGS::PARTY_CAN_LOOT ); } void Character::set_party_can_loot( bool b ) { mob_flags_.change( MOB_FLAGS::PARTY_CAN_LOOT, b ); } void Character::set_party_invite_timeout() { if ( this->party_decline_timeout_ != nullptr ) this->party_decline_timeout_->cancel(); Core::polclock_t timeout = Core::polclock() + Core::settingsManager.party_cfg.General.DeclineTimeout * Core::POLCLOCKS_PER_SEC; new Core::OneShotTaskInst( &this->party_decline_timeout_, timeout, Core::invite_timeout, this ); } bool Character::has_party_invite_timeout() const { if ( this->party_decline_timeout_ != nullptr ) return false; return true; } void Character::cancel_party_invite_timeout() { if ( this->party_decline_timeout_ != nullptr ) this->party_decline_timeout_->cancel(); } } // namespace Mobile } // namespace Pol