ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra/Deliantra/Protocol.pm
Revision: 1.14
Committed: Sun Apr 9 17:36:09 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.13: +171 -82 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 Crossfire::Protocol - client protocol module
4
5 =head1 SYNOPSIS
6
7 use base Crossfire::Protocol; # you have to subclass
8
9 =head1 DESCRIPTION
10
11 Base class to implement a corssfire client.
12
13 =over 4
14
15 =cut
16
17 package Crossfire::Protocol;
18
19 our $VERSION = '0.1';
20
21 use strict;
22
23 use AnyEvent;
24 use IO::Socket::INET;
25
26 =item new Crossfire::Protocol host => ..., port => ...
27
28 =cut
29
30 sub new {
31 my $class = shift;
32 my $self = bless {
33 mapw => 13,
34 maph => 13,
35 @_
36 }, $class;
37
38 $self->{fh} = new IO::Socket::INET PeerHost => $self->{host}, PeerPort => $self->{port}
39 or die "$self->{host}:$self->{port}: $!";
40 $self->{fh}->blocking (0); # stupid nonblock default
41
42 my $buf;
43
44 $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub {
45 if (sysread $self->{fh}, $buf, 16384, length $buf) {
46 for (;;) {
47 last unless 2 <= length $buf;
48 my $len = unpack "n", $buf;
49 last unless $len + 2 <= length $buf;
50
51 substr $buf, 0, 2, "";
52 $self->feed (substr $buf, 0, $len, "");
53 }
54 } else {
55 delete $self->{w};
56 close $self->{fh};
57 }
58 });
59
60 $self->{setup_req} = {
61 sound => 1,
62 exp64 => 1,
63 map1acmd => 1,
64 itemcmd => 2,
65 darkness => 1,
66 facecache => 1,
67 newmapcmd => 1,
68 extendedTextInfos => 1,
69 };
70
71 $self->send ("version 1023 1027 perlclient");
72 $self->send_setup;
73
74 $self
75 }
76
77 sub feed {
78 my ($self, $data) = @_;
79
80 $data =~ s/^(\S+)(?:\s|$)//
81 or return;
82
83 my $command = "feed_$1";
84
85 # warn "$command\n";#d#]
86 $self->$command ($data);
87 }
88
89 sub feed_version {
90 my ($self, $version) = @_;
91 }
92
93 sub feed_setup {
94 my ($self, $data) = @_;
95
96 $data =~ s/^ +//;
97
98 $self->{setup} = { split / +/, $data };
99
100 my ($mapw, $maph) = split /x/, $self->{setup}{mapsize};
101
102 if ($mapw != $self->{mapw} || $maph != $self->{maph}) {
103 ($self->{mapw}, $self->{maph}) = ($mapw, $maph);
104 $self->send_setup;
105 } else {
106 $self->send ("addme");
107 }
108
109 $self->feed_newmap;
110 }
111
112 sub feed_addme_success {
113 my ($self, $data) = @_;
114 }
115
116 sub feed_addme_failure {
117 my ($self, $data) = @_;
118 # maybe should notify user
119 }
120
121 =back
122
123 =head2 METHODS THAT CAN/MUST BE OVERWRITTEN
124
125 =over 4
126
127 =cut
128
129 sub feed_face1 {
130 my ($self, $data) = @_;
131
132 my ($num, $chksum, $name) = unpack "nNa*", $data;
133
134 my $face = $self->{face}[$num] = { name => $name, chksum => $chksum };
135
136 if (my $data = $self->face_find ($face)) {
137 $face->{image} = $data;
138 $self->face_update ($face);
139 } else {
140 $self->send ("askface $num");
141 }
142 }
143
144 =item $conn->anim_update ($num) [OVERWRITE]
145
146 =cut
147
148 sub anim_update { }
149
150 sub feed_anim {
151 my ($self, $data) = @_;
152
153 my ($num, @faces) = unpack "n*", $data;
154
155 $self->{anim}[$num] = \@faces;
156
157 $self->anim_update ($num);
158 }
159
160 =item $conn->play_sound ($x, $y, $soundnum, $type)
161
162 =cut
163
164 sub sound_play { }
165
166 sub feed_sound {
167 my ($self, $data) = @_;
168
169 $self->sound_play (unpack "CCnC", $data);
170 }
171
172 =item $conn->query ($flags, $prompt)
173
174 =cut
175
176 sub query { }
177
178 sub feed_query {
179 my ($self, $data) = @_;
180
181 my ($flags, $prompt) = split /\s+/, $data, 2;
182
183 if ($flags == 0 && $prompt =~ /^What is your name\?\s+:$/ && length $self->{user}) {
184 $self->send ("reply $self->{user}");
185 } elsif ($flags == 4 && $prompt =~ /^What is your password\?\s+:$/ && length $self->{pass}) {
186 $self->send ("reply $self->{pass}");
187 } elsif ($flags == 4 && $prompt =~ /^Please type your password again\.\s+:$/ && length $self->{pass}) {
188 $self->send ("reply $self->{pass}");
189 } else {
190 $self->query ($flags, $prompt);
191 }
192 }
193
194 =item $conn->drawinfo ($color, $text)
195
196 =cut
197
198 sub drawinfo { }
199
200 sub feed_drawinfo {
201 my ($self, $data) = @_;
202
203 $self->drawinfo (split / /, $data, 2);
204 }
205
206 =item $conn->player_update ($player)
207
208 tag, weight, face, name
209
210 =cut
211
212 sub player_update { }
213
214 sub feed_player {
215 my ($self, $data) = @_;
216
217 my ($tag, $weight, $face, $name) = unpack "NNN C/a", $data;
218
219 $self->player_update ($self->{player} = {
220 tag => $tag,
221 weight => $weight,
222 face => $face,
223 name => $name,
224 });
225 }
226
227 =item $conn->stats_update ($stats)
228
229 =cut
230
231 sub stats_update { }
232
233 sub feed_stats {
234 my ($self, $data) = @_;
235
236 while (length $data) {
237 my $stat = unpack "C", substr $data, 0, 1, "";
238 my $value;
239
240 if ($stat == 26 || $stat == 29 || $stat == 30 || $stat == 31 || $stat == 11) {
241 $value = unpack "N", substr $data, 0, 4, "";
242 } elsif ($stat == 17 || $stat == 19) {
243 $value = (1 / 100000) * unpack "N", substr $data, 0, 4, "";
244 } elsif ($stat == 20 || $stat == 21) {
245 my $len = unpack "C", substr $data, 0, 1, "";
246 $value = substr $data, 0, $len, "";
247 } elsif ($stat == 28) {
248 my ($lo, $hi) = unpack "NN", substr $data, 0, 8, "";
249 $value = $hi * 2**32 + $lo;
250 } elsif ($stat >= 118 && $stat <= 129) {
251 my ($level, $lo, $hi) = unpack "CNN", substr $data, 0, 9, "";
252 $value = [$level, $hi * 2**32 + $lo];
253 } else {
254 $value = unpack "n", substr $data, 0, 2, "";
255 }
256
257 $self->{stat}{$stat} = $value;
258 }
259
260 $self->stats_update ($self->{stat});
261 }
262
263 =item $conn->inventory_clear ($id)
264
265 =cut
266
267 sub inventory_clear { }
268
269 sub feed_delinv {
270 my ($self, $data) = @_;
271
272 $self->inventory_clear ($data);
273
274 delete $self->{inventory}[$data];
275 }
276
277 =item $conn->items_delete ($tag...)
278
279 =cut
280
281 sub items_delete { }
282
283 sub feed_delitem {
284 my ($self, $data) = @_;
285
286 $self->items_delete (unpack "n*", $data);
287 }
288
289 =item $conn->inventory_add ($id, [\%item...])
290
291 =cut
292
293 sub inventory_add {
294 }
295
296 sub feed_item2 {
297 my ($self, $data) = @_;
298
299 my ($location, @values) = unpack "N (NNNN C/a* nC Nn)*", $data;
300
301 my @items;
302
303 while (@values) {
304 my ($tag, $flags, $weight, $face, $names, $anim, $animspeed, $nrof, $type) =
305 splice @values, 0, 9, ();
306
307 my ($name, $name_pl) = split /\x000/, $names;
308
309 push @items, {
310 tag => $tag,
311 flags => $flags,
312 weight => $weight,
313 face => $face,
314 name => $name,
315 name_pl => $name_pl,
316 anim => $anim,
317 animspeed => $animspeed * 0.120, #???
318 nrof => $nrof,
319 type => $type,
320 };
321 }
322
323 $self->inventory_add ($location, \@items);
324 }
325
326 =item $conn->item_update ($tag)
327
328 =cut
329
330 sub item_update { }
331
332 sub feed_upditem {
333 #todo
334 }
335
336 sub feed_map1a {
337 my ($self, $data) = @_;
338
339 my $map = $self->{map} ||= [];
340
341 my ($dx, $dy) = delete @$self{qw(delayed_scroll_x delayed_scroll_y)};
342
343 if ($dx || $dy) {
344 my ($mx, $my, $mw, $mh) = @$self{qw(mapx mapy mapw maph)};
345
346 # TODO: optimise this a lot, or maybe just do it on c-level
347 # set flag to -1 for spaces we scroll out
348 {
349 my @darkness;
350
351 if ($dx > 0) {
352 push @darkness, [$mx, $my, $dx - 1, $mh];
353 } elsif ($dx < 0) {
354 push @darkness, [$mx + $mw + $dx + 1, $my, 1 - $dx, $mh];
355 }
356
357 if ($dy > 0) {
358 push @darkness, [$mx, $my, $mw, $dy - 1];
359 } elsif ($dy < 0) {
360 push @darkness, [$mx, $my + $mh + $dy + 1, $mw, 1 - $dy];
361 }
362
363 for (@darkness) {
364 my ($x0, $y0, $w, $h) = @$_;
365 for my $x ($x0 .. $x0 + $w) {
366 for my $y ($y0 .. $y0 + $h) {
367
368 my $cell = $map->[$x][$y]
369 or next;
370
371 $cell->[0] = -1;
372 }
373 }
374 }
375 }
376
377 # now scroll
378
379 $self->{mapx} += $dx;
380 $self->{mapy} += $dy;
381
382 # shift in new space if moving to "negative indices"
383 if ($self->{mapy} < 0) {
384 unshift @$_, ([]) x -$self->{mapy} for @$map;
385 $self->{mapy} = 0;
386 }
387
388 if ($self->{mapx} < 0) {
389 unshift @$map, ([]) x -$self->{mapx};
390 $self->{mapx} = 0;
391 }
392
393 $self->map_scroll ($dx, $dy);
394 }
395
396 my @dirty;
397 my ($coord, $x, $y, $darkness, $fa, $fb, $fc, $cell);
398
399 while (length $data) {
400 $coord = unpack "n", substr $data, 0, 2, "";
401
402 $x = (($coord >> 10) & 63) + $self->{mapx};
403 $y = (($coord >> 4) & 63) + $self->{mapy};
404
405 $cell = $map->[$x][$y] ||= [];
406
407 if ($coord & 15) {
408 @$cell = () if $cell->[0] < 0;
409
410 $cell->[0] = $coord & 8
411 ? unpack "C", substr $data, 0, 1, ""
412 : 255;
413
414 $cell->[1] = unpack "n", substr $data, 0, 2, ""
415 if $coord & 4;
416 $cell->[2] = unpack "n", substr $data, 0, 2, ""
417 if $coord & 2;
418 $cell->[3] = unpack "n", substr $data, 0, 2, ""
419 if $coord & 1;
420 } else {
421 $cell->[0] = -1;
422 }
423
424 push @dirty, [$x, $y];
425 }
426
427 $self->map_update (\@dirty);
428 }
429
430 sub feed_map_scroll {
431 my ($self, $data) = @_;
432
433 my ($dx, $dy) = split / /, $data;
434
435 $self->{delayed_scroll_x} += $dx;
436 $self->{delayed_scroll_y} += $dy;
437 }
438
439 sub feed_newmap {
440 my ($self) = @_;
441
442 $self->{map} = [];
443 $self->{mapx} = 0;
444 $self->{mapy} = 0;
445
446 delete $self->{delayed_scroll_x};
447 delete $self->{delayed_scroll_y};
448
449 $self->map_clear;
450 }
451
452 sub feed_image {
453 my ($self, $data) = @_;
454
455 my ($num, $len, $data) = unpack "NNa*", $data;
456
457 $self->{face}[$num]{image} = $data;
458 $self->face_update ($self->{face}[$num]);
459
460 my @dirty;
461
462 for my $x (0..$self->{mapw} - 1) {
463 for my $y (0..$self->{maph} - 1) {
464 push @dirty, [$x, $y]
465 if grep $_ == $num, @{$self->{map}[$x][$y] || []};
466 }
467 }
468
469 $self->map_update (\@dirty);
470 }
471
472 =item $conn->map_clear [OVERWRITE]
473
474 Called whenever the map is to be erased completely.
475
476 =cut
477
478 sub map_clear { }
479
480 =item $conn->map_update ([ [x,y], [x,y], ...]) [OVERWRITE]
481
482 Called with a list of x|y coordinate pairs (as arrayrefs) for cells that
483 have been updated and need refreshing.
484
485 =cut
486
487 sub map_update { }
488
489 =item $conn->map_scroll ($dx, $dy) [OVERWRITE]
490
491 Called whenever the map has been scrolled.
492
493 =cut
494
495 sub map_scroll { }
496
497 =item $conn->face_update ($face) [OVERWRITE]
498
499 Called with the face number of face structure whenever a face image has
500 changed.
501
502 =cut
503
504 sub face_update { }
505
506 =item $conn->face_find ($face) [OVERWRITE]
507
508 Find and return the png image for the given face, or the empty list if no
509 face could be found, in which case it will be requested from the server.
510
511 =cut
512
513 sub face_find { }
514
515 =item $conn->send ($data)
516
517 Send a single packet/line to the server.
518
519 =cut
520
521 sub send {
522 my ($self, $data) = @_;
523
524 $data = pack "na*", length $data, $data;
525
526 syswrite $self->{fh}, $data;
527 }
528
529 sub send_setup {
530 my ($self) = @_;
531
532 my $setup = join " ", setup => %{$self->{setup_req}},
533 mapsize => "$self->{mapw}x$self->{maph}";
534 warn "SET<$setup>\n";#d#
535 $self->send ($setup);
536 }
537
538 =back
539
540 =head1 AUTHOR
541
542 Marc Lehmann <schmorp@schmorp.de>
543 http://home.schmorp.de/
544
545 Robin Redeker <elmex@ta-sa.org>
546 http://www.ta-sa.org/
547
548 =cut
549
550 1