… | |
… | |
21 | use strict; |
21 | use strict; |
22 | |
22 | |
23 | use AnyEvent; |
23 | use AnyEvent; |
24 | use IO::Socket::INET; |
24 | use IO::Socket::INET; |
25 | |
25 | |
|
|
26 | BEGIN { |
|
|
27 | my %CONSTANTS = ( |
26 | my $TICK = 0.120; # one server tick, not exposed through the protocol of course |
28 | TICK => 0.120, # one server tick, not exposed through the protocol of course |
|
|
29 | CS_QUERY_YESNO => 0x1, |
|
|
30 | CS_QUERY_SINGLECHAR => 0x2, |
|
|
31 | CS_QUERY_HIDEINPUT => 0x4, |
|
|
32 | CS_SAY_NORMAL => 0x1, |
|
|
33 | CS_SAY_SHOUT => 0x2, |
|
|
34 | CS_SAY_GSAY => 0x4, |
|
|
35 | FLOAT_MULTI => 100000, |
|
|
36 | FLOAT_MULTF => 100000.0, |
|
|
37 | CS_STAT_HP => 1, |
|
|
38 | CS_STAT_MAXHP => 2, |
|
|
39 | CS_STAT_SP => 3, |
|
|
40 | CS_STAT_MAXSP => 4, |
|
|
41 | CS_STAT_STR => 5, |
|
|
42 | CS_STAT_INT => 6, |
|
|
43 | CS_STAT_WIS => 7, |
|
|
44 | CS_STAT_DEX => 8, |
|
|
45 | CS_STAT_CON => 9, |
|
|
46 | CS_STAT_CHA => 10, |
|
|
47 | CS_STAT_EXP => 11, |
|
|
48 | CS_STAT_LEVEL => 12, |
|
|
49 | CS_STAT_WC => 13, |
|
|
50 | CS_STAT_AC => 14, |
|
|
51 | CS_STAT_DAM => 15, |
|
|
52 | CS_STAT_ARMOUR => 16, |
|
|
53 | CS_STAT_SPEED => 17, |
|
|
54 | CS_STAT_FOOD => 18, |
|
|
55 | CS_STAT_WEAP_SP => 19, |
|
|
56 | CS_STAT_RANGE => 20, |
|
|
57 | CS_STAT_TITLE => 21, |
|
|
58 | CS_STAT_POW => 22, |
|
|
59 | CS_STAT_GRACE => 23, |
|
|
60 | CS_STAT_MAXGRACE => 24, |
|
|
61 | CS_STAT_FLAGS => 25, |
|
|
62 | CS_STAT_WEIGHT_LIM => 26, |
|
|
63 | CS_STAT_EXP64 => 28, |
|
|
64 | CS_STAT_SPELL_ATTUNE => 29, |
|
|
65 | CS_STAT_SPELL_REPEL => 30, |
|
|
66 | CS_STAT_SPELL_DENY => 31, |
|
|
67 | CS_STAT_RESIST_START => 100, |
|
|
68 | CS_STAT_RESIST_END => 117, |
|
|
69 | CS_STAT_RES_PHYS => 100, |
|
|
70 | CS_STAT_RES_MAG => 101, |
|
|
71 | CS_STAT_RES_FIRE => 102, |
|
|
72 | CS_STAT_RES_ELEC => 103, |
|
|
73 | CS_STAT_RES_COLD => 104, |
|
|
74 | CS_STAT_RES_CONF => 105, |
|
|
75 | CS_STAT_RES_ACID => 106, |
|
|
76 | CS_STAT_RES_DRAIN => 107, |
|
|
77 | CS_STAT_RES_GHOSTHIT => 108, |
|
|
78 | CS_STAT_RES_POISON => 109, |
|
|
79 | CS_STAT_RES_SLOW => 110, |
|
|
80 | CS_STAT_RES_PARA => 111, |
|
|
81 | CS_STAT_TURN_UNDEAD => 112, |
|
|
82 | CS_STAT_RES_FEAR => 113, |
|
|
83 | CS_STAT_RES_DEPLETE => 114, |
|
|
84 | CS_STAT_RES_DEATH => 115, |
|
|
85 | CS_STAT_RES_HOLYWORD => 116, |
|
|
86 | CS_STAT_RES_BLIND => 117, |
|
|
87 | CS_STAT_SKILLEXP_START => 118, |
|
|
88 | CS_STAT_SKILLEXP_END => 129, |
|
|
89 | CS_STAT_SKILLEXP_AGILITY => 118, |
|
|
90 | CS_STAT_SKILLEXP_AGLEVEL => 119, |
|
|
91 | CS_STAT_SKILLEXP_PERSONAL => 120, |
|
|
92 | CS_STAT_SKILLEXP_PELEVEL => 121, |
|
|
93 | CS_STAT_SKILLEXP_MENTAL => 122, |
|
|
94 | CS_STAT_SKILLEXP_MELEVEL => 123, |
|
|
95 | CS_STAT_SKILLEXP_PHYSIQUE => 124, |
|
|
96 | CS_STAT_SKILLEXP_PHLEVEL => 125, |
|
|
97 | CS_STAT_SKILLEXP_MAGIC => 126, |
|
|
98 | CS_STAT_SKILLEXP_MALEVEL => 127, |
|
|
99 | CS_STAT_SKILLEXP_WISDOM => 128, |
|
|
100 | CS_STAT_SKILLEXP_WILEVEL => 129, |
|
|
101 | CS_STAT_SKILLINFO => 140, |
|
|
102 | CS_NUM_SKILLS => 50, |
|
|
103 | SF_FIREON => 0x01, |
|
|
104 | SF_RUNON => 0x02, |
|
|
105 | NDI_BLACK => 0, |
|
|
106 | NDI_WHITE => 1, |
|
|
107 | NDI_NAVY => 2, |
|
|
108 | NDI_RED => 3, |
|
|
109 | NDI_ORANGE => 4, |
|
|
110 | NDI_BLUE => 5, |
|
|
111 | NDI_DK_ORANGE => 6, |
|
|
112 | NDI_GREEN => 7, |
|
|
113 | NDI_LT_GREEN => 8, |
|
|
114 | NDI_GREY => 9, |
|
|
115 | NDI_BROWN => 10, |
|
|
116 | NDI_GOLD => 11, |
|
|
117 | NDI_TAN => 12, |
|
|
118 | NDI_MAX_COLOR => 12, |
|
|
119 | NDI_COLOR_MASK => 0xff, |
|
|
120 | NDI_UNIQUE => 0x100, |
|
|
121 | NDI_ALL => 0x200, |
|
|
122 | a_none => 0, |
|
|
123 | a_readied => 1, |
|
|
124 | a_wielded => 2, |
|
|
125 | a_worn => 3, |
|
|
126 | a_active => 4, |
|
|
127 | a_applied => 5, |
|
|
128 | F_APPLIED => 0x000F, |
|
|
129 | F_LOCATION => 0x00F0, |
|
|
130 | F_UNPAID => 0x0200, |
|
|
131 | F_MAGIC => 0x0400, |
|
|
132 | F_CURSED => 0x0800, |
|
|
133 | F_DAMNED => 0x1000, |
|
|
134 | F_OPEN => 0x2000, |
|
|
135 | F_NOPICK => 0x4000, |
|
|
136 | F_LOCKED => 0x8000, |
|
|
137 | CF_FACE_NONE => 0, |
|
|
138 | CF_FACE_BITMAP => 1, |
|
|
139 | CF_FACE_XPM => 2, |
|
|
140 | CF_FACE_PNG => 3, |
|
|
141 | CF_FACE_CACHE => 0x10, |
|
|
142 | FACE_FLOOR => 0x80, |
|
|
143 | FACE_COLOR_MASK => 0xf, |
|
|
144 | UPD_LOCATION => 0x01, |
|
|
145 | UPD_FLAGS => 0x02, |
|
|
146 | UPD_WEIGHT => 0x04, |
|
|
147 | UPD_FACE => 0x08, |
|
|
148 | UPD_NAME => 0x10, |
|
|
149 | UPD_ANIM => 0x20, |
|
|
150 | UPD_ANIMSPEED => 0x40, |
|
|
151 | UPD_NROF => 0x80, |
|
|
152 | UPD_SP_MANA => 0x01, |
|
|
153 | UPD_SP_GRACE => 0x02, |
|
|
154 | UPD_SP_DAMAGE => 0x04, |
|
|
155 | SOUND_NORMAL => 0, |
|
|
156 | SOUND_SPELL => 1, |
|
|
157 | ); |
|
|
158 | |
|
|
159 | while (my ($k, $v) = each %CONSTANTS) { |
|
|
160 | eval "sub $k () { $v } 1" |
|
|
161 | or die; |
|
|
162 | } |
|
|
163 | } |
27 | |
164 | |
28 | =item new Crossfire::Protocol host => ..., port => ... |
165 | =item new Crossfire::Protocol host => ..., port => ... |
29 | |
166 | |
30 | =cut |
167 | =cut |
31 | |
168 | |
32 | sub new { |
169 | sub new { |
33 | my $class = shift; |
170 | my $class = shift; |
34 | my $self = bless { |
171 | my $self = bless { |
35 | mapw => 13, |
172 | mapw => 13, |
36 | maph => 13, |
173 | maph => 13, |
37 | max_outstanding => 6, |
174 | max_outstanding => 2, |
38 | token => "a0", |
175 | token => "a0", |
39 | @_ |
176 | @_ |
40 | }, $class; |
177 | }, $class; |
41 | |
178 | |
42 | $self->{fh} = new IO::Socket::INET PeerHost => $self->{host}, PeerPort => $self->{port} |
179 | $self->{fh} = new IO::Socket::INET PeerHost => $self->{host}, PeerPort => $self->{port} |
… | |
… | |
258 | |
395 | |
259 | =cut |
396 | =cut |
260 | |
397 | |
261 | sub stats_update { } |
398 | sub stats_update { } |
262 | |
399 | |
|
|
400 | my %stat_32bit = map +($_ => 1), |
|
|
401 | CS_STAT_WEIGHT_LIM, |
|
|
402 | CS_STAT_SPELL_ATTUNE, |
|
|
403 | CS_STAT_SPELL_REPEL, |
|
|
404 | CS_STAT_SPELL_DENY, |
|
|
405 | CS_STAT_EXP; |
|
|
406 | |
263 | sub feed_stats { |
407 | sub feed_stats { |
264 | my ($self, $data) = @_; |
408 | my ($self, $data) = @_; |
265 | |
409 | |
266 | while (length $data) { |
410 | while (length $data) { |
267 | my $stat = unpack "C", substr $data, 0, 1, ""; |
411 | my $stat = unpack "C", substr $data, 0, 1, ""; |
268 | my $value; |
412 | my $value; |
269 | |
413 | |
270 | if ($stat == 26 || $stat == 29 || $stat == 30 || $stat == 31 || $stat == 11) { |
414 | if ($stat_32bit{$stat}) { |
271 | $value = unpack "N", substr $data, 0, 4, ""; |
415 | $value = unpack "N", substr $data, 0, 4, ""; |
272 | } elsif ($stat == 17 || $stat == 19) { |
416 | } elsif ($stat == CS_STAT_SPEED || $stat == CS_STAT_WEAP_SP) { |
273 | $value = (1 / 100000) * unpack "N", substr $data, 0, 4, ""; |
417 | $value = (1 / FLOAT_MULTF) * unpack "N", substr $data, 0, 4, ""; |
274 | } elsif ($stat == 20 || $stat == 21) { |
418 | } elsif ($stat == CS_STAT_RANGE || $stat == CS_STAT_TITLE) { |
275 | my $len = unpack "C", substr $data, 0, 1, ""; |
419 | my $len = unpack "C", substr $data, 0, 1, ""; |
276 | $value = substr $data, 0, $len, ""; |
420 | $value = substr $data, 0, $len, ""; |
277 | } elsif ($stat == 28) { |
421 | } elsif ($stat == CS_STAT_EXP64) { |
278 | my ($hi, $lo) = unpack "NN", substr $data, 0, 8, ""; |
422 | my ($hi, $lo) = unpack "NN", substr $data, 0, 8, ""; |
279 | $value = $hi * 2**32 + $lo; |
423 | $value = $hi * 2**32 + $lo; |
280 | } elsif (($stat >= 118 && $stat <= 129) || ($stat >= 140 && $stat < 190)) { |
424 | } elsif (($stat >= CS_STAT_SKILLEXP_START && $stat <= CS_STAT_SKILLEXP_END) |
|
|
425 | || ($stat >= CS_STAT_SKILLINFO && $stat < CS_STAT_SKILLINFO + CS_NUM_SKILLS)) { |
281 | my ($level, $lo, $hi) = unpack "CNN", substr $data, 0, 9, ""; |
426 | my ($level, $lo, $hi) = unpack "CNN", substr $data, 0, 9, ""; |
282 | $value = [$level, $hi * 2**32 + $lo]; |
427 | $value = [$level, $hi * 2**32 + $lo]; |
283 | } else { |
428 | } else { |
284 | $value = unpack "s", pack "S", unpack "n", substr $data, 0, 2, ""; |
429 | $value = unpack "s", pack "S", unpack "n", substr $data, 0, 2, ""; |
285 | } |
430 | } |
… | |
… | |
288 | } |
433 | } |
289 | |
434 | |
290 | $self->stats_update ($self->{stat}); |
435 | $self->stats_update ($self->{stat}); |
291 | } |
436 | } |
292 | |
437 | |
|
|
438 | =item $conn->container_add ($id, $item...) |
|
|
439 | |
293 | =item $conn->inventory_clear ($id) |
440 | =item $conn->container_clear ($id) |
294 | |
441 | |
295 | =cut |
442 | =item $conn->item_update ($item) |
296 | |
443 | |
|
|
444 | =item $conn->item_delete ($item...) |
|
|
445 | |
|
|
446 | =cut |
|
|
447 | |
|
|
448 | sub container_add { } |
297 | sub inventory_clear { } |
449 | sub container_clear { } |
|
|
450 | sub item_delete { } |
|
|
451 | sub item_update { } |
|
|
452 | |
|
|
453 | sub _del_items { |
|
|
454 | my ($self, @items) = @_; |
|
|
455 | |
|
|
456 | for my $item (@items) { |
|
|
457 | delete $self->{item}{$item->{tag}}; |
|
|
458 | delete $self->{container}{$item->{container}}{$item->{tag}}; |
|
|
459 | } |
|
|
460 | } |
298 | |
461 | |
299 | sub feed_delinv { |
462 | sub feed_delinv { |
300 | my ($self, $data) = @_; |
463 | my ($self, $data) = @_; |
301 | |
464 | |
|
|
465 | $self->_del_items (values %{ $self->{container}{$data} }); |
302 | $self->inventory_clear ($data); |
466 | $self->container_clear ($data); |
303 | |
|
|
304 | delete $self->{inventory}[$data]; |
|
|
305 | } |
467 | } |
306 | |
|
|
307 | =item $conn->items_delete ($tag...) |
|
|
308 | |
|
|
309 | =cut |
|
|
310 | |
|
|
311 | sub items_delete { } |
|
|
312 | |
468 | |
313 | sub feed_delitem { |
469 | sub feed_delitem { |
314 | my ($self, $data) = @_; |
470 | my ($self, $data) = @_; |
315 | |
471 | |
316 | $self->items_delete (unpack "n*", $data); |
472 | my @items = map $self->{item}{$_}, unpack "N*", $data; |
317 | } |
473 | $self->_del_items (@items); |
318 | |
474 | $self->item_delete (@items); |
319 | =item $conn->inventory_add ($id, [\%item...]) |
|
|
320 | |
|
|
321 | =cut |
|
|
322 | |
|
|
323 | sub inventory_add { |
|
|
324 | } |
475 | } |
325 | |
476 | |
326 | sub feed_item2 { |
477 | sub feed_item2 { |
327 | my ($self, $data) = @_; |
478 | my ($self, $data) = @_; |
328 | |
479 | |
… | |
… | |
332 | |
483 | |
333 | while (@values) { |
484 | while (@values) { |
334 | my ($tag, $flags, $weight, $face, $names, $anim, $animspeed, $nrof, $type) = |
485 | my ($tag, $flags, $weight, $face, $names, $anim, $animspeed, $nrof, $type) = |
335 | splice @values, 0, 9, (); |
486 | splice @values, 0, 9, (); |
336 | |
487 | |
|
|
488 | utf8::decode $names if utf8::valid $names; |
337 | my ($name, $name_pl) = split /\x000/, $names; |
489 | my ($name, $name_pl) = split /\x00/, $names; |
338 | |
490 | |
339 | push @items, { |
491 | my $item = { |
|
|
492 | container => $location, |
340 | tag => $tag, |
493 | tag => $tag, |
341 | flags => $flags, |
494 | flags => $flags, |
342 | weight => $weight, |
495 | weight => $weight, |
343 | face => $face, |
496 | face => $face, |
344 | name => $name, |
497 | name => $name, |
345 | name_pl => $name_pl, |
498 | name_pl => $name_pl, |
346 | anim => $anim, |
499 | anim => $anim, |
347 | animspeed => $animspeed * $TICK, #??? |
500 | animspeed => $animspeed * TICK, #??? |
348 | nrof => $nrof, |
501 | nrof => $nrof, |
349 | type => $type, |
502 | type => $type, |
350 | }; |
503 | }; |
351 | } |
|
|
352 | |
504 | |
|
|
505 | $self->{item}{$tag} = $item; |
|
|
506 | $self->{container}{$location}{$tag} = $item; |
|
|
507 | push @items, $item; |
|
|
508 | } |
|
|
509 | |
353 | $self->inventory_add ($location, \@items); |
510 | $self->container_add ($location, \@items); |
354 | } |
511 | } |
355 | |
|
|
356 | =item $conn->item_update ($tag) |
|
|
357 | |
|
|
358 | =cut |
|
|
359 | |
|
|
360 | sub item_update { } |
|
|
361 | |
512 | |
362 | sub feed_upditem { |
513 | sub feed_upditem { |
363 | #todo |
514 | my ($self, $data) = @_; |
364 | #define UPD_LOCATION 0x01 |
515 | |
365 | #define UPD_FLAGS 0x02 |
516 | my ($flags, $tag) = unpack "NN", substr $data, 0, 8, ""; |
366 | #define UPD_WEIGHT 0x04 |
517 | |
367 | #define UPD_FACE 0x08 |
518 | my $item = $self->{item}{$tag}; |
368 | #define UPD_NAME 0x10 |
519 | |
369 | #define UPD_ANIM 0x20 |
520 | if ($flags & UPD_LOCATION) { |
370 | #define UPD_ANIMSPEED 0x40 |
521 | $self->item_delete ($item); |
371 | #define UPD_NROF 0x80 |
522 | delete $self->{container}{$item->{container}}{$tag}; |
|
|
523 | |
|
|
524 | $item->{container} = unpack "N", substr $data, 0, 4, ""; |
|
|
525 | |
|
|
526 | $self->{container}{$item->{container}}{$tag} = $item; |
|
|
527 | $self->container_add ($item->{location}, $item); |
|
|
528 | } |
|
|
529 | |
|
|
530 | $item->{flags} = unpack "N", substr $data, 0, 4, "" if $flags & UPD_FLAGS; |
|
|
531 | $item->{weight} = unpack "N", substr $data, 0, 4, "" if $flags & UPD_WEIGHT; |
|
|
532 | $item->{face} = unpack "N", substr $data, 0, 4, "" if $flags & UPD_FACE; |
|
|
533 | |
|
|
534 | if ($flags & UPD_NAME) { |
|
|
535 | my $len = unpack "C", substr $data, 0, 1, ""; |
|
|
536 | |
|
|
537 | my $names = substr $data, 0, $len, ""; |
|
|
538 | utf8::decode $names if utf8::valid $names; |
|
|
539 | @$item{qw(name name_pl)} = split /\x00/, $names; |
|
|
540 | } |
|
|
541 | |
|
|
542 | $item->{anim} = unpack "n", substr $data, 0, 2, "" if $flags & UPD_ANIM; |
|
|
543 | $item->{animspeed} = TICK * unpack "C", substr $data, 0, 1, "" if $flags & UPD_ANIMSPEED; |
|
|
544 | $item->{nrof} = unpack "N", substr $data, 0, 4, "" if $flags & UPD_NROF; |
|
|
545 | |
|
|
546 | $self->item_update ($item); |
372 | } |
547 | } |
373 | |
548 | |
374 | =item $conn->spell_add ($spell) |
549 | =item $conn->spell_add ($spell) |
375 | |
550 | |
376 | $spell = { |
551 | $spell = { |
… | |
… | |
441 | # only 1, 2, 4 supported |
616 | # only 1, 2, 4 supported |
442 | # completely untested |
617 | # completely untested |
443 | |
618 | |
444 | my $spell = $self->{spell}{$tag}; |
619 | my $spell = $self->{spell}{$tag}; |
445 | |
620 | |
446 | $spell->{mana} = unpack "n", substr $data, 0, 2, "" if $flags & 1; |
621 | $spell->{mana} = unpack "n", substr $data, 0, 2, "" if $flags & UPD_SP_MANA; |
447 | $spell->{grace} = unpack "n", substr $data, 0, 2, "" if $flags & 2; |
622 | $spell->{grace} = unpack "n", substr $data, 0, 2, "" if $flags & UPD_SP_GRACE; |
448 | $spell->{damage} = unpack "n", substr $data, 0, 2, "" if $flags & 4; |
623 | $spell->{damage} = unpack "n", substr $data, 0, 2, "" if $flags & UPD_SP_DAMAGE; |
449 | |
624 | |
450 | $self->spell_update ($spell); |
625 | $self->spell_update ($spell); |
451 | } |
626 | } |
452 | |
627 | |
453 | sub feed_delspell { |
628 | sub feed_delspell { |