Receive.pm
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:226k
- #########################################################################
- # OpenKore - Server message parsing
- #
- # This software is open source, licensed under the GNU General Public
- # License, version 2.
- # Basically, this means that you're allowed to modify and distribute
- # this software. However, if you distribute modified versions, you MUST
- # also distribute the source code.
- # See http://www.gnu.org/licenses/gpl.html for the full license.
- #########################################################################
- ##
- # MODULE DESCRIPTION: Server message parsing
- #
- # This class is responsible for parsing messages that are sent by the RO
- # server to Kore. Information in the messages are stored in global variables
- # (in the module Globals).
- #
- # Please also read <a href="http://www.openkore.com/wiki/index.php/Network_subsystem">the
- # network subsystem overview.</a>
- package Network::Receive;
- use strict;
- use Time::HiRes qw(time usleep);
- use encoding 'utf8';
- use Carp::Assert;
- use Scalar::Util;
- use Exception::Class ('Network::Receive::InvalidServerType', 'Network::Receive::CreationError');
- use Globals;
- use Actor;
- use Actor::You;
- use Actor::Player;
- use Actor::Monster;
- use Actor::Party;
- use Actor::Item;
- use Actor::Unknown;
- use Settings;
- use Log qw(message warning error debug);
- use FileParsers;
- use Interface;
- use Network;
- use Network::MessageTokenizer;
- use Network::Send ();
- use Misc;
- use Plugins;
- use Utils;
- use Skill;
- use AI;
- use Utils::Exceptions;
- use Utils::Crypton;
- use Translation;
- use I18N qw(bytesToString);
- ######################################
- ### Public methods
- ######################################
- # Do not call this directly. Use create() instead.
- sub new {
- my ($class) = @_;
- my %self;
- # If you are wondering about those funny strings like 'x2 v1' read http://perldoc.perl.org/functions/pack.html
- # and http://perldoc.perl.org/perlpacktut.html
- # Defines a list of Packet Handlers and decoding information
- # 'packetSwitch' => ['handler function','unpack string',[qw(argument names)]]
- $self{packet_list} = {
- '0069' => ['account_server_info', 'x2 a4 a4 a4 x30 C1 a*', [qw(sessionID accountID sessionID2 accountSex serverInfo)]],
- '006A' => ['login_error', 'C1', [qw(type)]],
- '006B' => ['received_characters'],
- '0072' => ['received_characters'],
- '006C' => ['login_error_game_login_server'],
- '006D' => ['character_creation_successful', 'a4 x4 V1 x62 Z24 C1 C1 C1 C1 C1 C1 C1', [qw(ID zenny name str agi vit int dex luk slot)]],
- '006E' => ['character_creation_failed'],
- '006F' => ['character_deletion_successful'],
- '0070' => ['character_deletion_failed'],
- '0071' => ['received_character_ID_and_Map', 'a4 Z16 a4 v1', [qw(charID mapName mapIP mapPort)]],
- '0073' => ['map_loaded', 'V a3', [qw(syncMapSync coords)]],
- '0075' => ['changeToInGameState'],
- '0077' => ['changeToInGameState'],
- '0078' => ['actor_display', 'a4 v14 a4 x7 C1 a3 x2 C1 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon lowhead shield tophead midhead hair_color clothes_color head_dir guildID sex coords act lv)]],
- '0079' => ['actor_display', 'a4 v14 a4 x7 C1 a3 x2 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon lowhead shield tophead midhead hair_color clothes_color head_dir guildID sex coords lv)]],
- '007A' => ['changeToInGameState'],
- '007B' => ['actor_display', 'a4 v8 x4 v6 a4 x7 C1 a5 x3 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon lowhead shield tophead midhead hair_color clothes_color head_dir guildID sex coords lv)]],
- #OLD '007C' => ['actor_display', 'a4 v1 v1 v1 v1 x6 v1 C1 x12 C1 a3', [qw(ID walk_speed param1 param2 param3 type pet sex coords)]],
- '007C' => ($rpackets{'007C'} == 41 # or 42
- ? ['actor_display', 'x1 a4 v13 C2 a3 C1', [qw(ID walk_speed param1 param2 param3 hair_style weapon lowhead type shield tophead midhead hair_color clothes_color head_dir karma sex coords unknown1)]]
- : ['actor_display', 'x1 a4 v13 C2 a3 C2', [qw(ID walk_speed param1 param2 param3 hair_style weapon lowhead type shield tophead midhead hair_color clothes_color head_dir karma sex coords unknown1 unknown2)]]
- ),
- '007F' => ['received_sync', 'V1', [qw(time)]],
- '0080' => ['actor_died_or_disappeared', 'a4 C1', [qw(ID type)]],
- '0081' => ['errors', 'C1', [qw(type)]],
- '0086' => ['actor_display', 'a4 a5', [qw(ID coords)]],
- '0087' => ['character_moves', 'x4 a5 C1', [qw(coords unknown)]],
- '0088' => ['actor_movement_interrupted', 'a4 v1 v1', [qw(ID x y)]],
- '008A' => ['actor_action', 'a4 a4 a4 V2 v1 v1 C1 v1', [qw(sourceID targetID tick src_speed dst_speed damage param2 type param3)]],
- '008D' => ['public_chat', 'v1 a4 Z*', [qw(len ID message)]],
- '008E' => ['self_chat', 'x2 Z*', [qw(message)]],
- '0091' => ['map_change', 'Z16 v1 v1', [qw(map x y)]],
- '0092' => ['map_changed', 'Z16 x4 a4 v1', [qw(map IP port)]],
- '0095' => ['actor_info', 'a4 Z24', [qw(ID name)]],
- '0097' => ['private_message', 'v1 Z24 Z*', [qw(len privMsgUser privMsg)]],
- '0098' => ['private_message_sent', 'C1', [qw(type)]],
- '009A' => ['system_chat', 'x2 Z*', [qw(message)]], #maybe use a* instead and $message =~ / 00$//; if there are problems
- '009C' => ['actor_look_at', 'a4 C1 x1 C1', [qw(ID head body)]],
- '009D' => ['item_exists', 'a4 v1 x1 v3', [qw(ID type x y amount)]],
- '009E' => ['item_appeared', 'a4 v1 x1 v1 v1 x2 v1', [qw(ID type x y amount)]],
- '00A0' => ['inventory_item_added', 'v1 v1 v1 C1 C1 C1 a8 v1 C1 C1', [qw(index amount nameID identified broken upgrade cards type_equip type fail)]],
- '00A1' => ['item_disappeared', 'a4', [qw(ID)]],
- '00A3' => ['inventory_items_stackable'],
- '00A4' => ['inventory_items_nonstackable'],
- '00A5' => ['storage_items_stackable'],
- '00A6' => ['storage_items_nonstackable'],
- '00A8' => ['use_item', 'v1 x2 C1', [qw(index amount)]],
- '00AA' => ['equip_item', 'v1 v1 C1', [qw(index type success)]],
- '00AC' => ['unequip_item', 'v1 v1', [qw(index type)]],
- '00AF' => ['inventory_item_removed', 'v1 v1', [qw(index amount)]],
- '00B0' => ['stat_info', 'v1 V1', [qw(type val)]],
- '00B1' => ['exp_zeny_info', 'v1 V1', [qw(type val)]],
- '00B3' => ['switch_character'],
- '00B4' => ['npc_talk'],
- '00B5' => ['npc_talk_continue'],
- '00B6' => ['npc_talk_close', 'a4', [qw(ID)]],
- '00B7' => ['npc_talk_responses'],
- '00BC' => ['stats_added', 'v1 x1 C1', [qw(type val)]],
- '00BD' => ['stats_info', 'v1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 v1 v1 v1 v1 v1 v1 v1 v1 v1 v1 v1 v1', [qw(points_free str points_str agi points_agi vit points_vit int points_int dex points_dex luk points_luk attack attack_bonus attack_magic_min attack_magic_max def def_bonus def_magic def_magic_bonus hit flee flee_bonus critical)]],
- '00BE' => ['stats_points_needed', 'v1 C1', [qw(type val)]],
- '00C0' => ['emoticon', 'a4 C1', [qw(ID type)]],
- '00CA' => ['buy_result', 'C1', [qw(fail)]],
- '00C2' => ['users_online', 'V1', [qw(users)]],
- '00C3' => ['job_equipment_hair_change', 'a4 C1 C1', [qw(ID part number)]],
- '00C4' => ['npc_store_begin', 'a4', [qw(ID)]],
- '00C6' => ['npc_store_info'],
- '00C7' => ['npc_sell_list'],
- '00D1' => ['ignore_player_result', 'C1 C1', [qw(type error)]],
- '00D2' => ['ignore_all_result', 'C1 C1', [qw(type error)]],
- '00D6' => ['chat_created'],
- '00D7' => ['chat_info', 'x2 a4 a4 v1 v1 C1 a*', [qw(ownerID ID limit num_users public title)]],
- '00DA' => ['chat_join_result', 'C1', [qw(type)]],
- '00D8' => ['chat_removed', 'a4', [qw(ID)]],
- '00DB' => ['chat_users'],
- '00DC' => ['chat_user_join', 'v1 Z24', [qw(num_users user)]],
- '00DD' => ['chat_user_leave', 'v1 Z24', [qw(num_users user)]],
- '00DF' => ['chat_modified', 'x2 a4 a4 v1 v1 C1 a*', [qw(ownerID ID limit num_users public title)]],
- '00E1' => ['chat_newowner', 'C1 x3 Z24', [qw(type user)]],
- '00E5' => ['deal_request', 'Z24', [qw(user)]],
- '00E7' => ['deal_begin', 'C1', [qw(type)]],
- '00E9' => ['deal_add_other', 'V1 v1 C1 C1 C1 a8', [qw(amount nameID identified broken upgrade cards)]],
- '00EA' => ['deal_add_you', 'v1 C1', [qw(index fail)]],
- '00EC' => ['deal_finalize', 'C1', [qw(type)]],
- '00EE' => ['deal_cancelled'],
- '00F0' => ['deal_complete'],
- '00F2' => ['storage_opened', 'v1 v1', [qw(items items_max)]],
- '00F4' => ['storage_item_added', 'v1 V1 v1 C1 C1 C1 a8', [qw(index amount ID identified broken upgrade cards)]],
- '00F6' => ['storage_item_removed', 'v1 V1', [qw(index amount)]],
- '00F8' => ['storage_closed'],
- '00FA' => ['party_organize_result', 'C1', [qw(fail)]],
- '00FB' => ['party_users_info', 'x2 Z24', [qw(party_name)]],
- '00FD' => ['party_invite_result', 'Z24 C1', [qw(name type)]],
- '00FE' => ['party_invite', 'a4 Z24', [qw(ID name)]],
- '0101' => ['party_exp', 'C1', [qw(type)]],
- '0104' => ['party_join', 'a4 x4 v1 v1 C1 Z24 Z24 Z16', [qw(ID x y type name user map)]],
- '0105' => ['party_leave', 'a4 Z24', [qw(ID name)]],
- '0106' => ['party_hp_info', 'a4 v1 v1', [qw(ID hp hp_max)]],
- '0107' => ['party_location', 'a4 v1 v1', [qw(ID x y)]],
- '0108' => ['item_upgrade', 'v1 v1 v1', [qw(type index upgrade)]],
- '0109' => ['party_chat', 'x2 a4 Z*', [qw(ID message)]],
- '0110' => ['skill_use_failed', 'v1 v1 v1 C1 C1', [qw(skillID btype unknown fail type)]],
- '010A' => ['mvp_item', 'v1', [qw(itemID)]],
- '010B' => ['mvp_you', 'V1', [qw(expAmount)]],
- '010C' => ['mvp_other', 'a4', [qw(ID)]],
- '010E' => ['skill_update', 'v1 v1 v1 v1 C1', [qw(skillID lv sp range up)]], # range = skill range, up = this skill can be leveled up further
- '010F' => ['skills_list'],
- '0111' => ['linker_skill', 'v2 x2 v3 Z24', [qw(skillID target lv sp range name)]],
- '0114' => ['skill_use', 'v1 a4 a4 V1 V1 V1 v1 v1 v1 C1', [qw(skillID sourceID targetID tick src_speed dst_speed damage level param3 type)]],
- '0117' => ['skill_use_location', 'v1 a4 v1 v1 v1', [qw(skillID sourceID lv x y)]],
- '0119' => ['character_status', 'a4 v3 x', [qw(ID param1 param2 param3)]],
- '011A' => ['skill_used_no_damage', 'v1 v1 a4 a4 C1', [qw(skillID amount targetID sourceID fail)]],
- '011C' => ['warp_portal_list', 'v1 Z16 Z16 Z16 Z16', [qw(type memo1 memo2 memo3 memo4)]],
- '011E' => ['memo_success', 'C1', [qw(fail)]],
- '011F' => ['area_spell', 'a4 a4 v2 C2', [qw(ID sourceID x y type fail)]],
- '0120' => ['area_spell_disappears', 'a4', [qw(ID)]],
- '0121' => ['cart_info', 'v1 v1 V1 V1', [qw(items items_max weight weight_max)]],
- '0122' => ['cart_equip_list'],
- '0123' => ['cart_items_list'],
- '0124' => ['cart_item_added', 'v1 V1 v1 x C1 C1 C1 a8', [qw(index amount ID identified broken upgrade cards)]],
- '0125' => ['cart_item_removed', 'v1 V1', [qw(index amount)]],
- '012C' => ['cart_add_failed', 'C1', [qw(fail)]],
- '012D' => ['shop_skill', 'v1', [qw(number)]],
- '0131' => ['vender_found', 'a4 A30', [qw(ID title)]],
- '0132' => ['vender_lost', 'a4', [qw(ID)]],
- '0133' => ['vender_items_list'],
- '0135' => ['vender_buy_fail', 'v1 v1 C1', [qw(index amount fail)]],
- '0136' => ['vending_start'],
- '0137' => ['shop_sold', 'v1 v1', [qw(number amount)]],
- '0139' => ['monster_ranged_attack', 'a4 v1 v1 v1 v1 C1', [qw(ID sourceX sourceY targetX targetY type)]],
- '013A' => ['attack_range', 'v1', [qw(type)]],
- '013B' => ['arrow_none', 'v1', [qw(type)]],
- '013D' => ['hp_sp_changed', 'v1 v1', [qw(type amount)]],
- '013E' => ['skill_cast', 'a4 a4 v1 v1 v1 v1 v1 V1', [qw(sourceID targetID x y skillID unknown type wait)]],
- '013C' => ['arrow_equipped', 'v1', [qw(index)]],
- '0141' => ['stat_info2', 'v1 x2 v1 x2 v1', [qw(type val val2)]],
- '0142' => ['npc_talk_number', 'a4', [qw(ID)]],
- '0144' => ['minimap_indicator', 'a4 V3 C5', [qw(npcID type x y ID blue green red alpha)]],
- '0147' => ['item_skill', 'v1 v1 v1 v1 v1 v1 A*', [qw(skillID targetType unknown skillLv sp unknown2 skillName)]],
- '0148' => ['resurrection', 'a4 v1', [qw(targetID type)]],
- '014C' => ['guild_allies_enemy_list'],
- '0154' => ['guild_members_list'],
- '015A' => ['guild_leave', 'Z24 Z40', [qw(name message)]],
- '015C' => ['guild_expulsion', 'Z24 Z40 Z24', [qw(name message unknown)]],
- '015E' => ['guild_broken', 'V1', [qw(flag)]], # clif_guild_broken
- '0160' => ['guild_member_setting_list'],
- '0162' => ['guild_skills_list'],
- '0163' => ['guild_expulsionlist'],
- '0166' => ['guild_members_title_list'],
- '0167' => ['guild_create_result', 'C1', [qw(type)]],
- '0169' => ['guild_invite_result', 'C1', [qw(type)]],
- '016A' => ['guild_request', 'a4 Z24', [qw(ID name)]],
- '016C' => ['guild_name', 'a4, V2 x5 Z24', [qw(guildID emblemID mode guildName)]],
- '016D' => ['guild_member_online_status', 'a4 a4 V1', [qw(ID charID online)]],
- '016F' => ['guild_notice'],
- '0171' => ['guild_ally_request', 'a4 Z24', [qw(ID name)]],
- #'0173' => ['guild_alliance', 'V1', [qw(flag)]],
- '0177' => ['identify_list'],
- '0179' => ['identify', 'v*', [qw(index)]],
- '017B' => ['card_merge_list'],
- '017D' => ['card_merge_status', 'v1 v1 C1', [qw(item_index card_index fail)]],
- '017F' => ['guild_chat', 'x2 Z*', [qw(message)]],
- #'0181' => ['guild_opposition_result', 'C1', [qw(flag)]], # clif_guild_oppositionack
- #'0184' => ['guild_unally', 'a4 V1', [qw(guildID flag)]], # clif_guild_delalliance
- '0187' => ['sync_request', 'a4', [qw(ID)]],
- '0188' => ['item_upgrade', 'v1 v1 v1', [qw(type index upgrade)]],
- '0189' => ['no_teleport', 'v1', [qw(fail)]],
- '018C' => ['sense_result', 'v1 v1 v1 V1 v1 v1 v1 v1 C1 C1 C1 C1 C1 C1 C1 C1 C1', [qw(nameID level size hp def race mdef element ice earth fire wind poison holy dark spirit undead)]],
- '018D' => ['forge_list'],
- '018F' => ['refine_result', 'v1 v1', [qw(fail nameID)]],
- #'0191' => ['talkie_box', 'a4 Z80', [qw(ID message)]], # talkie box message
- '0194' => ['character_name', 'a4 Z24', [qw(ID name)]],
- '0195' => ['actor_name_received', 'a4 Z24 Z24 Z24 Z24', [qw(ID name partyName guildName guildTitle)]],
- '0196' => ['actor_status_active', 'v1 a4 C1', [qw(type ID flag)]],
- '0199' => ['pvp_mode1', 'v1', [qw(type)]],
- '019A' => ['pvp_rank', 'x2 V1 V1 V1', [qw(ID rank num)]],
- '019B' => ['unit_levelup', 'a4 V1', [qw(ID type)]],
- '01A0' => ['pet_capture_result', 'C1', [qw(type)]],
- '01A2' => ($rpackets{'01A2'} == 35 # or 37
- ? ['pet_info', 'Z24 C1 v4', [qw(name nameflag level hungry friendly accessory)]]
- : ['pet_info', 'Z24 C1 v5', [qw(name nameflag level hungry friendly accessory type)]]
- ),
- '01A3' => ['pet_food', 'C1 v1', [qw(success foodID)]],
- '01A4' => ['pet_info2', 'C a4 V', [qw(type ID value)]],
- '01A6' => ['egg_list'],
- '01AA' => ['pet_emotion', 'a4 V1', [qw(ID type)]],
- '01AB' => ['actor_muted', 'x2 a4 x2 L1', [qw(ID duration)]],
- '01AC' => ['actor_trapped', 'a4', [qw(ID)]],
- '01AD' => ['arrowcraft_list'],
- '01B0' => ['monster_typechange', 'a4 a1 V1', [qw(ID unknown type)]],
- '01B3' => ['npc_image', 'Z63 C1', [qw(npc_image type)]],
- '01B5' => ['account_payment_info', 'V1 V1', [qw(D_minute H_minute)]],
- '01B6' => ['guild_info', 'a4 V1 V1 V1 V1 V1 V1 x12 V1 Z24 Z24', [qw(ID lvl conMember maxMember average exp next_exp members name master)]],
- '01B9' => ['cast_cancelled', 'a4', [qw(ID)]],
- '01C3' => ['local_broadcast', 'x2 a3 x9 Z*', [qw(color message)]],
- '01C4' => ['storage_item_added', 'v1 V1 v1 C1 C1 C1 C1 a8', [qw(index amount ID type identified broken upgrade cards)]],
- '01C5' => ['cart_item_added', 'v1 V1 v1 x C1 C1 C1 a8', [qw(index amount ID identified broken upgrade cards)]],
- '01C8' => ['item_used', 'v1 v1 a4 v1 C1', [qw(index itemID ID remaining success)]],
- '01C9' => ['area_spell', 'a4 a4 v2 C2 C Z80', [qw(ID sourceID x y type fail scribbleLen scribbleMsg)]],
- '01CD' => ['sage_autospell'],
- '01CF' => ['devotion', 'a4 a20', [qw(sourceID data)]],
- '01D0' => ['revolving_entity', 'a4 v', [qw(sourceID entity)]],
- '01D2' => ['combo_delay', 'a4 V1', [qw(ID delay)]],
- '01D4' => ['npc_talk_text', 'a4', [qw(ID)]],
- '01D7' => ['player_equipment', 'a4 C1 v2', [qw(sourceID type ID1 ID2)]],
- '01D8' => ['actor_display', 'a4 v14 a4 x4 v1 x1 C1 a3 x2 C1 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir guildID skillstatus sex coords act lv)]],
- '01D9' => ['actor_display', 'a4 v14 a4 x4 v1 x1 C1 a3 x2 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir guildID skillstatus sex coords lv)]],
- '01DA' => ['actor_display', 'a4 v5 C1 x1 v3 x4 v5 a4 x4 v1 x1 C1 a5 x3 v1', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir guildID skillstatus sex coords lv)]],
- '01DC' => ['secure_login_key', 'x2 a*', [qw(secure_key)]],
- '01D6' => ['pvp_mode2', 'v1', [qw(type)]],
- '01DE' => ['skill_use', 'v1 a4 a4 V1 V1 V1 V1 v1 v1 C1', [qw(skillID sourceID targetID tick src_speed dst_speed damage level param3 type)]],
- '01E1' => ['revolving_entity', 'a4 v1', [qw(sourceID entity)]],
- #'01E2' => ['marriage_unknown'], clif_parse_ReqMarriage
- #'01E4' => ['marriage_unknown'], clif_marriage_process
- ##
- #01E6 26 Some Player Name.
- '01E9' => ['party_join', 'a4 x4 v1 v1 C1 Z24 Z24 Z16 v C2', [qw(ID x y type name user map lv item_pickup item_share)]],
- '01EB' => ['guild_location', 'a4 v1 v1', [qw(ID x y)]],
- '01EA' => ['married', 'a4', [qw(ID)]],
- '01EE' => ['inventory_items_stackable'],
- '01EF' => ['cart_items_list'],
- '01F2' => ['guild_member_online_status', 'a4 a4 V1 v3', [qw(ID charID online sex hair_style hair_color)]],
- # weather/misceffect2 packet
- '01F3' => ['npc_effect', 'a4 a4', [qw(ID effect)]],
- '01F4' => ['deal_request', 'Z24 x4 v1', [qw(user level)]],
- '01F5' => ['deal_begin', 'C1 a4 v1', [qw(type targetID level)]],
- #'01F6' => ['adopt_unknown'], # clif_parse_ReqAdopt
- #'01F8' => ['adopt_unknown'], # clif_adopt_process
- '01F0' => ['storage_items_stackable'],
- '01FC' => ['repair_list'],
- '01FE' => ['repair_result', 'v1 C1', [qw(nameID flag)]],
- '0201' => ['friend_list'],
- #'0205' => ['divorce_unknown', 'Z24', [qw(name)]], # clif_divorced
- '0206' => ['friend_logon', 'a4 a4 C1', [qw(friendAccountID friendCharID isNotOnline)]],
- '0207' => ['friend_request', 'a4 a4 Z24', [qw(accountID charID name)]],
- '0209' => ['friend_response', 'C1 Z24', [qw(type name)]],
- '020A' => ['friend_removed', 'a4 a4', [qw(friendAccountID friendCharID)]],
- '020E' => ['taekwon_mission_receive', 'Z24 a4 c1', [qw(monName ID value)]],
- '0215' => ['gospel_buff_aligned', 'a4', [qw(ID)]],
- '0219' => ['top10_blacksmith_rank'],
- '021A' => ['top10_alchemist_rank'],
- '021B' => ['blacksmith_points', 'V1 V1', [qw(points total)]],
- '021C' => ['alchemist_point', 'V1 V1', [qw(points total)]],
- '0224' => ['taekwon_rank', 'c1 x3 c1', [qw(type rank)]],
- '0226' => ['top10_taekwon_rank'],
- '0227' => ['gameguard_request'],
- '0229' => ['character_status', 'a4 v1 v1 v1', [qw(ID param1 param2 param3)]],
- '022A' => ['actor_display', 'a4 v4 x2 v8 x2 v a4 a4 v x2 C2 a3 x2 C v', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color head_dir guildID guildEmblem visual_effects stance sex coords act lv)]],
- '022B' => ['actor_display', 'a4 v4 x2 v8 x2 v a4 a4 v x2 C2 a3 x2 v', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color head_dir guildID guildEmblem visual_effects stance sex coords lv)]],
- '022C' => ['actor_display', 'a4 v4 x2 v5 V1 v3 x4 a4 a4 v x2 C2 a5 x3 v', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead timestamp tophead midhead hair_color guildID guildEmblem visual_effects stance sex coords lv)]],
- '022E' => ['homunculus_stats', 'Z24 C v16 V2 v2', [qw(name state lvl hunger intimacy accessory atk matk hit critical def mdef flee aspd hp hp_max sp sp_max exp exp_max points_skill unknown)]],
- '022F' => ['homunculus_food', 'C1 v1', [qw(success foodID)]],
- '0230' => ['homunculus_info', 'x1 C1 a4 V1',[qw(type ID val)]],
- '0235' => ['skills_list'], # homunculus skills
- '0238' => ['top10_pk_rank'],
- # homunculus skill update
- '0239' => ['skill_update', 'v1 v1 v1 v1 C1', [qw(skillID lv sp range up)]], # range = skill range, up = this skill can be leveled up further
- '023A' => ['storage_password_request', 'v1', [qw(flag)]],
- '023C' => ['storage_password_result', 'v1 v1', [qw(type val)]],
- '023E' => ['storage_password_request', 'v1', [qw(flag)]],
- '0240' => ['mail_refreshinbox', 'v1 V1', [qw(size count)]],
- '0242' => ['mail_read', 'v1 V1 Z40 Z24 x4 V2 v1 C1 x1 C3 a8 x1 Z*', [qw(lenght mailID title sender zeny amount nameID type identified broken upgrade cards message)]],
- '0245' => ['mail_getattachment', 'C1', [qw(fail)]],
- '0249' => ['mail_send', 'C1', [qw(fail)]],
- '024A' => ['mail_new', 'V1 Z24 Z24', [qw(mailID sender title)]],
- '0250' => ['auction_result', 'C1', [qw(flag)]],
- '0252' => ['auction_item_request_search', 'v1 V1 V1', [qw(size pages count)]],
- '0255' => ['mail_setattachment', 'v1 C1', [qw(index fail)]],
- '0256' => ['auction_add_item', 'v1 C1', [qw(index fail)]],
- '0257' => ['mail_delete', 'V1 v1', [qw(mailID fail)]],
- '0259' => ['gameguard_grant', 'C1', [qw(server)]],
- '025D' => ['auction_my_sell_stop', 'V1', [qw(flag)]],
- '025F' => ['auction_windows', 'V1 C1 C1 C1 C1 v1', [qw(flag unknown1 unknown2 unknown3 unknown4 unknown5)]],
- '0260' => ['mail_window', 'v1', [qw(flag)]],
- '0274' => ['mail_return', 'V1 v1', [qw(mailID fail)]],
- # mail_return packet: '0274' => ['account_server_info', 'x2 a4 a4 a4 x30 C1 x4 a*', [qw(sessionID accountID sessionID2 accountSex serverInfo)]],
- # tRO new packets, need some work on them
- '0283' => ['account_id', 'V1', [qw(accountID)]],
- '0287' => ['cash_dealer'],
- '0291' => ['message_string', 'v1', [qw(msg_id)]],
- '0295' => ['inventory_items_nonstackable'],
- '0296' => ['storage_items_nonstackable'],
- '0297' => ['cart_equip_list'],
- '029A' => ['inventory_item_added', 'v1 v1 v1 C1 C1 C1 a8 v1 C1 C1 a4', [qw(index amount nameID identified broken upgrade cards type_equip type fail cards_ext)]],
- # mercenaries
- '029B' => ($rpackets{'029B'} == 72 # or 80 # mercenary stats
- ? ['homunculus_stats', 'a4 v8 Z24 v5 V1 v2', [qw(ID atk matk hit critical def mdef flee aspd name lvl hp hp_max sp sp_max contract_end faith summons)]]
- : ['homunculus_stats', 'a4 v8 Z24 v1 V5 v1 V2 v1', [qw(ID atk matk hit critical def mdef flee aspd name lvl hp hp_max sp sp_max contract_end faith summons kills range)]]
- ),
- # TODO: test on officials: '029B' => ['homunculus_stats', 'a4 v8 Z24 v1 V5 v1 V2 v1', [qw(ID atk matk hit critical def mdef flee aspd name lvl hp hp_max sp sp_max contract_end faith summons killcount range)]], # mercenary stats
- '029D' => ['skills_list'], # mercenary skills
- '02A2' => ['mercenary_param_change', 'v1 V1', [qw(type param)]],
- # tRO HShield packet challenge.
- # Borrow sub gameguard_request because it use the same mechanic.
- '02A6' => ['gameguard_request'],
- # mRO PIN code Check
- '02AD' => ['login_pin_code_request', 'v1 V', [qw(flag key)]],
- # Packet Prefix encryption Support
- '02AE' => ['initialize_message_id_encryption', 'V1 V1', [qw(param1 param2)]],
- # tRO new packets (2008-09-16Ragexe12_Th)
- '02B1' => ['quest_list'],
- '02B2' => ['objective_info'],
- '02B9' => ['hotkeys'],
- '02C5' => ['party_invite_result', 'Z24 V1', [qw(name type)]],
- '02C6' => ['party_invite', 'a4 Z24', [qw(ID name)]],
- '02C9' => ['party_allow_invite', 'C1', [qw(type)]],
- '02D0' => ['inventory_items_nonstackable'],
- '02D1' => ['storage_items_nonstackable'],
- '02D2' => ['cart_equip_list'],
- '02D4' => ['inventory_item_added', 'v1 v1 v1 C1 C1 C1 a8 v1 C1 C1 a6', [qw(index amount nameID identified broken upgrade cards type_equip type fail unknown)]],
- '02DA' => ['show_equipment', 'C1', [qw(type)]],
- # 02E1 packet unsure of param3 needs more testing
- '02E1' => ['actor_action', 'a4 a4 a4 V2 v1 x2 v1 x2 C1 v1', [qw(sourceID targetID tick src_speed dst_speed damage param2 type param3)]],
- '02E8' => ['inventory_items_stackable'],
- '02E9' => ['cart_items_list'],
- '02EA' => ['storage_items_stackable'],
- '02EB' => ['map_loaded', 'V1 a3 x2 v1', [qw(syncMapSync coords unknown)]],
- '02EC' => ['actor_display', 'x1 a4 v3 V1 v5 V1 v5 a4 a4 V1 C2 a5 x3 v2', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead timestamp tophead midhead hair_color clothes_color head_dir guildID guildEmblem visual_effects stance sex coords lv unknown)]],
- '02ED' => ['actor_display', 'a4 v3 V1 v10 a4 a4 V1 C2 a3 v v2', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir guildID guildEmblem visual_effects stance sex coords act lv unknown)]],
- '02EE' => ['actor_display', 'a4 v3 V1 v10 a4 a4 V1 C2 a3 x1 v v2', [qw(ID walk_speed param1 param2 param3 type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir guildID guildEmblem visual_effects stance sex coords act lv unknown)]],
- # status timers (eA has 12 unknown bytes)
- '043F' => ['actor_status_active', 'v1 a4 C1 V4', [qw(type ID flag tick unknown1 unknown2 unknown3)]],
- # HackShield alarm
- '0449' => ['hack_shield_alarm'],
- };
- return bless %self, $class;
- }
- ##
- # Network::Receive->create(String serverType)
- #
- # Create a new server message parsing object for the specified server type.
- #
- # Throws Network::Receive::InvalidServerType if the specified server type does
- # not exist.
- # Throws Network::Receive::CreationError if some other error occured.
- sub create {
- my ($self, $type) = @_;
- ($type) = $type =~ /([0-9_]+)/;
- $type = 0 if $type eq '';
- my $class = "Network::Receive::ServerType" . $type;
- undef $@;
- eval "use $class;";
- if ($@ =~ /^Can't locate /s) {
- Network::Receive::InvalidServerType->throw(
- TF("Cannot load server message parser for server type '%s'.", $type)
- );
- } elsif ($@) {
- Network::Receive::CreationError->throw(
- TF("An error occured while loading the server message parser for server type '%s':n%s",
- $type, $@)
- );
- } else {
- return $class->new();
- }
- }
- # $packetParser->reconstruct($args)
- #
- # Reconstructs a raw packet from $args using $self->{packet_list}.
- sub reconstruct {
- my ($self, $args) = @_;
- my $switch = $args->{switch};
- my $packet = $self->{packet_list}{$switch};
- my ($name, $packString, $varNames) = @{$packet};
- my @vars = ();
- for my $varName (@{$varNames}) {
- push(@vars, $args->{$varName});
- }
- my $packet = pack("H2 H2 $packString", substr($switch, 2, 2), substr($switch, 0, 2), @vars);
- return $packet;
- }
- sub parse {
- my ($self, $msg) = @_;
- $bytesReceived += length($msg);
- my $switch = Network::MessageTokenizer::getMessageID($msg);
- my $handler = $self->{packet_list}{$switch};
- return undef unless $handler;
- debug "Received packet: $switch Handler: $handler->[0]n", "packetParser", 2;
- # RAW_MSG is the entire message, including packet switch
- my %args = (
- switch => $switch,
- RAW_MSG => $msg,
- RAW_MSG_SIZE => length($msg)
- );
- if ($handler->[1]) {
- my @unpacked_data = unpack("x2 $handler->[1]", $msg);
- my $keys = $handler->[2];
- foreach my $key (@{$keys}) {
- $args{$key} = shift @unpacked_data;
- }
- }
- my $callback = $self->can($handler->[0]);
- if ($callback) {
- Plugins::callHook("packet_pre/$handler->[0]", %args);
- Misc::checkValidity("Packet: " . $handler->[0] . " (pre)");
- $self->$callback(%args);
- Misc::checkValidity("Packet: " . $handler->[0]);
- } else {
- debug "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]n", "packetParser", 2;
- }
- Plugins::callHook("packet/$handler->[0]", %args);
- return %args;
- }
- ##
- # boolean $packetParser->willMangle(Bytes messageID)
- # messageID: a message ID, such as "008A".
- #
- # Check whether the message with the specified message ID will be mangled.
- # If the bot is running in X-Kore mode, then messages that will be mangled will not
- # be sent to the RO client.
- #
- # By default, a message will never be mangled. Plugins can register mangling procedures
- # though. This is done by using the following hooks:
- # `l
- # - "Network::Receive/willMangle" - This hook has arguments 'messageID' (Bytes) and 'name' (String).
- # 'name' is a human-readable description of the message, and may be undef. Plugins
- # should set the 'return' argument to 1 if they want willMangle() to return 1.
- # - "Network::Receive/mangle" - This hook has arguments 'messageArgs' and 'messageName' (the latter may be undef).
- # `l`
- # The following example demonstrates how this is done:
- # <pre class="example">
- # Plugins::addHook("Network::Receive/willMangle", &willMangle);
- # Plugins::addHook("Network::Receive/mangle", &mangle);
- #
- # sub willMangle {
- # my (undef, $args) = @_;
- # if ($args->{messageID} eq '008A') {
- # $args->{willMangle} = 1;
- # }
- # }
- #
- # sub mangle {
- # my (undef, $args) = @_;
- # my $message_args = $args->{messageArgs};
- # if ($message_args->{switch} eq '008A') {
- # ...Modify $message_args as necessary....
- # }
- # }
- # </pre>
- sub willMangle {
- if (Plugins::hasHook("Network::Receive/willMangle")) {
- my ($self, $messageID) = @_;
- my $packet = $self->{packet_list}{$messageID};
- my $name;
- $name = $packet->[0] if ($packet);
- my %args = (
- messageID => $messageID,
- name => $name
- );
- Plugins::callHook("Network::Receive/willMangle", %args);
- return $args{return};
- } else {
- return undef;
- }
- }
- # boolean $packetParser->mangle(Array* args)
- #
- # Calls the appropriate plugin function to mangle the packet, which
- # destructively modifies $args.
- # Returns false if the packet should be suppressed.
- sub mangle {
- my ($self, $args) = @_;
- my %hook_args = (messageArgs => $args);
- my $entry = $self->{packet_list}{$args->{switch}};
- if ($entry) {
- $hook_args{messageName} = $entry->[0];
- }
- Plugins::callHook("Network::Receive/mangle", %hook_args);
- return $hook_args{return};
- }
- ##
- # Network::Receive->decrypt(r_msg, themsg)
- # r_msg: a reference to a scalar.
- # themsg: the message to decrypt.
- #
- # Decrypts the packets in $themsg and put the result in the scalar
- # referenced by $r_msg.
- #
- # This is an old method used back in the iRO beta 2 days when iRO had encrypted packets.
- # At the moment (December 20 2006) there are no servers that still use encrypted packets.
- #
- # Example:
- # } elsif ($switch eq "ABCD") {
- # my $level;
- # Network::Receive->decrypt($level, substr($msg, 0, 2));
- sub decrypt {
- use bytes;
- my ($self, $r_msg, $themsg) = @_;
- my @mask;
- my $i;
- my ($temp, $msg_temp, $len_add, $len_total, $loopin, $len, $val);
- if ($config{encrypt} == 1) {
- undef $$r_msg;
- undef $len_add;
- undef $msg_temp;
- for ($i = 0; $i < 13;$i++) {
- $mask[$i] = 0;
- }
- $len = unpack("v1",substr($themsg,0,2));
- $val = unpack("v1",substr($themsg,2,2));
- {
- use integer;
- $temp = ($val * $val * 1391);
- }
- $temp = ~(~($temp));
- $temp = $temp % 13;
- $mask[$temp] = 1;
- {
- use integer;
- $temp = $val * 1397;
- }
- $temp = ~(~($temp));
- $temp = $temp % 13;
- $mask[$temp] = 1;
- for($loopin = 0; ($loopin + 4) < $len; $loopin++) {
- if (!($mask[$loopin % 13])) {
- $msg_temp .= substr($themsg,$loopin + 4,1);
- }
- }
- if (($len - 4) % 8 != 0) {
- $len_add = 8 - (($len - 4) % 8);
- }
- $len_total = $len + $len_add;
- $$r_msg = $msg_temp.substr($themsg, $len_total, length($themsg) - $len_total);
- } elsif ($config{encrypt} >= 2) {
- undef $$r_msg;
- undef $len_add;
- undef $msg_temp;
- for ($i = 0; $i < 17;$i++) {
- $mask[$i] = 0;
- }
- $len = unpack("v1",substr($themsg,0,2));
- $val = unpack("v1",substr($themsg,2,2));
- {
- use integer;
- $temp = ($val * $val * 34953);
- }
- $temp = ~(~($temp));
- $temp = $temp % 17;
- $mask[$temp] = 1;
- {
- use integer;
- $temp = $val * 2341;
- }
- $temp = ~(~($temp));
- $temp = $temp % 17;
- $mask[$temp] = 1;
- for($loopin = 0; ($loopin + 4) < $len; $loopin++) {
- if (!($mask[$loopin % 17])) {
- $msg_temp .= substr($themsg,$loopin + 4,1);
- }
- }
- if (($len - 4) % 8 != 0) {
- $len_add = 8 - (($len - 4) % 8);
- }
- $len_total = $len + $len_add;
- $$r_msg = $msg_temp.substr($themsg, $len_total, length($themsg) - $len_total);
- } else {
- $$r_msg = $themsg;
- }
- }
- #######################################
- ###### Private methods
- #######################################
- sub queryLoginPinCode {
- my $message = $_[0] || T("You've never set a login PIN code before.nPlease enter a new login PIN code:");
- do {
- my $input = $interface->query($message, isPassword => 1,);
- if (!defined($input)) {
- quit();
- return;
- } else {
- if ($input !~ /^d+$/) {
- $interface->errorDialog(T("The PIN code may only contain digits."));
- } elsif ((length($input) <= 3) || (length($input) >= 9)) {
- $interface->errorDialog(T("The PIN code must be between 4 and 9 characters."));
- } else {
- return $input;
- }
- }
- } while (1);
- }
- sub queryAndSaveLoginPinCode {
- my ($message) = @_;
- my $pin = queryLoginPinCode($message);
- if (defined $pin) {
- configModify('loginPinCode', $pin, silent => 1);
- return 1;
- } else {
- return 0;
- }
- }
- #######################################
- ###### Packet handling callbacks ######
- #######################################
- # This is for what eA calls PacketVersion 9, they send the AID in a 'proper' packet
- sub account_id {
- my ($self, $args) = @_;
- # the account ID is already unpacked into PLAIN TEXT when it gets to this function...
- # So lets not fuckup the $accountID since we need that later... someone will prolly have to fix this later on
- #$accountID = $args->{accountID};
- }
- sub account_payment_info {
- my ($self, $args) = @_;
- my $D_minute = $args->{D_minute};
- my $H_minute = $args->{H_minute};
- my $D_d = int($D_minute / 1440);
- my $D_h = int(($D_minute % 1440) / 60);
- my $D_m = int(($D_minute % 1440) % 60);
- my $H_d = int($H_minute / 1440);
- my $H_h = int(($H_minute % 1440) / 60);
- my $H_m = int(($H_minute % 1440) % 60);
- message T("============= Account payment information =============n"), "info";
- message TF("Pay per day : %s day(s) %s hour(s) and %s minute(s)n", $D_d, $D_h, $D_m), "info";
- message TF("Pay per hour : %s day(s) %s hour(s) and %s minute(s)n", $H_d, $H_h, $H_m), "info";
- message T("-------------------------------------------------------n"), "info";
- }
- sub account_server_info {
- my ($self, $args) = @_;
- my $msg = $args->{serverInfo};
- my $msg_size = length($msg);
- $net->setState(2);
- undef $conState_tries;
- $sessionID = $args->{sessionID};
- $accountID = $args->{accountID};
- $sessionID2 = $args->{sessionID2};
- # Account sex should only be 0 (female) or 1 (male)
- # inRO gives female as 2 but expects 0 back
- # do modulus of 2 here to fix?
- # FIXME: we should check exactly what operation the client does to the number given
- $accountSex = $args->{accountSex} % 2;
- $accountSex2 = ($config{'sex'} ne "") ? $config{'sex'} : $accountSex;
- message swrite(
- T("-----------Account Info------------n" .
- "Account ID: @<<<<<<<<< @<<<<<<<<<<n" .
- "Sex: @<<<<<<<<<<<<<<<<<<<<<n" .
- "Session ID: @<<<<<<<<< @<<<<<<<<<<n" .
- " @<<<<<<<<< @<<<<<<<<<<n" .
- "-----------------------------------"),
- [unpack("V1",$accountID), getHex($accountID), $sex_lut{$accountSex}, unpack("V1",$sessionID), getHex($sessionID),
- unpack("V1",$sessionID2), getHex($sessionID2)]), 'connection';
- my $num = 0;
- undef @servers;
- for (my $i = 0; $i < $msg_size; $i+=32) {
- $servers[$num]{ip} = makeIP(substr($msg, $i, 4));
- $servers[$num]{ip} = $masterServer->{ip} if ($masterServer && $masterServer->{private});
- $servers[$num]{port} = unpack("v1", substr($msg, $i+4, 2));
- ($servers[$num]{name}) = bytesToString(unpack("Z*", substr($msg, $i + 6, 20)));
- $servers[$num]{users} = unpack("V",substr($msg, $i + 26, 4));
- $num++;
- }
- message T("--------- Servers ----------n" .
- "# Name Users IP Portn"), 'connection';
- for (my $num = 0; $num < @servers; $num++) {
- message(swrite(
- "@<< @<<<<<<<<<<<<<<<<<<<< @<<<<< @<<<<<<<<<<<<<< @<<<<<",
- [$num, $servers[$num]{name}, $servers[$num]{users}, $servers[$num]{ip}, $servers[$num]{port}]
- ), 'connection');
- }
- message("-------------------------------n", 'connection');
- if ($net->version != 1) {
- message T("Closing connection to Account Servern"), 'connection';
- $net->serverDisconnect();
- if (!$masterServer->{charServer_ip} && $config{server} eq "") {
- my @serverList;
- foreach my $server (@servers) {
- push @serverList, $server->{name};
- }
- my $ret = $interface->showMenu(
- T("Please select your login server."),
- @serverList,
- title => T("Select Login Server"));
- if ($ret == -1) {
- quit();
- } else {
- main::configModify('server', $ret, 1);
- }
- } elsif ($masterServer->{charServer_ip}) {
- message TF("Forcing connect to char server %s: %sn", $masterServer->{charServer_ip}, $masterServer->{charServer_port}), 'connection';
- } else {
- message TF("Server %s selectedn",$config{server}), 'connection';
- }
- }
- }
- sub actor_action {
- my ($self,$args) = @_;
- return unless changeToInGameState();
- $args->{damage} = intToSignedShort($args->{damage});
- if ($args->{type} == 1) {
- # Take item
- my $source = Actor::get($args->{sourceID});
- my $verb = $source->verb('pick up', 'picks up');
- my $target = getActorName($args->{targetID});
- debug "$source $verb $targetn", 'parseMsg_presence';
- my $item = $itemsList->getByID($args->{targetID});
- $item->{takenBy} = $args->{sourceID} if ($item);
- } elsif ($args->{type} == 2) {
- # Sit
- my ($source, $verb) = getActorNames($args->{sourceID}, 0, 'are', 'is');
- if ($args->{sourceID} eq $accountID) {
- message T("You are sitting.n") if (!$char->{sitting});
- $char->{sitting} = 1;
- AI::queue("sitAuto") unless (AI::inQueue("sitAuto")) || $ai_v{sitAuto_forcedBySitCommand};
- } else {
- message TF("%s is sitting.n", getActorName($args->{sourceID})), 'parseMsg_statuslook', 2;
- my $player = $playersList->getByID($args->{sourceID});
- $player->{sitting} = 1 if ($player);
- }
- Misc::checkValidity("actor_action (take item)");
- } elsif ($args->{type} == 3) {
- # Stand
- my ($source, $verb) = getActorNames($args->{sourceID}, 0, 'are', 'is');
- if ($args->{sourceID} eq $accountID) {
- message T("You are standing.n") if ($char->{sitting});
- if ($config{sitAuto_idle}) {
- $timeout{ai_sit_idle}{time} = time;
- }
- $char->{sitting} = 0;
- } else {
- message TF("%s is standing.n", getActorName($args->{sourceID})), 'parseMsg_statuslook', 2;
- my $player = $playersList->getByID($args->{sourceID});
- $player->{sitting} = 0 if ($player);
- }
- Misc::checkValidity("actor_action (stand)");
- } else {
- # Attack
- my $dmgdisplay;
- my $totalDamage = $args->{damage} + $args->{param3};
- if ($totalDamage == 0) {
- $dmgdisplay = "Miss!";
- $dmgdisplay .= "!" if ($args->{type} == 11);
- } else {
- $dmgdisplay = $args->{damage};
- $dmgdisplay .= "!" if ($args->{type} == 10);
- $dmgdisplay .= " + $args->{param3}" if $args->{param3};
- }
- Misc::checkValidity("actor_action (attack 1)");
- updateDamageTables($args->{sourceID}, $args->{targetID}, $totalDamage);
- Misc::checkValidity("actor_action (attack 2)");
- my $source = Actor::get($args->{sourceID});
- my $target = Actor::get($args->{targetID});
- my $verb = $source->verb('attack', 'attacks');
- $target->{sitting} = 0 unless $args->{type} == 4 || $args->{type} == 9 || $totalDamage == 0;
- my $msg = attack_string($source, $target, $dmgdisplay, ($args->{src_speed}/10));
- Plugins::callHook('packet_attack', {sourceID => $args->{sourceID}, targetID => $args->{targetID}, msg => $msg, dmg => $totalDamage, type => $args->{type}});
- my $status = sprintf("[%3d/%3d]", percent_hp($char), percent_sp($char));
- Misc::checkValidity("actor_action (attack 3)");
- if ($args->{sourceID} eq $accountID) {
- message("$status $msg", $totalDamage > 0 ? "attackMon" : "attackMonMiss");
- if ($startedattack) {
- $monstarttime = time();
- $monkilltime = time();
- $startedattack = 0;
- }
- Misc::checkValidity("actor_action (attack 4)");
- calcStat($args->{damage});
- Misc::checkValidity("actor_action (attack 5)");
- } elsif ($args->{targetID} eq $accountID) {
- message("$status $msg", $args->{damage} > 0 ? "attacked" : "attackedMiss");
- if ($args->{damage} > 0) {
- $damageTaken{$source->{name}}{attack} += $args->{damage};
- }
- } elsif ($char->{slaves} && $char->{slaves}{$args->{sourceID}}) {
- message(sprintf("[%3d/%3d]", $char->{slaves}{$args->{sourceID}}{hpPercent}, $char->{slaves}{$args->{sourceID}}{spPercent}) . " $msg", $totalDamage > 0 ? "attackMon" : "attackMonMiss");
- } elsif ($char->{slaves} && $char->{slaves}{$args->{targetID}}) {
- message(sprintf("[%3d/%3d]", $char->{slaves}{$args->{targetID}}{hpPercent}, $char->{slaves}{$args->{targetID}}{spPercent}) . " $msg", $args->{damage} > 0 ? "attacked" : "attackedMiss");
- } else {
- debug("$msg", 'parseMsg_damage');
- }
- Misc::checkValidity("actor_action (attack 6)");
- }
- }
- sub actor_died_or_disappeared {
- my ($self,$args) = @_;
- return unless changeToInGameState();
- my $ID = $args->{ID};
- avoidList_ID($ID);
- if ($ID eq $accountID) {
- message T("You have diedn") if (!$char->{dead});
- Plugins::callHook('self_died');
- closeShop() unless !$shopstarted || $config{'dcOnDeath'} == -1 || !$AI;
- $char->{deathCount}++;
- $char->{dead} = 1;
- $char->{dead_time} = time;
- } elsif (defined $monstersList->getByID($ID)) {
- my $monster = $monstersList->getByID($ID);
- if ($args->{type} == 0) {
- debug "Monster Disappeared: " . $monster->name . " ($monster->{binID})n", "parseMsg_presence";
- $monster->{disappeared} = 1;
- } elsif ($args->{type} == 1) {
- debug "Monster Died: " . $monster->name . " ($monster->{binID})n", "parseMsg_damage";
- $monster->{dead} = 1;
- if ((AI::action ne "attack" || AI::args(0)->{ID} ne $ID) &&
- ($config{itemsTakeAuto_party} &&
- ($monster->{dmgFromParty} > 0 ||
- $monster->{dmgFromYou} > 0))) {
- AI::clear("items_take");
- ai_items_take($monster->{pos}{x}, $monster->{pos}{y},
- $monster->{pos_to}{x}, $monster->{pos_to}{y});
- }
- } elsif ($args->{type} == 2) { # What's this?
- debug "Monster Disappeared: " . $monster->name . " ($monster->{binID})n", "parseMsg_presence";
- $monster->{disappeared} = 1;
- } elsif ($args->{type} == 3) {
- debug "Monster Teleported: " . $monster->name . " ($monster->{binID})n", "parseMsg_presence";
- $monster->{teleported} = 1;
- }
- $monster->{gone_time} = time;
- $monsters_old{$ID} = $monster->deepCopy();
- Plugins::callHook('monster_disappeared', {monster => $monster});
- $monstersList->remove($monster);
- } elsif (defined $playersList->getByID($ID)) {
- my $player = $playersList->getByID($ID);
- if ($args->{type} == 1) {
- message TF("Player Died: %s (%d) %s %sn", $player->name, $player->{binID}, $sex_lut{$player->{sex}}, $jobs_lut{$player->{jobID}});
- if ($char->{homunculus} && $char->{homunculus}{ID} eq $player->{ID}) {
- $playersList->remove($player);
- } else {
- $player->{dead} = 1;
- $player->{dead_time} = time;
- }
- } else {
- if ($args->{type} == 0) {
- debug "Player Disappeared: " . $player->name . " ($player->{binID}) $sex_lut{$player->{sex}} $jobs_lut{$player->{jobID}} ($player->{pos_to}{x}, $player->{pos_to}{y})n", "parseMsg_presence";
- $player->{disappeared} = 1;
- } elsif ($args->{type} == 2) {
- debug "Player Disconnected: ".$player->name." ($player->{binID}) $sex_lut{$player->{sex}} $jobs_lut{$player->{jobID}} ($player->{pos_to}{x}, $player->{pos_to}{y})n", "parseMsg_presence";
- $player->{disconnected} = 1;
- } elsif ($args->{type} == 3) {
- debug "Player Teleported: ".$player->name." ($player->{binID}) $sex_lut{$player->{sex}} $jobs_lut{$player->{jobID}} ($player->{pos_to}{x}, $player->{pos_to}{y})n", "parseMsg_presence";
- $player->{teleported} = 1;
- } else {
- debug "Player Disappeared in an unknown way: ".$player->name." ($player->{binID}) $sex_lut{$player->{sex}} $jobs_lut{$player->{jobID}}n", "parseMsg_presence";
- $player->{disappeared} = 1;
- }
- $player->{gone_time} = time;
- $players_old{$ID} = $player->deepCopy();
- Plugins::callHook('player_disappeared', {player => $player});
-
- $playersList->remove($player);
- }
- } elsif ($players_old{$ID}) {
- if ($args->{type} == 2) {
- debug "Player Disconnected: " . $players_old{$ID}->name . "n", "parseMsg_presence";
- $players_old{$ID}{disconnected} = 1;
- } elsif ($args->{type} == 3) {
- debug "Player Teleported: " . $players_old{$ID}->name . "n", "parseMsg_presence";
- $players_old{$ID}{teleported} = 1;
- }
- } elsif (defined $portalsList->getByID($ID)) {
- my $portal = $portalsList->getByID($ID);
- debug "Portal Disappeared: " . $portal->name . " ($portal->{binID})n", "parseMsg";
- $portal->{disappeared} = 1;
- $portal->{gone_time} = time;
- $portals_old{$ID} = $portal->deepCopy();
- $portalsList->remove($portal);
- } elsif (defined $npcsList->getByID($ID)) {
- my $npc = $npcsList->getByID($ID);
- debug "NPC Disappeared: " . $npc->name . " ($npc->{nameID})n", "parseMsg";
- $npc->{disappeared} = 1;
- $npc->{gone_time} = time;
- $npcs_old{$ID} = $npc->deepCopy();
- $npcsList->remove($npc);
- } elsif (defined $petsList->getByID($ID)) {
- my $pet = $petsList->getByID($ID);
- debug "Pet Disappeared: " . $pet->name . " ($pet->{binID})n", "parseMsg";
- $pet->{disappeared} = 1;
- $pet->{gone_time} = time;
- $petsList->remove($pet);
- } elsif (defined $slavesList->getByID($ID)) {
- my $slave = $slavesList->getByID($ID);
- if ($args->{type} == 1) {
- message TF("Slave Died: %s (%d) %sn", $slave->name, $slave->{binID}, $slave->{actorType});
- } else {
- if ($args->{type} == 0) {
- debug "Slave Disappeared: " . $slave->name . " ($slave->{binID}) $slave->{actorType} ($slave->{pos_to}{x}, $slave->{pos_to}{y})n", "parseMsg_presence";
- $slave->{disappeared} = 1;
- } elsif ($args->{type} == 2) {
- debug "Slave Disconnected: ".$slave->name." ($slave->{binID}) $slave->{actorType} ($slave->{pos_to}{x}, $slave->{pos_to}{y})n", "parseMsg_presence";
- $slave->{disconnected} = 1;
- } elsif ($args->{type} == 3) {
- debug "Slave Teleported: ".$slave->name." ($slave->{binID}) $slave->{actorType} ($slave->{pos_to}{x}, $slave->{pos_to}{y})n", "parseMsg_presence";
- $slave->{teleported} = 1;
- } else {
- debug "Slave Disappeared in an unknown way: ".$slave->name." ($slave->{binID}) $slave->{actorType}n", "parseMsg_presence";
- $slave->{disappeared} = 1;
- }
- $slave->{gone_time} = time;
- Plugins::callHook('slave_disappeared', {slave => $slave});
- }
-
- $slavesList->remove($slave);
- } else {
- debug "Unknown Disappeared: ".getHex($ID)."n", "parseMsg";
- }
- }
- # This function is a merge of actor_exists, actor_connected, actor_moved, etc...
- sub actor_display {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my ($actor, $mustAdd);
- #### Initialize ####
- my $nameID = unpack("V1", $args->{ID});
- my (%coordsFrom, %coordsTo);
- if ($args->{switch} eq "007C") {
- makeCoords(%coordsTo, $args->{coords});
- %coordsFrom = %coordsTo;
- } elsif ($args->{switch} eq "01DA") {
- makeCoords(%coordsFrom, substr($args->{RAW_MSG}, 50, 3));
- makeCoords2(%coordsTo, substr($args->{RAW_MSG}, 52, 3));
- } elsif (length($args->{coords}) >= 5) {
- my $coordsArg = $args->{coords};
- unShiftPack($coordsArg, $coordsTo{y}, 10);
- unShiftPack($coordsArg, $coordsTo{x}, 10);
- unShiftPack($coordsArg, $coordsFrom{y}, 10);
- unShiftPack($coordsArg, $coordsFrom{x}, 10);
- } else {
- my $coordsArg = $args->{coords};
- unShiftPack($coordsArg, $args->{body_dir}, 4);
- unShiftPack($coordsArg, $coordsTo{y}, 10);
- unShiftPack($coordsArg, $coordsTo{x}, 10);
- %coordsFrom = %coordsTo;
- }
- if ($args->{switch} eq "0086") {
- # Message 0086 contains less information about the actor than other similar
- # messages. So we use the existing actor information.
- $args = Actor::get($args->{ID})->deepCopy();
- $args->{switch} = "0086";
- }
- # Remove actors with a distance greater than removeActorWithDistance. Useful for vending (so you don't spam
- # too many packets in prontera and cause server lag). As a side effect, you won't be able to "see" actors
- # beyond removeActorWithDistance.
- if ($config{removeActorWithDistance}) {
- if ((my $block_dist = blockDistance($char->{pos_to}, %coordsTo)) > ($config{removeActorWithDistance})) {
- my $nameIdTmp = unpack("V1", $args->{ID});
- debug "Removed out of sight actor $nameIdTmp at ($coordsTo{x}, $coordsTo{y}) (distance: $block_dist)n";
- return;
- }
- }
- #### Step 1: create/get the correct actor object ####
- if ($jobs_lut{$args->{type}}) {
- unless ($args->{type} > 6000) {
- # Actor is a player
- $actor = $playersList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = new Actor::Player($args->{type});
- $actor->{appear_time} = time;
- $mustAdd = 1;
- }
- $actor->{nameID} = $nameID;
- } else {
- # Actor is a homunculus or a mercenary
- $actor = $slavesList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = $char->{slaves} && $char->{slaves}{$args->{ID}}
- ? $char->{slaves}{$args->{ID}} : new Actor::Slave ($args->{type});
-
- $actor->{appear_time} = time;
- $mustAdd = 1;
- }
- $actor->{nameID} = $nameID;
- }
- } elsif ($args->{type} == 45) {
- # Actor is a portal
- $actor = $portalsList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = new Actor::Portal();
- $actor->{appear_time} = time;
- my $exists = portalExists($field{name}, %coordsTo);
- $actor->{source}{map} = $field{name};
- if ($exists ne "") {
- $actor->setName("$portals_lut{$exists}{source}{map} -> " . getPortalDestName($exists));
- }
- $mustAdd = 1;
- # Strangely enough, portals (like all other actors) have names, too.
- # We _could_ send a "actor_info_request" packet to find the names of each portal,
- # however I see no gain from this. (And it might even provide another way of private
- # servers to auto-ban bots.)
- }
- $actor->{nameID} = $nameID;
- } elsif ($args->{type} >= 1000) {
- # Actor might be a monster
- if ($args->{hair_style} == 0x64) {
- # Actor is a pet
- $actor = $petsList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = new Actor::Pet();
- $actor->{appear_time} = time;
- if ($monsters_lut{$args->{type}}) {
- $actor->setName($monsters_lut{$args->{type}});
- }
- $actor->{name_given} = "Unknown";
- $mustAdd = 1;
- # Previously identified monsters could suddenly be identified as pets.
- if ($monstersList->getByID($args->{ID})) {
- $monstersList->removeByID($args->{ID});
- }
- }
- } else {
- # Actor really is a monster
- $actor = $monstersList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = new Actor::Monster();
- $actor->{appear_time} = time;
- if ($monsters_lut{$args->{type}}) {
- $actor->setName($monsters_lut{$args->{type}});
- }
- $actor->{name_given} = "Unknown";
- $actor->{binType} = $args->{type};
- $mustAdd = 1;
- }
- }
- # Why do monsters and pets use nameID as type?
- $actor->{nameID} = $args->{type};
- } else { # ($args->{type} < 1000 && $args->{type} != 45 && !$jobs_lut{$args->{type}})
- # Actor is an NPC
- $actor = $npcsList->getByID($args->{ID});
- if (!defined $actor) {
- $actor = new Actor::NPC();
- $actor->{appear_time} = time;
- $mustAdd = 1;
- }
- $actor->{nameID} = $nameID;
- }
- #### Step 2: update actor information ####
- $actor->{ID} = $args->{ID};
- $actor->{jobID} = $args->{type};
- $actor->{type} = $args->{type};
- $actor->{lv} = $args->{lv};
- $actor->{pos} = {%coordsFrom};
- $actor->{pos_to} = {%coordsTo};
- $actor->{walk_speed} = $args->{walk_speed} / 1000 if (exists $args->{walk_speed});
- $actor->{time_move} = time;
- $actor->{time_move_calc} = distance(%coordsFrom, %coordsTo) * $actor->{walk_speed};
- if (UNIVERSAL::isa($actor, "Actor::Player")) {
- # None of this stuff should matter if the actor isn't a player...
- # Interesting note about guildEmblem. If it is 0 (or none), the Ragnarok
- # client will display "Send (Player) a guild invitation" (assuming one has
- # invitation priveledges), regardless of whether or not guildID is set.
- # I bet that this is yet another brilliant "feature" by GRAVITY's good programmers.
- $actor->{guildEmblem} = $args->{guildEmblem} if (exists $args->{guildEmblem});
- $actor->{guildID} = $args->{guildID} if (exists $args->{guildID});
- if (exists $args->{lowhead}) {
- $actor->{headgear}{low} = $args->{lowhead};
- $actor->{headgear}{mid} = $args->{midhead};
- $actor->{headgear}{top} = $args->{tophead};
- $actor->{weapon} = $args->{weapon};
- $actor->{shield} = $args->{shield};
- }
- $actor->{sex} = $args->{sex};
- if ($args->{act} == 1) {
- $actor->{dead} = 1;
- } elsif ($args->{act} == 2) {
- $actor->{sitting} = 1;
- }
- # Monsters don't have hair colors or heads to look around...
- $actor->{hair_color} = $args->{hair_color} if (exists $args->{hair_color});
- }
- # But hair_style is used for pets, and their bodies can look different ways...
- $actor->{hair_style} = $args->{hair_style} if (exists $args->{hair_style});
- $actor->{look}{body} = $args->{body_dir} if (exists $args->{body_dir});
- $actor->{look}{head} = $args->{head_dir} if (exists $args->{head_dir});
- # When stance is non-zero, character is bobbing as if they had just got hit,
- # but the cursor also turns to a sword when they are mouse-overed.
- $actor->{stance} = $args->{stance} if (exists $args->{stance});
- # Visual effects are a set of flags
- $actor->{visual_effects} = $args->{visual_effects} if (exists $args->{visual_effects});
- # Known visual effects:
- # 0x0001 = Yellow tint (eg, a quicken skill)
- # 0x0002 = Red tint (eg, power-thrust)
- # 0x0004 = Gray tint (eg, energy coat)
- # 0x0008 = Slow lightning (eg, mental strength)
- # 0x0010 = Fast lightning (eg, MVP fury)
- # 0x0020 = Black non-moving statue (eg, stone curse)
- # 0x0040 = Translucent weapon
- # 0x0080 = Translucent red sprite (eg, marionette control?)
- # 0x0100 = Spaztastic weapon image (eg, mystical amplification)
- # 0x0200 = Gigantic glowy sphere-thing
- # 0x0400 = Translucent pink sprite (eg, marionette control?)
- # 0x0800 = Glowy sprite outline (eg, assumptio)
- # 0x1000 = Bright red sprite, slowly moving red lightning (eg, MVP fury?)
- # 0x2000 = Vortex-type effect
- # Note that these are flags, and you can mix and match them
- # Example: 0x000C (0x0008 & 0x0004) = gray tint with slow lightning
- # Save these parameters ...
- $actor->{param1} = $args->{param1};
- $actor->{param2} = $args->{param2};
- $actor->{param3} = $args->{param3};
- # And use them to set status flags.
- if (setStatus($actor, $args->{param1}, $args->{param2}, $args->{param3})) {
- $mustAdd = 0;
- }
- #### Step 3: Add actor to actor list ####
- if ($mustAdd) {
- if (UNIVERSAL::isa($actor, "Actor::Player")) {
- $playersList->add($actor);
- } elsif (UNIVERSAL::isa($actor, "Actor::Monster")) {
- $monstersList->add($actor);
- } elsif (UNIVERSAL::isa($actor, "Actor::Pet")) {
- $petsList->add($actor);
- } elsif (UNIVERSAL::isa($actor, "Actor::Portal")) {
- $portalsList->add($actor);
- } elsif (UNIVERSAL::isa($actor, "Actor::NPC")) {
- my $ID = $args->{ID};
- my $location = "$field{name} $actor->{pos}{x} $actor->{pos}{y}";
- if ($npcs_lut{$location}) {
- $actor->setName($npcs_lut{$location});
- }
- $npcsList->add($actor);
- } elsif (UNIVERSAL::isa($actor, "Actor::Slave")) {
- $slavesList->add($actor);
- }
- }
- #### Packet specific ####
- if ($args->{switch} eq "0078" ||
- $args->{switch} eq "01D8" ||
- $args->{switch} eq "022A" ||
- $args->{switch} eq "02EE") {
- # Actor Exists
- if ($actor->isa('Actor::Player')) {
- my $domain = existsInList($config{friendlyAID}, unpack("V1", $actor->{ID})) ? 'parseMsg_presence' : 'parseMsg_presence/player';
- debug "Player Exists: " . $actor->name . " ($actor->{binID}) Level $actor->{lv} $sex_lut{$actor->{sex}} $jobs_lut{$actor->{jobID}} ($coordsFrom{x}, $coordsFrom{y})n", $domain;
- Plugins::callHook('player', {player => $actor}); #backwards compatibailty
- Plugins::callHook('player_exist', {player => $actor});
- } elsif ($actor->isa('Actor::NPC')) {
- message TF("NPC Exists: %s (%d, %d) (ID %d) - (%d)n", $actor->name, $actor->{pos_to}{x}, $actor->{pos_to}{y}, $actor->{nameID}, $actor->{binID}), "parseMsg_presence", 1;
- } elsif ($actor->isa('Actor::Portal')) {
- message TF("Portal Exists: %s (%s, %s) - (%s)n", $actor->name, $actor->{pos_to}{x}, $actor->{pos_to}{y}, $actor->{binID}), "portals", 1;
- } elsif ($actor->isa('Actor::Monster')) {
- debug sprintf("Monster Exists: %s (%d)n", $actor->name, $actor->{binID}), "parseMsg_presence", 1;
- } elsif ($actor->isa('Actor::Pet')) {
- debug sprintf("Pet Exists: %s (%d)n", $actor->name, $actor->{binID}), "parseMsg_presence", 1;
- } elsif ($actor->isa('Actor::Slave')) {
- debug sprintf("Slave Exists: %s (%d)n", $actor->name, $actor->{binID}), "parseMsg_presence", 1;
- } else {
- debug sprintf("Unknown Actor Exists: %s (%d)n", $actor->name, $actor->{binID}), "parseMsg_presence", 1;
- }
- } elsif ($args->{switch} eq "0079" ||
- $args->{switch} eq "01DB" ||
- $args->{switch} eq "022B" ||
- $args->{switch} eq "02ED" ||
- $args->{switch} eq "01D9") {
- # Actor Connected
- if ($actor->isa('Actor::Player')) {
- my $domain = existsInList($config{friendlyAID}, unpack("V1", $args->{ID})) ? 'parseMsg_presence' : 'parseMsg_presence/player';
- debug "Player Connected: ".$actor->name." ($actor->{binID}) Level $args->{lv} $sex_lut{$actor->{sex}} $jobs_lut{$actor->{jobID}} ($coordsTo{x}, $coordsTo{y})n", $domain;
- Plugins::callHook('player', {player => $actor}); #backwards compatibailty
- Plugins::callHook('player_connected', {player => $actor});
- } else {
- debug "Unknown Connected: $args->{type} - ", "parseMsg";
- }
- } elsif ($args->{switch} eq "007B" ||
- $args->{switch} eq "01DA" ||
- $args->{switch} eq "022C" ||
- $args->{switch} eq "02EC" ||
- $args->{switch} eq "0086") {
- # Actor Moved
- # Correct the direction in which they're looking
- my %vec;
- getVector(%vec, %coordsTo, %coordsFrom);
- my $direction = int sprintf("%.0f", (360 - vectorToDegree(%vec)) / 45);
- $actor->{look}{body} = $direction;
- $actor->{look}{head} = 0;
- if ($actor->isa('Actor::Player')) {
- debug "Player Moved: " . $actor->name . " ($actor->{binID}) Level $actor->{lv} $sex_lut{$actor->{sex}} $jobs_lut{$actor->{jobID}} - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } elsif ($actor->isa('Actor::Monster')) {
- debug "Monster Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } elsif ($actor->isa('Actor::Pet')) {
- debug "Pet Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } elsif ($actor->isa('Actor::Slave')) {
- debug "Slave Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } elsif ($actor->isa('Actor::Portal')) {
- # This can never happen of course.
- debug "Portal Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } elsif ($actor->isa('Actor::NPC')) {
- # Neither can this.
- debug "Monster Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- } else {
- debug "Unknown Actor Moved: " . $actor->nameIdx . " - ($coordsFrom{x}, $coordsFrom{y}) -> ($coordsTo{x}, $coordsTo{y})n", "parseMsg";
- }
- } elsif ($args->{switch} eq "007C") {
- # Actor Spawned
- if ($actor->isa('Actor::Player')) {
- debug "Player Spawned: " . $actor->nameIdx . " $sex_lut{$actor->{sex}} $jobs_lut{$actor->{jobID}}n", "parseMsg";
- } elsif ($actor->isa('Actor::Monster')) {
- debug "Monster Spawned: " . $actor->nameIdx . "n", "parseMsg";
- } elsif ($actor->isa('Actor::Slave')) {
- debug "Slave Spawned: " . $actor->nameIdx . "n", "parseMsg";
- } elsif ($actor->isa('NPC')) {
- debug "NPC Spawned: " . $actor->nameIdx . "n", "parseMsg";
- } else {
- debug "Unknown Spawned: " . $actor->nameIdx . "n", "parseMsg";
- }
- }
- }
- sub actor_info {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- debug "Received object info: $args->{name}n", "parseMsg_presence/name", 2;
- my $player = $playersList->getByID($args->{ID});
- if ($player) {
- # This packet tells us the names of players who aren't in a guild, as opposed to 0195.
- $player->setName(bytesToString($args->{name}));
- message "Player Info: " . $player->nameIdx . "n", "parseMsg_presence", 2;
- updatePlayerNameCache($player);
- Plugins::callHook('charNameUpdate', $player);
- }
- my $monster = $monstersList->getByID($args->{ID});
- if ($monster) {
- my $name = bytesToString($args->{name});
- debug "Monster Info: $name ($monster->{binID})n", "parseMsg", 2;
- $monster->{name_given} = $name;
- if ($monsters_lut{$monster->{nameID}} eq "") {
- $monster->setName($name);
- $monsters_lut{$monster->{nameID}} = $name;
- updateMonsterLUT(Settings::getTableFilename("monsters.txt"), $monster->{nameID}, $name);
- }
- }
- my $npc = $npcs{$args->{ID}};
- if ($npc) {
- $npc->setName(bytesToString($args->{name}));
- if ($config{debug} >= 2) {
- my $binID = binFind(@npcsID, $args->{ID});
- debug "NPC Info: $npc->{name} ($binID)n", "parseMsg", 2;
- }
- my $location = "$field{name} $npc->{pos}{x} $npc->{pos}{y}";
- if (!$npcs_lut{$location}) {
- $npcs_lut{$location} = $npc->{name};
- updateNPCLUT(Settings::getTableFilename("npcs.txt"), $location, $npc->{name});
- }
- }
- my $pet = $pets{$args->{ID}};
- if ($pet) {
- my $name = bytesToString($args->{name});
- $pet->{name_given} = $name;
- $pet->setName($name);
- if ($config{debug} >= 2) {
- my $binID = binFind(@petsID, $args->{ID});
- debug "Pet Info: $pet->{name_given} ($binID)n", "parseMsg", 2;
- }
- }
- my $slave = $slavesList->getByID($args->{ID});
- if ($slave) {
- my $name = bytesToString($args->{name});
- #$slave->{name_given} = $name;
- $slave->setName($name);
- my $binID = binFind(@slavesID, $args->{ID});
- debug "Slave Info: $name ($binID)n", "parseMsg_presence", 2;
- updatePlayerNameCache($slave);
- }
- }
- sub actor_look_at {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my $actor = Actor::get($args->{ID});
- $actor->{look}{head} = $args->{head};
- $actor->{look}{body} = $args->{body};
- debug $actor->nameString . " looks at $args->{body}, $args->{head}n", "parseMsg";
- }
- sub actor_movement_interrupted {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my %coords;
- $coords{x} = $args->{x};
- $coords{y} = $args->{y};
- my $actor = Actor::get($args->{ID});
- $actor->{pos} = {%coords};
- $actor->{pos_to} = {%coords};
- if ($actor->isa('Actor::You') || $actor->isa('Actor::Player')) {
- $actor->{sitting} = 0;
- }
- if ($actor->isa('Actor::You')) {
- debug "Movement interrupted, your coordinates: $coords{x}, $coords{y}n", "parseMsg_move";
- AI::clear("move");
- }
- if ($char->{homunculus} && $char->{homunculus}{ID} eq $actor->{ID}) {
- AI::clear("move");
- }
- }
- sub actor_muted {
- my ($self, $args) = @_;
- my $ID = $args->{ID};
- my $duration = $args->{duration};
- if ($duration > 0) {
- $duration = 0xFFFFFFFF - $duration + 1;
- message TF("%s is muted for %d minutesn", getActorName($ID), $duration), "parseMsg_statuslook", 2;
- } else {
- message TF("%s is no longer mutedn", getActorName($ID)), "parseMsg_statuslook", 2;
- }
- }
- sub actor_name_received {
- my ($self, $args) = @_;
- # FIXME: There is more to this packet than just party name and guild name.
- # This packet is received when you leave a guild
- # (with cryptic party and guild name fields, at least for now)
- my $player = $playersList->getByID($args->{ID});
- if (defined $player) {
- # Receive names of players who are in a guild.
- $player->setName(bytesToString($args->{name}));
- $player->{party}{name} = bytesToString($args->{partyName});
- $player->{guild}{name} = bytesToString($args->{guildName});
- $player->{guild}{title} = bytesToString($args->{guildTitle});
- updatePlayerNameCache($player);
- debug "Player Info: $player->{name} ($player->{binID})n", "parseMsg_presence", 2;
- Plugins::callHook('charNameUpdate', $player);
- } else {
- debug "Player Info for " . unpack("V", $args->{ID}) .
- " (not on screen): " . bytesToString($args->{name}) . "n",
- "parseMsg_presence/remote", 2;
- }
- my $monster = $monstersList->getByID($args->{ID});
- if ($monster) {
- my $name = bytesToString($args->{name});
- debug "Monster Info 2: $name ($monster->{binID})n", "parseMsg", 2;
- $monster->{name_given} = $name;
- if ($monsters_lut{$monster->{nameID}} eq "") {
- $monster->setName($name);
- $monsters_lut{$monster->{nameID}} = $name;
- updateMonsterLUT(Settings::getTableFilename("monsters.txt"), $monster->{nameID}, $name);
- }
- }
- }
- sub actor_status_active {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my ($type, $ID, $flag) = @{$args}{qw(type ID flag)};
- my $tick = 0;
- $tick = $args->{tick} if ($args->{switch} eq "043F");
- my $skillName = (defined($skillsStatus{$type})) ? $skillsStatus{$type} : "Unknown $type";
- $args->{skillName} = $skillName;
- my $actor = Actor::get($ID);
- $args->{actor} = $actor;
- my ($name, $is) = getActorNames($ID, 0, 'are', 'is');
- if ($flag) {
- # Skill activated
- my $again = 'now';
- if ($actor) {
- $again = 'again' if $actor->{statuses}{$skillName};
- $actor->{statuses}{$skillName} = 1;
- }
- if ($char->{party}{users}{$ID}{name}) {
- $again = 'again' if $char->{party}{users}{$ID}{statuses}{$skillName};
- $char->{party}{users}{$ID}{statuses}{$skillName} = 1;
- }
- my $disp = status_string($actor, $skillName, $again);
- if ($tick > 0) {
- $disp = status_string($actor, $skillName, $again, $tick/1000);
- };
- message $disp, "parseMsg_statuslook", ($ID eq $accountID or $char->{slaves} && $char->{slaves}{$ID}) ? 1 : 2;
- } else {
- # Skill de-activated (expired)
- delete $actor->{statuses}{$skillName} if $actor;
- delete $char->{party}{users}{$ID}{statuses}{$skillName} if ($char->{party}{users}{$ID}{name});
- my $disp = status_string($actor, $skillName, 'no longer');
- message $disp, "parseMsg_statuslook", ($ID eq $accountID or $char->{slaves} && $char->{slaves}{$ID}) ? 1 : 2;
- }
- }
- sub actor_trapped {
- my ($self, $args) = @_;
- # original comment was that ID is not a valid ID
- # but it seems to be, at least on eAthena/Freya
- my $actor = Actor::get($args->{ID});
- debug "$actor is trapped.n";
- }
- sub area_spell {
- my ($self, $args) = @_;
- # Area effect spell; including traps!
- my $ID = $args->{ID};
- my $sourceID = $args->{sourceID};
- my $x = $args->{x};
- my $y = $args->{y};
- my $type = $args->{type};
- my $fail = $args->{fail};
- # graffiti message, might only be for one of these switches
- #my $message = unpack("Z80", substr($msg, 17, 80));
- $spells{$ID}{'sourceID'} = $sourceID;
- $spells{$ID}{'pos'}{'x'} = $x;
- $spells{$ID}{'pos'}{'y'} = $y;
- $spells{$ID}{'pos_to'}{'x'} = $x;
- $spells{$ID}{'pos_to'}{'y'} = $y;
- my $binID = binAdd(@spellsID, $ID);
- $spells{$ID}{'binID'} = $binID;
- $spells{$ID}{'type'} = $type;
- if ($type == 0x81) {
- message TF("%s opened Warp Portal on (%d, %d)n", getActorName($sourceID), $x, $y), "skill";
- }
- debug "Area effect ".getSpellName($type)." ($binID) from ".getActorName($sourceID)." appeared on ($x, $y)n", "skill", 2;
- Plugins::callHook('packet_areaSpell', {
- fail => $fail,
- sourceID => $sourceID,
- type => $type,
- x => $x,
- y => $y
- });
- }
- sub area_spell_disappears {
- my ($self, $args) = @_;
- # The area effect spell with ID dissappears
- my $ID = $args->{ID};
- my $spell = $spells{$ID};
- debug "Area effect ".getSpellName($spell->{type})." ($spell->{binID}) from ".getActorName($spell->{sourceID})." disappeared from ($spell->{pos}{x}, $spell->{pos}{y})n", "skill", 2;
- delete $spells{$ID};
- binRemove(@spellsID, $ID);
- }
- sub arrow_equipped {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- return unless $args->{index};
- $char->{arrow} = $args->{index};
- my $item = $char->inventory->getByServerIndex($args->{index});
- if ($item && $char->{equipment}{arrow} != $item) {
- $char->{equipment}{arrow} = $item;
- $item->{equipped} = 32768;
- $ai_v{temp}{waitForEquip}-- if $ai_v{temp}{waitForEquip};
- message TF("Arrow/Bullet equipped: %s (%d)n", $item->{name}, $item->{invIndex});
- }
- }
- sub arrow_none {
- my ($self, $args) = @_;
- my $type = $args->{type};
- if ($type == 0) {
- delete $char->{'arrow'};
- if ($config{'dcOnEmptyArrow'}) {
- $interface->errorDialog(T("Please equip arrow first."));
- quit();
- } else {
- error T("Please equip arrow first.n");
- }
- } elsif ($type == 3) {
- debug "Arrow equippedn";
- }
- }
- sub arrowcraft_list {
- my ($self, $args) = @_;
- my $newmsg;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4));
- $msg = substr($msg, 0, 4).$newmsg;
- undef @arrowCraftID;
- for (my $i = 4; $i < $msg_size; $i += 2) {
- my $ID = unpack("v1", substr($msg, $i, 2));
- my $item = $char->inventory->getByNameID($ID);
- binAdd(@arrowCraftID, $item->{invIndex});
- }
- message T("Received Possible Arrow Craft List - type 'arrowcraft'n");
- }
- sub attack_range {
- my ($self, $args) = @_;
- my $type = $args->{type};
- debug "Your attack range is: $typen";
- return unless changeToInGameState();
- $char->{attack_range} = $type;
- if ($config{attackDistanceAuto} && $config{attackDistance} != $type) {
- message TF("Autodetected attackDistance = %sn", $type), "success";
- configModify('attackDistance', $type, 1);
- configModify('attackMaxDistance', $type, 1);
- }
- }
- sub buy_result {
- my ($self, $args) = @_;
- if ($args->{fail} == 0) {
- message T("Buy completed.n"), "success";
- } elsif ($args->{fail} == 1) {
- error T("Buy failed (insufficient zeny).n");
- } elsif ($args->{fail} == 2) {
- error T("Buy failed (insufficient weight capacity).n");
- } elsif ($args->{fail} == 3) {
- error T("Buy failed (too many different inventory items).n");
- } else {
- error TF("Buy failed (failure code %s).n", $args->{fail});
- }
- }
- sub card_merge_list {
- my ($self, $args) = @_;
- # You just requested a list of possible items to merge a card into
- # The RO client does this when you double click a card
- my $newmsg;
- my $msg = $args->{RAW_MSG};
- $self->decrypt($newmsg, substr($msg, 4));
- $msg = substr($msg, 0, 4).$newmsg;
- my ($len) = unpack("x2 v1", $msg);
- my $display;
- $display .= T("-----Card Merge Candidates-----n");
- my $index;
- for (my $i = 4; $i < $len; $i += 2) {
- $index = unpack("v1", substr($msg, $i, 2));
- my $item = $char->inventory->getByServerIndex($index);
- binAdd(@cardMergeItemsID, $item->{invIndex});
- $display .= "$item->{invIndex} $item->{name}n";
- }
- $display .= "-------------------------------n";
- message $display, "list";
- }
- sub card_merge_status {
- my ($self, $args) = @_;
- # something about successful compound?
- my $item_index = $args->{item_index};
- my $card_index = $args->{card_index};
- my $fail = $args->{fail};
- if ($fail) {
- message T("Card merging failedn");
- } else {
- my $item = $char->inventory->getByServerIndex($item_index);
- my $card = $char->inventory->getByServerIndex($card_index);
- message TF("%s has been successfully merged into %sn",
- $card->{name}, $item->{name}), "success";
- # Remove one of the card
- $card->{amount} -= 1;
- if ($card->{amount} <= 0) {
- $char->inventory->remove($card);
- }
- # Rename the slotted item now
- # FIXME: this is unoptimized
- use bytes;
- no encoding 'utf8';
- my $newcards = '';
- my $addedcard;
- for (my $i = 0; $i < 4; $i++) {
- my $cardData = substr($item->{cards}, $i * 2, 2);
- if (unpack("v", $cardData)) {
- $newcards .= $cardData;
- } elsif (!$addedcard) {
- $newcards .= pack("v", $card->{nameID});
- $addedcard = 1;
- } else {
- $newcards .= pack("v", 0);
- }
- }
- $item->{cards} = $newcards;
- $item->setName(itemName($item));
- }
- undef @cardMergeItemsID;
- undef $cardMergeIndex;
- }
- sub cart_info {
- my ($self, $args) = @_;
- $cart{items} = $args->{items};
- $cart{items_max} = $args->{items_max};
- $cart{weight} = int($args->{weight} / 10);
- $cart{weight_max} = int($args->{weight_max} / 10);
- $cart{exists} = 1;
- debug "[cart_info] received.n", "parseMsg";
- }
- sub cart_add_failed {
- my ($self, $args) = @_;
- my $reason;
- if ($args->{fail} == 0) {
- $reason = 'overweight';
- } elsif ($args->{fail} == 1) {
- $reason = 'too many items';
- } else {
- $reason = "Unknown code $args->{fail}";
- }
- error TF("Can't Add Cart Item (%s)n", $reason);
- }
- sub cart_equip_list {
- my ($self, $args) = @_;
- # "0122" sends non-stackable item info
- # "0123" sends stackable item info
- my ($newmsg, $psize);
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4));
- $msg = substr($msg, 0, 4).$newmsg;
- if ($args->{switch} eq '0297') {
- $psize = 24;
- } elsif ($args->{switch} eq '02D2') {
- $psize = 26;
- } else {
- $psize = 20;
- }
- for (my $i = 4; $i < $msg_size; $i += $psize) {
- my $index = unpack("v1", substr($msg, $i, 2));
- my $ID = unpack("v1", substr($msg, $i+2, 2));
- my $type = unpack("C1",substr($msg, $i+4, 1));
- my $item = $cart{inventory}[$index] = {};
- $item->{nameID} = $ID;
- $item->{amount} = 1;
- $item->{index} = $index;
- $item->{identified} = unpack("C1", substr($msg, $i+5, 1));
- $item->{type_equip} = unpack("v1", substr($msg, $i+6, 2));
- $item->{broken} = unpack("C1", substr($msg, $i+10, 1));
- $item->{upgrade} = unpack("C1", substr($msg, $i+11, 1));
- $item->{cards} = ($args->{switch} eq '0297') ? substr($msg, $i+12, 12) : substr($msg, $i+12, 8);
- $item->{name} = itemName($item);
- debug "Non-Stackable Cart Item: $item->{name} ($index) x 1n", "parseMsg";
- Plugins::callHook('packet_cart', {index => $index});
- }
- $ai_v{'inventory_time'} = time + 1;
- $ai_v{'cart_time'} = time + 1;
- }
- sub cart_item_added {
- my ($self, $args) = @_;
- my $item = $cart{inventory}[$args->{index}] ||= {};
- if ($item->{amount}) {
- $item->{amount} += $args->{amount};
- } else {
- $item->{index} = $args->{index};
- $item->{nameID} = $args->{ID};
- $item->{amount} = $args->{amount};
- $item->{identified} = $args->{identified};
- $item->{broken} = $args->{broken};
- $item->{upgrade} = $args->{upgrade};
- $item->{cards} = $args->{cards};
- $item->{name} = itemName($item);
- }
- message TF("Cart Item Added: %s (%d) x %sn", $item->{name}, $args->{index}, $args->{amount});
- $itemChange{$item->{name}} += $args->{amount};
- $args->{item} = $item;
- }
- sub cart_items_list {
- my ($self, $args) = @_;
- my ($newmsg, $psize);
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- my $switch = $args->{switch};
- $self->decrypt($newmsg, substr($msg, 4));
- $msg = substr($msg, 0, 4).$newmsg;
- if ($switch eq '0123') {
- $psize = 10;
- } elsif ($switch eq '02E9') {
- $psize = 22;
- } else {
- $psize = 18;
- }
- for (my $i = 4; $i < $msg_size; $i += $psize) {
- my $index = unpack("v1", substr($msg, $i, 2));
- my $ID = unpack("v1", substr($msg, $i+2, 2));
- my $amount = unpack("v1", substr($msg, $i+6, 2));
- my $item = $cart{inventory}[$index] ||= {};
- if ($item->{amount}) {
- $item->{amount} += $amount;
- } else {
- $item->{index} = $index;
- $item->{nameID} = $ID;
- $item->{amount} = $amount;
- $item->{cards} = substr($msg, $i + 10, 8) if ($psize == 18);
- $item->{name} = itemName($item);
- $item->{identified} = 1;
- }
- debug "Stackable Cart Item: $item->{name} ($index) x $amountn", "parseMsg";
- Plugins::callHook('packet_cart', {index => $index});
- }
- $ai_v{'inventory_time'} = time + 1;
- $ai_v{'cart_time'} = time + 1;
- }
- sub cash_dealer {
- my ($self, $args) = @_;
- undef @cashList;
- my $cashList = 0;
- $char->{cashpoint} = unpack("x4 V", $args->{RAW_MSG});
- for (my $i = 8; $i < $args->{RAW_MSG_SIZE}; $i += 11) {
- my ($price, $dcprice, $type, $ID) = unpack("V2 C v", substr($args->{RAW_MSG}, $i, 11));
- my $store = $cashList[$cashList] = {};
- my $display = ($items_lut{$ID} ne "") ? $items_lut{$ID} : "Unknown $ID";
- $store->{name} = $display;
- $store->{nameID} = $ID;
- $store->{type} = $type;
- $store->{price} = $dcprice;
- $cashList++;
- }
- $ai_v{npc_talk}{talk} = 'cash';
- # continue talk sequence now
- $ai_v{npc_talk}{time} = time;
- message TF("-----------CashList (Cash Point: %-5d)------------n" .
- "# Name Type Pricen", $char->{cashpoint}), "list";
- my $display;
- for (my $i = 0; $i < @cashList; $i++) {
- $display = $cashList[$i]{name};
- message(swrite(
- "@< @<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<< @>>>>>>>p",
- [$i, $display, $itemTypes_lut{$cashList[$i]{type}}, $cashList[$i]{price}]),
- "list");
- }
- message("---------------------------------------------------n", "list");
- }
- sub combo_delay {
- my ($self, $args) = @_;
- $char->{combo_packet} = ($args->{delay}); #* 15) / 100000;
- # How was the above formula derived? I think it's better that the manipulation be
- # done in functions.pl (or whatever sub that handles this) instead of here.
- $args->{actor} = Actor::get($args->{ID});
- my $verb = $args->{actor}->verb('have', 'has');
- debug "$args->{actor} $verb combo delay $args->{delay}n", "parseMsg_comboDelay";
- }
- sub cart_item_removed {
- my ($self, $args) = @_;
- my ($index, $amount) = @{$args}{qw(index amount)};
- my $item = $cart{inventory}[$index];
- $item->{amount} -= $amount;
- message TF("Cart Item Removed: %s (%d) x %sn", $item->{name}, $index, $amount);
- $itemChange{$item->{name}} -= $amount;
- if ($item->{amount} <= 0) {
- $cart{'inventory'}[$index] = undef;
- }
- $args->{item} = $item;
- }
- sub change_to_constate25 {
- $net->setState(2.5);
- undef $accountID;
- }
- sub changeToInGameState {
- if ($net->version() == 1) {
- if ($accountID && UNIVERSAL::isa($char, 'Actor::You')) {
- if ($net->getState() != Network::IN_GAME) {
- $net->setState(Network::IN_GAME);
- }
- return 1;
- } else {
- if ($net->getState() != Network::IN_GAME_BUT_UNINITIALIZED) {
- $net->setState(Network::IN_GAME_BUT_UNINITIALIZED);
- if ($config{verbose} && $messageSender && !$sentWelcomeMessage) {
- $messageSender->injectAdminMessage("Please relogin to enable X-${Settings::NAME}.");
- $sentWelcomeMessage = 1;
- }
- }
- return 0;
- }
- } else {
- return 1;
- }
- }
- sub character_creation_failed {
- message T("Character creation failed. " .
- "If you didn't make any mistake, then the name you chose already exists.n"), "info";
- if (charSelectScreen() == 1) {
- $net->setState(3);
- $firstLoginMap = 1;
- $startingZenny = $chars[$config{'char'}]{'zenny'} unless defined $startingZenny;
- $sentWelcomeMessage = 1;
- }
- }
- sub character_creation_successful {
- my ($self, $args) = @_;
- my $char = new Actor::You;
- $char->{ID} = $args->{ID};
- $char->{name} = bytesToString($args->{name});
- $char->{zenny} = $args->{zenny};
- $char->{jobID} = 0;
- $char->{str} = $args->{str};
- $char->{agi} = $args->{agi};
- $char->{vit} = $args->{vit};
- $char->{int} = $args->{int};
- $char->{dex} = $args->{dex};
- $char->{luk} = $args->{luk};
- my $slot = $args->{slot};
- $char->{lv} = 1;
- $char->{lv_job} = 1;
- $char->{sex} = $accountSex2;
- $chars[$slot] = $char;
- $net->setState(3);
- message TF("Character %s (%d) created.n", $char->{name}, $slot), "info";
- if (charSelectScreen() == 1) {
- $firstLoginMap = 1;
- $startingZenny = $chars[$config{'char'}]{'zenny'} unless defined $startingZenny;
- $sentWelcomeMessage = 1;
- }
- }
- sub character_deletion_successful {
- if (defined $AI::temp::delIndex) {
- message TF("Character %s (%d) deleted.n", $chars[$AI::temp::delIndex]{name}, $AI::temp::delIndex), "info";
- delete $chars[$AI::temp::delIndex];
- undef $AI::temp::delIndex;
- for (my $i = 0; $i < @chars; $i++) {
- delete $chars[$i] if ($chars[$i] && !scalar(keys %{$chars[$i]}))
- }
- } else {
- message T("Character deleted.n"), "info";
- }
- if (charSelectScreen() == 1) {
- $net->setState(3);
- $firstLoginMap = 1;
- $startingZenny = $chars[$config{'char'}]{'zenny'} unless defined $startingZenny;
- $sentWelcomeMessage = 1;
- }
- }
- sub character_deletion_failed {
- error T("Character cannot be deleted. Your e-mail address was probably wrong.n");
- undef $AI::temp::delIndex;
- if (charSelectScreen() == 1) {
- $net->setState(3);
- $firstLoginMap = 1;
- $startingZenny = $chars[$config{'char'}]{'zenny'} unless defined $startingZenny;
- $sentWelcomeMessage = 1;
- }
- }
- sub character_moves {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- makeCoords($char->{pos}, substr($args->{RAW_MSG}, 6, 3));
- makeCoords2($char->{pos_to}, substr($args->{RAW_MSG}, 8, 3));
- my $dist = sprintf("%.1f", distance($char->{pos}, $char->{pos_to}));
- debug "You're moving from ($char->{pos}{x}, $char->{pos}{y}) to ($char->{pos_to}{x}, $char->{pos_to}{y}) - distance $dist, unknown $args->{unknown}n", "parseMsg_move";
- $char->{time_move} = time;
- $char->{time_move_calc} = distance($char->{pos}, $char->{pos_to}) * ($char->{walk_speed} || 0.12);
- # Correct the direction in which we're looking
- my (%vec, $degree);
- getVector(%vec, $char->{pos_to}, $char->{pos});
- $degree = vectorToDegree(%vec);
- if (defined $degree) {
- my $direction = int sprintf("%.0f", (360 - $degree) / 45);
- $char->{look}{body} = $direction & 0x07;
- $char->{look}{head} = 0;
- }
- # Ugly; AI code in network subsystem! This must be fixed.
- if (AI::action eq "mapRoute" && $config{route_escape_reachedNoPortal} && $dist eq "0.0"){
- if (!$portalsID[0]) {
- if ($config{route_escape_shout} ne "" && !defined($timeout{ai_route_escape}{time})){
- sendMessage("c", $config{route_escape_shout});
- }
- $timeout{ai_route_escape}{time} = time;
- AI::queue("escape");
- }
- }
- }
- sub character_name {
- my ($self, $args) = @_;
- my $name; # Type: String
- $name = bytesToString($args->{name});
- debug "Character name received: $namen";
- }
- sub character_status {
- my ($self, $args) = @_;
- if ($args->{ID} eq $accountID) {
- $char->{param1} = $args->{param1};
- $char->{param2} = $args->{param2};
- $char->{param3} = $args->{param3};
- }
- setStatus(Actor::get($args->{ID}), $args->{param1}, $args->{param2}, $args->{param3});
- }
- sub chat_created {
- my ($self, $args) = @_;
- $currentChatRoom = $accountID;
- $chatRooms{$accountID} = {%createdChatRoom};
- binAdd(@chatRoomsID, $accountID);
- binAdd(@currentChatRoomUsers, $char->{name});
- message T("Chat Room Createdn");
- }
- sub chat_info {
- my ($self, $args) = @_;
- my $title;
- $self->decrypt($title, $args->{title});
- $title = bytesToString($title);
- my $chat = $chatRooms{$args->{ID}};
- if (!$chat || !%{$chat}) {
- $chat = $chatRooms{$args->{ID}} = {};
- binAdd(@chatRoomsID, $args->{ID});
- }
- $chat->{title} = $title;
- $chat->{ownerID} = $args->{ownerID};
- $chat->{limit} = $args->{limit};
- $chat->{public} = $args->{public};
- $chat->{num_users} = $args->{num_users};
- }
- sub chat_join_result {
- my ($self, $args) = @_;
- if ($args->{type} == 1) {
- message T("Can't join Chat Room - Incorrect Passwordn");
- } elsif ($args->{type} == 2) {
- message T("Can't join Chat Room - You're bannedn");
- }
- }
- sub chat_modified {
- my ($self, $args) = @_;
- my $title;
- $self->decrypt($title, $args->{title});
- $title = bytesToString($title);
- my ($ownerID, $ID, $limit, $public, $num_users) = @{$args}{qw(ownerID ID limit public num_users)};
- if ($ownerID eq $accountID) {
- $chatRooms{new}{title} = $title;
- $chatRooms{new}{ownerID} = $ownerID;
- $chatRooms{new}{limit} = $limit;
- $chatRooms{new}{public} = $public;
- $chatRooms{new}{num_users} = $num_users;
- } else {
- $chatRooms{$ID}{title} = $title;
- $chatRooms{$ID}{ownerID} = $ownerID;
- $chatRooms{$ID}{limit} = $limit;
- $chatRooms{$ID}{public} = $public;
- $chatRooms{$ID}{num_users} = $num_users;
- }
- message T("Chat Room Properties Modifiedn");
- }
- sub chat_newowner {
- my ($self, $args) = @_;
- my $user = bytesToString($args->{user});
- if ($args->{type} == 0) {
- if ($user eq $char->{name}) {
- $chatRooms{$currentChatRoom}{ownerID} = $accountID;
- } else {
- my $players = $playersList->getItems();
- my $player;
- foreach my $p (@{$players}) {
- if ($p->{name} eq $user) {
- $player = $p;
- last;
- }
- }
- if ($player) {
- my $key = $player->{ID};
- $chatRooms{$currentChatRoom}{ownerID} = $key;
- }
- }
- $chatRooms{$currentChatRoom}{users}{$user} = 2;
- } else {
- $chatRooms{$currentChatRoom}{users}{$user} = 1;
- }
- }
- sub chat_user_join {
- my ($self, $args) = @_;
- my $user = bytesToString($args->{user});
- if ($currentChatRoom ne "") {
- binAdd(@currentChatRoomUsers, $user);
- $chatRooms{$currentChatRoom}{users}{$user} = 1;
- $chatRooms{$currentChatRoom}{num_users} = $args->{num_users};
- message TF("%s has joined the Chat Roomn", $user);
- }
- }
- sub chat_user_leave {
- my ($self, $args) = @_;
- my $user = bytesToString($args->{user});
- delete $chatRooms{$currentChatRoom}{users}{$user};
- binRemove(@currentChatRoomUsers, $user);
- $chatRooms{$currentChatRoom}{num_users} = $args->{num_users};
- if ($user eq $char->{name}) {
- binRemove(@chatRoomsID, $currentChatRoom);
- delete $chatRooms{$currentChatRoom};
- undef @currentChatRoomUsers;
- $currentChatRoom = "";
- message T("You left the Chat Roomn");
- } else {
- message TF("%s has left the Chat Roomn", $user);
- }
- }
- sub chat_users {
- my ($self, $args) = @_;
- my $newmsg;
- $self->decrypt($newmsg, substr($args->{RAW_MSG}, 8));
- my $msg = substr($args->{RAW_MSG}, 0, 8).$newmsg;
- my $ID = substr($args->{RAW_MSG},4,4);
- $currentChatRoom = $ID;
- my $chat = $chatRooms{$currentChatRoom} ||= {};
- $chat->{num_users} = 0;
- for (my $i = 8; $i < $args->{RAW_MSG_SIZE}; $i += 28) {
- my $type = unpack("C1",substr($msg,$i,1));
- my ($chatUser) = unpack("Z*", substr($msg,$i + 4,24));
- $chatUser = bytesToString($chatUser);
- if ($chat->{users}{$chatUser} eq "") {
- binAdd(@currentChatRoomUsers, $chatUser);
- if ($type == 0) {
- $chat->{users}{$chatUser} = 2;
- } else {
- $chat->{users}{$chatUser} = 1;
- }
- $chat->{num_users}++;
- }
- }
- message TF("You have joined the Chat Room %sn", $chat->{title});
- }
- sub cast_cancelled {
- my ($self, $args) = @_;
- # Cast is cancelled
- my $ID = $args->{ID};
- my $source = Actor::get($ID);
- $source->{cast_cancelled} = time;
- my $skill = $source->{casting}->{skill};
- my $skillName = $skill ? $skill->getName() : 'Unknown';
- my $domain = ($ID eq $accountID) ? "selfSkill" : "skill";
- message TF("%s failed to cast %sn", $source, $skillName), $domain;
- Plugins::callHook('packet_castCancelled', {
- sourceID => $ID
- });
- delete $source->{casting};
- }
- sub chat_removed {
- my ($self, $args) = @_;
- binRemove(@chatRoomsID, $args->{ID});
- delete $chatRooms{ $args->{ID} };
- }
- sub deal_add_other {
- my ($self, $args) = @_;
- if ($args->{nameID} > 0) {
- my $item = $currentDeal{other}{ $args->{nameID} } ||= {};
- $item->{amount} += $args->{amount};
- $item->{nameID} = $args->{nameID};
- $item->{identified} = $args->{identified};
- $item->{broken} = $args->{broken};
- $item->{upgrade} = $args->{upgrade};
- $item->{cards} = $args->{cards};
- $item->{name} = itemName($item);
- message TF("%s added Item to Deal: %s x %sn", $currentDeal{name}, $item->{name}, $args->{amount}), "deal";
- } elsif ($args->{amount} > 0) {
- $currentDeal{other_zenny} += $args->{amount};
- my $amount = formatNumber($args->{amount});
- message TF("%s added %s z to Dealn", $currentDeal{name}, $amount), "deal";
- }
- }
- sub deal_add_you {
- my ($self, $args) = @_;
- if ($args->{fail} == 1) {
- error T("That person is overweight; you cannot trade.n"), "deal";
- return;
- } elsif ($args->{fail} == 2) {
- error T("This item cannot be traded.n"), "deal";
- return;
- } elsif ($args->{fail}) {
- error TF("You cannot trade (fail code %s).n", $args->{fail}), "deal";
- return;
- }
- return unless $args->{index} > 0;
- my $item = $char->inventory->getByServerIndex($args->{index});
- $currentDeal{you}{$item->{nameID}}{amount} += $currentDeal{lastItemAmount};
- $item->{amount} -= $currentDeal{lastItemAmount};
- message TF("You added Item to Deal: %s x %sn", $item->{name}, $currentDeal{lastItemAmount}), "deal";
- $itemChange{$item->{name}} -= $currentDeal{lastItemAmount};
- $currentDeal{you_items}++;
- $args->{item} = $item;
- $char->inventory->remove($item) if ($item->{amount} <= 0);
- }
- sub deal_begin {
- my ($self, $args) = @_;
- if ($args->{type} == 0) {
- error T("That person is too far from you to trade.n");
- } elsif ($args->{type} == 2) {
- error T("That person is in another deal.n");
- } elsif ($args->{type} == 3) {
- if (%incomingDeal) {
- $currentDeal{name} = $incomingDeal{name};
- undef %incomingDeal;
- } else {
- my $ID = $outgoingDeal{ID};
- my $player;
- $player = $playersList->getByID($ID) if (defined $ID);
- $currentDeal{ID} = $ID;
- if ($player) {
- $currentDeal{name} = $player->{name};
- } else {
- $currentDeal{name} = 'Unknown #' . unpack("V", $ID);
- }
- undef %outgoingDeal;
- }
- message TF("Engaged Deal with %sn", $currentDeal{name}), "deal";
- } else {
- error TF("Deal request failed (unknown error %s).n", $args->{type});
- }
- }
- sub deal_cancelled {
- undef %incomingDeal;
- undef %outgoingDeal;
- undef %currentDeal;
- message T("Deal Cancelledn"), "deal";
- }
- sub deal_complete {
- undef %outgoingDeal;
- undef %incomingDeal;
- undef %currentDeal;
- message T("Deal Completen"), "deal";
- }
- sub deal_finalize {
- my ($self, $args) = @_;
- if ($args->{type} == 1) {
- $currentDeal{other_finalize} = 1;
- message TF("%s finalized the Dealn", $currentDeal{name}), "deal";
- } else {
- $currentDeal{you_finalize} = 1;
- # FIXME: shouldn't we do this when we actually complete the deal?
- $char->{zenny} -= $currentDeal{you_zenny};
- message T("You finalized the Dealn"), "deal";
- }
- }
- sub deal_request {
- my ($self, $args) = @_;
- my $level = $args->{level} || 'Unknown';
- my $user = bytesToString($args->{user});
- $incomingDeal{name} = $user;
- $timeout{ai_dealAutoCancel}{time} = time;
- message TF("%s (level %s) Requests a Dealn", $user, $level), "deal";
- message T("Type 'deal' to start dealing, or 'deal no' to deny the deal.n"), "deal";
- }
- sub devotion {
- my ($self, $args) = @_;
- my $source = Actor::get($args->{sourceID});
- my $msg = '';
- for (my $i = 0; $i < 5; $i++) {
- my $ID = substr($args->{data}, $i*4, 4);
- last if unpack("V", $ID) == 0;
- my $actor = Actor::get($ID);
- $msg .= skillUseNoDamage_string($source, $actor, 0, 'devotion');
- }
- message "$msg";
- }
- sub egg_list {
- my ($self, $args) = @_;
- message T("----- Egg Hatch Candidates -----n"), "list";
- for (my $i = 4; $i < $args->{RAW_MSG_SIZE}; $i += 2) {
- my $index = unpack("v1", substr($args->{RAW_MSG}, $i, 2));
- my $item = $char->inventory->getByServerIndex($index);
- message "$item->{invIndex} $item->{name}n", "list";
- }
- message "------------------------------n", "list";
- }
- sub emoticon {
- my ($self, $args) = @_;
- my $emotion = $emotions_lut{$args->{type}}{display} || "<emotion #$args->{type}>";
- if ($args->{ID} eq $accountID) {
- message "$char->{name}: $emotionn", "emotion";
- chatLog("e", "$char->{name}: $emotionn") if (existsInList($config{'logEmoticons'}, $args->{type}) || $config{'logEmoticons'} eq "all");
- } elsif (my $player = $playersList->getByID($args->{ID})) {
- my $name = $player->name;
- #my $dist = "unknown";
- my $dist = distance($char->{pos_to}, $player->{pos_to});
- $dist = sprintf("%.1f", $dist) if ($dist =~ /./);
- # Translation Comment: "[dist=$dist] $name ($player->{binID}): $emotionn"
- message TF("[dist=%s] %s (%d): %sn", $dist, $name, $player->{binID}, $emotion), "emotion";
- chatLog("e", "$name".": $emotionn") if (existsInList($config{'logEmoticons'}, $args->{type}) || $config{'logEmoticons'} eq "all");
- my $index = AI::findAction("follow");
- if ($index ne "") {
- my $masterID = AI::args($index)->{ID};
- if ($config{'followEmotion'} && $masterID eq $args->{ID} &&
- distance($char->{pos_to}, $player->{pos_to}) <= $config{'followEmotion_distance'})
- {
- my %args = ();
- $args{timeout} = time + rand (1) + 0.75;
- if ($args->{type} == 30) {
- $args{emotion} = 31;
- } elsif ($args->{type} == 31) {
- $args{emotion} = 30;
- } else {
- $args{emotion} = $args->{type};
- }
- AI::queue("sendEmotion", %args);
- }
- }
- } elsif (my $monster = $monstersList->getByID($args->{ID}) || $slavesList->getByID($args->{ID})) {
- my $dist = distance($char->{pos_to}, $monster->{pos_to});
- $dist = sprintf("%.1f", $dist) if ($dist =~ /./);
- # Translation Comment: "[dist=$dist] $monster->name ($monster->{binID}): $emotionn"
- message TF("[dist=%s] %s %s (%d): %sn", $dist, $monster->{actorType}, $monster->name, $monster->{binID}, $emotion), "emotion";
- } else {
- my $actor = Actor::get($args->{ID});
- my $name = $actor->name;
- my $dist = "unknown";
- if (!$actor->isa('Actor::Unknown')) {
- $dist = distance($char->{pos_to}, $actor->{pos_to});
- $dist = sprintf("%.1f", $dist) if ($dist =~ /./);
- }
- message TF("[dist=%s] %s: %sn", $dist, $actor->nameIdx, $emotion), "emotion";
- chatLog("e", "$name".": $emotionn") if (existsInList($config{'logEmoticons'}, $args->{type}) || $config{'logEmoticons'} eq "all");
- }
- }
- sub equip_item {
- my ($self, $args) = @_;
- my $item = $char->inventory->getByServerIndex($args->{index});
- if (!$args->{success}) {
- message TF("You can't put on %s (%d)n", $item->{name}, $item->{invIndex});
- } else {
- $item->{equipped} = $args->{type};
- if ($args->{type} == 10) {
- $char->{equipment}{arrow} = $item;
- } else {
- foreach (%equipSlot_rlut){
- if ($_ & $args->{type}){
- next if $_ == 10; # work around Arrow bug
- $char->{equipment}{$equipSlot_lut{$_}} = $item;
- }
- }
- }
- message TF("You equip %s (%d) - %s (type %s)n", $item->{name}, $item->{invIndex},
- $equipTypes_lut{$item->{type_equip}}, $args->{type}), 'inventory';
- }
- $ai_v{temp}{waitForEquip}-- if $ai_v{temp}{waitForEquip};
- }
- sub errors {
- my ($self, $args) = @_;
- Plugins::callHook('disconnected') if ($net->getState() == Network::IN_GAME);
- if ($net->getState() == Network::IN_GAME &&
- ($config{dcOnDisconnect} > 1 ||
- ($config{dcOnDisconnect} &&
- $args->{type} != 3 &&
- $args->{type} != 10))) {
- message T("Lost connection; exitingn");
- $quit = 1;
- }
- $net->setState(1);
- undef $conState_tries;
- $timeout_ex{'master'}{'time'} = time;
- $timeout_ex{'master'}{'timeout'} = $timeout{'reconnect'}{'timeout'};
- if (($args->{type} != 0)) {
- $net->serverDisconnect();
- }
- if ($args->{type} == 0) {
- error T("Server shutting downn"), "connection";
- } elsif ($args->{type} == 1) {
- error T("Error: Server is closedn"), "connection";
- } elsif ($args->{type} == 2) {
- if ($config{'dcOnDualLogin'} == 1) {
- $interface->errorDialog(TF("Critical Error: Dual login prohibited - Someone trying to login!nn" .
- "%s will now immediately disconnect.", $Settings::NAME));
- $quit = 1;
- } elsif ($config{'dcOnDualLogin'} >= 2) {
- error T("Critical Error: Dual login prohibited - Someone trying to login!n"), "connection";
- message TF("Disconnect for %s seconds...n", $config{'dcOnDualLogin'}), "connection";
- $timeout_ex{'master'}{'timeout'} = $config{'dcOnDualLogin'};
- } else {
- error T("Critical Error: Dual login prohibited - Someone trying to login!n"), "connection";
- }
- } elsif ($args->{type} == 3) {
- error T("Error: Out of sync with servern"), "connection";
- } elsif ($args->{type} == 4) {
- error T("Error: Server is jammed due to over-population.n"), "connection";
- } elsif ($args->{type} == 5) {
- error T("Error: You are underaged and cannot join this server.n"), "connection";
- } elsif ($args->{type} == 6) {
- $interface->errorDialog(T("Critical Error: You must pay to play this account!n"));
- $quit = 1 unless ($net->version == 1);
- } elsif ($args->{type} == 8) {
- error T("Error: The server still recognizes your last connectionn"), "connection";
- } elsif ($args->{type} == 9) {
- error T("Error: IP capacity of this Internet Cafe is full. Would you like to pay the personal base?n"), "connection";
- } elsif ($args->{type} == 10) {
- error T("Error: You are out of available time paid forn"), "connection";
- } elsif ($args->{type} == 15) {
- error T("Error: You have been forced to disconnect by a GMn"), "connection";
- } elsif ($args->{type} == 101) {
- error T("Error: Your account has been suspended until the next maintenance period for possible use of 3rd party programsn"), "connection";
- } elsif ($args->{type} == 102) {
- error T("Error: For an hour, more than 10 connections having same IP address, have made. Please check this matter.n"), "connection";
- } else {
- error TF("Unknown error %sn", $args->{type}), "connection";
- }
- }
- sub exp_zeny_info {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- if ($args->{type} == 1) {
- $char->{exp_last} = $char->{exp};
- $char->{exp} = $args->{val};
- debug "Exp: $args->{val}n", "parseMsg";
- if (!$bExpSwitch) {
- $bExpSwitch = 1;
- } else {
- if ($char->{exp_last} > $char->{exp}) {
- $monsterBaseExp = 0;
- } else {
- $monsterBaseExp = $char->{exp} - $char->{exp_last};
- }
- $totalBaseExp += $monsterBaseExp;
- if ($bExpSwitch == 1) {
- $totalBaseExp += $monsterBaseExp;
- $bExpSwitch = 2;
- }
- }
- } elsif ($args->{type} == 2) {
- $char->{exp_job_last} = $char->{exp_job};
- $char->{exp_job} = $args->{val};
- debug "Job Exp: $args->{val}n", "parseMsg";
- if ($jExpSwitch == 0) {
- $jExpSwitch = 1;
- } else {
- if ($char->{exp_job_last} > $char->{exp_job}) {
- $monsterJobExp = 0;
- } else {
- $monsterJobExp = $char->{exp_job} - $char->{exp_job_last};
- }
- $totalJobExp += $monsterJobExp;
- if ($jExpSwitch == 1) {
- $totalJobExp += $monsterJobExp;
- $jExpSwitch = 2;
- }
- }
- my $basePercent = $char->{exp_max} ?
- ($monsterBaseExp / $char->{exp_max} * 100) :
- 0;
- my $jobPercent = $char->{exp_job_max} ?
- ($monsterJobExp / $char->{exp_job_max} * 100) :
- 0;
- message TF("Exp gained: %d/%d (%.2f%%/%.2f%%)n", $monsterBaseExp, $monsterJobExp, $basePercent, $jobPercent), "exp";
- Plugins::callHook('exp_gained');
- } elsif ($args->{type} == 20) {
- my $change = $args->{val} - $char->{zenny};
- if ($change > 0) {
- message TF("You gained %s zeny.n", formatNumber($change));
- } elsif ($change < 0) {
- message TF("You lost %s zeny.n", formatNumber(-$change));
- if ($config{dcOnZeny} && $args->{val} <= $config{dcOnZeny}) {
- $interface->errorDialog(TF("Disconnecting due to zeny lower than %s.", $config{dcOnZeny}));
- $quit = 1;
- }
- }
- $char->{zenny} = $args->{val};
- debug "Zenny: $args->{val}n", "parseMsg";
- } elsif ($args->{type} == 22) {
- $char->{exp_max_last} = $char->{exp_max};
- $char->{exp_max} = $args->{val};
- debug(TF("Required Exp: %sn", $args->{val}), "parseMsg");
- if (!$net->clientAlive() && $initSync && $masterServer->{serverType} == 2) {
- $messageSender->sendSync(1);
- $initSync = 0;
- }
- } elsif ($args->{type} == 23) {
- $char->{exp_job_max_last} = $char->{exp_job_max};
- $char->{exp_job_max} = $args->{val};
- debug("Required Job Exp: $args->{val}n", "parseMsg");
- message TF("BaseExp: %s | JobExp: %sn", $monsterBaseExp, $monsterJobExp), "info", 2 if ($monsterBaseExp);
- }
- }
- sub forge_list {
- my ($self, $args) = @_;
- message T("========Forge List========n");
- for (my $i = 4; $i < $args->{RAW_MSG_SIZE}; $i += 8) {
- my $viewID = unpack("v1", substr($args->{RAW_MSG}, $i, 2));
- message "$viewID $items_lut{$viewID}n";
- # always 0x0012
- #my $unknown = unpack("v1", substr($args->{RAW_MSG}, $i+2, 2));
- # ???
- #my $charID = substr($args->{RAW_MSG}, $i+4, 4);
- }
- message "=========================n";
- }
- sub friend_list {
- my ($self, $args) = @_;
- # Friend list
- undef @friendsID;
- undef %friends;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- my $ID = 0;
- for (my $i = 4; $i < $msg_size; $i += 32) {
- binAdd(@friendsID, $ID);
- $friends{$ID}{'accountID'} = substr($msg, $i, 4);
- $friends{$ID}{'charID'} = substr($msg, $i + 4, 4);
- $friends{$ID}{'name'} = bytesToString(unpack("Z24", substr($msg, $i + 8 , 24)));
- $friends{$ID}{'online'} = 0;
- $ID++;
- }
- }
- sub friend_logon {
- my ($self, $args) = @_;
- # Friend In/Out
- my $friendAccountID = $args->{friendAccountID};
- my $friendCharID = $args->{friendCharID};
- my $isNotOnline = $args->{isNotOnline};
- for (my $i = 0; $i < @friendsID; $i++) {
- if ($friends{$i}{'accountID'} eq $friendAccountID && $friends{$i}{'charID'} eq $friendCharID) {
- $friends{$i}{'online'} = 1 - $isNotOnline;
- if ($isNotOnline) {
- message TF("Friend %s has disconnectedn", $friends{$i}{name}), undef, 1;
- } else {
- message TF("Friend %s has connectedn", $friends{$i}{name}), undef, 1;
- }
- last;
- }
- }
- }
- sub friend_request {
- my ($self, $args) = @_;
- # Incoming friend request
- $incomingFriend{'accountID'} = $args->{accountID};
- $incomingFriend{'charID'} = $args->{charID};
- $incomingFriend{'name'} = bytesToString($args->{name});
- message TF("%s wants to be your friendn", $incomingFriend{'name'});
- message TF("Type 'friend accept' to be friend with %s, otherwise type 'friend reject'n", $incomingFriend{'name'});
- }
- sub friend_removed {
- my ($self, $args) = @_;
- # Friend removed
- my $friendAccountID = $args->{friendAccountID};
- my $friendCharID = $args->{friendCharID};
- for (my $i = 0; $i < @friendsID; $i++) {
- if ($friends{$i}{'accountID'} eq $friendAccountID && $friends{$i}{'charID'} eq $friendCharID) {
- message TF("%s is no longer your friendn", $friends{$i}{'name'});
- binRemove(@friendsID, $i);
- delete $friends{$i};
- last;
- }
- }
- }
- sub friend_response {
- my ($self, $args) = @_;
- # Response to friend request
- my $type = $args->{type};
- my $name = bytesToString($args->{name});
- if ($type) {
- message TF("%s rejected to be your friendn", $name);
- } else {
- my $ID = @friendsID;
- binAdd(@friendsID, $ID);
- $friends{$ID}{accountID} = substr($args->{RAW_MSG}, 4, 4);
- $friends{$ID}{charID} = substr($args->{RAW_MSG}, 8, 4);
- $friends{$ID}{name} = $name;
- $friends{$ID}{online} = 1;
- message TF("%s is now your friendn", $incomingFriend{'name'});
- }
- }
- sub homunculus_food {
- my ($self, $args) = @_;
- if ($args->{success}) {
- message TF("Fed homunculus with %sn", itemNameSimple($args->{foodID})), "homunculus";
- } else {
- error TF("Failed to feed homunculus with %s: no food in inventory.n", itemNameSimple($args->{foodID})), "homunculus";
- # auto-vaporize
- if ($char->{homunculus}{hunger} <= 11 && timeOut($char->{homunculus}{vaporize_time}, 5)) {
- $messageSender->sendSkillUse(244, 1, $accountID);
- $char->{homunculus}{vaporize_time} = time;
- error "Critical hunger level reached. Homunculus is put to rest.n", "homunculus";
- }
- }
- }
- use constant {
- HO_PRE_INIT => 0x0,
- HO_RELATIONSHIP_CHANGED => 0x1,
- HO_FULLNESS_CHANGED => 0x2,
- HO_ACCESSORY_CHANGED => 0x3,
- HO_HEADTYPE_CHANGED => 0x4,
- };
- sub homunculus_info {
- my ($self, $args) = @_;
- if ($args->{type} == HO_PRE_INIT) {
- my $state = $char->{homunculus}{state}
- if ($char->{homunculus} && $char->{homunculus}{ID} && $char->{homunculus}{ID} ne $args->{ID});
- $char->{homunculus} = Actor::get($args->{ID});
- $char->{homunculus}{state} = $state if (defined $state);
- $char->{homunculus}{map} = $field{name};
- unless ($char->{slaves}{$char->{homunculus}{ID}}) {
- AI::SlaveManager::addSlave ($char->{homunculus});
- }
- } elsif ($args->{type} == HO_RELATIONSHIP_CHANGED) {
- $char->{homunculus}{intimacy} = $args->{val};
- } elsif ($args->{type} == HO_FULLNESS_CHANGED) {
- $char->{homunculus}{hunger} = $args->{val};
- } elsif ($args->{type} == HO_ACCESSORY_CHANGED) {
- $char->{homunculus}{accessory} = $args->{val};
- } elsif ($args->{type} == HO_HEADTYPE_CHANGED) {
- #
- }
- }
- sub homunculus_stats { # homunculus and mercenary stats
- my ($self, $args) = @_;
-
- if ($args->{switch} eq '029B') {
- # Mercenary
- $char->{mercenary} = Actor::get ($args->{ID});
- $char->{mercenary}{map} = $field{name};
- unless ($char->{slaves}{$char->{mercenary}{ID}}) {
- AI::SlaveManager::addSlave ($char->{mercenary});
- }
- }
-
- my $slave;
- if ($args->{switch} eq '022E') {
- $slave = $char->{homunculus};
- } elsif ($args->{switch} eq '029B') {
- $slave = $char->{mercenary};
- }
-
- $slave->{name} = bytesToString ($args->{name});
-
- if ($args->{switch} eq '022E') {
- # Homunculus states:
- # 0 - alive
- # 2 - rest
- # 4 - dead
-
- if (($args->{state} & ~8) > 1) {
- foreach my $handle (@{$char->{homunculus}{slave_skillsID}}) {
- delete $char->{skills}{$handle};
- }
- $char->{homunculus}->clear();
- undef @{$char->{homunculus}{slave_skillsID}};
- if (defined $slave->{state} && $slave->{state} != $args->{state}) {
- if ($args->{state} & 2) {
- message T("Your Homunculus was vaporized!n"), 'homunculus';
- } elsif ($args->{state} & 4) {
- message T("Your Homunculus died!n"), 'homunculus';
- }
- }
- } elsif (defined $slave->{state} && $slave->{state} != $args->{state}) {
- if ($slave->{state} & 2) {
- message T("Your Homunculus was recalled!n"), 'homunculus';
- } elsif ($slave->{state} & 4) {
- message T("Your Homunculus was resurrected!n"), 'homunculus';
- }
- }
- }
-
- $slave->{level} = $args->{lvl};
- $slave->{atk} = $args->{atk};
- $slave->{matk} = $args->{matk};
- $slave->{hit} = $args->{hit};
- $slave->{critical} = $args->{critical};
- $slave->{def} = $args->{def};
- $slave->{mdef} = $args->{mdef};
- $slave->{flee} = $args->{flee};
- $slave->{aspd} = $args->{aspd};
- $slave->{hp} = $args->{hp};
- $slave->{hp_max} = ($args->{hp_max} > 0) ? $args->{hp_max} : $args->{hp};
- $slave->{sp} = $args->{sp};
- $slave->{sp_max} = ($args->{sp_max} > 0) ? $args->{sp_max} : $args->{sp};
-
- $slave->{aspdDisp} = int (200 - (($args->{aspd} < 10) ? 10 : ($args->{aspd} / 10)));
- $slave->{hpPercent} = ($slave->{hp} / $slave->{hp_max}) * 100;
- $slave->{spPercent} = ($slave->{sp} / $slave->{sp_max}) * 100;
-
- if ($args->{switch} eq '022E') {
- $slave->{state} = $args->{state};
- $slave->{hunger} = $args->{hunger};
- $slave->{intimacy} = $args->{intimacy};
- $slave->{accessory} = $args->{accessory};
- $slave->{exp} = $args->{exp};
- $slave->{exp_max} = $args->{exp_max};
- $slave->{expPercent} = ($args->{exp_max}) ? ($args->{exp} / $args->{exp_max}) * 100 : 0;
- $slave->{points_skill} = $args->{points_skill};
- } elsif ($args->{switch} eq '029B') {
- #$slave->{contract_end} = $args->{contract_end}
- $slave->{faith} = $args->{faith};
- $slave->{summons} = $args->{summons};
- }
- }
- sub gameguard_grant {
- my ($self, $args) = @_;
- if ($args->{server} == 0) {
- error T("The server Denied the login because GameGuard packets where not replied " .
- "correctly or too many time has been spent to send the response.n" .
- "Please verify the version of your poseidon server and try againn"), "poseidon";
- return;
- } elsif ($args->{server} == 1) {
- message T("Server granted login request to account servern"), "poseidon";
- } else {
- message T("Server granted login request to char/map servern"), "poseidon";
- change_to_constate25 if ($config{'gameGuard'} eq "2");
- }
- $net->setState(1.3) if ($net->getState() == 1.2);
- }
- sub gameguard_request {
- my ($self, $args) = @_;
- return if ($net->version == 1 && $config{gameGuard} ne '2');
- Poseidon::Client::getInstance()->query(
- substr($args->{RAW_MSG}, 0, $args->{RAW_MSG_SIZE})
- );
- debug "Querying Poseidonn", "poseidon";
- }
- sub guild_allies_enemy_list {
- my ($self, $args) = @_;
- # Guild Allies/Enemy List
- # <len>.w (<type>.l <guildID>.l <guild name>.24B).*
- # type=0 Ally
- # type=1 Enemy
- # This is the length of the entire packet
- my $msg = $args->{RAW_MSG};
- my $len = unpack("v", substr($msg, 2, 2));
- # clear $guild{enemy} and $guild{ally} otherwise bot will misremember alliances -zdivpsa
- $guild{enemy} = $guild{ally} = {};
- for (my $i = 4; $i < $len; $i += 32) {
- my ($type, $guildID, $guildName) = unpack("V1 V1 Z24", substr($msg, $i, 32));
- $guildName = bytesToString($guildName);
- if ($type) {
- # Enemy guild
- $guild{enemy}{$guildID} = $guildName;
- } else {
- # Allied guild
- $guild{ally}{$guildID} = $guildName;
- }
- debug "Your guild is ".($type ? 'enemy' : 'ally')." with guild $guildID ($guildName)n", "guild";
- }
- }
- sub guild_ally_request {
- my ($self, $args) = @_;
- my $ID = $args->{ID}; # is this a guild ID or account ID? Freya calls it an account ID
- my $name = bytesToString($args->{name}); # Type: String
- message TF("Incoming Request to Ally Guild '%s'n", $name);
- $incomingGuild{ID} = $ID;
- $incomingGuild{Type} = 2;
- $timeout{ai_guildAutoDeny}{time} = time;
- }
- sub guild_broken {
- my ($self, $args) = @_;
- # FIXME: determine the real significance of flag
- my $flag = $args->{flag};
- message T("Guild broken.n");
- undef %{$char->{guild}};
- undef $char->{guildID};
- undef %guild;
- }
- sub guild_member_setting_list {
- my ($self, $args) = @_;
- my $newmsg;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4, length($msg)-4));
- $msg = substr($msg, 0, 4).$newmsg;
- my $gtIndex;
- for (my $i = 4; $i < $msg_size; $i += 16) {
- $gtIndex = unpack("V1", substr($msg, $i, 4));
- $guild{positions}[$gtIndex]{invite} = (unpack("C1", substr($msg, $i + 4, 1)) & 0x01) ? 1 : '';
- $guild{positions}[$gtIndex]{punish} = (unpack("C1", substr($msg, $i + 4, 1)) & 0x10) ? 1 : '';
- $guild{positions}[$gtIndex]{feeEXP} = unpack("V1", substr($msg, $i + 12, 4));
- }
- }
- sub guild_skills_list {
- my ($self, $args) = @_;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- for (my $i = 6; $i < $msg_size; $i += 37) {
- my $skillID = unpack("v1", substr($msg, $i, 2));
- my $targetType = unpack("v1", substr($msg, $i+2, 2));
- my $level = unpack("v1", substr($msg, $i + 6, 2));
- my $sp = unpack("v1", substr($msg, $i + 8, 2));
- my ($skillName) = unpack("Z*", substr($msg, $i + 12, 24));
- my $up = unpack("C1", substr($msg, $i+36, 1));
- $guild{skills}{$skillName}{ID} = $skillID;
- $guild{skills}{$skillName}{sp} = $sp;
- $guild{skills}{$skillName}{up} = $up;
- $guild{skills}{$skillName}{targetType} = $targetType;
- if (!$guild{skills}{$skillName}{lv}) {
- $guild{skills}{$skillName}{lv} = $level;
- }
- }
- }
- sub guild_chat {
- my ($self, $args) = @_;
- my ($chatMsgUser, $chatMsg); # Type: String
- my $chat; # Type: String
- return unless changeToInGameState();
- $chat = bytesToString($args->{message});
- if (($chatMsgUser, $chatMsg) = $chat =~ /(.*?) : (.*)/) {
- $chatMsgUser =~ s/ $//;
- stripLanguageCode($chatMsg);
- $chat = "$chatMsgUser : $chatMsg";
- }
- chatLog("g", "$chatn") if ($config{'logGuildChat'});
- # Translation Comment: Guild Chat
- message TF("[Guild] %sn", $chat), "guildchat";
- # Only queue this if it's a real chat message
- ChatQueue::add('g', 0, $chatMsgUser, $chatMsg) if ($chatMsgUser);
- Plugins::callHook('packet_guildMsg', {
- MsgUser => $chatMsgUser,
- Msg => $chatMsg
- });
- }
- sub guild_create_result {
- my ($self, $args) = @_;
- my $type = $args->{type};
- my %types = (
- 0 => T("Guild create successful.n"),
- 2 => T("Guild create failed: Guild name already exists.n"),
- 3 => T("Guild create failed: Emperium is needed.n")
- );
- if ($types{$type}) {
- message $types{$type};
- } else {
- message TF("Guild create: Unknown error %sn", $type);
- }
- }
- sub guild_expulsionlist {
- my ($self, $args) = @_;
- for (my $i = 4; $i < $args->{RAW_MSG_SIZE}; $i += 88) {
- my ($name) = unpack("Z24", substr($args->{'RAW_MSG'}, $i, 24));
- my $acc = unpack("Z24", substr($args->{'RAW_MSG'}, $i + 24, 24));
- my ($cause) = unpack("Z44", substr($args->{'RAW_MSG'}, $i + 48, 44));
- $guild{expulsion}{$acc}{name} = bytesToString($name);
- $guild{expulsion}{$acc}{cause} = bytesToString($cause);
- }
- }
- sub guild_info {
- my ($self, $args) = @_;
- # Guild Info
- foreach (qw(ID lvl conMember maxMember average exp next_exp members)) {
- $guild{$_} = $args->{$_};
- }
- $guild{name} = bytesToString($args->{name});
- $guild{master} = bytesToString($args->{master});
- $guild{members}++; # count ourselves in the guild members count
- }
- sub guild_invite_result {
- my ($self, $args) = @_;
- my $type = $args->{type};
- my %types = (
- 0 => 'Target is already in a guild.',
- 1 => 'Target has denied.',
- 2 => 'Target has accepted.',
- 3 => 'Your guild is full.'
- );
- if ($types{$type}) {
- message TF("Guild join request: %sn", $types{$type});
- } else {
- message TF("Guild join request: Unknown %sn", $type);
- }
- }
- sub guild_location {
- # FIXME: not implemented
- my ($self, $args) = @_;
- }
- sub guild_leave {
- my ($self, $args) = @_;
- message TF("%s has left the guild.n" .
- "Reason: %sn", bytesToString($args->{name}), bytesToString($args->{message})), "schat";
- }
- sub guild_expulsion {
- my ($self, $args) = @_;
- message TF("%s has been removed from the guild.n" .
- "Reason: %sn", bytesToString($args->{name}), bytesToString($args->{message})), "schat";
- }
- sub guild_members_list {
- my ($self, $args) = @_;
- my $newmsg;
- my $jobID;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4, length($msg) - 4));
- $msg = substr($msg, 0, 4) . $newmsg;
- my $c = 0;
- delete $guild{member};
- for (my $i = 4; $i < $msg_size; $i+=104){
- $guild{member}[$c]{ID} = substr($msg, $i, 4);
- $guild{member}[$c]{charID} = substr($msg, $i+4, 4);
- $jobID = unpack("v1", substr($msg, $i + 14, 2));
- if ($jobID =~ /^40/) {
- $jobID =~ s/^40/1/;
- $jobID += 60;
- }
- $guild{member}[$c]{jobID} = $jobID;
- $guild{member}[$c]{lvl} = unpack("v1", substr($msg, $i + 16, 2));
- $guild{member}[$c]{contribution} = unpack("V1", substr($msg, $i + 18, 4));
- $guild{member}[$c]{online} = unpack("v1", substr($msg, $i + 22, 2));
- my $gtIndex = unpack("V1", substr($msg, $i + 26, 4));
- $guild{member}[$c]{title} = $guild{title}[$gtIndex];
- $guild{member}[$c]{name} = bytesToString(unpack("Z24", substr($msg, $i + 80, 24)));
- $c++;
- }
- }
- sub guild_member_online_status {
- my ($self, $args) = @_;
- foreach my $guildmember (@{$guild{member}}) {
- if ($guildmember->{charID} eq $args->{charID}) {
- if ($guildmember->{online} = $args->{online}) {
- message TF("Guild member %s logged in.n", $guildmember->{name}), "guildchat";
- } else {
- message TF("Guild member %s logged out.n", $guildmember->{name}), "guildchat";
- }
- last;
- }
- }
- }
- sub npc_effect {
- my ($self, $args) = @_;
- my $effect = unpack("V1", $args->{effect});
- my $ID = $args->{ID};
- my $name = getNPCName($ID);
-
- message TF("%s: *%s*n", $name, ''), "npc";
- }
- sub guild_members_title_list {
- my ($self, $args) = @_;
- my $newmsg;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4, length($msg) - 4));
- $msg = substr($msg, 0, 4) . $newmsg;
- my $gtIndex;
- for (my $i = 4; $i < $msg_size; $i+=28) {
- $gtIndex = unpack("V1", substr($msg, $i, 4));
- $guild{positions}[$gtIndex]{title} = bytesToString(unpack("Z24", substr($msg, $i + 4, 24)));
- }
- }
- sub guild_name {
- my ($self, $args) = @_;
- my $guildID = $args->{guildID};
- my $emblemID = $args->{emblemID};
- my $mode = $args->{mode};
- my $guildName = bytesToString($args->{guildName});
- $char->{guild}{name} = $guildName;
- $char->{guildID} = $guildID;
- $char->{guild}{emblem} = $emblemID;
- $messageSender->sendGuildInfoRequest(); # Is this necessary?? (requests for guild info packet 014E)
- $messageSender->sendGuildRequest(0); #requests for guild info packet 01B6 and 014C
- $messageSender->sendGuildRequest(1); #requests for guild member packet 0166 and 0154
- }
- sub guild_notice {
- my ($self, $args) = @_;
- my $msg = $args->{RAW_MSG};
- my ($address) = unpack("Z*", substr($msg, 2, 60));
- my ($message) = unpack("Z*", substr($msg, 62, 120));
- stripLanguageCode($address);
- stripLanguageCode($message);
- $address = bytesToString($address);
- $message = bytesToString($message);
- # don't show the huge guildmessage notice if there is none
- # the client does something similar to this...
- if ($address || $message) {
- my $msg = TF("---Guild Notice---n" .
- "%snn" .
- "%sn" .
- "------------------n", $address, $message);
- message $msg, "guildnotice";
- }
- #message T("Requesting guild information...n"), "info"; # Lets Disable this, its kinda useless.
- $messageSender->sendGuildInfoRequest();
- # Replies 01B6 (Guild Info) and 014C (Guild Ally/Enemy List)
- $messageSender->sendGuildRequest(0);
- # Replies 0166 (Guild Member Titles List) and 0154 (Guild Members List)
- $messageSender->sendGuildRequest(1);
- }
- sub guild_request {
- my ($self, $args) = @_;
- # Guild request
- my $ID = $args->{ID};
- my $name = bytesToString($args->{name});
- message TF("Incoming Request to join Guild '%s'n", $name);
- $incomingGuild{'ID'} = $ID;
- $incomingGuild{'Type'} = 1;
- $timeout{'ai_guildAutoDeny'}{'time'} = time;
- }
- sub identify {
- my ($self, $args) = @_;
- my $index = $args->{index};
- my $item = $char->inventory->getByServerIndex($index);
- $item->{identified} = 1;
- $item->{type_equip} = $itemSlots_lut{$item->{nameID}};
- message TF("Item Identified: %s (%d)n", $item->{name}, $item->{invIndex}), "info";
- undef @identifyID;
- }
- sub identify_list {
- my ($self, $args) = @_;
- my $newmsg;
- my $msg = $args->{RAW_MSG};
- my $msg_size = $args->{RAW_MSG_SIZE};
- $self->decrypt($newmsg, substr($msg, 4));
- $msg = substr($msg, 0, 4).$newmsg;
- undef @identifyID;
- for (my $i = 4; $i < $msg_size; $i += 2) {
- my $index = unpack("v1", substr($msg, $i, 2));
- my $item = $char->inventory->getByServerIndex($index);
- binAdd(@identifyID, $item->{invIndex});
- }
- my $num = @identifyID;
- message TF("Received Possible Identify List (%s item(s)) - type 'identify'n", $num), 'info';
- }
- sub ignore_all_result {
- my ($self, $args) = @_;
- if ($args->{type} == 0) {
- message T("All Players ignoredn");
- } elsif ($args->{type} == 1) {
- if ($args->{error} == 0) {
- message T("All players unignoredn");
- }
- }
- }
- sub ignore_player_result {
- my ($self, $args) = @_;
- if ($args->{type} == 0) {
- message T("Player ignoredn");
- } elsif ($args->{type} == 1) {
- if ($args->{error} == 0) {
- message T("Player unignoredn");
- }
- }
- }
- sub inventory_item_added {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my ($index, $amount, $fail) = ($args->{index}, $args->{amount}, $args->{fail});
- if (!$fail) {
- my $item = $char->inventory->getByServerIndex($index);
- if (!$item) {
- # Add new item
- $item = new Actor::Item();
- $item->{index} = $index;
- $item->{nameID} = $args->{nameID};
- $item->{type} = $args->{type};
- $item->{type_equip} = $args->{type_equip};
- $item->{amount} = $amount;
- $item->{identified} = $args->{identified};
- $item->{broken} = $args->{broken};
- $item->{upgrade} = $args->{upgrade};
- $item->{cards} = ($args->{switch} eq '029A') ? $args->{cards} + $args->{cards_ext}: $args->{cards};
- if ($args->{switch} eq '029A') {
- $args->{cards} .= $args->{cards_ext};
- }
- $item->{name} = itemName($item);
- $char->inventory->add($item);
- } else {
- # Add stackable item
- $item->{amount} += $amount;
- }
- $itemChange{$item->{name}} += $amount;
- my $disp = TF("Item added to inventory: %s (%d) x %d - %s",
- $item->{name}, $item->{invIndex}, $amount, $itemTypes_lut{$item->{type}});
- message "$dispn", "drop";
- $disp .= " ($field{name})n";
- itemLog($disp);
- Plugins::callHook('item_gathered',{item => $item->{name}});
- $args->{item} = $item;
- # TODO: move this stuff to AI()
- if ($ai_v{npc_talk}{itemID} eq $item->{nameID}) {
- $ai_v{'npc_talk'}{'talk'} = 'buy';
- $ai_v{'npc_talk'}{'time'} = time;
- }
- if ($AI == 2) {
- # Auto-drop item
- if (pickupitems(lc($item->{name})) == -1 && !AI::inQueue('storageAuto', 'buyAuto')) {
- $messageSender->sendDrop($item->{index}, $amount);
- message TF("Auto-dropping item: %s (%d) x %dn", $item->{name}, $item->{invIndex}, $amount), "drop";
- }
- }
- } elsif ($fail == 6) {
- message T("Can't loot item...wait...n"), "drop";
- } elsif ($fail == 2) {
- message T("Cannot pickup item (inventory full)n"), "drop";
- } elsif ($fail == 1) {
- message T("Cannot pickup item (you're Frozen?)n"), "drop";
- } else {
- message TF("Cannot pickup item (failure code %d)n", $fail), "drop";
- }
- }
- sub inventory_item_removed {
- my ($self, $args) = @_;
- return unless changeToInGameState();
- my $item = $char->inventory->getByServerIndex($args->{index});
- if ($item) {
- inventoryItemRemoved($item->{invIndex}, $args->{amount});
- Plugins::callHook('packet_item_removed', {index => $item->{invIndex}});
- }
- }
- sub item_used {
- my ($self, $args) = @_;
- my ($index, $itemID, $ID, $remaining, $success) =
- @{$args}{qw(index itemID ID remaining success)};
- my %hook_args = (
- serverIndex => $index,
- itemID => $itemID,
- userID => $ID,
- remaining => $remaining,
- success => $success
- );
- if ($ID eq $accountID) {
- my $item = $char->inventory->getByServerIndex($index);
- if ($item) {
- if ($success == 1) {
- my $amount = $item->{amount} - $remaining;
- $item->{amount} -= $amount;
- message TF("You used Item: %s (%d) x %d - %d leftn", $item->{name}, $item->{invIndex},
- $amount, $remaining), "useItem", 1;
- $itemChange{$item->{name}}--;
- if ($item->{amount} <= 0) {
- $char->inventory->remove($item);
- }
- $hook_args{item} = $item;
- $hook_args{invIndex} = $item->{invIndex};
- $hook_args{name} => $item->{name};
- $hook_args{amount} = $amount;
- } else {
- message TF("You failed to use item: %s (%d)n", $item ? $item->{name} : "#$itemID", $remaining), "useItem", 1;
- }
- } else {
- if ($success == 1) {
- message TF("You used unknown item #%d - %d leftn", $itemID, $remaining), "useItem", 1;
- } else {
- message TF("You failed to use unknown item #%d - %d leftn", $itemID, $remaining), "useItem", 1;
- }
- }