ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/Deliantra/Deliantra.pm
(Generate patch)

Comparing deliantra/Deliantra/Deliantra.pm (file contents):
Revision 1.18 by root, Wed Feb 22 22:41:22 2006 UTC vs.
Revision 1.71 by elmex, Thu Aug 31 21:09:32 2006 UTC

4 4
5=cut 5=cut
6 6
7package Crossfire; 7package Crossfire;
8 8
9our $VERSION = '0.1'; 9our $VERSION = '0.9';
10 10
11use strict; 11use strict;
12 12
13use base 'Exporter'; 13use base 'Exporter';
14 14
15use Carp (); 15use Carp ();
16use Storable; 16use File::Spec;
17use List::Util qw(min max); 17use List::Util qw(min max);
18use Storable qw(freeze thaw);
18 19
19#XXX: The map_* procedures scream for a map-object
20
21our @EXPORT = 20our @EXPORT = qw(
22 qw(read_pak read_arch %ARCH TILESIZE $TILE %FACE editor_archs arch_extents); 21 read_pak read_arch *ARCH TILESIZE $TILE *FACE editor_archs arch_extents
22);
23 23
24use JSON::Syck (); #TODO#d# replace by JSON::PC when it becomes available == working
25
26sub from_json($) {
27 $JSON::Syck::ImplicitUnicode = 1;
28 JSON::Syck::Load $_[0]
29}
30
31sub to_json($) {
32 $JSON::Syck::ImplicitUnicode = 0;
33 JSON::Syck::Dump $_[0]
34}
35
24our $LIB = $ENV{CROSSFIRE_LIBDIR} 36our $LIB = $ENV{CROSSFIRE_LIBDIR};
25 or Carp::croak "\$CROSSFIRE_LIBDIR must be set\n"; 37
38our $VARDIR = $ENV{HOME} ? "$ENV{HOME}/.crossfire" : File::Spec->tmpdir . "/crossfire";
39
40mkdir $VARDIR, 0777;
26 41
27sub TILESIZE (){ 32 } 42sub TILESIZE (){ 32 }
28 43
29our $CACHEDIR;
30our %ARCH; 44our %ARCH;
31our %FACE; 45our %FACE;
32our $TILE; 46our $TILE;
33 47
34our %FIELD_MULTILINE = ( 48our %FIELD_MULTILINE = (
35 msg => "endmsg", 49 msg => "endmsg",
36 lore => "endlore", 50 lore => "endlore",
51 maplore => "endmaplore",
37); 52);
38 53
39# not used yet, maybe alphabetical is ok 54# movement bit type, PITA
55our %FIELD_MOVEMENT = map +($_ => undef),
56 qw(move_type move_block move_allow move_on move_off move_slow);
57
58# same as in server save routine, to (hopefully) be compatible
59# to the other editors.
60our @FIELD_ORDER_MAP = (qw(
61 name attach swap_time reset_timeout fixed_resettime difficulty region
62 shopitems shopgreed shopmin shopmax shoprace
63 darkness width height enter_x enter_y msg maplore
64 unique template
65 outdoor temp pressure humid windspeed winddir sky nosmooth
66 tile_path_1 tile_path_2 tile_path_3 tile_path_4
67));
68
40our @FIELD_ORDER = (qw(name name_pl)); 69our @FIELD_ORDER = (qw(
70 elevation
41 71
72 name name_pl custom_name attach title race
73 slaying skill msg lore other_arch face
74 #todo-events
75 animation is_animated
76 str dex con wis pow cha int
77 hp maxhp sp maxsp grace maxgrace
78 exp perm_exp expmul
79 food dam luck wc ac x y speed speed_left move_state attack_movement
80 nrof level direction type subtype attacktype
81
82 resist_physical resist_magic resist_fire resist_electricity
83 resist_cold resist_confusion resist_acid resist_drain
84 resist_weaponmagic resist_ghosthit resist_poison resist_slow
85 resist_paralyze resist_turn_undead resist_fear resist_cancellation
86 resist_deplete resist_death resist_chaos resist_counterspell
87 resist_godpower resist_holyword resist_blind resist_internal
88 resist_life_stealing resist_disease
89
90 path_attuned path_repelled path_denied material materialname
91 value carrying weight invisible state magic
92 last_heal last_sp last_grace last_eat
93 connected glow_radius randomitems npx_status npc_program
94 run_away pick_up container will_apply smoothlevel
95 current_weapon_script weapontype tooltype elevation client_type
96 item_power duration range
97 range_modifier duration_modifier dam_modifier gen_sp_armour
98 move_type move_block move_allow move_on move_off move_on move_slow move_slow_penalty
99
100 alive wiz was_wiz applied unpaid can_use_shield no_pick is_animated monster
101 friendly generator is_thrown auto_apply treasure player sold see_invisible
102 can_roll overlay_floor is_turnable is_used_up identified reflecting changing
103 splitting hitback startequip blocksview undead scared unaggressive
104 reflect_missile reflect_spell no_magic no_fix_player is_lightable tear_down
105 run_away pick_up unique no_drop can_cast_spell can_use_scroll can_use_range
106 can_use_bow can_use_armour can_use_weapon can_use_ring has_ready_range
107 has_ready_bow xrays is_floor lifesave no_strength sleep stand_still
108 random_move only_attack confused stealth cursed damned see_anywhere
109 known_magical known_cursed can_use_skill been_applied has_ready_scroll
110 can_use_rod can_use_horn make_invisible inv_locked is_wooded is_hilly
111 has_ready_skill has_ready_weapon no_skill_ident is_blind can_see_in_dark
112 is_cauldron is_dust no_steal one_hit berserk neutral no_attack no_damage
113 activate_on_push activate_on_release is_water use_content_on_gen is_buildable
114
115 body_range body_arm body_torso body_head body_neck body_skill
116 body_finger body_shoulder body_foot body_hand body_wrist body_waist
117));
118
119our %EVENT_TYPE = (
120 apply => 1,
121 attack => 2,
122 death => 3,
123 drop => 4,
124 pickup => 5,
125 say => 6,
126 stop => 7,
127 time => 8,
128 throw => 9,
129 trigger => 10,
130 close => 11,
131 timer => 12,
132);
133
42sub MOVE_WALK (){ 0x1 } 134sub MOVE_WALK (){ 0x01 }
43sub MOVE_FLY_LOW (){ 0x2 } 135sub MOVE_FLY_LOW (){ 0x02 }
44sub MOVE_FLY_HIGH (){ 0x4 } 136sub MOVE_FLY_HIGH (){ 0x04 }
45sub MOVE_FLYING (){ 0x6 } 137sub MOVE_FLYING (){ 0x06 }
46sub MOVE_SWIM (){ 0x8 } 138sub MOVE_SWIM (){ 0x08 }
47sub MOVE_ALL (){ 0xf } 139sub MOVE_BOAT (){ 0x10 }
140sub MOVE_KNOWN (){ 0x1f } # all of above
141sub MOVE_ALLBIT (){ 0x10000 }
142sub MOVE_ALL (){ 0x1001f } # very special value, more PITA
48 143
144sub load_ref($) {
145 my ($path) = @_;
146
147 open my $fh, "<:raw:perlio", $path
148 or die "$path: $!";
149 local $/;
150
151 thaw <$fh>
152}
153
154sub save_ref($$) {
155 my ($ref, $path) = @_;
156
157 open my $fh, ">:raw:perlio", "$path~"
158 or die "$path~: $!";
159 print $fh freeze $ref;
160 close $fh;
161 rename "$path~", $path
162 or die "$path: $!";
163}
164
165# object as in "Object xxx", i.e. archetypes
166sub normalize_object($) {
167 my ($ob) = @_;
168
169 # nuke outdated or never supported fields
170 delete $ob->{$_} for qw(
171 can_knockback can_parry can_impale can_cut can_dam_armour
172 can_apply pass_thru can_pass_thru
173 );
174
175 # convert movement strings to bitsets
176 for my $attr (keys %FIELD_MOVEMENT) {
177 next unless exists $ob->{$attr};
178
179 $ob->{$attr} = MOVE_ALL if $ob->{$attr} == 255; #d# compatibility
180
181 next if $ob->{$attr} =~ /^\d+$/;
182
183 my $flags = 0;
184
185 # assume list
186 for my $flag (map lc, split /\s+/, $ob->{$attr}) {
187 $flags |= MOVE_WALK if $flag eq "walk";
188 $flags |= MOVE_FLY_LOW if $flag eq "fly_low";
189 $flags |= MOVE_FLY_HIGH if $flag eq "fly_high";
190 $flags |= MOVE_FLYING if $flag eq "flying";
191 $flags |= MOVE_SWIM if $flag eq "swim";
192 $flags |= MOVE_BOAT if $flag eq "boat";
193 $flags |= MOVE_ALL if $flag eq "all";
194
195 $flags &= ~MOVE_WALK if $flag eq "-walk";
196 $flags &= ~MOVE_FLY_LOW if $flag eq "-fly_low";
197 $flags &= ~MOVE_FLY_HIGH if $flag eq "-fly_high";
198 $flags &= ~MOVE_FLYING if $flag eq "-flying";
199 $flags &= ~MOVE_SWIM if $flag eq "-swim";
200 $flags &= ~MOVE_BOAT if $flag eq "-boat";
201 $flags &= ~MOVE_ALL if $flag eq "-all";
202 }
203
204 $ob->{$attr} = $flags;
205 }
206
207 # convert outdated movement flags to new movement sets
208 if (defined (my $v = delete $ob->{no_pass})) {
209 $ob->{move_block} = $v ? MOVE_ALL : 0;
210 }
211 if (defined (my $v = delete $ob->{slow_move})) {
212 $ob->{move_slow} |= MOVE_WALK;
213 $ob->{move_slow_penalty} = $v;
214 }
215 if (defined (my $v = delete $ob->{walk_on})) {
216 $ob->{move_on} = MOVE_ALL unless exists $ob->{move_on};
217 $ob->{move_on} = $v ? $ob->{move_on} | MOVE_WALK
218 : $ob->{move_on} & ~MOVE_WALK;
219 }
220 if (defined (my $v = delete $ob->{walk_off})) {
221 $ob->{move_off} = MOVE_ALL unless exists $ob->{move_off};
222 $ob->{move_off} = $v ? $ob->{move_off} | MOVE_WALK
223 : $ob->{move_off} & ~MOVE_WALK;
224 }
225 if (defined (my $v = delete $ob->{fly_on})) {
226 $ob->{move_on} = MOVE_ALL unless exists $ob->{move_on};
227 $ob->{move_on} = $v ? $ob->{move_on} | MOVE_FLY_LOW
228 : $ob->{move_on} & ~MOVE_FLY_LOW;
229 }
230 if (defined (my $v = delete $ob->{fly_off})) {
231 $ob->{move_off} = MOVE_ALL unless exists $ob->{move_off};
232 $ob->{move_off} = $v ? $ob->{move_off} | MOVE_FLY_LOW
233 : $ob->{move_off} & ~MOVE_FLY_LOW;
234 }
235 if (defined (my $v = delete $ob->{flying})) {
236 $ob->{move_type} = MOVE_ALL unless exists $ob->{move_type};
237 $ob->{move_type} = $v ? $ob->{move_type} | MOVE_FLY_LOW
238 : $ob->{move_type} & ~MOVE_FLY_LOW;
239 }
240
241 # convert idiotic event_xxx things into objects
242 while (my ($event, $subtype) = each %EVENT_TYPE) {
243 if (exists $ob->{"event_${event}_plugin"}) {
244 push @{$ob->{inventory}}, {
245 _name => "event_$event",
246 title => delete $ob->{"event_${event}_plugin"},
247 slaying => delete $ob->{"event_${event}"},
248 name => delete $ob->{"event_${event}_options"},
249 };
250 }
251 }
252
253 $ob
254}
255
256# arch as in "arch xxx", ie.. objects
49sub normalize_arch($) { 257sub normalize_arch($) {
50 my ($ob) = @_; 258 my ($ob) = @_;
51 259
260 normalize_object $ob;
261
52 my $arch = $ARCH{$ob->{_name}} 262 my $arch = $ARCH{$ob->{_name}}
53 or (warn "$ob->{_name}: no such archetype", return $ob); 263 or (warn "$ob->{_name}: no such archetype", return $ob);
54
55 delete $ob->{$_} for qw(can_knockback can_parry can_impale can_cut can_dam_armour can_apply);
56 264
57 if ($arch->{type} == 22) { # map 265 if ($arch->{type} == 22) { # map
58 my %normalize = ( 266 my %normalize = (
59 "enter_x" => "hp", 267 "enter_x" => "hp",
60 "enter_y" => "sp", 268 "enter_y" => "sp",
70 while (my ($k2, $k1) = each %normalize) { 278 while (my ($k2, $k1) = each %normalize) {
71 if (defined (my $v = delete $ob->{$k1})) { 279 if (defined (my $v = delete $ob->{$k1})) {
72 $ob->{$k2} = $v; 280 $ob->{$k2} = $v;
73 } 281 }
74 } 282 }
75 } 283 } else {
76
77 if (defined (my $v = delete $ob->{no_pass})) {
78 $ob->{move_block} = $v ? MOVE_ALL : 0;
79 }
80 if (defined (my $v = delete $ob->{walk_on})) {
81 $ob->{move_on} = $v ? $ob->{move_on} | MOVE_WALK
82 : $ob->{move_on} & ~MOVE_WALK;
83 }
84 if (defined (my $v = delete $ob->{walk_off})) {
85 $ob->{move_off} = $v ? $ob->{move_off} | MOVE_WALK
86 : $ob->{move_off} & ~MOVE_WALK;
87 }
88 if (defined (my $v = delete $ob->{fly_on})) {
89 $ob->{move_on} = $v ? $ob->{move_on} | MOVE_FLY_LOW
90 : $ob->{move_on} & ~MOVE_FLY_LOW;
91 }
92 if (defined (my $v = delete $ob->{fly_off})) {
93 $ob->{move_off} = $v ? $ob->{move_off} | MOVE_FLY_LOW
94 : $ob->{move_off} & ~MOVE_FLY_LOW;
95 }
96 if (defined (my $v = delete $ob->{flying})) {
97 $ob->{move_type} = $v ? $ob->{move_type} | MOVE_FLY_LOW
98 : $ob->{move_type} & ~MOVE_FLY_LOW;
99 }
100
101 # if value matches archetype default, delete 284 # if value matches archetype default, delete
102 while (my ($k, $v) = each %$ob) { 285 while (my ($k, $v) = each %$ob) {
103 if (exists $arch->{$k} and $arch->{$k} eq $v) { 286 if (exists $arch->{$k} and $arch->{$k} eq $v) {
104 next if $k eq "_name"; 287 next if $k eq "_name";
105 delete $ob->{$k}; 288 delete $ob->{$k};
106 } 289 }
290 }
291 }
292
293 # a speciality for the editor
294 if (exists $ob->{attack_movement}) {
295 my $am = delete $ob->{attack_movement};
296 $ob->{attack_movement_bits_0_3} = $am & 15;
297 $ob->{attack_movement_bits_4_7} = $am & 240;
107 } 298 }
108 299
109 $ob 300 $ob
110} 301}
111 302
303sub attr_thaw($) {
304 my ($ob) = @_;
305
306 $ob->{attach} = from_json $ob->{attach}
307 if exists $ob->{attach};
308
309 $ob
310}
311
312sub attr_freeze($) {
313 my ($ob) = @_;
314
315 $ob->{attach} = Crossfire::to_json $ob->{attach}
316 if exists $ob->{attach};
317
318 $ob
319}
320
112sub read_pak($;$) { 321sub read_pak($) {
113 my ($path, $cache) = @_; 322 my ($path) = @_;
114 323
115 eval {
116 defined $cache
117 && -M $cache < -M $path
118 && Storable::retrieve $cache
119 } or do {
120 my %pak; 324 my %pak;
121 325
122 open my $fh, "<:raw", $path 326 open my $fh, "<:raw:perlio", $path
123 or Carp::croak "$_[0]: $!"; 327 or Carp::croak "$_[0]: $!";
328 binmode $fh;
124 while (<$fh>) { 329 while (<$fh>) {
125 my ($type, $id, $len, $path) = split; 330 my ($type, $id, $len, $path) = split;
126 $path =~ s/.*\///; 331 $path =~ s/.*\///;
127 read $fh, $pak{$path}, $len; 332 read $fh, $pak{$path}, $len;
128 } 333 }
129 334
130 Storable::nstore \%pak, $cache
131 if defined $cache;
132
133 \%pak 335 \%pak
134 }
135} 336}
136 337
137sub read_arch($;$) { 338sub read_arch($;$) {
138 my ($path, $cache) = @_; 339 my ($path, $toplevel) = @_;
139 340
140 eval {
141 defined $cache
142 && -M $cache < -M $path
143 && Storable::retrieve $cache
144 } or do {
145 my %arc; 341 my %arc;
146 my ($more, $prev); 342 my ($more, $prev);
147 343
148 open my $fh, "<:raw", $path 344 open my $fh, "<:raw:perlio:utf8", $path
149 or Carp::croak "$path: $!"; 345 or Carp::croak "$path: $!";
150 346
347# binmode $fh;
348
151 my $parse_block; $parse_block = sub { 349 my $parse_block; $parse_block = sub {
152 my %arc = @_; 350 my %arc = @_;
153
154 while (<$fh>) {
155 s/\s+$//;
156 if (/^end$/i) {
157 last;
158 } elsif (/^arch (\S+)$/) {
159 push @{ $arc{inventory} }, normalize_arch $parse_block->(_name => $1);
160 } elsif (/^lore$/) {
161 while (<$fh>) {
162 last if /^endlore\s*$/i;
163 $arc{lore} .= $_;
164 }
165 } elsif (/^msg$/) {
166 while (<$fh>) {
167 last if /^endmsg\s*$/i;
168 $arc{msg} .= $_;
169 }
170 } elsif (/^(\S+)\s*(.*)$/) {
171 $arc{lc $1} = $2;
172 } elsif (/^\s*($|#)/) {
173 #
174 } else {
175 warn "$path: unparsable line '$_' in arch $arc{_name}";
176 }
177 }
178
179 \%arc
180 };
181 351
182 while (<$fh>) { 352 while (<$fh>) {
183 s/\s+$//; 353 s/\s+$//;
184 if (/^more$/i) { 354 if (/^end$/i) {
185 $more = $prev; 355 last;
186 } elsif (/^object (\S+)$/i) { 356 } elsif (/^arch (\S+)$/i) {
187 my $name = $1; 357 push @{ $arc{inventory} }, attr_thaw normalize_arch $parse_block->(_name => $1);
188 my $arc = $parse_block->(_name => $name); 358 } elsif (/^lore$/i) {
189 359 while (<$fh>) {
190 if ($more) { 360 last if /^endlore\s*$/i;
191 $more->{more} = $arc;
192 } else {
193 $arc{$name} = $arc; 361 $arc{lore} .= $_;
194 } 362 }
195 $prev = $arc; 363 } elsif (/^msg$/i) {
196 $more = undef; 364 while (<$fh>) {
365 last if /^endmsg\s*$/i;
366 $arc{msg} .= $_;
367 }
368 } elsif (/^anim$/i) {
369 while (<$fh>) {
370 last if /^mina\s*$/i;
371 chomp;
372 push @{ $arc{anim} }, $_;
373 }
197 } elsif (/^arch (\S+)$/i) { 374 } elsif (/^(\S+)\s*(.*)$/) {
198 push @{ $arc{arch} }, normalize_arch $parse_block->(_name => $1); 375 $arc{lc $1} = $2;
199 } elsif (/^\s*($|#)/) { 376 } elsif (/^\s*($|#)/) {
200 # 377 #
201 } else { 378 } else {
202 warn "$path: unparseable top-level line '$_'"; 379 warn "$path: unparsable line '$_' in arch $arc{_name}";
203 } 380 }
204 } 381 }
205 382
206 undef $parse_block; # work around bug in perl not freeing $fh etc.
207
208 Storable::nstore \%arc, $cache
209 if defined $cache;
210
211 \%arc 383 \%arc
212 } 384 };
385
386 while (<$fh>) {
387 s/\s+$//;
388 if (/^more$/i) {
389 $more = $prev;
390 } elsif (/^object (\S+)$/i) {
391 my $name = $1;
392 my $arc = attr_thaw normalize_object $parse_block->(_name => $name);
393 $arc->{_atype} = 'object';
394
395 if ($more) {
396 $more->{more} = $arc;
397 } else {
398 $arc{$name} = $arc;
399 }
400 $prev = $arc;
401 $more = undef;
402 } elsif (/^arch (\S+)$/i) {
403 my $name = $1;
404 my $arc = attr_thaw normalize_arch $parse_block->(_name => $name);
405 $arc->{_atype} = 'arch';
406
407 if ($more) {
408 $more->{more} = $arc;
409 } else {
410 push @{ $arc{arch} }, $arc;
411 }
412 $prev = $arc;
413 $more = undef;
414 } elsif ($toplevel && /^(\S+)\s+(.*)$/) {
415 if ($1 eq "lev_array") {
416 while (<$fh>) {
417 last if /^endplst\s*$/;
418 push @{$toplevel->{lev_array}}, $_+0;
419 }
420 } else {
421 $toplevel->{$1} = $2;
422 }
423 } elsif (/^\s*($|#)/) {
424 #
425 } else {
426 die "$path: unparseable top-level line '$_'";
427 }
428 }
429
430 undef $parse_block; # work around bug in perl not freeing $fh etc.
431
432 \%arc
433}
434
435sub archlist_to_string {
436 my ($arch) = @_;
437
438 my $str;
439
440 my $append; $append = sub {
441 my %a = %{$_[0]};
442
443 Crossfire::attr_freeze \%a;
444 Crossfire::normalize_arch \%a;
445
446 # undo the bit-split we did before
447 if (exists $a{attack_movement_bits_0_3} or exists $a{attack_movement_bits_4_7}) {
448 $a{attack_movement} = (delete $a{attack_movement_bits_0_3})
449 | (delete $a{attack_movement_bits_4_7});
450 }
451
452 $str .= ((exists $a{_atype}) ? $a{_atype} : 'arch'). " $a{_name}\n";
453
454 my $inv = delete $a{inventory};
455 my $more = delete $a{more}; # arches do not support 'more', but old maps can contain some
456 my $anim = delete $a{anim};
457
458 my @kv;
459
460 for ($a{_name} eq "map"
461 ? @Crossfire::FIELD_ORDER_MAP
462 : @Crossfire::FIELD_ORDER) {
463 push @kv, [$_, delete $a{$_}]
464 if exists $a{$_};
465 }
466
467 for (sort keys %a) {
468 next if /^_/; # ignore our _-keys
469 push @kv, [$_, delete $a{$_}];
470 }
471
472 for (@kv) {
473 my ($k, $v) = @$_;
474
475 if (my $end = $Crossfire::FIELD_MULTILINE{$k}) {
476 $v =~ s/\n$//;
477 $str .= "$k\n$v\n$end\n";
478 } elsif (exists $Crossfire::FIELD_MOVEMENT{$k}) {
479 if ($v & ~Crossfire::MOVE_ALL or !$v) {
480 $str .= "$k $v\n";
481
482 } elsif ($v & Crossfire::MOVE_ALLBIT) {
483 $str .= "$k all";
484
485 $str .= " -walk" unless $v & Crossfire::MOVE_WALK;
486 $str .= " -fly_low" unless $v & Crossfire::MOVE_FLY_LOW;
487 $str .= " -fly_high" unless $v & Crossfire::MOVE_FLY_HIGH;
488 $str .= " -swim" unless $v & Crossfire::MOVE_SWIM;
489 $str .= " -boat" unless $v & Crossfire::MOVE_BOAT;
490
491 $str .= "\n";
492
493 } else {
494 $str .= $k;
495
496 $str .= " walk" if $v & Crossfire::MOVE_WALK;
497 $str .= " fly_low" if $v & Crossfire::MOVE_FLY_LOW;
498 $str .= " fly_high" if $v & Crossfire::MOVE_FLY_HIGH;
499 $str .= " swim" if $v & Crossfire::MOVE_SWIM;
500 $str .= " boat" if $v & Crossfire::MOVE_BOAT;
501
502 $str .= "\n";
503 }
504 } else {
505 $str .= "$k $v\n";
506 }
507 }
508
509 if ($inv) {
510 $append->($_) for @$inv;
511 }
512
513 if ($a{_atype} eq 'object') {
514 $str .= join "\n", "anim", @$anim, "mina\n"
515 if $anim;
516 }
517
518 $str .= "end\n";
519
520 if (($a{_atype} eq 'object') && $more) {
521 $str .= "\nmore\n";
522 $append->($more) if $more;
523 }
524 };
525
526 for (@$arch) {
527 $append->($_);
528 }
529
530 $str
213} 531}
214 532
215# put all archs into a hash with editor_face as it's key 533# put all archs into a hash with editor_face as it's key
216# NOTE: the arrays in the hash values are references to 534# NOTE: the arrays in the hash values are references to
217# the archs from $ARCH 535# the archs from $ARCH
218sub editor_archs { 536sub editor_archs {
219 my %paths; 537 my %paths;
220 538
221 for (keys %ARCH) { 539 for (keys %ARCH) {
222 my $arch = $ARCH{$_}; 540 my $arch = $ARCH{$_};
223 push @{$paths{$arch->{editor_folder}}}, \$arch; 541 push @{$paths{$arch->{editor_folder}}}, $arch;
224 } 542 }
225 543
226 \%paths 544 \%paths
227} 545}
228 546
238 my ($a) = @_; 556 my ($a) = @_;
239 557
240 my $o = $ARCH{$a->{_name}} 558 my $o = $ARCH{$a->{_name}}
241 or return; 559 or return;
242 560
243 my $face = $FACE{$a->{face} || $o->{face}} 561 my $face = $FACE{$a->{face} || $o->{face} || "blank.111"}
244 or (warn "no face data found for arch '$a->{_name}'"), return; 562 or (warn "no face data found for arch '$a->{_name}'"), return;
245 563
246 if ($face->{w} > 1 || $face->{h} > 1) { 564 if ($face->{w} > 1 || $face->{h} > 1) {
247 # bigface 565 # bigface
248 return (0, 0, $face->{w} - 1, $face->{h} - 1); 566 return (0, 0, $face->{w} - 1, $face->{h} - 1);
264 # single face 582 # single face
265 return (0, 0, 0, 0); 583 return (0, 0, 0, 0);
266 } 584 }
267} 585}
268 586
269sub init($) {
270 my ($cachedir) = @_;
271
272 return if %ARCH;
273
274 *ARCH = read_arch "$LIB/archetypes", "$cachedir/archetypes.pst";
275}
276
277=item $data = arch_attr $arch 587=item $type = arch_attr $arch
278 588
279Returns a hashref describing the object and its attributes. It can contain 589Returns a hashref describing the object and its attributes. It can contain
280the following keys: 590the following keys:
281 591
282 name the name, suitable for display purposes 592 name the name, suitable for display purposes
283 ignore 593 ignore
284 attr 594 attr
285 desc 595 desc
286 use 596 use
287 section => [name => \%attr, name => \%attr] 597 section => [name => \%attr, name => \%attr]
598 import
288 599
289=cut 600=cut
290 601
291sub arch_attr($) { 602sub arch_attr($) {
292 my ($arch) = @_; 603 my ($obj) = @_;
293 604
294 require Crossfire::Data; 605 require Crossfire::Data;
295 606
607 my $root;
296 my $attr; 608 my $attr = { };
609
610 my $arch = $ARCH{ $obj->{_name} };
611 my $type = $obj->{type} || $arch->{type};
297 612
298 if ($arch->{type} > 0) { 613 if ($type > 0) {
299 $attr = $Crossfire::Data::ATTR{$arch->{type}+0}; 614 $root = $Crossfire::Data::ATTR{$type};
300 } else { 615 } else {
616 my %a = (%$arch, %$obj);
617
618 if ($a{is_floor} && !$a{alive}) {
619 $root = $Crossfire::Data::TYPE{Floor};
620 } elsif (!$a{is_floor} && $a{alive} && !$a{tear_down}) {
621 $root = $Crossfire::Data::TYPE{"Monster & NPC"};
622 } elsif (!$a{is_floor} && !$a{alive} && $a{move_block}) {
623 $root = $Crossfire::Data::TYPE{Wall};
624 } elsif (!$a{is_floor} && $a{alive} && $a{tear_down}) {
625 $root = $Crossfire::Data::TYPE{"Weak Wall"};
626 } else {
301 $attr = $Crossfire::Data::TYPE{Misc}; 627 $root = $Crossfire::Data::TYPE{Misc};
628 }
629 }
302 630
303 type: 631 my @import = ($root);
304 for (@Crossfire::Data::ATTR0) { 632
305 my $req = $_->{required} 633 unshift @import, \%Crossfire::Data::DEFAULT_ATTR
306 or die "internal error: ATTR0 without 'required'"; 634 unless $type == 116;
307 635
308 while (my ($k, $v) = each %$req) { 636 my (%ignore);
309 next type 637 my (@section_order, %section, @attr_order);
310 unless $arch->{$k} == $v; 638
639 while (my $type = shift @import) {
640 push @import, @{$type->{import} || []};
641
642 $attr->{$_} ||= $type->{$_}
643 for qw(name desc use);
644
645 for (@{$type->{ignore} || []}) {
646 $ignore{$_}++ for ref $_ ? @$_ : $_;
647 }
648
649 for ([general => ($type->{attr} || [])], @{$type->{section} || []}) {
650 my ($name, $attr) = @$_;
651 push @section_order, $name;
652 for (@$attr) {
653 my ($k, $v) = @$_;
654 push @attr_order, $k;
655 $section{$name}{$k} ||= $v;
311 } 656 }
657 }
658 }
312 659
313 $attr = $_; 660 $attr->{section} = [
661 map !exists $section{$_} ? () : do {
662 my $attr = delete $section{$_};
663
664 [
665 $_,
666 map exists $attr->{$_} && !$ignore{$_}
667 ? [$_ => delete $attr->{$_}] : (),
668 @attr_order
669 ]
314 } 670 },
315 } 671
672 exists $section{$_} ? [$_ => delete $section{$_}] : (),
673 @section_order
674 ];
316 675
317 use PApp::Util; 676 $attr
318 warn PApp::Util::dumpval $attr;
319} 677}
320 678
321sub arch_edit_sections { 679sub arch_edit_sections {
322# if (edit_type == IGUIConstants.TILE_EDIT_NONE) 680# if (edit_type == IGUIConstants.TILE_EDIT_NONE)
323# edit_type = 0; 681# edit_type = 0;
377# return(edit_type); 735# return(edit_type);
378# 736#
379# 737#
380} 738}
381 739
382$CACHEDIR ||= "$ENV{HOME}/.crossfire"; 740sub cache_file($$&&) {
741 my ($src, $cache, $load, $create) = @_;
383 742
384init $CACHEDIR; 743 my ($size, $mtime) = (stat $src)[7,9]
744 or Carp::croak "$src: $!";
745
746 if (-e $cache) {
747 my $ref = eval { load_ref $cache };
748
749 if ($ref->{version} == 1
750 && $ref->{size} == $size
751 && $ref->{mtime} == $mtime
752 && eval { $load->($ref->{data}); 1 }) {
753 return;
754 }
755 }
756
757 my $ref = {
758 version => 1,
759 size => $size,
760 mtime => $mtime,
761 data => $create->(),
762 };
763
764 $load->($ref->{data});
765
766 save_ref $ref, $cache;
767}
768
769=item set_libdir $path
770
771Sets the library directory to the given path
772(default: $ENV{CROSSFIRE_LIBDIR}).
773
774You have to (re-)load the archetypes and tilecache manually after steting
775the library path.
776
777=cut
778
779sub set_libdir($) {
780 $LIB = $_[0];
781}
782
783=item load_archetypes
784
785(Re-)Load archetypes into %ARCH.
786
787=cut
788
789sub load_archetypes() {
790 cache_file "$LIB/archetypes", "$VARDIR/archetypes.pst", sub {
791 *ARCH = $_[0];
792 }, sub {
793 read_arch "$LIB/archetypes"
794 };
795}
796
797=item load_tilecache
798
799(Re-)Load %TILE and %FACE.
800
801=cut
802
803sub load_tilecache() {
804 require Gtk2;
805
806 cache_file "$LIB/crossfire.0", "$VARDIR/tilecache.pst", sub {
807 $TILE = new_from_file Gtk2::Gdk::Pixbuf "$VARDIR/tilecache.png"
808 or die "$VARDIR/tilecache.png: $!";
809 *FACE = $_[0];
810 }, sub {
811 my $tile = read_pak "$LIB/crossfire.0";
812
813 my %cache;
814
815 my $idx = 0;
816
817 for my $name (sort keys %$tile) {
818 my $pb = new Gtk2::Gdk::PixbufLoader;
819 $pb->write ($tile->{$name});
820 $pb->close;
821 my $pb = $pb->get_pixbuf;
822
823 my $tile = $cache{$name} = {
824 pb => $pb,
825 idx => $idx,
826 w => int $pb->get_width / TILESIZE,
827 h => int $pb->get_height / TILESIZE,
828 };
829
830
831 $idx += $tile->{w} * $tile->{h};
832 }
833
834 my $pb = new Gtk2::Gdk::Pixbuf "rgb", 1, 8, 64 * TILESIZE, TILESIZE * int +($idx + 63) / 64;
835
836 while (my ($name, $tile) = each %cache) {
837 my $tpb = delete $tile->{pb};
838 my $ofs = $tile->{idx};
839
840 for my $x (0 .. $tile->{w} - 1) {
841 for my $y (0 .. $tile->{h} - 1) {
842 my $idx = $ofs + $x + $y * $tile->{w};
843 $tpb->copy_area ($x * TILESIZE, $y * TILESIZE, TILESIZE, TILESIZE,
844 $pb, ($idx % 64) * TILESIZE, TILESIZE * int $idx / 64);
845 }
846 }
847 }
848
849 $pb->save ("$VARDIR/tilecache.png", "png", compression => 1);
850
851 \%cache
852 };
853}
385 854
386=head1 AUTHOR 855=head1 AUTHOR
387 856
388 Marc Lehmann <schmorp@schmorp.de> 857 Marc Lehmann <schmorp@schmorp.de>
389 http://home.schmorp.de/ 858 http://home.schmorp.de/

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines