=head1 NAME Crossfire::Protocol - client protocol module =head1 SYNOPSIS use base Crossfire::Protocol; # you have to subclass =head1 DESCRIPTION Base class to implement a corssfire client. =over 4 =cut package Crossfire::Protocol; our $VERSION = '0.1'; use strict; use AnyEvent; use IO::Socket::INET; =item new Crossfire::Rptocol host => ..., port => ... =cut sub new { my $class = shift; my $self = bless { @_ }, $class; $self->{fh} = new IO::Socket::INET PeerHost => $self->{host}, PeerPort => $self->{port} or die "$self->{host}:$self->{port}: $!"; $self->{fh}->blocking (0); # stupid nonblock default my $buf; $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub { if (sysread $self->{fh}, $buf, 16384, length $buf) { if (2 <= length $buf) { my $len = unpack "n", $buf; if ($len + 2 <= length $buf) { substr $buf, 0, 2, ""; $self->feed (substr $buf, 0, $len, ""); } } } else { delete $self->{w}; close $self->{fh}; } }); $self->send ("version 1023 1027 perlclient"); $self->send ("setup sound 1 exp 1 map1acmd 1 itemcmd 2 darkness 1 mapsize 63x63 newmapcmd 1 facecache 1 extendedMapInfos 1 extendedTextInfos 1"); $self->send ("addme"); $self } sub feed { my ($self, $data) = @_; $data =~ s/^(\S+)\s// or return; my $command = "feed_$1"; $self->$command ($data); } sub feed_version { my ($self, $version) = @_; } sub feed_setup { my ($self, $data) = @_; $data =~ s/^ +//; $self->{setup} = { split / +/, $data }; ($self->{mapw}, $self->{maph}) = split /x/, $self->{setup}{mapsize}; $self->feed_newmap; } sub feed_query { my ($self, $data) = @_; warn "Q<$data>\n"; } sub feed_stats { my ($self, $data) = @_; # warn "S<$data>\n"; } sub feed_face1 { my ($self, $data) = @_; my ($num, $chksum, $name) = unpack "nNa*", $data; $self->{face}[$num] = { name => $name, chksum => $chksum }; } sub feed_drawinfo { my ($self, $data) = @_; # warn "<$data>\n"; } sub feed_delinv { my ($self, $data) = @_; } sub feed_item2 { my ($self, $data) = @_; } sub feed_map1a { my ($self, $data) = @_; my $map = $self->{map} ||= []; my @dirty; my ($coord, $x, $y, $darkness, $fa, $fb, $fc, $cell); while (length $data) { $coord = unpack "n", substr $data, 0, 2, ""; $x = ($coord >> 10) & 63; $y = ($coord >> 4) & 63; $cell = $map->[$x][$y] ||= []; $cell->[3] = unpack "C", substr $data, 0, 1, "" if $coord & 8; $cell->[0] = unpack "n", substr $data, 0, 2, "" if $coord & 4; $cell->[1] = unpack "n", substr $data, 0, 2, "" if $coord & 2; $cell->[2] = unpack "n", substr $data, 0, 2, "" if $coord & 1; @$cell = () unless $coord & 15; push @dirty, [$x, $y]; } $self->map_update (\@dirty); } sub feed_map_scroll { my ($self, $data) = @_; my ($dx, $dy) = split / /, $data; my $map = $self->{map} ||= []; $self->{mapx} += $dx; $self->{mapy} += $dy; if ($dx > 0) { unshift @$_, ([]) x $dx for @$map; } elsif ($dx < 0) { splice @$_, 0, -$dx, () for @$map; } if ($dy > 0) { unshift @$map, ([]) x $dy; } elsif ($dy < 0) { splice @$map, 0, -$dy, (); } $self->map_scroll ($dx, $dy); } sub feed_newmap { my ($self) = @_; $self->{map} = []; $self->{mapx} = 0; $self->{mapy} = 0; $self->map_clear; } sub feed_image { my ($self, $data) = @_; my ($face, $len, $data) = unpack "NNa*", $data; $self->{face}[$face]{image} = $data; $self->face_update ($face, $self->{face}[$face]); for my $x (0..$self->{mapw} - 1) { for my $y (0..$self->{maph} - 1) { push @dirty, [$x, $y] if grep $_ == $num, @{$self->{map}[$x][$y] || []}; } } $self->map_update (\@dirty); } =item $conn->map_clear [OVERWRITE] Called whenever the map is to be erased completely. =cut sub map_clear { } =item $conn->map_update ([ [x,y], [x,y], ...]) [OVERWRITE] Called with a list of x|y coordinate pairs (as arrayrefs) for cells that have been updated and need refreshing. =cut sub map_update { } =item $conn->map_scroll ($dx, $dy) [OVERWRITE] Called whenever the map has been scrolled. =cut sub map_scroll { } =item $conn->face_update ($facenum, $face) [OVERWRITE] Called with the face number of face structure whenever a face image has changed. =cut sub face_update { } =item $conn->send ($data) Send a single packet/line to the server. =cut sub send { my ($self, $data) = @_; $data = pack "na*", length $data, $data; syswrite $self->{fh}, $data; } =back =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ Robin Redeker http://www.ta-sa.org/ =cut 1