ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra/Deliantra/Protocol.pm
Revision: 1.22
Committed: Sat Apr 15 23:58:18 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.21: +17 -1 lines
Log Message:
more map_info support

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 max_outstanding => 2,
36 @_
37 }, $class;
38
39 $self->{fh} = new IO::Socket::INET PeerHost => $self->{host}, PeerPort => $self->{port}
40 or die "$self->{host}:$self->{port}: $!";
41 $self->{fh}->blocking (0); # stupid nonblock default
42
43 my $buf;
44
45 $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub {
46 if (sysread $self->{fh}, $buf, 16384, length $buf) {
47 for (;;) {
48 last unless 2 <= length $buf;
49 my $len = unpack "n", $buf;
50 last unless $len + 2 <= length $buf;
51
52 substr $buf, 0, 2, "";
53 $self->feed (substr $buf, 0, $len, "");
54 }
55 } else {
56 delete $self->{w};
57 close $self->{fh};
58 }
59 });
60
61 $self->{setup_req} = {
62 sound => 1,
63 exp64 => 1,
64 map1acmd => 1,
65 itemcmd => 2,
66 darkness => 1,
67 facecache => 1,
68 newmapcmd => 1,
69 mapinfocmd => 1,
70 plugincmd => 1,
71 extendedTextInfos => 1,
72 };
73
74 $self->send ("version 1023 1027 perlclient");
75 $self->send_setup;
76
77 $self
78 }
79
80 sub feed {
81 my ($self, $data) = @_;
82
83 $data =~ s/^(\S+)(?:\s|$)//
84 or return;
85
86 my $command = "feed_$1";
87
88 $self->$command ($data);
89 }
90
91 sub feed_version {
92 my ($self, $version) = @_;
93 }
94
95 sub feed_setup {
96 my ($self, $data) = @_;
97
98 $data =~ s/^ +//;
99
100 $self->{setup} = { split / +/, $data };
101
102 my ($mapw, $maph) = split /x/, $self->{setup}{mapsize};
103
104 if ($mapw != $self->{mapw} || $maph != $self->{maph}) {
105 ($self->{mapw}, $self->{maph}) = ($mapw, $maph);
106 $self->send_setup;
107 } else {
108 $self->send ("addme");
109 }
110
111 $self->feed_newmap;
112 }
113
114 sub feed_addme_success {
115 my ($self, $data) = @_;
116 }
117
118 sub feed_addme_failure {
119 my ($self, $data) = @_;
120 # maybe should notify user
121 }
122
123 =back
124
125 =head2 METHODS THAT CAN/MUST BE OVERWRITTEN
126
127 =over 4
128
129 =cut
130
131 sub feed_face1 {
132 my ($self, $data) = @_;
133
134 my ($num, $chksum, $name) = unpack "nNa*", $data;
135
136 my $face = $self->{face}[$num] = { name => $name, chksum => $chksum };
137
138 if (my $data = $self->face_find ($face)) {
139 $face->{image} = $data;
140 $self->face_update ($num, $face);
141 } else {
142 $self->send_queue ("askface $num");
143 }
144 }
145
146 =item $conn->anim_update ($num) [OVERWRITE]
147
148 =cut
149
150 sub anim_update { }
151
152 sub feed_anim {
153 my ($self, $data) = @_;
154
155 my ($num, @faces) = unpack "n*", $data;
156
157 $self->{anim}[$num] = \@faces;
158
159 $self->anim_update ($num);
160 }
161
162 =item $conn->play_sound ($x, $y, $soundnum, $type)
163
164 =cut
165
166 sub sound_play { }
167
168 sub feed_sound {
169 my ($self, $data) = @_;
170
171 $self->sound_play (unpack "CCnC", $data);
172 }
173
174 =item $conn->query ($flags, $prompt)
175
176 =cut
177
178 sub query { }
179
180 sub feed_query {
181 my ($self, $data) = @_;
182
183 my ($flags, $prompt) = split /\s+/, $data, 2;
184
185 if ($flags == 0 && $prompt =~ /^What is your name\?\s+:$/ && length $self->{user}) {
186 $self->send ("reply $self->{user}");
187 } elsif ($flags == 4 && $prompt =~ /^What is your password\?\s+:$/ && length $self->{pass}) {
188 $self->send ("reply $self->{pass}");
189 } elsif ($flags == 4 && $prompt =~ /^Please type your password again\.\s+:$/ && length $self->{pass}) {
190 $self->send ("reply $self->{pass}");
191 } else {
192 $self->query ($flags, $prompt);
193 }
194 }
195
196 =item $conn->drawinfo ($color, $text)
197
198 =cut
199
200 sub drawinfo { }
201
202 sub feed_drawinfo {
203 my ($self, $data) = @_;
204
205 $self->drawinfo (split / /, $data, 2);
206 }
207
208 =item $conn->player_update ($player)
209
210 tag, weight, face, name
211
212 =cut
213
214 sub player_update { }
215
216 sub feed_player {
217 my ($self, $data) = @_;
218
219 my ($tag, $weight, $face, $name) = unpack "NNN C/a", $data;
220
221 $self->player_update ($self->{player} = {
222 tag => $tag,
223 weight => $weight,
224 face => $face,
225 name => $name,
226 });
227 }
228
229 =item $conn->stats_update ($stats)
230
231 =cut
232
233 sub stats_update { }
234
235 sub feed_stats {
236 my ($self, $data) = @_;
237
238 while (length $data) {
239 my $stat = unpack "C", substr $data, 0, 1, "";
240 my $value;
241
242 if ($stat == 26 || $stat == 29 || $stat == 30 || $stat == 31 || $stat == 11) {
243 $value = unpack "N", substr $data, 0, 4, "";
244 } elsif ($stat == 17 || $stat == 19) {
245 $value = (1 / 100000) * unpack "N", substr $data, 0, 4, "";
246 } elsif ($stat == 20 || $stat == 21) {
247 my $len = unpack "C", substr $data, 0, 1, "";
248 $value = substr $data, 0, $len, "";
249 } elsif ($stat == 28) {
250 my ($lo, $hi) = unpack "NN", substr $data, 0, 8, "";
251 $value = $hi * 2**32 + $lo;
252 } elsif ($stat >= 118 && $stat <= 129) {
253 my ($level, $lo, $hi) = unpack "CNN", substr $data, 0, 9, "";
254 $value = [$level, $hi * 2**32 + $lo];
255 } else {
256 $value = unpack "n", substr $data, 0, 2, "";
257 }
258
259 $self->{stat}{$stat} = $value;
260 }
261
262 $self->stats_update ($self->{stat});
263 }
264
265 =item $conn->inventory_clear ($id)
266
267 =cut
268
269 sub inventory_clear { }
270
271 sub feed_delinv {
272 my ($self, $data) = @_;
273
274 $self->inventory_clear ($data);
275
276 delete $self->{inventory}[$data];
277 }
278
279 =item $conn->items_delete ($tag...)
280
281 =cut
282
283 sub items_delete { }
284
285 sub feed_delitem {
286 my ($self, $data) = @_;
287
288 $self->items_delete (unpack "n*", $data);
289 }
290
291 =item $conn->inventory_add ($id, [\%item...])
292
293 =cut
294
295 sub inventory_add {
296 }
297
298 sub feed_item2 {
299 my ($self, $data) = @_;
300
301 my ($location, @values) = unpack "N (NNNN C/a* nC Nn)*", $data;
302
303 my @items;
304
305 while (@values) {
306 my ($tag, $flags, $weight, $face, $names, $anim, $animspeed, $nrof, $type) =
307 splice @values, 0, 9, ();
308
309 my ($name, $name_pl) = split /\x000/, $names;
310
311 push @items, {
312 tag => $tag,
313 flags => $flags,
314 weight => $weight,
315 face => $face,
316 name => $name,
317 name_pl => $name_pl,
318 anim => $anim,
319 animspeed => $animspeed * 0.120, #???
320 nrof => $nrof,
321 type => $type,
322 };
323 }
324
325 $self->inventory_add ($location, \@items);
326 }
327
328 =item $conn->item_update ($tag)
329
330 =cut
331
332 sub item_update { }
333
334 sub feed_upditem {
335 #todo
336 }
337
338 sub feed_map1a {
339 my ($self, $data) = @_;
340
341 my $map = $self->{map} ||= [];
342
343 my ($dx, $dy) = delete @$self{qw(delayed_scroll_x delayed_scroll_y)};
344
345 if ($dx || $dy) {
346 my ($mx, $my, $mw, $mh) = @$self{qw(mapx mapy mapw maph)};
347
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 @$_, (undef) x -$self->{mapy} for @$map;
385 $self->{mapy} = 0;
386 }
387
388 if ($self->{mapx} < 0) {
389 unshift @$map, (undef) 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_mapinfo {
453 my ($self, $data) = @_;
454
455 $self->map_info (split / /, $data, 7);
456 }
457
458 sub feed_image {
459 my ($self, $data) = @_;
460
461
462 my ($num, $len, $data) = unpack "NNa*", $data;
463
464 $self->send_queue;
465 $self->{face}[$num]{image} = $data;
466 $self->face_update ($num, $self->{face}[$num]);
467
468 my @dirty;
469
470 for my $x (0..$self->{mapw} - 1) {
471 for my $y (0..$self->{maph} - 1) {
472 push @dirty, [$x, $y]
473 if grep $_ == $num, @{$self->{map}[$x][$y] || []};
474 }
475 }
476
477 $self->map_update (\@dirty);
478 }
479
480 =item $conn->map_info ($mode, ...) [OVERWRITE]
481
482 current <flags> <x> <y> <width> <height> <hashstring>
483
484 =cut
485
486 sub map_info { }
487
488 =item $conn->map_clear [OVERWRITE]
489
490 Called whenever the map is to be erased completely.
491
492 =cut
493
494 sub map_clear { }
495
496 =item $conn->map_update ([ [x,y], [x,y], ...]) [OVERWRITE]
497
498 Called with a list of x|y coordinate pairs (as arrayrefs) for cells that
499 have been updated and need refreshing.
500
501 =cut
502
503 sub map_update { }
504
505 =item $conn->map_scroll ($dx, $dy) [OVERWRITE]
506
507 Called whenever the map has been scrolled.
508
509 =cut
510
511 sub map_scroll { }
512
513 =item $conn->face_update ($facenum, $facedata) [OVERWRITE]
514
515 Called with the face number of face structure whenever a face image has
516 changed.
517
518 =cut
519
520 sub face_update { }
521
522 =item $conn->face_find ($face) [OVERWRITE]
523
524 Find and return the png image for the given face, or the empty list if no
525 face could be found, in which case it will be requested from the server.
526
527 =cut
528
529 sub face_find { }
530
531 =item $conn->send ($data)
532
533 Send a single packet/line to the server.
534
535 =cut
536
537 sub send {
538 my ($self, $data) = @_;
539
540 $data = pack "na*", length $data, $data;
541
542 syswrite $self->{fh}, $data;
543 }
544
545 sub send_queue {
546 my ($self, $cmd) = @_;
547
548 if (defined $cmd) {
549 push @{ $self->{send_queue} }, $cmd;
550 } else {
551 --$self->{outstanding};
552 }
553
554 if ($self->{outstanding} < $self->{max_outstanding} && @{ $self->{send_queue} }) {
555 ++$self->{outstanding};
556 $self->send (pop @{ $self->{send_queue} });
557 }
558 }
559
560 sub send_setup {
561 my ($self) = @_;
562
563 my $setup = join " ", setup => %{$self->{setup_req}},
564 mapsize => "$self->{mapw}x$self->{maph}";
565
566 $self->send ($setup);
567 }
568
569 =back
570
571 =head1 AUTHOR
572
573 Marc Lehmann <schmorp@schmorp.de>
574 http://home.schmorp.de/
575
576 Robin Redeker <elmex@ta-sa.org>
577 http://www.ta-sa.org/
578
579 =cut
580
581 1