Moving Network::Receive packet handling to hooks

Forum closed. All further discussion to be discussed at https://github.com/OpenKore/

Moderator: Moderators

Message
Author
EternalHarvest
Developers
Developers
Posts: 1798
Joined: 05 Dec 2008, 05:42
Noob?: Yes

Moving Network::Receive packet handling to hooks

#1 Post by EternalHarvest »

Tested with XKore 0, seems to be working.

What needed: to determine the right moment when to remove packet hooks and to store them in $self.

What follows: move all specific packet handlers away from Network, and split them to modules based on their job (connection, party, guild, deal etc) in process.

Code: Select all

Index: src/Network/Receive.pm
===================================================================
--- src/Network/Receive.pm	(revision 7093)
+++ src/Network/Receive.pm	(working copy)
@@ -87,9 +87,34 @@
 			TF("An error occured while loading the server message parser for server type '%s':\n%s",
 				$type, $@)
 		);
+	}
+	
+	my $self = $class->new;
+	
+	# hook all handlers from packet_list
+	
+	my @handlers = grep { $self->can ($_) } keys %{{map { $_->[0] => 1 } values %{$self->{packet_list}}}};
+	
+	if (@handlers) {
+		debug TF("Adding hooks for handlers from %s: %s\n", $class, join ', ', @handlers);
+		
+		# this check is only good for first run
+		# TODO: move delHooks to a more suitable place, do not use global vars
+		Plugins::delHooks ($Globals::recvpacketHandleHooks) if $Globals::recvpacketHandleHooks;
+		
+		$Globals::recvpacketHandleHooks = Plugins::addHooks (
+			map { ["packet_handle/$_", sub {
+				my (undef, $args, $callback) = @_;
+				
+				$self->$callback ($args);
+				$args->{handleReturn} = 1;
+			}, $_] } @handlers
+		);
 	} else {
-		return $class->new();
+		debug T("No suitable handlers found in %s\n", $class);
 	}
+	
+	return $self;
 }
 
 # $packetParser->reconstruct($args)
@@ -138,6 +163,17 @@
 		}
 	}
 
+	Plugins::callHook("packet_pre/$handler->[0]", \%args);
+	Misc::checkValidity("Packet: " . $handler->[0] . " (pre)");
+	unless ($args{handleReturn}) {
+		Plugins::callHook("packet_handle/$handler->[0]", \%args);
+		Misc::checkValidity("Packet: " . $handler->[0]);
+		unless ($args{handleReturn}) {
+			warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
+			debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
+		}
+	}
+=pod
 	my $callback = $self->can($handler->[0]);
 	if ($callback) {
 		Plugins::callHook("packet_pre/$handler->[0]", \%args);
@@ -148,6 +184,7 @@
 		warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
 		debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
 	}
+=cut
 
 	Plugins::callHook("packet/$handler->[0]", \%args);
 	return \%args;

EternalHarvest
Developers
Developers
Posts: 1798
Joined: 05 Dec 2008, 05:42
Noob?: Yes

Re: Moving Network::Receive packet handling to hooks

#2 Post by EternalHarvest »

Update.
Also, includes example of separated module (with stuff related to deal) - to load it, run with --plugins=plugins:src/ext

Code: Select all

Index: src/ext/deal/deal.pl
===================================================================
--- src/ext/deal/deal.pl	(revision 0)
+++ src/ext/deal/deal.pl	(revision 0)
@@ -0,0 +1,336 @@
+package deal;
+use strict;
+
+use Carp::Assert;
+
+use I18N qw/bytesToString/;
+use Log qw/message error/;
+use Misc qw/itemName/;
+use Translation qw/T TF/;
+use Utils qw/formatNumber/;
+
+use Globals qw/$net $messageSender $char $playersList @playersID %timeout %items_lut %itemChange/;
+
+our %currentDeal;
+our %incomingDeal;
+our %outgoingDeal;
+
+Plugins::register 'deal', 'OpenKore deal extension', \&unload;
+
+my $hooks = Plugins::addHooks (
+	['packet_handle/deal_request', \&deal_request],
+	['packet_handle/deal_begin', \&deal_begin],
+	['packet_handle/deal_add_other', \&deal_add_other],
+	['packet_handle/deal_add_you', \&deal_add_you],
+	['packet_handle/deal_finalize', \&deal_finalize],
+	['packet_handle/deal_cancelled', \&deal_cancelled],
+	['packet_handle/deal_complete', \&deal_complete],
+	['packet_handle/deal_undo', \&deal_undo],
+);
+
+my $commands = Commands::register (
+	['deal', '', \&cmdDeal],
+	['dl', '', \&cmdDealList],
+);
+
+sub unload {
+	Plugins::delHooks ($hooks);
+	Commands::unregister ($commands);
+}
+
+# misc
+
+##
+# void deal(Actor::Player player)
+# Requires: defined($player)
+# Ensures: exists $outgoingDeal{ID}
+#
+# Sends $player a deal request.
+sub deal {
+	my $player = $_[0];
+	assert(defined $player) if DEBUG;
+	assert(UNIVERSAL::isa($player, 'Actor::Player')) if DEBUG;
+
+	$outgoingDeal{ID} = $player->{ID};
+	$messageSender->sendDeal($player->{ID});
+}
+
+##
+# dealAddItem($item, $amount)
+#
+# Adds $amount of $item to the current deal.
+sub dealAddItem {
+	my ($item, $amount) = @_;
+
+	$messageSender->sendDealAddItem($item->{index}, $amount);
+	$currentDeal{lastItemAmount} = $amount;
+}
+
+# commands
+
+sub cmdDeal {
+	if (!$net || $net->getState() != Network::IN_GAME) {
+		error TF("You must be logged in the game to use this command (%s)\n", shift);
+		return;
+	}
+
+	my (undef, $args) = @_;
+	my @arg = split / /, $args;
+
+	if (%currentDeal && $arg[0] =~ /\d+/) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You are already in a deal\n");
+	} elsif (%incomingDeal && $arg[0] =~ /\d+/) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You must first cancel the incoming deal\n");
+	} elsif ($arg[0] =~ /\d+/ && !$playersID[$arg[0]]) {
+		error TF("Error in function 'deal' (Deal a Player)\n" .
+			"Player %s does not exist\n", $arg[0]);
+	} elsif ($arg[0] =~ /\d+/) {
+		my $ID = $playersID[$arg[0]];
+		my $player = Actor::get($ID);
+		message TF("Attempting to deal %s\n", $player);
+		deal($player);
+
+	} elsif ($arg[0] eq "no" && !%incomingDeal && !%outgoingDeal && !%currentDeal) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"There is no incoming/current deal to cancel\n");
+	} elsif ($arg[0] eq "no" && (%incomingDeal || %outgoingDeal)) {
+		$messageSender->sendDealReply(4);
+	} elsif ($arg[0] eq "no" && %currentDeal) {
+		$messageSender->sendCurrentDealCancel();
+
+	} elsif ($arg[0] eq "" && !%incomingDeal && !%currentDeal) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"There is no deal to accept\n");
+	} elsif ($arg[0] eq "" && $currentDeal{'you_finalize'} && !$currentDeal{'other_finalize'}) {
+		error TF("Error in function 'deal' (Deal a Player)\n" .
+			"Cannot make the trade - %s has not finalized\n", $currentDeal{'name'});
+	} elsif ($arg[0] eq "" && $currentDeal{'final'}) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You already accepted the final deal\n");
+	} elsif ($arg[0] eq "" && %incomingDeal) {
+		$messageSender->sendDealReply(3);
+	} elsif ($arg[0] eq "" && $currentDeal{'you_finalize'} && $currentDeal{'other_finalize'}) {
+		$messageSender->sendDealTrade();
+		$currentDeal{'final'} = 1;
+		message T("You accepted the final Deal\n"), "deal";
+	} elsif ($arg[0] eq "" && %currentDeal) {
+		$messageSender->sendDealAddItem(0, $currentDeal{'you_zeny'});
+		$messageSender->sendDealFinalize();
+
+	} elsif ($arg[0] eq "add" && !%currentDeal) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"No deal in progress\n");
+	} elsif ($arg[0] eq "add" && $currentDeal{'you_finalize'}) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Can't add any Items - You already finalized the deal\n");
+	} elsif ($arg[0] eq "add" && $arg[1] =~ /\d+/ && !$char->inventory->get($arg[1])) {
+		error TF("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Inventory Item %s does not exist.\n", $arg[1]);
+	} elsif ($arg[0] eq "add" && $arg[2] && $arg[2] !~ /\d+/) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Amount must either be a number, or not specified.\n");
+	} elsif ($arg[0] eq "add" && $arg[1] =~ /\d+/) {
+		if ($currentDeal{you_items} < 10) {
+			my $item = $char->inventory->get($arg[1]);
+			my $amount = $item->{amount};
+			if (!$arg[2] || $arg[2] > $amount) {
+				$arg[2] = $amount;
+			}
+			dealAddItem($item, $arg[2]);
+		} else {
+			error T("You can't add any more items to the deal\n"), "deal";
+		}
+	} elsif ($arg[0] eq "add" && $arg[1] eq "z") {
+		if (!$arg[2] && !($arg[2] eq "0") || $arg[2] > $char->{'zeny'}) {
+			$arg[2] = $char->{'zeny'};
+		}
+		$currentDeal{'you_zeny'} = $arg[2];
+		message TF("You put forward %sz to Deal\n", formatNumber($arg[2])), "deal";
+
+	} else {
+		error T("Syntax Error in function 'deal' (Deal a player)\n" .
+			"Usage: deal [<Player # | no | add>] [<item #>] [<amount>]\n");
+	}
+}
+
+sub cmdDealList {
+	if (!%currentDeal) {
+		error T("There is no deal list - You are not in a deal\n");
+
+	} else {
+		message T("-----------Current Deal-----------\n"), "list";
+		my $other_string = $currentDeal{'name'};
+		my $you_string = "You";
+		if ($currentDeal{'other_finalize'}) {
+			$other_string .= " - Finalized";
+		}
+		if ($currentDeal{'you_finalize'}) {
+			$you_string .= " - Finalized";
+		}
+
+		message(swrite(
+			"@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+			[$you_string, $other_string]),
+			"list");
+
+		my @currentDealYou;
+		my @currentDealOther;
+		foreach (keys %{$currentDeal{'you'}}) {
+			push @currentDealYou, $_;
+		}
+		foreach (keys %{$currentDeal{'other'}}) {
+			push @currentDealOther, $_;
+		}
+
+		my ($lastindex, $display, $display2);
+		$lastindex = @currentDealOther;
+		$lastindex = @currentDealYou if (@currentDealYou > $lastindex);
+		for (my $i = 0; $i < $lastindex; $i++) {
+			if ($i < @currentDealYou) {
+				$display = ($items_lut{$currentDealYou[$i]} ne "")
+					? $items_lut{$currentDealYou[$i]}
+					: "Unknown ".$currentDealYou[$i];
+				$display .= " x $currentDeal{'you'}{$currentDealYou[$i]}{'amount'}";
+			} else {
+				$display = "";
+			}
+			if ($i < @currentDealOther) {
+				$display2 = ($items_lut{$currentDealOther[$i]} ne "")
+					? $items_lut{$currentDealOther[$i]}
+					: "Unknown ".$currentDealOther[$i];
+				$display2 .= " x $currentDeal{'other'}{$currentDealOther[$i]}{'amount'}";
+			} else {
+				$display2 = "";
+			}
+
+			message(swrite(
+				"@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+				[$display, $display2]),
+				"list");
+		}
+		$you_string = ($currentDeal{'you_zeny'} ne "") ? $currentDeal{'you_zeny'} : 0;
+		$other_string = ($currentDeal{'other_zeny'} ne "") ? $currentDeal{'other_zeny'} : 0;
+
+		message TF("zeny: %-25s zeny: %-14s", 
+			formatNumber($you_string), formatNumber($other_string)), "list";
+		message("----------------------------------\n", "list");
+	}
+}
+
+# receive handlers (from kRO ST)
+
+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 %s\n", $currentDeal{name}, $item->{name}, $args->{amount}), "deal";
+	} elsif ($args->{amount} > 0) {
+		$currentDeal{other_zeny} += $args->{amount};
+		my $amount = formatNumber($args->{amount});
+		message TF("%s added %s z to Deal\n", $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 %s\n", $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 %s\n", $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 Cancelled\n"), "deal";
+}
+
+sub deal_complete {
+	undef %outgoingDeal;
+	undef %incomingDeal;
+	undef %currentDeal;
+	message T("Deal Complete\n"), "deal";
+}
+
+sub deal_finalize {
+	my ($self, $args) = @_;
+	if ($args->{type} == 1) {
+		$currentDeal{other_finalize} = 1;
+		message TF("%s finalized the Deal\n", $currentDeal{name}), "deal";
+
+	} else {
+		$currentDeal{you_finalize} = 1;
+		# FIXME: shouldn't we do this when we actually complete the deal?
+		$char->{zeny} -= $currentDeal{you_zeny};
+		message T("You finalized the Deal\n"), "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 Deal\n", $user, $level), "deal";
+	message T("Type 'deal' to start dealing, or 'deal no' to deny the deal.\n"), "deal";
+}
Index: src/Commands.pm
===================================================================
--- src/Commands.pm	(revision 7095)
+++ src/Commands.pm	(working copy)
@@ -79,9 +79,9 @@
 	conf               => \&cmdConf,
 	connect            => \&cmdConnect,
 	damage             => \&cmdDamage,
-	deal               => \&cmdDeal,
+	#deal               => \&cmdDeal,
 	debug              => \&cmdDebug,
-	dl                 => \&cmdDealList,
+	#dl                 => \&cmdDealList,
 	doridori           => \&cmdDoriDori,
 	drop               => \&cmdDrop,
 	dump               => \&cmdDump,
@@ -1245,6 +1245,7 @@
 	}
 }
 
+=pod
 sub cmdDeal {
 	if (!$net || $net->getState() != Network::IN_GAME) {
 		error TF("You must be logged in the game to use this command (%s)\n", shift);
@@ -1395,6 +1396,7 @@
 		message("----------------------------------\n", "list");
 	}
 }
+=cut
 
 sub cmdDebug {
 	my (undef, $args) = @_;
Index: src/Network/Receive.pm
===================================================================
--- src/Network/Receive.pm	(revision 7095)
+++ src/Network/Receive.pm	(working copy)
@@ -87,9 +87,34 @@
 			TF("An error occured while loading the server message parser for server type '%s':\n%s",
 				$type, $@)
 		);
+	}
+	
+	my $self = $class->new;
+	
+	# hook all handlers from packet_list
+	
+	my @handlers = grep { $self->can ($_) && !Plugins::hasHook ("packet_handle/$_") } keys %{{map { $_->[0] => 1 } values %{$self->{packet_list}}}};
+	
+	if (@handlers) {
+		debug TF("Adding hooks for handlers from %s: %s\n", $class, join ', ', @handlers);
+		
+		# this check is only good for first run
+		# TODO: move delHooks to a more suitable place, do not use global vars
+		Plugins::delHooks ($Globals::recvpacketHandleHooks) if $Globals::recvpacketHandleHooks;
+		
+		$Globals::recvpacketHandleHooks = Plugins::addHooks (
+			map { ["packet_handle/$_", sub {
+				my (undef, $args, $callback) = @_;
+				
+				$self->$callback ($args);
+				$args->{return} = 1;
+			}, $_] } @handlers
+		);
 	} else {
-		return $class->new();
+		debug T("No suitable handlers found in %s\n", $class);
 	}
+	
+	return $self;
 }
 
 # $packetParser->reconstruct($args)
@@ -138,6 +163,18 @@
 		}
 	}
 
+	Plugins::callHook("packet_pre/$handler->[0]", \%args);
+	Misc::checkValidity("Packet: " . $handler->[0] . " (pre)");
+	unless ($args{return}) {
+		if (Plugins::hasHook("packet_handle/$handler->[0]", \%args)) {
+			Plugins::callHook("packet_handle/$handler->[0]", \%args);
+			Misc::checkValidity("Packet: " . $handler->[0]);
+		} else {
+			warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
+			debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
+		}
+	}
+=pod
 	my $callback = $self->can($handler->[0]);
 	if ($callback) {
 		Plugins::callHook("packet_pre/$handler->[0]", \%args);
@@ -148,6 +185,7 @@
 		warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
 		debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
 	}
+=cut
 
 	Plugins::callHook("packet/$handler->[0]", \%args);
 	return \%args;

EternalHarvest
Developers
Developers
Posts: 1798
Joined: 05 Dec 2008, 05:42
Noob?: Yes

Re: Moving Network::Receive packet handling to hooks

#3 Post by EternalHarvest »

Update.
Moved hooks to DESTROY.
But Receive object currently created new, and only then deleted old one. Easy to make it another way. In this patch checks for hasHook are disabled, so handlers from deal.pm need to be removed from Receive to work correctly.

Code: Select all

Index: ext/deal/deal.pl
===================================================================
--- ext/deal/deal.pl	(revision 0)
+++ ext/deal/deal.pl	(revision 0)
@@ -0,0 +1,336 @@
+package deal;
+use strict;
+return 1;
+use Carp::Assert;
+
+use I18N qw/bytesToString/;
+use Log qw/message error/;
+use Misc qw/itemName/;
+use Translation qw/T TF/;
+use Utils qw/formatNumber/;
+
+use Globals qw/$net $messageSender $char $playersList @playersID %timeout %items_lut %itemChange/;
+
+our %currentDeal;
+our %incomingDeal;
+our %outgoingDeal;
+
+Plugins::register 'deal', 'OpenKore deal extension', \&unload;
+
+my $hooks = Plugins::addHooks (
+	['packet_handle/deal_request', \&deal_request],
+	['packet_handle/deal_begin', \&deal_begin],
+	['packet_handle/deal_add_other', \&deal_add_other],
+	['packet_handle/deal_add_you', \&deal_add_you],
+	['packet_handle/deal_finalize', \&deal_finalize],
+	['packet_handle/deal_cancelled', \&deal_cancelled],
+	['packet_handle/deal_complete', \&deal_complete],
+	['packet_handle/deal_undo', \&deal_undo],
+);
+
+my $commands = Commands::register (
+	['deal', '', \&cmdDeal],
+	['dl', '', \&cmdDealList],
+);
+
+sub unload {
+	Plugins::delHooks ($hooks);
+	Commands::unregister ($commands);
+}
+
+# misc
+
+##
+# void deal(Actor::Player player)
+# Requires: defined($player)
+# Ensures: exists $outgoingDeal{ID}
+#
+# Sends $player a deal request.
+sub deal {
+	my $player = $_[0];
+	assert(defined $player) if DEBUG;
+	assert(UNIVERSAL::isa($player, 'Actor::Player')) if DEBUG;
+
+	$outgoingDeal{ID} = $player->{ID};
+	$messageSender->sendDeal($player->{ID});
+}
+
+##
+# dealAddItem($item, $amount)
+#
+# Adds $amount of $item to the current deal.
+sub dealAddItem {
+	my ($item, $amount) = @_;
+
+	$messageSender->sendDealAddItem($item->{index}, $amount);
+	$currentDeal{lastItemAmount} = $amount;
+}
+
+# commands
+
+sub cmdDeal {
+	if (!$net || $net->getState() != Network::IN_GAME) {
+		error TF("You must be logged in the game to use this command (%s)\n", shift);
+		return;
+	}
+
+	my (undef, $args) = @_;
+	my @arg = split / /, $args;
+
+	if (%currentDeal && $arg[0] =~ /\d+/) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You are already in a deal\n");
+	} elsif (%incomingDeal && $arg[0] =~ /\d+/) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You must first cancel the incoming deal\n");
+	} elsif ($arg[0] =~ /\d+/ && !$playersID[$arg[0]]) {
+		error TF("Error in function 'deal' (Deal a Player)\n" .
+			"Player %s does not exist\n", $arg[0]);
+	} elsif ($arg[0] =~ /\d+/) {
+		my $ID = $playersID[$arg[0]];
+		my $player = Actor::get($ID);
+		message TF("Attempting to deal %s\n", $player);
+		deal($player);
+
+	} elsif ($arg[0] eq "no" && !%incomingDeal && !%outgoingDeal && !%currentDeal) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"There is no incoming/current deal to cancel\n");
+	} elsif ($arg[0] eq "no" && (%incomingDeal || %outgoingDeal)) {
+		$messageSender->sendDealReply(4);
+	} elsif ($arg[0] eq "no" && %currentDeal) {
+		$messageSender->sendCurrentDealCancel();
+
+	} elsif ($arg[0] eq "" && !%incomingDeal && !%currentDeal) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"There is no deal to accept\n");
+	} elsif ($arg[0] eq "" && $currentDeal{'you_finalize'} && !$currentDeal{'other_finalize'}) {
+		error TF("Error in function 'deal' (Deal a Player)\n" .
+			"Cannot make the trade - %s has not finalized\n", $currentDeal{'name'});
+	} elsif ($arg[0] eq "" && $currentDeal{'final'}) {
+		error T("Error in function 'deal' (Deal a Player)\n" .
+			"You already accepted the final deal\n");
+	} elsif ($arg[0] eq "" && %incomingDeal) {
+		$messageSender->sendDealReply(3);
+	} elsif ($arg[0] eq "" && $currentDeal{'you_finalize'} && $currentDeal{'other_finalize'}) {
+		$messageSender->sendDealTrade();
+		$currentDeal{'final'} = 1;
+		message T("You accepted the final Deal\n"), "deal";
+	} elsif ($arg[0] eq "" && %currentDeal) {
+		$messageSender->sendDealAddItem(0, $currentDeal{'you_zeny'});
+		$messageSender->sendDealFinalize();
+
+	} elsif ($arg[0] eq "add" && !%currentDeal) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"No deal in progress\n");
+	} elsif ($arg[0] eq "add" && $currentDeal{'you_finalize'}) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Can't add any Items - You already finalized the deal\n");
+	} elsif ($arg[0] eq "add" && $arg[1] =~ /\d+/ && !$char->inventory->get($arg[1])) {
+		error TF("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Inventory Item %s does not exist.\n", $arg[1]);
+	} elsif ($arg[0] eq "add" && $arg[2] && $arg[2] !~ /\d+/) {
+		error T("Error in function 'deal_add' (Add Item to Deal)\n" .
+			"Amount must either be a number, or not specified.\n");
+	} elsif ($arg[0] eq "add" && $arg[1] =~ /\d+/) {
+		if ($currentDeal{you_items} < 10) {
+			my $item = $char->inventory->get($arg[1]);
+			my $amount = $item->{amount};
+			if (!$arg[2] || $arg[2] > $amount) {
+				$arg[2] = $amount;
+			}
+			dealAddItem($item, $arg[2]);
+		} else {
+			error T("You can't add any more items to the deal\n"), "deal";
+		}
+	} elsif ($arg[0] eq "add" && $arg[1] eq "z") {
+		if (!$arg[2] && !($arg[2] eq "0") || $arg[2] > $char->{'zeny'}) {
+			$arg[2] = $char->{'zeny'};
+		}
+		$currentDeal{'you_zeny'} = $arg[2];
+		message TF("You put forward %sz to Deal\n", formatNumber($arg[2])), "deal";
+
+	} else {
+		error T("Syntax Error in function 'deal' (Deal a player)\n" .
+			"Usage: deal [<Player # | no | add>] [<item #>] [<amount>]\n");
+	}
+}
+
+sub cmdDealList {
+	if (!%currentDeal) {
+		error T("There is no deal list - You are not in a deal\n");
+
+	} else {
+		message T("-----------Current Deal-----------\n"), "list";
+		my $other_string = $currentDeal{'name'};
+		my $you_string = "You";
+		if ($currentDeal{'other_finalize'}) {
+			$other_string .= " - Finalized";
+		}
+		if ($currentDeal{'you_finalize'}) {
+			$you_string .= " - Finalized";
+		}
+
+		message(swrite(
+			"@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+			[$you_string, $other_string]),
+			"list");
+
+		my @currentDealYou;
+		my @currentDealOther;
+		foreach (keys %{$currentDeal{'you'}}) {
+			push @currentDealYou, $_;
+		}
+		foreach (keys %{$currentDeal{'other'}}) {
+			push @currentDealOther, $_;
+		}
+
+		my ($lastindex, $display, $display2);
+		$lastindex = @currentDealOther;
+		$lastindex = @currentDealYou if (@currentDealYou > $lastindex);
+		for (my $i = 0; $i < $lastindex; $i++) {
+			if ($i < @currentDealYou) {
+				$display = ($items_lut{$currentDealYou[$i]} ne "")
+					? $items_lut{$currentDealYou[$i]}
+					: "Unknown ".$currentDealYou[$i];
+				$display .= " x $currentDeal{'you'}{$currentDealYou[$i]}{'amount'}";
+			} else {
+				$display = "";
+			}
+			if ($i < @currentDealOther) {
+				$display2 = ($items_lut{$currentDealOther[$i]} ne "")
+					? $items_lut{$currentDealOther[$i]}
+					: "Unknown ".$currentDealOther[$i];
+				$display2 .= " x $currentDeal{'other'}{$currentDealOther[$i]}{'amount'}";
+			} else {
+				$display2 = "";
+			}
+
+			message(swrite(
+				"@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<",
+				[$display, $display2]),
+				"list");
+		}
+		$you_string = ($currentDeal{'you_zeny'} ne "") ? $currentDeal{'you_zeny'} : 0;
+		$other_string = ($currentDeal{'other_zeny'} ne "") ? $currentDeal{'other_zeny'} : 0;
+
+		message TF("zeny: %-25s zeny: %-14s", 
+			formatNumber($you_string), formatNumber($other_string)), "list";
+		message("----------------------------------\n", "list");
+	}
+}
+
+# receive handlers (from kRO ST)
+
+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 %s\n", $currentDeal{name}, $item->{name}, $args->{amount}), "deal";
+	} elsif ($args->{amount} > 0) {
+		$currentDeal{other_zeny} += $args->{amount};
+		my $amount = formatNumber($args->{amount});
+		message TF("%s added %s z to Deal\n", $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 %s\n", $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 %s\n", $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 Cancelled\n"), "deal";
+}
+
+sub deal_complete {
+	undef %outgoingDeal;
+	undef %incomingDeal;
+	undef %currentDeal;
+	message T("Deal Complete\n"), "deal";
+}
+
+sub deal_finalize {
+	my ($self, $args) = @_;
+	if ($args->{type} == 1) {
+		$currentDeal{other_finalize} = 1;
+		message TF("%s finalized the Deal\n", $currentDeal{name}), "deal";
+
+	} else {
+		$currentDeal{you_finalize} = 1;
+		# FIXME: shouldn't we do this when we actually complete the deal?
+		$char->{zeny} -= $currentDeal{you_zeny};
+		message T("You finalized the Deal\n"), "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 Deal\n", $user, $level), "deal";
+	message T("Type 'deal' to start dealing, or 'deal no' to deny the deal.\n"), "deal";
+}
Index: Commands.pm
===================================================================
--- Commands.pm	(revision 7095)
+++ Commands.pm	(working copy)
@@ -79,9 +79,9 @@
 	conf               => \&cmdConf,
 	connect            => \&cmdConnect,
 	damage             => \&cmdDamage,
-	deal               => \&cmdDeal,
+	#deal               => \&cmdDeal,
 	debug              => \&cmdDebug,
-	dl                 => \&cmdDealList,
+	#dl                 => \&cmdDealList,
 	doridori           => \&cmdDoriDori,
 	drop               => \&cmdDrop,
 	dump               => \&cmdDump,
@@ -1245,6 +1245,7 @@
 	}
 }
 
+=pod
 sub cmdDeal {
 	if (!$net || $net->getState() != Network::IN_GAME) {
 		error TF("You must be logged in the game to use this command (%s)\n", shift);
@@ -1395,6 +1396,7 @@
 		message("----------------------------------\n", "list");
 	}
 }
+=cut
 
 sub cmdDebug {
 	my (undef, $args) = @_;
Index: Network/Receive.pm
===================================================================
--- Network/Receive.pm	(revision 7095)
+++ Network/Receive.pm	(working copy)
@@ -87,11 +87,43 @@
 			TF("An error occured while loading the server message parser for server type '%s':\n%s",
 				$type, $@)
 		);
-	} else {
-		return $class->new();
 	}
+	
+	my $self = $class->new;
+	
+	# hook all handlers from packet_list
+	
+	my @handlers = grep { $self->can ($_) } keys %{{map { $_->[0] => 1 } values %{$self->{packet_list}}}};
+	# && !Plugins::hasHook ("packet_handle/$_")
+	
+	if (@handlers) {
+		debug TF("Adding hooks for packet handlers in %s: %s\n", $class, join ', ', @handlers), 'network_compatibility';
+		
+		Scalar::Util::weaken (my $weakSelf = $self);
+		
+		my $handler = sub {
+			my (undef, $args, $callback) = @_;
+			
+			$weakSelf->$callback ($args);
+			$args->{return} = 1;
+		};
+		
+		$self->{recvpacketHandleHooks} = Plugins::addHooks (map { ["packet_handle/$_", $handler, $_] } @handlers);
+	}
+	
+	return $self;
 }
 
+sub DESTROY {
+	my ($self) = @_;
+	
+	if ($self->{recvpacketHandleHooks}) {
+		debug T("Removing hooks for packet handlers in Network::Receive\n"), 'network_compatibility';
+		
+		Plugins::delHooks ($self->{recvpacketHandleHooks});
+	}
+}
+
 # $packetParser->reconstruct($args)
 #
 # Reconstructs a raw packet from $args using $self->{packet_list}.
@@ -138,6 +170,18 @@
 		}
 	}
 
+	Plugins::callHook("packet_pre/$handler->[0]", \%args);
+	Misc::checkValidity("Packet: " . $handler->[0] . " (pre)");
+	unless ($args{return}) {
+		if (Plugins::hasHook("packet_handle/$handler->[0]", \%args)) {
+			Plugins::callHook("packet_handle/$handler->[0]", \%args);
+			Misc::checkValidity("Packet: " . $handler->[0]);
+		} else {
+			warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
+			debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
+		}
+	}
+=pod
 	my $callback = $self->can($handler->[0]);
 	if ($callback) {
 		Plugins::callHook("packet_pre/$handler->[0]", \%args);
@@ -148,6 +192,7 @@
 		warning "Packet Parser: Unhandled Packet: $switch Handler: $handler->[0]\n";
 		debug ("Unpacked: " . join(', ', @{\%args}{@{$handler->[2]}}) . "\n"), "packetParser", 2;
 	}
+=cut
 
 	Plugins::callHook("packet/$handler->[0]", \%args);
 	return \%args;

Locked