ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra/Deliantra.pm
Revision: 1.53
Committed: Wed Mar 22 03:32:09 2006 UTC (18 years, 1 month ago) by root
Branch: MAIN
Changes since 1.52: +3 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 elmex 1.1 =head1 NAME
2    
3     Crossfire - Crossfire maphandling
4    
5     =cut
6    
7 root 1.4 package Crossfire;
8    
9 elmex 1.3 our $VERSION = '0.1';
10 elmex 1.1
11     use strict;
12    
13 root 1.7 use base 'Exporter';
14    
15 root 1.13 use Carp ();
16 root 1.21 use File::Spec;
17 root 1.15 use List::Util qw(min max);
18 root 1.53 use Storable qw(freeze thaw);
19 elmex 1.1
20 root 1.50 our @EXPORT = qw(
21     read_pak read_arch *ARCH TILESIZE $TILE *FACE editor_archs arch_extents
22     );
23    
24     our $LIB = $ENV{CROSSFIRE_LIBDIR};
25 root 1.7
26 root 1.50 our $VARDIR = $ENV{HOME} ? "$ENV{HOME}/.crossfire" : File::Spec->tmpdir . "/crossfire";
27    
28     mkdir $VARDIR, 0777;
29 elmex 1.1
30 root 1.7 sub TILESIZE (){ 32 }
31 elmex 1.1
32 root 1.15 our %ARCH;
33     our %FACE;
34     our $TILE;
35 elmex 1.1
36 root 1.13 our %FIELD_MULTILINE = (
37 root 1.50 msg => "endmsg",
38     lore => "endlore",
39     maplore => "endmaplore",
40 root 1.13 );
41    
42     # not used yet, maybe alphabetical is ok
43     our @FIELD_ORDER = (qw(name name_pl));
44    
45 root 1.50 sub MOVE_WALK (){ 0x01 }
46     sub MOVE_FLY_LOW (){ 0x02 }
47     sub MOVE_FLY_HIGH (){ 0x04 }
48     sub MOVE_FLYING (){ 0x06 }
49     sub MOVE_SWIM (){ 0x08 }
50     sub MOVE_BOAT (){ 0x10 }
51     sub MOVE_ALL (){ 0xff }
52 root 1.14
53 root 1.28 sub load_ref($) {
54     my ($path) = @_;
55    
56 root 1.31 open my $fh, "<", $path
57     or die "$path: $!";
58     binmode $fh;
59 root 1.28 local $/;
60 root 1.33
61 root 1.53 thaw <$fh>
62 root 1.28 }
63    
64     sub save_ref($$) {
65     my ($ref, $path) = @_;
66    
67 root 1.31 open my $fh, ">", "$path~"
68 root 1.28 or die "$path~: $!";
69 root 1.31 binmode $fh;
70 root 1.53 print $fh freeze $ref;
71 root 1.28 close $fh;
72     rename "$path~", $path
73     or die "$path: $!";
74     }
75    
76 root 1.52 sub normalize_object($) {
77 root 1.14 my ($ob) = @_;
78    
79 root 1.50 delete $ob->{$_} for qw(
80     can_knockback can_parry can_impale can_cut can_dam_armour
81     can_apply pass_thru can_pass_thru
82     );
83 root 1.14
84 root 1.50 for my $attr (qw(move_type move_block move_allow move_on move_off move_slow)) {
85     next unless exists $ob->{$attr};
86     next if $ob->{$attr} =~ /^\d+$/;
87    
88     my $flags = 0;
89    
90     # assume list
91     for my $flag (map lc, split /\s+/, $ob->{$attr}) {
92     $flags |= MOVE_WALK if $flag eq "walk";
93     $flags |= MOVE_FLY_LOW if $flag eq "fly_low";
94     $flags |= MOVE_FLY_HIGH if $flag eq "fly_high";
95     $flags |= MOVE_FLYING if $flag eq "flying";
96     $flags |= MOVE_SWIM if $flag eq "swim";
97     $flags |= MOVE_BOAT if $flag eq "boat";
98     $flags |= MOVE_ALL if $flag eq "all";
99    
100     $flags &= ~MOVE_WALK if $flag eq "-walk";
101     $flags &= ~MOVE_FLY_LOW if $flag eq "-fly_low";
102     $flags &= ~MOVE_FLY_HIGH if $flag eq "-fly_high";
103     $flags &= ~MOVE_FLYING if $flag eq "-flying";
104     $flags &= ~MOVE_SWIM if $flag eq "-swim";
105     $flags &= ~MOVE_BOAT if $flag eq "-boat";
106     $flags &= ~MOVE_ALL if $flag eq "-all";
107     }
108    
109     $ob->{$attr} = $flags;
110     }
111    
112 root 1.14 if (defined (my $v = delete $ob->{no_pass})) {
113     $ob->{move_block} = $v ? MOVE_ALL : 0;
114     }
115 root 1.50 if (defined (my $v = delete $ob->{slow_move})) {
116     $ob->{move_slow} |= MOVE_WALK;
117     $ob->{move_slow_penalty} = $v;
118     }
119 root 1.14 if (defined (my $v = delete $ob->{walk_on})) {
120     $ob->{move_on} = $v ? $ob->{move_on} | MOVE_WALK
121     : $ob->{move_on} & ~MOVE_WALK;
122     }
123     if (defined (my $v = delete $ob->{walk_off})) {
124     $ob->{move_off} = $v ? $ob->{move_off} | MOVE_WALK
125     : $ob->{move_off} & ~MOVE_WALK;
126     }
127     if (defined (my $v = delete $ob->{fly_on})) {
128     $ob->{move_on} = $v ? $ob->{move_on} | MOVE_FLY_LOW
129     : $ob->{move_on} & ~MOVE_FLY_LOW;
130     }
131     if (defined (my $v = delete $ob->{fly_off})) {
132     $ob->{move_off} = $v ? $ob->{move_off} | MOVE_FLY_LOW
133     : $ob->{move_off} & ~MOVE_FLY_LOW;
134     }
135     if (defined (my $v = delete $ob->{flying})) {
136     $ob->{move_type} = $v ? $ob->{move_type} | MOVE_FLY_LOW
137     : $ob->{move_type} & ~MOVE_FLY_LOW;
138     }
139    
140 root 1.52 $ob
141     }
142    
143     sub normalize_arch($) {
144     my ($ob) = @_;
145    
146     normalize_object $ob;
147    
148     my $arch = $ARCH{$ob->{_name}}
149     or (warn "$ob->{_name}: no such archetype", return $ob);
150    
151     if ($arch->{type} == 22) { # map
152     my %normalize = (
153     "enter_x" => "hp",
154     "enter_y" => "sp",
155     "width" => "x",
156     "height" => "y",
157     "reset_timeout" => "weight",
158     "swap_time" => "value",
159     "difficulty" => "level",
160     "darkness" => "invisible",
161     "fixed_resettime" => "stand_still",
162     );
163    
164     while (my ($k2, $k1) = each %normalize) {
165     if (defined (my $v = delete $ob->{$k1})) {
166     $ob->{$k2} = $v;
167     }
168     }
169     } else {
170     # if value matches archetype default, delete
171     while (my ($k, $v) = each %$ob) {
172     if (exists $arch->{$k} and $arch->{$k} eq $v) {
173     next if $k eq "_name";
174     delete $ob->{$k};
175     }
176 root 1.14 }
177     }
178    
179     $ob
180     }
181 root 1.13
182 root 1.50 sub read_pak($) {
183     my ($path) = @_;
184    
185     my %pak;
186 elmex 1.1
187 root 1.50 open my $fh, "<", $path
188     or Carp::croak "$_[0]: $!";
189     binmode $fh;
190     while (<$fh>) {
191     my ($type, $id, $len, $path) = split;
192     $path =~ s/.*\///;
193     read $fh, $pak{$path}, $len;
194 elmex 1.1 }
195 root 1.50
196     \%pak
197 elmex 1.1 }
198    
199 root 1.50 sub read_arch($) {
200     my ($path) = @_;
201    
202     my %arc;
203     my ($more, $prev);
204    
205     open my $fh, "<", $path
206     or Carp::croak "$path: $!";
207    
208     binmode $fh;
209 elmex 1.1
210 root 1.50 my $parse_block; $parse_block = sub {
211     my %arc = @_;
212 elmex 1.1
213     while (<$fh>) {
214     s/\s+$//;
215 root 1.50 if (/^end$/i) {
216     last;
217     } elsif (/^arch (\S+)$/) {
218     push @{ $arc{inventory} }, normalize_arch $parse_block->(_name => $1);
219     } elsif (/^lore$/) {
220     while (<$fh>) {
221     last if /^endlore\s*$/i;
222     $arc{lore} .= $_;
223 elmex 1.1 }
224 root 1.50 } elsif (/^msg$/) {
225     while (<$fh>) {
226     last if /^endmsg\s*$/i;
227     $arc{msg} .= $_;
228     }
229     } elsif (/^(\S+)\s*(.*)$/) {
230     $arc{lc $1} = $2;
231 elmex 1.1 } elsif (/^\s*($|#)/) {
232     #
233     } else {
234 root 1.50 warn "$path: unparsable line '$_' in arch $arc{_name}";
235 elmex 1.1 }
236     }
237    
238 root 1.50 \%arc
239     };
240 elmex 1.2
241 root 1.50 while (<$fh>) {
242     s/\s+$//;
243     if (/^more$/i) {
244     $more = $prev;
245     } elsif (/^object (\S+)$/i) {
246     my $name = $1;
247 root 1.52 my $arc = normalize_object $parse_block->(_name => $name);
248 elmex 1.1
249 root 1.50 if ($more) {
250     $more->{more} = $arc;
251     } else {
252     $arc{$name} = $arc;
253     }
254     $prev = $arc;
255     $more = undef;
256     } elsif (/^arch (\S+)$/i) {
257     push @{ $arc{arch} }, normalize_arch $parse_block->(_name => $1);
258     } elsif (/^\s*($|#)/) {
259     #
260     } else {
261     warn "$path: unparseable top-level line '$_'";
262     }
263 elmex 1.2 }
264 root 1.50
265     undef $parse_block; # work around bug in perl not freeing $fh etc.
266    
267     \%arc
268 elmex 1.1 }
269    
270 elmex 1.10 # put all archs into a hash with editor_face as it's key
271     # NOTE: the arrays in the hash values are references to
272     # the archs from $ARCH
273     sub editor_archs {
274     my %paths;
275    
276 root 1.15 for (keys %ARCH) {
277     my $arch = $ARCH{$_};
278 elmex 1.10 push @{$paths{$arch->{editor_folder}}}, \$arch;
279     }
280    
281 root 1.15 \%paths
282 elmex 1.10 }
283    
284 root 1.17 =item ($minx, $miny, $maxx, $maxy) = arch_extents $arch
285    
286     arch_extents determines the extents of the given arch's face(s), linked
287     faces and single faces are handled here it returns (minx, miny, maxx,
288     maxy)
289    
290     =cut
291    
292 root 1.15 sub arch_extents {
293 elmex 1.10 my ($a) = @_;
294    
295 root 1.15 my $o = $ARCH{$a->{_name}}
296     or return;
297 elmex 1.10
298 root 1.45 my $face = $FACE{$a->{face} || $o->{face} || "blank.111"}
299 root 1.15 or (warn "no face data found for arch '$a->{_name}'"), return;
300 elmex 1.10
301 root 1.15 if ($face->{w} > 1 || $face->{h} > 1) {
302     # bigface
303     return (0, 0, $face->{w} - 1, $face->{h} - 1);
304    
305     } elsif ($o->{more}) {
306     # linked face
307     my ($minx, $miny, $maxx, $maxy) = ($o->{x}, $o->{y}) x 2;
308    
309     for (; $o; $o = $o->{more}) {
310     $minx = min $minx, $o->{x};
311     $miny = min $miny, $o->{y};
312     $maxx = max $maxx, $o->{x};
313     $maxy = max $maxy, $o->{y};
314     }
315    
316     return ($minx, $miny, $maxx, $maxy);
317 elmex 1.10
318     } else {
319     # single face
320 root 1.15 return (0, 0, 0, 0);
321 elmex 1.10 }
322     }
323    
324 root 1.19 =item $type = arch_attr $arch
325 root 1.17
326     Returns a hashref describing the object and its attributes. It can contain
327     the following keys:
328    
329     name the name, suitable for display purposes
330     ignore
331     attr
332     desc
333     use
334     section => [name => \%attr, name => \%attr]
335 root 1.19 import
336 root 1.17
337     =cut
338    
339     sub arch_attr($) {
340 root 1.46 my ($obj) = @_;
341 root 1.17
342     require Crossfire::Data;
343    
344 root 1.36 my $root;
345 root 1.49 my $attr = { };
346 root 1.46
347     my $arch = $ARCH{ $obj->{_name} };
348     my $type = $obj->{type} || $arch->{type};
349 root 1.17
350 root 1.46 if ($type > 0) {
351     $root = $Crossfire::Data::ATTR{$type};
352 root 1.17 } else {
353 root 1.36 $root = $Crossfire::Data::TYPE{Misc};
354 root 1.18
355     type:
356     for (@Crossfire::Data::ATTR0) {
357     my $req = $_->{required}
358     or die "internal error: ATTR0 without 'required'";
359    
360 root 1.35 keys %$req;
361 root 1.18 while (my ($k, $v) = each %$req) {
362     next type
363 root 1.48 unless $obj->{$k} == $v || $arch->{$k} == $v;
364 root 1.18 }
365    
366 root 1.36 $root = $_;
367 root 1.18 }
368 root 1.17 }
369    
370 root 1.47 my @import = ($root);
371    
372     unshift @import, \%Crossfire::Data::DEFAULT_ATTR
373     unless $type == 116;
374    
375 root 1.36 my (%ignore);
376     my (@section_order, %section, @attr_order);
377    
378     while (my $type = shift @import) {
379     push @import, @{$type->{import} || []};
380    
381     $attr->{$_} ||= $type->{$_}
382     for qw(name desc use);
383    
384     for (@{$type->{ignore} || []}) {
385     $ignore{$_}++ for ref $_ ? @$_ : $_;
386     }
387    
388 root 1.43 for ([general => ($type->{attr} || [])], @{$type->{section} || []}) {
389 root 1.36 my ($name, $attr) = @$_;
390     push @section_order, $name;
391 root 1.43 for (@$attr) {
392 root 1.36 my ($k, $v) = @$_;
393     push @attr_order, $k;
394     $section{$name}{$k} ||= $v;
395     }
396     }
397     }
398    
399     $attr->{section} = [
400     map !exists $section{$_} ? () : do {
401     my $attr = delete $section{$_};
402    
403     [
404     $_,
405 root 1.38 map exists $attr->{$_} && !$ignore{$_}
406     ? [$_ => delete $attr->{$_}] : (),
407 root 1.36 @attr_order
408     ]
409     },
410    
411     exists $section{$_} ? [$_ => delete $section{$_}] : (),
412     @section_order
413     ];
414    
415     $attr
416 root 1.17 }
417    
418 root 1.16 sub arch_edit_sections {
419     # if (edit_type == IGUIConstants.TILE_EDIT_NONE)
420     # edit_type = 0;
421     # else if (edit_type != 0) {
422     # // all flags from 'check_type' must be unset in this arch because they get recalculated now
423     # edit_type &= ~check_type;
424     # }
425     #
426     # }
427     # if ((check_type & IGUIConstants.TILE_EDIT_MONSTER) != 0 &&
428     # getAttributeValue("alive", defarch) == 1 &&
429     # (getAttributeValue("monster", defarch) == 1 ||
430     # getAttributeValue("generator", defarch) == 1)) {
431     # // Monster: monsters/npcs/generators
432     # edit_type |= IGUIConstants.TILE_EDIT_MONSTER;
433     # }
434     # if ((check_type & IGUIConstants.TILE_EDIT_WALL) != 0 &&
435     # arch_type == 0 && getAttributeValue("no_pass", defarch) == 1) {
436     # // Walls
437     # edit_type |= IGUIConstants.TILE_EDIT_WALL;
438     # }
439     # if ((check_type & IGUIConstants.TILE_EDIT_CONNECTED) != 0 &&
440     # getAttributeValue("connected", defarch) != 0) {
441     # // Connected Objects
442     # edit_type |= IGUIConstants.TILE_EDIT_CONNECTED;
443     # }
444     # if ((check_type & IGUIConstants.TILE_EDIT_EXIT) != 0 &&
445     # arch_type == 66 || arch_type == 41 || arch_type == 95) {
446     # // Exit: teleporter/exit/trapdoors
447     # edit_type |= IGUIConstants.TILE_EDIT_EXIT;
448     # }
449     # if ((check_type & IGUIConstants.TILE_EDIT_TREASURE) != 0 &&
450     # getAttributeValue("no_pick", defarch) == 0 && (arch_type == 4 ||
451     # arch_type == 5 || arch_type == 36 || arch_type == 60 ||
452     # arch_type == 85 || arch_type == 111 || arch_type == 123 ||
453     # arch_type == 124 || arch_type == 130)) {
454     # // Treasure: randomtreasure/money/gems/potions/spellbooks/scrolls
455     # edit_type |= IGUIConstants.TILE_EDIT_TREASURE;
456     # }
457     # if ((check_type & IGUIConstants.TILE_EDIT_DOOR) != 0 &&
458     # arch_type == 20 || arch_type == 23 || arch_type == 26 ||
459     # arch_type == 91 || arch_type == 21 || arch_type == 24) {
460     # // Door: door/special door/gates + keys
461     # edit_type |= IGUIConstants.TILE_EDIT_DOOR;
462     # }
463     # if ((check_type & IGUIConstants.TILE_EDIT_EQUIP) != 0 &&
464     # getAttributeValue("no_pick", defarch) == 0 && ((arch_type >= 13 &&
465     # arch_type <= 16) || arch_type == 33 || arch_type == 34 ||
466     # arch_type == 35 || arch_type == 39 || arch_type == 70 ||
467     # arch_type == 87 || arch_type == 99 || arch_type == 100 ||
468     # arch_type == 104 || arch_type == 109 || arch_type == 113 ||
469     # arch_type == 122 || arch_type == 3)) {
470     # // Equipment: weapons/armour/wands/rods
471     # edit_type |= IGUIConstants.TILE_EDIT_EQUIP;
472     # }
473     #
474     # return(edit_type);
475     #
476     #
477     }
478    
479 root 1.50 sub cache_file($$&&) {
480     my ($src, $cache, $load, $create) = @_;
481 root 1.24
482 root 1.50 my ($size, $mtime) = (stat $src)[7,9]
483     or Carp::croak "$src: $!";
484    
485     if (-e $cache) {
486     my $ref = eval { load_ref $cache };
487    
488     if ($ref->{version} == 1
489     && $ref->{size} == $size
490     && $ref->{mtime} == $mtime
491     && eval { $load->($ref->{data}); 1 }) {
492     return;
493     }
494     }
495    
496     my $ref = {
497     version => 1,
498     size => $size,
499     mtime => $mtime,
500     data => $create->(),
501     };
502    
503     $load->($ref->{data});
504    
505     save_ref $ref, $cache;
506     }
507    
508     =item set_libdir $path
509    
510     Sets the library directory to the given path
511     (default: $ENV{CROSSFIRE_LIBDIR}).
512    
513     You have to (re-)load the archetypes and tilecache manually after steting
514     the library path.
515    
516     =cut
517    
518     sub set_libdir($) {
519     $LIB = $_[0];
520 root 1.24 }
521    
522 root 1.50 =item load_archetypes
523    
524     (Re-)Load archetypes into %ARCH.
525    
526     =cut
527    
528     sub load_archetypes() {
529     cache_file "$LIB/archetypes", "$VARDIR/archetypes.pst", sub {
530     *ARCH = $_[0];
531     }, sub {
532     read_arch "$LIB/archetypes"
533     };
534     }
535 root 1.15
536 root 1.50 =item load_tilecache
537    
538     (Re-)Load %TILE and %FACE.
539    
540     =cut
541    
542     sub load_tilecache() {
543     require Gtk2;
544    
545     cache_file "$LIB/crossfire.0", "$VARDIR/tilecache.pst", sub {
546     $TILE = new_from_file Gtk2::Gdk::Pixbuf "$VARDIR/tilecache.png"
547     or die "$VARDIR/tilecache.png: $!";
548     *FACE = $_[0];
549     }, sub {
550     require File::Temp;
551    
552     my $tile = read_pak "$LIB/crossfire.0";
553    
554     my %cache;
555    
556     my $idx = 0;
557    
558     for my $name (sort keys %$tile) {
559     my ($fh, $filename) = File::Temp::tempfile ();
560     print $fh $tile->{$name};
561     close $fh;
562     my $pb = new_from_file Gtk2::Gdk::Pixbuf $filename;
563     unlink $filename;
564    
565     my $tile = $cache{$name} = {
566     pb => $pb,
567     idx => $idx,
568     w => int $pb->get_width / TILESIZE,
569     h => int $pb->get_height / TILESIZE,
570     };
571    
572    
573     $idx += $tile->{w} * $tile->{h};
574     }
575    
576     my $pb = new Gtk2::Gdk::Pixbuf "rgb", 1, 8, 64 * TILESIZE, TILESIZE * int +($idx + 63) / 64;
577    
578     while (my ($name, $tile) = each %cache) {
579     my $tpb = delete $tile->{pb};
580     my $ofs = $tile->{idx};
581    
582     for my $x (0 .. $tile->{w} - 1) {
583     for my $y (0 .. $tile->{h} - 1) {
584     my $idx = $ofs + $x + $y * $tile->{w};
585     $tpb->copy_area ($x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
586     $pb, ($idx % 64) * TILESIZE, TILESIZE * int $idx / 64);
587     }
588     }
589     }
590    
591     $pb->save ("$VARDIR/tilecache.png", "png");
592    
593     \%cache
594     };
595     }
596 root 1.15
597 elmex 1.1 =head1 AUTHOR
598    
599     Marc Lehmann <schmorp@schmorp.de>
600     http://home.schmorp.de/
601    
602     Robin Redeker <elmex@ta-sa.org>
603     http://www.ta-sa.org/
604    
605     =cut
606 root 1.4
607     1