diff --git a/src/Misc.pm b/src/Misc.pm index 6728af3a6a..e1382a13d4 100644 --- a/src/Misc.pm +++ b/src/Misc.pm @@ -1194,6 +1194,7 @@ sub charSelectScreen { # An array which maps an index in @charNames to an index in @chars my @charNameIndices; my $mode; + my $charSlots; # Check system version to delete a character my $charDeleteVersion; @@ -1250,8 +1251,8 @@ sub charSelectScreen { $messageMapName = sprintf(", %s", $chars[$num]{last_map}); } } - - push @charNames, TF("Slot %d: %s (%s, %s, level %d/%d%s)%s", + + my $char_str = TF("Slot %d: %s (%s, %s, level %d/%d%s)%s", $num, $chars[$num]{name}, $jobs_lut{$chars[$num]{'jobID'}}, @@ -1260,7 +1261,9 @@ sub charSelectScreen { $chars[$num]{lv_job}, $messageMapName, $messageDeleteDate); + push @charNames, $char_str; push @charNameIndices, $num; + $charSlots->{$num} = $char_str; } if (@charNames) { @@ -1297,6 +1300,8 @@ sub charSelectScreen { } else { push @choices, T('Delete a character'); } + push @choices, T('Move a character slot'); + push @choices, T('Rename a character'); } else { message T("There are no characters on this account.\n"), "connection"; if ($config{char} ne "switch" && defined($char)) { @@ -1330,6 +1335,12 @@ sub charSelectScreen { # 'Create character' chosen $mode = "create"; + } elsif ($choice == @charNames+2) { + # 'Move a character slot' chosen + $mode = "move"; + } elsif ($choice == @charNames+3) { + # 'Rename a character' chosen + $mode = "rename"; } else { # 'Delete character' chosen $mode = "delete"; @@ -1438,6 +1449,84 @@ sub charSelectScreen { $AI::temp::delIndex = $charIndex; $timeout{charlogin}{time} = time; } + + } elsif ($mode eq "move") { + my $choice = $interface->showMenu( + T("Select the character you want to move."), + \@charNames, + title => T("Move a character - Origin")); + if ($choice == -1) { + goto TOP; + } + my $charIndex = @charNameIndices[$choice]; + + if (!$chars[$charIndex]{slot_addon}) { + message TF("Character %s cannot be moved.\n", $chars[$charIndex]{name}), "info"; + goto TOP; + } + my @available_slots; + #TODO: + # 1. Check for premium service slots + # 2. Overslots. Example char slots are 40 in screen and producible only 6 but players have 27 chars! + # 3. Billing slots, idk how it works + for (my $i = 0; $i < ($charSvrSet{total_slot} > 0 ? $charSvrSet{total_slot} : $charSvrSet{producible_slot}); $i++) { + if ($charSlots->{$i} ne "") { + push @available_slots,$charSlots->{$i}; + } else { + push @available_slots, TF("Slot %d: Empty", $i); + } + } + my $choice2 = $interface->showMenu( + T("Select the destination slot."), + \@available_slots, + title => T("Move a character - Destination")); + if ($choice2 == -1) { + goto TOP; + } + my $charToIndex = (defined @charNameIndices[$choice2] ? @charNameIndices[$choice2] : $choice2); + + $messageSender->sendCharMoveSlot($charIndex, $charToIndex, $chars[$charIndex]{slot_addon}); + message TF("Moving character %s from slot %d to %d...\n", $chars[$charIndex]{name}, $charIndex, $charToIndex), "connection"; + $AI::temp::moveIndex = $charIndex; + $AI::temp::moveToIndex = $charToIndex; + $timeout{charlogin}{time} = time; + + } elsif ($mode eq "rename") { + my $choice = $interface->showMenu( + T("Select the character you want to rename."), + \@charNames, + title => T("Rename a character")); + if ($choice == -1) { + goto TOP; + } + my $charIndex = @charNameIndices[$choice]; + + if (!$chars[$charIndex]{rename_addon}) { + message TF("Character %s cannot be renamed.\n", $chars[$charIndex]{name}), "info"; + goto TOP; + } + + my $message = TF("Input new name for \"%s\" (4-23 characters)\n", $chars[$charIndex]{name}); + my $input = $interface->query($message); + unless ($input =~ /\S/) { + goto TOP; + } else { + my @args = parseArgs($input); + if (@args < 1) { + $interface->errorDialog(T("You didn't specify enough parameters."), 0); + goto TOP; + } + if (length $args[0] > 23) { + $interface->errorDialog(T("Name must not be longer than 23 characters."), 0); + goto TOP; + } + + $messageSender->sendCharRename($accountID, $chars[$charIndex]{charID}, $args[0]); + message TF("Renaming character \"%s\" to \"%s\"...\n", $chars[$charIndex]{name}, $args[0]), "connection"; + $AI::temp::charRenameIndex = $charIndex; + $AI::temp::charRenameName = $args[0]; + $timeout{charlogin}{time} = time; + } } return 2; } diff --git a/src/Network/Receive.pm b/src/Network/Receive.pm index 53e10dee03..7cc8876d27 100644 --- a/src/Network/Receive.pm +++ b/src/Network/Receive.pm @@ -818,6 +818,8 @@ sub character_creation_successful { $character->{exp} = 0; $character->{exp_job} = 0; + $character->{rename_addon} = 0; + $character->{slot_addon} = 0; if (!exists($character->{sex})) { $character->{sex} = $accountSex2; } @@ -7311,4 +7313,125 @@ sub cash_dealer { message("-----------------------------------------------------\n", "list"); } +## +# 08D5 .W .W .W +# @author [Cydh] +## +sub char_move_slot_reply { + my ($self, $args) = @_; + if ($args->{reply} == 0) { + if (defined $AI::temp::moveIndex) { + message TF("Character %s moved from %d to %d.\n", $chars[$AI::temp::moveIndex]{name}, $AI::temp::moveIndex, $AI::temp::moveToIndex), "info"; + my $movedChar = $chars[$AI::temp::moveIndex]; # Save temp + $chars[$AI::temp::moveIndex] = $chars[$AI::temp::moveToIndex]; + $chars[$AI::temp::moveToIndex] = $movedChar; + $chars[$AI::temp::moveToIndex]{slot_addon} = $args->{slot_addon}; + undef $AI::temp::moveIndex; + undef $AI::temp::moveToIndex; + } else { + message T("Character moved.\n"), "info"; + relog(10); + return; + } + } else { + if (defined $AI::temp::moveIndex) { + message TF("Failed to move character %s from %d to %d.\n", $chars[$AI::temp::moveIndex]{name}, $AI::temp::moveIndex, $AI::temp::moveToIndex), "info"; + undef $AI::temp::moveIndex; + undef $AI::temp::moveToIndex; + } else { + message T("Failed to move a character slot.\n"), "info"; + } + } + + if (charSelectScreen() == 1) { + $net->setState(3); + $firstLoginMap = 1; + $startingzeny = $chars[$config{'char'}]{'zeny'} unless defined $startingzeny; + $sentWelcomeMessage = 1; + } + debug "char_move_slot_reply unknown:$args->{unknown}, reply:$args->{reply}, slot_addon:$args->{slot_addon}\n", "parseMsg"; +} + +## +# 08E3 .L .74B .66B +# CharInfo: 'Z24 C8 v Z16 V4' +# @author [Cydh] +## +sub char_renamed { + my ($self, $args) = @_; + + if (defined $AI::temp::charRenameIndex) { + my $charIndex = $AI::temp::charRenameIndex; + message TF("Character \"%s\" renamed to \"%s\"\n", $chars[$charIndex]{name}, $AI::temp::charRenameName), "info"; + my $char_info = $self->received_characters_unpackString; + my $character = new Actor::You; + @{$character}{@{$char_info->{keys}}} = unpack($char_info->{types}, substr($args->{charInfo}, 0, $masterServer->{charBlockSize})); + my $name = bytesToString($character->{name}); + + if ($name ne $AI::temp::charRenameName) { + error TF("Name mismatch! %s <-> %s\n", $name, $AI::temp::charRenameName), "info"; + } + + $chars[$charIndex]{name} = $name; + $chars[$charIndex]{rename_addon} = $character->{rename_addon}; + + undef $AI::temp::charRenameIndex; + undef $AI::temp::charRenameName; + + if (charSelectScreen() == 1) { + $net->setState(3); + $firstLoginMap = 1; + $startingzeny = $chars[$config{'char'}]{'zeny'} unless defined $startingzeny; + $sentWelcomeMessage = 1; + } + } else { + message T("Character renamed.\n"), "info"; + relog(10); + return; + } + + debug "char_renamed result:$args->{result}\n", "parseMsg"; +} + +## +# 08FD .W .W +# @author [Cydh] +## +sub char_rename_result { + my ($self, $args) = @_; + + # 0: Success + if ($args->{result} == 0 && defined $AI::temp::charRenameIndex) { + my $charIndex = $AI::temp::charRenameIndex; + message TF("Character \"%s\" renamed to \"%s\"\n", $chars[$charIndex]{name}, $AI::temp::charRenameName), "info"; + return; + # 4: Fail already exists + } elsif ($args->{result} == 4) { + error TF("Cannot rename character to \"%s\". Name is already exists.\n", $AI::temp::charRenameName); + # 5: Fail in guild + } elsif ($args->{result} == 5) { + error TF("To rename a character you must withdraw from the guild.\n"); + # 5: Fail in party + } elsif ($args->{result} == 6) { + error TF("To rename a character you must withdraw from the party.\n"); + # 9: Invalid name + } elsif ($args->{result} == 9) { + error TF("Invalid new name \"%s\".\n", $AI::temp::charRenameName); + } else { + error TF("Unknown rename result occured:%d\n", $args->{result}); + } + + debug "char_rename_result result:$args->{result}\n"; + + undef $AI::temp::charRenameIndex; + undef $AI::temp::charRenameName; + + if (charSelectScreen() == 1) { + $net->setState(3); + $firstLoginMap = 1; + $startingzeny = $chars[$config{'char'}]{'zeny'} unless defined $startingzeny; + $sentWelcomeMessage = 1; + } +} + 1; diff --git a/src/Network/Receive/ServerType0.pm b/src/Network/Receive/ServerType0.pm index fcb5abe4f2..632e521864 100644 --- a/src/Network/Receive/ServerType0.pm +++ b/src/Network/Receive/ServerType0.pm @@ -535,6 +535,9 @@ sub new { '08CD' => ['actor_movement_interrupted', 'a4 v2', [qw(ID x y)]], '08CF' => ['revolving_entity', 'a4 v v', [qw(sourceID type entity)]], '08D2' => ['high_jump', 'a4 v2', [qw(ID x y)]], + '08D5' => ['char_move_slot_reply', 'v3', [qw(unknown reply moveCount)]], + '08E3' => ['char_renamed', 'a*', [qw(charInfo)]], + '08FD' => ['char_rename_result', 'v x', [qw(result)]], '08FF' => ['actor_status_active', 'a4 v V4', [qw(ID type tick unknown1 unknown2 unknown3)]], '0900' => ['inventory_items_stackable', 'v a*', [qw(len itemInfo)]], '0901' => ['inventory_items_nonstackable', 'v a*', [qw(len itemInfo)]], diff --git a/src/Network/Receive/kRO/Sakexe_0.pm b/src/Network/Receive/kRO/Sakexe_0.pm index 5bbe56d4c4..2a61d6c7f4 100644 --- a/src/Network/Receive/kRO/Sakexe_0.pm +++ b/src/Network/Receive/kRO/Sakexe_0.pm @@ -535,6 +535,9 @@ sub new { '08D2' => ['high_jump', 'a4 v2', [qw(ID x y)]], # 10 '08B9' => ['login_pin_code_request', 'V a4 v', [qw(seed accountID flag)]], '08C8' => ['actor_action', 'a4 a4 a4 V3 x v C V', [qw(sourceID targetID tick src_speed dst_speed damage div type dual_wield_damage)]], + '08D5' => ['char_move_slot_reply', 'v3', [qw(unknown reply moveCount)]], + '08E3' => ['char_renamed', 'a*', [qw(charInfo)]], + '08FD' => ['char_rename_result', 'v x', [qw(result)]], '0906' => ['show_eq', 'v Z24 x17 a*', [qw(len name equips_info)]], '0908' => ['inventory_item_favorite', 'a2 C', [qw(ID flag)]],#5 '090F' => ['actor_connected', 'v C a4 v3 V v11 a4 a2 v V C2 a3 C2 v2 a9 Z*', [qw(len object_type ID walk_speed opt1 opt2 option type hair_style weapon shield lowhead tophead midhead hair_color clothes_color head_dir costume guildID emblemID manner opt3 stance sex coords xSize ySize lv font opt4 name)]], diff --git a/src/Network/Send.pm b/src/Network/Send.pm index b533567aed..74380d870a 100644 --- a/src/Network/Send.pm +++ b/src/Network/Send.pm @@ -3076,4 +3076,45 @@ sub sendStopSkillUse { $self->sendToServer($self->reconstruct({switch => 'stop_skill_use',skillID => $ID})); debug "Stop Skill Use: $ID\n", "sendPacket", 2; } + +## +# Move character slot +# 08D4 .W .W .W +# @author [Cydh] +## +sub sendCharMoveSlot { + my ($self, $from, $to, $count) = @_; + $self->sendToServer($self->reconstruct({ + switch => 'char_move_slot', + fromSlot => $from, + toSlot => $to, + movesCount => $count + })); +} + +sub reconstruct_char_move_slot { + my ($self, $args) = @_; + debug "Move character slot from $args->{from} to $args->{to}. Move chance $args->{movesCount}\n", "sendPacket"; +} + +## +# Request to rename char +# 08FC .L .24B +# @author [Cydh] +## +sub sendCharRename { + my ($self, $accID, $charID, $newName) = @_; + $self->sendToServer($self->reconstruct({ + switch => 'char_rename', + accountID => $accID, + charID => $charID, + newName => $newName + })); +} + +sub reconstruct_char_rename { + my ($self, $args) = @_; + debug "Renaming character to $args->{newName} CID:".unpack("V",$args->{charID})."/AID:".unpack("V",$args->{accountID})."\n", "sendPacket"; +} + 1; diff --git a/src/Network/Send/ServerType0.pm b/src/Network/Send/ServerType0.pm index 22f546ea61..0b985b3e18 100644 --- a/src/Network/Send/ServerType0.pm +++ b/src/Network/Send/ServerType0.pm @@ -260,6 +260,8 @@ sub new { '08C1' => ['macro_start'],#2 '08C2' => ['macro_stop'],#2 '08C9' => ['request_cashitems'],#2 + '08D4' => ['char_move_slot','v3', [qw(fromSlot toSlot movesCount)]], + '08FC' => ['char_rename', 'a4 a24', [qw(charID newName)]], '0970' => ['char_create', 'a24 C v2', [qw(name slot hair_style hair_color)]], '097C' => ['rank_general', 'v', [qw(type)]], '0987' => ['master_login', 'V Z24 a32 C', [qw(version username password_md5_hex master_version)]], diff --git a/src/Network/Send/kRO/Sakexe_0.pm b/src/Network/Send/kRO/Sakexe_0.pm index c345d9193b..c9dbdb1a56 100644 --- a/src/Network/Send/kRO/Sakexe_0.pm +++ b/src/Network/Send/kRO/Sakexe_0.pm @@ -220,6 +220,8 @@ sub new { '08B8' => ['send_pin_password','a4 Z*', [qw(accountID pin)]], '08C1' => ['macro_start'],#2 '08C2' => ['macro_stop'],#2 + '08D4' => ['char_move_slot','v3', [qw(fromSlot toSlot movesCount)]], + '08FC' => ['char_rename', 'a4 a24', [qw(charID newName)]], '097C' => ['rank_general', 'v', [qw(type)]], '098D' => ['clan_chat', 'v Z*', [qw(len message)]], '09E9' => ['rodex_close_mailbox'], # 2 -- RodexCloseMailbox diff --git a/tables/packetdescriptions.txt b/tables/packetdescriptions.txt index 2a41bc8859..2982bda715 100644 --- a/tables/packetdescriptions.txt +++ b/tables/packetdescriptions.txt @@ -227,6 +227,9 @@ 02AE Initialize Message ID Encryption 0A3B Hat Effect Info 07E3 Skill Exchange Item +08D5 Char Move Slot Reply +08E3 Char Renamed +08FD Char Rename Result [Send] 0064 Account Server Login @@ -340,3 +343,5 @@ 098F Char Delete Accept 082B Char Delete Cancel 07E4 Item List Window Selected +08D4 Char Move Slot +08FC Char Rename