ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra/Deliantra.pm
Revision: 1.93
Committed: Sat Mar 3 19:38:06 2007 UTC (17 years, 2 months ago) by root
Branch: MAIN
Changes since 1.92: +1 -0 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 root 1.78 our $VERSION = '0.96';
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 root 1.70 use JSON::Syck (); #TODO#d# replace by JSON::PC when it becomes available == working
25    
26     sub from_json($) {
27     $JSON::Syck::ImplicitUnicode = 1;
28     JSON::Syck::Load $_[0]
29     }
30    
31     sub to_json($) {
32     $JSON::Syck::ImplicitUnicode = 0;
33     JSON::Syck::Dump $_[0]
34     }
35    
36 root 1.50 our $LIB = $ENV{CROSSFIRE_LIBDIR};
37 root 1.7
38 root 1.75 our $VARDIR = $ENV{HOME} ? "$ENV{HOME}/.crossfire"
39     : $ENV{AppData} ? "$ENV{APPDATA}/crossfire"
40     : File::Spec->tmpdir . "/crossfire";
41 root 1.50
42     mkdir $VARDIR, 0777;
43 elmex 1.1
44 root 1.7 sub TILESIZE (){ 32 }
45 elmex 1.1
46 root 1.15 our %ARCH;
47     our %FACE;
48     our $TILE;
49 elmex 1.1
50 root 1.13 our %FIELD_MULTILINE = (
51 root 1.50 msg => "endmsg",
52     lore => "endlore",
53     maplore => "endmaplore",
54 root 1.13 );
55    
56 root 1.58 # movement bit type, PITA
57     our %FIELD_MOVEMENT = map +($_ => undef),
58     qw(move_type move_block move_allow move_on move_off move_slow);
59    
60 root 1.54 # same as in server save routine, to (hopefully) be compatible
61     # to the other editors.
62 root 1.56 our @FIELD_ORDER_MAP = (qw(
63 root 1.86 file_format_version
64 root 1.70 name attach swap_time reset_timeout fixed_resettime difficulty region
65 root 1.56 shopitems shopgreed shopmin shopmax shoprace
66     darkness width height enter_x enter_y msg maplore
67     unique template
68     outdoor temp pressure humid windspeed winddir sky nosmooth
69     tile_path_1 tile_path_2 tile_path_3 tile_path_4
70     ));
71    
72 root 1.54 our @FIELD_ORDER = (qw(
73 root 1.57 elevation
74    
75 root 1.70 name name_pl custom_name attach title race
76 root 1.54 slaying skill msg lore other_arch face
77 root 1.56 #todo-events
78 root 1.54 animation is_animated
79 root 1.59 str dex con wis pow cha int
80 root 1.54 hp maxhp sp maxsp grace maxgrace
81     exp perm_exp expmul
82     food dam luck wc ac x y speed speed_left move_state attack_movement
83 root 1.59 nrof level direction type subtype attacktype
84 root 1.54
85     resist_physical resist_magic resist_fire resist_electricity
86     resist_cold resist_confusion resist_acid resist_drain
87     resist_weaponmagic resist_ghosthit resist_poison resist_slow
88     resist_paralyze resist_turn_undead resist_fear resist_cancellation
89     resist_deplete resist_death resist_chaos resist_counterspell
90     resist_godpower resist_holyword resist_blind resist_internal
91     resist_life_stealing resist_disease
92    
93     path_attuned path_repelled path_denied material materialname
94     value carrying weight invisible state magic
95     last_heal last_sp last_grace last_eat
96     connected glow_radius randomitems npx_status npc_program
97     run_away pick_up container will_apply smoothlevel
98     current_weapon_script weapontype tooltype elevation client_type
99     item_power duration range
100     range_modifier duration_modifier dam_modifier gen_sp_armour
101     move_type move_block move_allow move_on move_off move_on move_slow move_slow_penalty
102    
103     alive wiz was_wiz applied unpaid can_use_shield no_pick is_animated monster
104     friendly generator is_thrown auto_apply treasure player sold see_invisible
105     can_roll overlay_floor is_turnable is_used_up identified reflecting changing
106     splitting hitback startequip blocksview undead scared unaggressive
107     reflect_missile reflect_spell no_magic no_fix_player is_lightable tear_down
108     run_away pick_up unique no_drop can_cast_spell can_use_scroll can_use_range
109     can_use_bow can_use_armour can_use_weapon can_use_ring has_ready_range
110     has_ready_bow xrays is_floor lifesave no_strength sleep stand_still
111     random_move only_attack confused stealth cursed damned see_anywhere
112     known_magical known_cursed can_use_skill been_applied has_ready_scroll
113     can_use_rod can_use_horn make_invisible inv_locked is_wooded is_hilly
114     has_ready_skill has_ready_weapon no_skill_ident is_blind can_see_in_dark
115     is_cauldron is_dust no_steal one_hit berserk neutral no_attack no_damage
116     activate_on_push activate_on_release is_water use_content_on_gen is_buildable
117    
118     body_range body_arm body_torso body_head body_neck body_skill
119     body_finger body_shoulder body_foot body_hand body_wrist body_waist
120     ));
121 root 1.13
122 root 1.62 our %EVENT_TYPE = (
123     apply => 1,
124     attack => 2,
125     death => 3,
126     drop => 4,
127     pickup => 5,
128     say => 6,
129     stop => 7,
130     time => 8,
131     throw => 9,
132     trigger => 10,
133     close => 11,
134     timer => 12,
135     );
136    
137 root 1.50 sub MOVE_WALK (){ 0x01 }
138     sub MOVE_FLY_LOW (){ 0x02 }
139     sub MOVE_FLY_HIGH (){ 0x04 }
140     sub MOVE_FLYING (){ 0x06 }
141     sub MOVE_SWIM (){ 0x08 }
142     sub MOVE_BOAT (){ 0x10 }
143 root 1.58 sub MOVE_KNOWN (){ 0x1f } # all of above
144 root 1.90 sub MOVE_ALL (){ 0x10000 } # very special value
145    
146     our %MOVE_TYPE = (
147     walk => MOVE_WALK,
148     fly_low => MOVE_FLY_LOW,
149     fly_high => MOVE_FLY_HIGH,
150     flying => MOVE_FLYING,
151     swim => MOVE_SWIM,
152     boat => MOVE_BOAT,
153     all => MOVE_ALL,
154     );
155    
156     our @MOVE_TYPE = qw(all walk flying fly_low fly_high swim boat);
157    
158     {
159     package Crossfire::MoveType;
160    
161     use overload
162 root 1.93 '=' => sub { bless [@{$_[0]}], ref $_[0] },
163 root 1.90 '""' => \&as_string,
164     '>=' => sub { $_[0][0] & $MOVE_TYPE{$_[1]} ? $_[0][1] & $MOVE_TYPE{$_[1]} : undef },
165     '+=' => sub { $_[0][0] |= $MOVE_TYPE{$_[1]}; $_[0][1] |= $MOVE_TYPE{$_[1]}; &normalise },
166     '-=' => sub { $_[0][0] |= $MOVE_TYPE{$_[1]}; $_[0][1] &= ~$MOVE_TYPE{$_[1]}; &normalise },
167     '/=' => sub { $_[0][0] &= ~$MOVE_TYPE{$_[1]}; &normalise },
168     'x=' => sub {
169     my $cur = $_[0] >= $_[1];
170     if (!defined $cur) {
171     $_[0] += $_[1];
172     } elsif ($cur) {
173     $_[0] -= $_[1];
174     } else {
175     $_[0] /= $_[1];
176     }
177    
178     $_[0]
179     },
180 root 1.91 'eq' => sub { "$_[0]" eq "$_[1]" },
181     'ne' => sub { "$_[0]" ne "$_[1]" },
182 root 1.90 ;
183     }
184    
185     sub Crossfire::MoveType::new {
186     my ($class, $string) = @_;
187    
188     my $mask;
189     my $value;
190    
191 root 1.92 if ($string =~ /^\s*\d+\s*$/) {
192     $mask = MOVE_ALL;
193     $value = $string+0;
194     } else {
195     for (split /\s+/, lc $string) {
196     if (s/^-//) {
197     $mask |= $MOVE_TYPE{$_};
198     $value &= ~$MOVE_TYPE{$_};
199     } else {
200     $mask |= $MOVE_TYPE{$_};
201     $value |= $MOVE_TYPE{$_};
202     }
203 root 1.90 }
204     }
205    
206     (bless [$mask, $value], $class)->normalise
207     }
208    
209     sub Crossfire::MoveType::normalise {
210     my ($self) = @_;
211    
212     if ($self->[0] & MOVE_ALL) {
213     my $mask = ~(($self->[1] & MOVE_ALL ? $self->[1] : ~$self->[1]) & $self->[0] & ~MOVE_ALL);
214     $self->[0] &= $mask;
215     $self->[1] &= $mask;
216     }
217    
218     $self->[1] &= $self->[0];
219    
220     $self
221     }
222    
223     sub Crossfire::MoveType::as_string {
224     my ($self) = @_;
225    
226     my @res;
227    
228     my ($mask, $value) = @$self;
229    
230     for (@Crossfire::MOVE_TYPE) {
231     my $bit = $Crossfire::MOVE_TYPE{$_};
232     if (($mask & $bit) == $bit && (($value & $bit) == $bit || ($value & $bit) == 0)) {
233     $mask &= ~$bit;
234     push @res, $value & $bit ? $_ : "-$_";
235     }
236     }
237    
238     join " ", @res
239     }
240 root 1.14
241 root 1.28 sub load_ref($) {
242     my ($path) = @_;
243    
244 root 1.63 open my $fh, "<:raw:perlio", $path
245 root 1.31 or die "$path: $!";
246 root 1.28 local $/;
247 root 1.33
248 root 1.53 thaw <$fh>
249 root 1.28 }
250    
251     sub save_ref($$) {
252     my ($ref, $path) = @_;
253    
254 root 1.63 open my $fh, ">:raw:perlio", "$path~"
255 root 1.28 or die "$path~: $!";
256 root 1.53 print $fh freeze $ref;
257 root 1.28 close $fh;
258     rename "$path~", $path
259     or die "$path: $!";
260     }
261    
262 root 1.72 my %attack_mask = (
263     physical => 0x00000001,
264     magic => 0x00000002,
265     fire => 0x00000004,
266     electricity => 0x00000008,
267     cold => 0x00000010,
268     confusion => 0x00000020,
269     acid => 0x00000040,
270     drain => 0x00000080,
271     weaponmagic => 0x00000100,
272     ghosthit => 0x00000200,
273     poison => 0x00000400,
274     slow => 0x00000800,
275     paralyze => 0x00001000,
276     turn_undead => 0x00002000,
277     fear => 0x00004000,
278     cancellation => 0x00008000,
279     deplete => 0x00010000,
280     death => 0x00020000,
281     chaos => 0x00040000,
282     counterspell => 0x00080000,
283     godpower => 0x00100000,
284     holyword => 0x00200000,
285     blind => 0x00400000,
286     internal => 0x00800000,
287     life_stealing => 0x01000000,
288     disease => 0x02000000,
289     );
290    
291     sub _add_resist($$$) {
292     my ($ob, $mask, $value) = @_;
293    
294     while (my ($k, $v) = each %attack_mask) {
295 root 1.73 $ob->{"resist_$k"} = min 100, max -100, $ob->{"resist_$k"} + $value if $mask & $v;
296 root 1.72 }
297     }
298    
299 root 1.82 my %MATERIAL = reverse
300     paper => 1,
301     iron => 2,
302     glass => 4,
303     leather => 8,
304     wood => 16,
305     organic => 32,
306     stone => 64,
307     cloth => 128,
308     adamant => 256,
309     liquid => 512,
310     tin => 1024,
311     bone => 2048,
312     ice => 4096,
313 root 1.83
314     # guesses
315     runestone => 12,
316     bronze => 18,
317     "ancient wood" => 20,
318     glass => 36,
319     marble => 66,
320     ice => 68,
321     stone => 70,
322     stone => 80,
323     cloth => 136,
324     ironwood => 144,
325 root 1.85 adamantium => 258,
326 root 1.84 glacium => 260,
327 root 1.83 blood => 544,
328 root 1.82 ;
329    
330 root 1.62 # object as in "Object xxx", i.e. archetypes
331 root 1.52 sub normalize_object($) {
332 root 1.14 my ($ob) = @_;
333    
334 root 1.82 # convert material bitset to materialname, if possible
335     if (exists $ob->{material}) {
336     if (!$ob->{material}) {
337     delete $ob->{material};
338     } elsif (exists $ob->{materialname}) {
339     if ($MATERIAL{$ob->{material}} eq $ob->{materialname}) {
340     delete $ob->{material};
341     } else {
342     warn "object $ob->{_name} has both materialname ($ob->{materialname}) and material ($ob->{material}) set.\n";
343     delete $ob->{material}; # assume materilname is more specific and nuke material
344     }
345 root 1.83 } elsif (my $name = $MATERIAL{$ob->{material}}) {
346     delete $ob->{material};
347     $ob->{materialname} = $name;
348 root 1.82 } else {
349 root 1.83 warn "object $ob->{_name} has unknown material ($ob->{material}) set.\n";
350 root 1.82 }
351     }
352    
353 root 1.62 # nuke outdated or never supported fields
354 root 1.72 delete @$ob{qw(
355 root 1.50 can_knockback can_parry can_impale can_cut can_dam_armour
356     can_apply pass_thru can_pass_thru
357 root 1.72 )};
358    
359     if (my $mask = delete $ob->{immune} ) { _add_resist $ob, $mask, 100; }
360     if (my $mask = delete $ob->{protected} ) { _add_resist $ob, $mask, 30; }
361     if (my $mask = delete $ob->{vulnerable}) { _add_resist $ob, $mask, -100; }
362 root 1.14
363 root 1.62 # convert movement strings to bitsets
364 root 1.58 for my $attr (keys %FIELD_MOVEMENT) {
365 root 1.50 next unless exists $ob->{$attr};
366 root 1.58
367 root 1.90 $ob->{$attr} = new Crossfire::MoveType $ob->{$attr};
368 root 1.50 }
369    
370 root 1.62 # convert outdated movement flags to new movement sets
371 root 1.14 if (defined (my $v = delete $ob->{no_pass})) {
372 root 1.90 $ob->{move_block} = new Crossfire::MoveType $v ? "all" : "";
373 root 1.14 }
374 root 1.50 if (defined (my $v = delete $ob->{slow_move})) {
375 root 1.90 $ob->{move_slow} += "walk";
376 root 1.50 $ob->{move_slow_penalty} = $v;
377     }
378 root 1.14 if (defined (my $v = delete $ob->{walk_on})) {
379 root 1.90 $ob->{move_on} ||= new Crossfire::MoveType; if ($v) { $ob->{move_on} += "walk" } else { $ob->{move_on} -= "walk" }
380 root 1.14 }
381     if (defined (my $v = delete $ob->{walk_off})) {
382 root 1.90 $ob->{move_off} ||= new Crossfire::MoveType; if ($v) { $ob->{move_off} += "walk" } else { $ob->{move_off} -= "walk" }
383 root 1.14 }
384     if (defined (my $v = delete $ob->{fly_on})) {
385 root 1.90 $ob->{move_on} ||= new Crossfire::MoveType; if ($v) { $ob->{move_on} += "fly_low" } else { $ob->{move_on} -= "fly_low" }
386 root 1.14 }
387     if (defined (my $v = delete $ob->{fly_off})) {
388 root 1.90 $ob->{move_off} ||= new Crossfire::MoveType; if ($v) { $ob->{move_off} += "fly_low" } else { $ob->{move_off} -= "fly_low" }
389 root 1.14 }
390     if (defined (my $v = delete $ob->{flying})) {
391 root 1.90 $ob->{move_type} ||= new Crossfire::MoveType; if ($v) { $ob->{move_type} += "fly_low" } else { $ob->{move_type} -= "fly_low" }
392 root 1.14 }
393    
394 root 1.62 # convert idiotic event_xxx things into objects
395     while (my ($event, $subtype) = each %EVENT_TYPE) {
396     if (exists $ob->{"event_${event}_plugin"}) {
397     push @{$ob->{inventory}}, {
398     _name => "event_$event",
399     title => delete $ob->{"event_${event}_plugin"},
400     slaying => delete $ob->{"event_${event}"},
401     name => delete $ob->{"event_${event}_options"},
402     };
403     }
404     }
405    
406 root 1.73 # some archetypes had "+3" instead of the canonical "3", so fix
407     $ob->{dam} *= 1 if exists $ob->{dam};
408    
409 root 1.52 $ob
410     }
411    
412 root 1.62 # arch as in "arch xxx", ie.. objects
413 root 1.52 sub normalize_arch($) {
414     my ($ob) = @_;
415    
416     normalize_object $ob;
417    
418     my $arch = $ARCH{$ob->{_name}}
419     or (warn "$ob->{_name}: no such archetype", return $ob);
420    
421     if ($arch->{type} == 22) { # map
422     my %normalize = (
423     "enter_x" => "hp",
424     "enter_y" => "sp",
425     "width" => "x",
426     "height" => "y",
427     "reset_timeout" => "weight",
428     "swap_time" => "value",
429     "difficulty" => "level",
430     "darkness" => "invisible",
431     "fixed_resettime" => "stand_still",
432     );
433    
434     while (my ($k2, $k1) = each %normalize) {
435     if (defined (my $v = delete $ob->{$k1})) {
436     $ob->{$k2} = $v;
437     }
438     }
439     } else {
440     # if value matches archetype default, delete
441     while (my ($k, $v) = each %$ob) {
442     if (exists $arch->{$k} and $arch->{$k} eq $v) {
443     next if $k eq "_name";
444     delete $ob->{$k};
445     }
446 root 1.14 }
447     }
448    
449 root 1.57 # a speciality for the editor
450     if (exists $ob->{attack_movement}) {
451     my $am = delete $ob->{attack_movement};
452     $ob->{attack_movement_bits_0_3} = $am & 15;
453     $ob->{attack_movement_bits_4_7} = $am & 240;
454     }
455    
456 root 1.14 $ob
457     }
458 root 1.13
459 root 1.70 sub attr_thaw($) {
460     my ($ob) = @_;
461    
462     $ob->{attach} = from_json $ob->{attach}
463     if exists $ob->{attach};
464    
465     $ob
466     }
467    
468     sub attr_freeze($) {
469     my ($ob) = @_;
470    
471     $ob->{attach} = Crossfire::to_json $ob->{attach}
472     if exists $ob->{attach};
473    
474     $ob
475     }
476    
477 root 1.50 sub read_pak($) {
478     my ($path) = @_;
479    
480     my %pak;
481 elmex 1.1
482 root 1.63 open my $fh, "<:raw:perlio", $path
483 root 1.50 or Carp::croak "$_[0]: $!";
484     binmode $fh;
485     while (<$fh>) {
486     my ($type, $id, $len, $path) = split;
487     $path =~ s/.*\///;
488     read $fh, $pak{$path}, $len;
489 elmex 1.1 }
490 root 1.50
491     \%pak
492 elmex 1.1 }
493    
494 root 1.64 sub read_arch($;$) {
495     my ($path, $toplevel) = @_;
496 root 1.50
497     my %arc;
498     my ($more, $prev);
499 root 1.80 my $comment;
500 root 1.50
501 root 1.63 open my $fh, "<:raw:perlio:utf8", $path
502 root 1.50 or Carp::croak "$path: $!";
503    
504 elmex 1.68 # binmode $fh;
505 elmex 1.1
506 root 1.50 my $parse_block; $parse_block = sub {
507     my %arc = @_;
508 elmex 1.1
509     while (<$fh>) {
510     s/\s+$//;
511 root 1.50 if (/^end$/i) {
512     last;
513 root 1.80
514 root 1.55 } elsif (/^arch (\S+)$/i) {
515 root 1.70 push @{ $arc{inventory} }, attr_thaw normalize_arch $parse_block->(_name => $1);
516 root 1.80
517 root 1.55 } elsif (/^lore$/i) {
518 root 1.50 while (<$fh>) {
519     last if /^endlore\s*$/i;
520     $arc{lore} .= $_;
521 elmex 1.1 }
522 root 1.55 } elsif (/^msg$/i) {
523 root 1.50 while (<$fh>) {
524     last if /^endmsg\s*$/i;
525     $arc{msg} .= $_;
526     }
527 root 1.65 } elsif (/^anim$/i) {
528     while (<$fh>) {
529     last if /^mina\s*$/i;
530     chomp;
531     push @{ $arc{anim} }, $_;
532     }
533 root 1.50 } elsif (/^(\S+)\s*(.*)$/) {
534     $arc{lc $1} = $2;
535 root 1.80 } elsif (/^\s*#/) {
536 root 1.81 $arc{_comment} .= "$_\n";
537 root 1.80
538     } elsif (/^\s*$/) {
539 elmex 1.1 #
540     } else {
541 root 1.50 warn "$path: unparsable line '$_' in arch $arc{_name}";
542 elmex 1.1 }
543     }
544    
545 root 1.50 \%arc
546     };
547 elmex 1.2
548 root 1.50 while (<$fh>) {
549     s/\s+$//;
550     if (/^more$/i) {
551     $more = $prev;
552     } elsif (/^object (\S+)$/i) {
553     my $name = $1;
554 root 1.80 my $arc = attr_thaw normalize_object $parse_block->(_name => $name, _comment => $comment);
555 root 1.81 undef $comment;
556 root 1.80 delete $arc{_comment} unless length $arc{_comment};
557 elmex 1.71 $arc->{_atype} = 'object';
558 elmex 1.1
559 root 1.50 if ($more) {
560     $more->{more} = $arc;
561     } else {
562     $arc{$name} = $arc;
563     }
564     $prev = $arc;
565     $more = undef;
566     } elsif (/^arch (\S+)$/i) {
567 root 1.55 my $name = $1;
568 root 1.80 my $arc = attr_thaw normalize_arch $parse_block->(_name => $name, _comment => $comment);
569 root 1.81 undef $comment;
570 root 1.80 delete $arc{_comment} unless length $arc{_comment};
571 elmex 1.71 $arc->{_atype} = 'arch';
572 root 1.55
573     if ($more) {
574     $more->{more} = $arc;
575     } else {
576     push @{ $arc{arch} }, $arc;
577     }
578     $prev = $arc;
579     $more = undef;
580 root 1.64 } elsif ($toplevel && /^(\S+)\s+(.*)$/) {
581     if ($1 eq "lev_array") {
582     while (<$fh>) {
583     last if /^endplst\s*$/;
584     push @{$toplevel->{lev_array}}, $_+0;
585     }
586     } else {
587     $toplevel->{$1} = $2;
588     }
589 root 1.81 } elsif (/^\s*#/) {
590     $comment .= "$_\n";
591 root 1.50 } elsif (/^\s*($|#)/) {
592     #
593     } else {
594 root 1.64 die "$path: unparseable top-level line '$_'";
595 root 1.50 }
596 elmex 1.2 }
597 root 1.50
598     undef $parse_block; # work around bug in perl not freeing $fh etc.
599    
600     \%arc
601 elmex 1.1 }
602    
603 elmex 1.71 sub archlist_to_string {
604     my ($arch) = @_;
605    
606     my $str;
607    
608     my $append; $append = sub {
609     my %a = %{$_[0]};
610    
611     Crossfire::attr_freeze \%a;
612     Crossfire::normalize_arch \%a;
613    
614     # undo the bit-split we did before
615     if (exists $a{attack_movement_bits_0_3} or exists $a{attack_movement_bits_4_7}) {
616     $a{attack_movement} = (delete $a{attack_movement_bits_0_3})
617     | (delete $a{attack_movement_bits_4_7});
618     }
619    
620 root 1.81 if (my $comment = delete $a{_comment}) {
621     if ($comment =~ /[^\n\s#]/) {
622     $str .= $comment;
623     }
624     }
625    
626 elmex 1.71 $str .= ((exists $a{_atype}) ? $a{_atype} : 'arch'). " $a{_name}\n";
627    
628 root 1.81 my $inv = delete $a{inventory};
629 elmex 1.71 my $more = delete $a{more}; # arches do not support 'more', but old maps can contain some
630     my $anim = delete $a{anim};
631    
632 root 1.87 if ($a{_atype} eq 'object') {
633     $str .= join "\n", "anim", @$anim, "mina\n"
634     if $anim;
635     }
636    
637 elmex 1.71 my @kv;
638    
639     for ($a{_name} eq "map"
640     ? @Crossfire::FIELD_ORDER_MAP
641     : @Crossfire::FIELD_ORDER) {
642     push @kv, [$_, delete $a{$_}]
643     if exists $a{$_};
644     }
645    
646     for (sort keys %a) {
647     next if /^_/; # ignore our _-keys
648     push @kv, [$_, delete $a{$_}];
649     }
650    
651     for (@kv) {
652     my ($k, $v) = @$_;
653    
654     if (my $end = $Crossfire::FIELD_MULTILINE{$k}) {
655     $v =~ s/\n$//;
656     $str .= "$k\n$v\n$end\n";
657     } else {
658     $str .= "$k $v\n";
659     }
660     }
661    
662     if ($inv) {
663     $append->($_) for @$inv;
664     }
665    
666     $str .= "end\n";
667    
668 root 1.81 if ($a{_atype} eq 'object') {
669     if ($more) {
670     $str .= "more\n";
671     $append->($more) if $more;
672     } else {
673     $str .= "\n";
674     }
675 elmex 1.71 }
676     };
677    
678     for (@$arch) {
679     $append->($_);
680     }
681    
682     $str
683     }
684    
685 elmex 1.10 # put all archs into a hash with editor_face as it's key
686     # NOTE: the arrays in the hash values are references to
687     # the archs from $ARCH
688     sub editor_archs {
689     my %paths;
690    
691 root 1.15 for (keys %ARCH) {
692     my $arch = $ARCH{$_};
693 root 1.62 push @{$paths{$arch->{editor_folder}}}, $arch;
694 elmex 1.10 }
695    
696 root 1.15 \%paths
697 elmex 1.10 }
698    
699 root 1.17 =item ($minx, $miny, $maxx, $maxy) = arch_extents $arch
700    
701     arch_extents determines the extents of the given arch's face(s), linked
702     faces and single faces are handled here it returns (minx, miny, maxx,
703     maxy)
704    
705     =cut
706    
707 root 1.15 sub arch_extents {
708 elmex 1.10 my ($a) = @_;
709    
710 root 1.15 my $o = $ARCH{$a->{_name}}
711     or return;
712 elmex 1.10
713 elmex 1.79 my $face = $FACE{$a->{face} || $o->{face} || "blank.111"};
714     unless ($face) {
715     $face = $FACE{"blank.x11"}
716     or (warn "no face data found for arch '$a->{_name}'"), return;
717     }
718 elmex 1.10
719 root 1.15 if ($face->{w} > 1 || $face->{h} > 1) {
720     # bigface
721     return (0, 0, $face->{w} - 1, $face->{h} - 1);
722    
723     } elsif ($o->{more}) {
724     # linked face
725     my ($minx, $miny, $maxx, $maxy) = ($o->{x}, $o->{y}) x 2;
726    
727     for (; $o; $o = $o->{more}) {
728     $minx = min $minx, $o->{x};
729     $miny = min $miny, $o->{y};
730     $maxx = max $maxx, $o->{x};
731     $maxy = max $maxy, $o->{y};
732     }
733    
734     return ($minx, $miny, $maxx, $maxy);
735 elmex 1.10
736     } else {
737     # single face
738 root 1.15 return (0, 0, 0, 0);
739 elmex 1.10 }
740     }
741    
742 root 1.19 =item $type = arch_attr $arch
743 root 1.17
744     Returns a hashref describing the object and its attributes. It can contain
745     the following keys:
746    
747     name the name, suitable for display purposes
748     ignore
749     attr
750     desc
751     use
752     section => [name => \%attr, name => \%attr]
753 root 1.19 import
754 root 1.17
755     =cut
756    
757     sub arch_attr($) {
758 root 1.46 my ($obj) = @_;
759 root 1.17
760     require Crossfire::Data;
761    
762 root 1.36 my $root;
763 root 1.49 my $attr = { };
764 root 1.46
765     my $arch = $ARCH{ $obj->{_name} };
766     my $type = $obj->{type} || $arch->{type};
767 root 1.17
768 root 1.46 if ($type > 0) {
769     $root = $Crossfire::Data::ATTR{$type};
770 root 1.17 } else {
771 root 1.61 my %a = (%$arch, %$obj);
772 root 1.18
773 root 1.61 if ($a{is_floor} && !$a{alive}) {
774     $root = $Crossfire::Data::TYPE{Floor};
775     } elsif (!$a{is_floor} && $a{alive} && !$a{tear_down}) {
776     $root = $Crossfire::Data::TYPE{"Monster & NPC"};
777     } elsif (!$a{is_floor} && !$a{alive} && $a{move_block}) {
778     $root = $Crossfire::Data::TYPE{Wall};
779     } elsif (!$a{is_floor} && $a{alive} && $a{tear_down}) {
780     $root = $Crossfire::Data::TYPE{"Weak Wall"};
781     } else {
782     $root = $Crossfire::Data::TYPE{Misc};
783 root 1.18 }
784 root 1.17 }
785    
786 root 1.47 my @import = ($root);
787    
788     unshift @import, \%Crossfire::Data::DEFAULT_ATTR
789     unless $type == 116;
790    
791 root 1.36 my (%ignore);
792     my (@section_order, %section, @attr_order);
793    
794     while (my $type = shift @import) {
795     push @import, @{$type->{import} || []};
796    
797     $attr->{$_} ||= $type->{$_}
798     for qw(name desc use);
799    
800     for (@{$type->{ignore} || []}) {
801     $ignore{$_}++ for ref $_ ? @$_ : $_;
802     }
803    
804 root 1.43 for ([general => ($type->{attr} || [])], @{$type->{section} || []}) {
805 root 1.36 my ($name, $attr) = @$_;
806     push @section_order, $name;
807 root 1.43 for (@$attr) {
808 root 1.36 my ($k, $v) = @$_;
809     push @attr_order, $k;
810     $section{$name}{$k} ||= $v;
811     }
812     }
813     }
814    
815     $attr->{section} = [
816     map !exists $section{$_} ? () : do {
817     my $attr = delete $section{$_};
818    
819     [
820     $_,
821 root 1.38 map exists $attr->{$_} && !$ignore{$_}
822     ? [$_ => delete $attr->{$_}] : (),
823 root 1.36 @attr_order
824     ]
825     },
826    
827     exists $section{$_} ? [$_ => delete $section{$_}] : (),
828     @section_order
829     ];
830    
831     $attr
832 root 1.17 }
833    
834 root 1.50 sub cache_file($$&&) {
835     my ($src, $cache, $load, $create) = @_;
836 root 1.24
837 root 1.50 my ($size, $mtime) = (stat $src)[7,9]
838     or Carp::croak "$src: $!";
839    
840     if (-e $cache) {
841     my $ref = eval { load_ref $cache };
842    
843     if ($ref->{version} == 1
844     && $ref->{size} == $size
845     && $ref->{mtime} == $mtime
846     && eval { $load->($ref->{data}); 1 }) {
847     return;
848     }
849     }
850    
851     my $ref = {
852     version => 1,
853     size => $size,
854     mtime => $mtime,
855     data => $create->(),
856     };
857    
858     $load->($ref->{data});
859    
860     save_ref $ref, $cache;
861     }
862    
863     =item set_libdir $path
864    
865     Sets the library directory to the given path
866     (default: $ENV{CROSSFIRE_LIBDIR}).
867    
868     You have to (re-)load the archetypes and tilecache manually after steting
869     the library path.
870    
871     =cut
872    
873     sub set_libdir($) {
874     $LIB = $_[0];
875 root 1.24 }
876    
877 root 1.50 =item load_archetypes
878    
879     (Re-)Load archetypes into %ARCH.
880    
881     =cut
882    
883     sub load_archetypes() {
884     cache_file "$LIB/archetypes", "$VARDIR/archetypes.pst", sub {
885     *ARCH = $_[0];
886     }, sub {
887     read_arch "$LIB/archetypes"
888     };
889     }
890 root 1.15
891 root 1.50 =item load_tilecache
892    
893     (Re-)Load %TILE and %FACE.
894    
895     =cut
896    
897     sub load_tilecache() {
898     require Gtk2;
899    
900     cache_file "$LIB/crossfire.0", "$VARDIR/tilecache.pst", sub {
901     $TILE = new_from_file Gtk2::Gdk::Pixbuf "$VARDIR/tilecache.png"
902     or die "$VARDIR/tilecache.png: $!";
903     *FACE = $_[0];
904     }, sub {
905     my $tile = read_pak "$LIB/crossfire.0";
906    
907     my %cache;
908    
909     my $idx = 0;
910    
911     for my $name (sort keys %$tile) {
912 root 1.60 my $pb = new Gtk2::Gdk::PixbufLoader;
913     $pb->write ($tile->{$name});
914     $pb->close;
915     my $pb = $pb->get_pixbuf;
916 root 1.50
917     my $tile = $cache{$name} = {
918     pb => $pb,
919     idx => $idx,
920     w => int $pb->get_width / TILESIZE,
921     h => int $pb->get_height / TILESIZE,
922     };
923    
924    
925     $idx += $tile->{w} * $tile->{h};
926     }
927    
928     my $pb = new Gtk2::Gdk::Pixbuf "rgb", 1, 8, 64 * TILESIZE, TILESIZE * int +($idx + 63) / 64;
929    
930     while (my ($name, $tile) = each %cache) {
931     my $tpb = delete $tile->{pb};
932     my $ofs = $tile->{idx};
933    
934     for my $x (0 .. $tile->{w} - 1) {
935     for my $y (0 .. $tile->{h} - 1) {
936     my $idx = $ofs + $x + $y * $tile->{w};
937     $tpb->copy_area ($x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
938     $pb, ($idx % 64) * TILESIZE, TILESIZE * int $idx / 64);
939     }
940     }
941     }
942    
943 root 1.60 $pb->save ("$VARDIR/tilecache.png", "png", compression => 1);
944 root 1.50
945     \%cache
946     };
947     }
948 root 1.15
949 elmex 1.1 =head1 AUTHOR
950    
951     Marc Lehmann <schmorp@schmorp.de>
952     http://home.schmorp.de/
953    
954     Robin Redeker <elmex@ta-sa.org>
955     http://www.ta-sa.org/
956    
957     =cut
958 root 1.4
959     1