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.120 by root, Wed Dec 26 18:26:15 2007 UTC

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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines