ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/utils/cfutil.in
Revision: 1.58
Committed: Tue Aug 21 19:44:57 2007 UTC (16 years, 9 months ago) by root
Branch: MAIN
Changes since 1.57: +5 -2 lines
Log Message:
- allow archetypes to refer to tiled subparts of bigfaces individually.
  (used for the new nimfloor)

File Contents

# User Rev Content
1 root 1.1 #!@PERL@
2    
3 root 1.2 use strict;
4    
5     my $prefix = "@prefix@";
6     my $exec_prefix = "@exec_prefix@";
7     my $datarootdir = "@datarootdir@";
8     my $DATADIR = "@datadir@/@PACKAGE@";
9    
10     my $CONVERT = "@CONVERT@";
11 root 1.36 #my $IDENTIFY = "@IDENTIFY@";
12 root 1.3 my $OPTIPNG = "@OPTIPNG@";
13 root 1.2 my $RSYNC = "@RSYNC@";
14 root 1.31 my $PNGNQ = "@PNGNQ@";
15 root 1.2
16     use Getopt::Long;
17 root 1.3 use Coro::Event;
18     use AnyEvent;
19     use IO::AIO ();
20 root 1.2 use File::Temp;
21     use Crossfire;
22 root 1.3 use Coro;
23     use Coro::AIO;
24 root 1.51 use Coro::Util;
25 root 1.3 use POSIX ();
26 root 1.42 use Carp;
27 root 1.53 use Coro::Channel;
28 root 1.25 use Coro::Storable; $Storable::canonical = 1;
29 root 1.2
30 root 1.42 $SIG{QUIT} = sub { Carp::cluck "QUIT" };
31    
32 root 1.2 sub usage {
33     warn <<EOF;
34 root 1.3 Usage: cfutil [-v] [-q] [--force] [--cache]
35 root 1.2 [--install-arch path]
36     [--install-maps maps]
37     [--print-statedir]
38     [--print-confdir]
39     [--print-datadir]
40     [--print-libdir]
41     [--print-bindir]
42     EOF
43     exit 1;
44     }
45    
46     my $VERBOSE = 1;
47 root 1.3 my $CACHE = 0;
48 root 1.2 my $FORCE;
49 root 1.3 my $TMPDIR = "/tmp/cfutil$$~";
50     my $TMPFILE = "aaaa0";
51 root 1.2
52 root 1.35 our %COLOR = (
53     black => 0,
54     white => 1,
55     navy => 2,
56     red => 3,
57     orange => 4,
58     blue => 5,
59     darkorange => 6,
60     green => 7,
61     lightgreen => 8,
62     grey => 9,
63     brown => 10,
64     gold => 11,
65     tan => 12,
66     );
67    
68 root 1.2 END { system "rm", "-rf", $TMPDIR }
69    
70 root 1.3 Event->signal (signal => "INT", cb => sub { exit 1 });
71     Event->signal (signal => "TERM", cb => sub { exit 1 });
72    
73 root 1.2 mkdir $TMPDIR, 0700
74     or die "$TMPDIR: $!";
75    
76 root 1.3 sub fork_sub(&) {
77     my ($cb) = @_;
78    
79     if (my $pid = fork) {
80     my $current = $Coro::current;
81     my $w = AnyEvent->child (pid => $pid, cb => sub { $current->ready });
82     Coro::schedule;
83     } else {
84     eval { $cb->() };
85     POSIX::_exit 0 unless $@;
86     warn $@;
87     POSIX::_exit 1;
88     }
89     }
90    
91 root 1.2 sub inst_maps($) {
92     my (undef, $path) = @_;
93    
94 root 1.29 print "\nInstalling '$path' to '$DATADIR/maps'\n\n";
95 root 1.2
96     if (!-f "$path/regions") {
97     warn "'$path' does not look like a maps directory ('regions' file is missing).\n";
98     exit 1 unless $FORCE;
99     }
100    
101 root 1.41 system $RSYNC, "-a", "--chmod=u=rwX,go=rX", "$path/.", "$DATADIR/maps/.", "--delete", "--exclude", "CVS", "--delete-excluded"
102 root 1.16 and die "map installation failed.\n";
103 root 1.13
104     print "maps installed successfully.\n";
105 root 1.2 }
106    
107     {
108 root 1.24 our %ANIMINFO;
109 root 1.6 our %FACEINFO;
110 root 1.37 our %RESOURCE;
111 root 1.30 our @ARC;
112 root 1.27 our %ARC;
113 root 1.12 our $TRS;
114 root 1.3 our $NFILE;
115 root 1.37 our $PATH;
116 root 1.3
117 root 1.19 our $QUANTIZE = "+dither -colorspace RGB -colors 256";
118    
119 root 1.53 our $c_arc = new Coro::Channel;
120     our $c_trs = new Coro::Channel;
121     our $c_res = new Coro::Channel;
122    
123     our @c_png;
124 root 1.2
125 root 1.19 sub commit_png($$$) {
126     my ($name, $data, $T) = @_;
127 root 1.5
128 root 1.19 $FACEINFO{$name}{"data$T"} = $data;
129 root 1.2 }
130    
131 root 1.3 sub process_png {
132 root 1.53 while (@c_png) {
133     my ($path, $delete) = @{pop @c_png};
134 root 1.2
135 root 1.3 my $png;
136     aio_lstat $path;
137 root 1.2 my ($size, $mtime) = (stat _)[7,9];
138    
139 root 1.3 if (0 > aio_load $path, $png) {
140     warn "$path: $!, skipping.\n";
141 root 1.5 next;
142 root 1.3 }
143    
144 root 1.19 my $stem = $path;
145     my $T;
146    
147     if ($stem =~ s/\.32x32\.png~?$//) {
148     $T = 32;
149     } elsif ($stem =~ s/\.64x64\.png~?$//) {
150     $T = 64;
151     } else {
152     warn "$path: weird filename, skipping.\n";
153     next;
154     }
155    
156 root 1.6 # quickly extract width and height of the (necessarily PNG) image
157 root 1.3 unless ($png =~ /^\x89PNG\x0d\x0a\x1a\x0a....IHDR(........)/s) {
158 root 1.19 warn "$path: not a recognized png file, skipping.\n";
159 root 1.5 next;
160 root 1.3 }
161 root 1.2
162 root 1.3 my ($w, $h) = unpack "NN", $1;
163 root 1.2
164 root 1.3 if ($w < $T || $h < $T) {
165     warn "$path: too small ($w $h), skipping.\n";
166 root 1.5 next;
167 root 1.3 }
168    
169     if ($w % $T || $h % $T) {
170     warn "$path: weird png size ($w $h), skipping.\n";
171 root 1.5 next;
172 root 1.3 }
173 root 1.2
174 root 1.54 (my $base = $stem) =~ s/^.*\///;
175    
176     my $fi = $FACEINFO{$base};
177     unless ($fi) {
178     warn "$path: <$base> not referenced by any archetype, skipping.\n";
179     next;
180     }
181    
182     my $arc = $FACEINFO{$base}{arc}
183 root 1.56 or die "FATAL: internal error <$base>, cannot continue";
184 root 1.54
185 root 1.19 unless ($path =~ /~$/) {
186     # possibly enlarge
187     if (0 > aio_stat "$stem.64x64.png") {
188     my $other = "$stem.64x64.png~";
189    
190     if (0 > aio_lstat $other or (-M _) > (-M $path)) {
191     fork_sub {
192 root 1.52 my $CROP;
193     my $SRC = "png:\Q$path\E";
194    
195 root 1.54 my $is_floor = $arc->{is_floor};
196     my $is_wall = 0;
197    
198     my ($wall_pfx, $wall_dir, $wall_sfx);
199    
200     if (
201     !$is_floor
202     && !$arc->{alive}
203 root 1.55 && $arc->{move_block} eq "all"
204 root 1.54 && $path =~ /^(.*_)([0-9A-F])(\.x11.*\.png)$/
205     ) {
206     ($wall_pfx, $wall_dir, $wall_sfx) = ($1, hex $2, $3);
207    
208     unless (grep { !-e sprintf "%s%X%s", $wall_pfx, $_, $wall_sfx } 0..15) {
209     $is_wall = 1;
210     }
211     }
212    
213     if ($is_wall || $is_floor) {
214     # add a 4px border and add other images around it
215     $CROP = "-shave 8x8 +repage";
216    
217     $w += 8;
218     $h += 8;
219    
220     $SRC = "-size ${w}x${h} xc:transparent";
221     $SRC .= " png:\Q$path\E -geometry +4+4 -composite";
222    
223     # 8 surrounding images
224     for (
225     # x y b r0 r1
226     [-1, -1, 0, 6],
227     [ 0, -1, 1, 10, 14],
228     [+1, -1, 0, 12],
229    
230     [-1, 0, 8, 5, 7],
231     #
232     [+1, 0, 2, 5, 13],
233    
234     [-1, +1, 0, 3],
235     [ 0, +1, 4, 10, 11],
236     [+1, +1, 0, 9],
237     ) {
238     my ($x, $y, $d, $r0, $r1) = @$_;
239    
240     my $tile = $is_floor ? $path
241     : $is_wall ? sprintf "%s%X%s", $wall_pfx, ($wall_dir & $d) ? $r1 : $r0, $wall_sfx
242     : die;
243    
244     $SRC .= sprintf " png:%s -geometry %+d%+d -composite",
245     "\Q$tile",
246     $x * ($w - 8) + 4,
247     $y * ($h - 8) + 4;
248 root 1.52 }
249     }
250    
251     system "convert -depth 8 $SRC rgba:-"
252 root 1.51 . "| $exec_prefix/bin/cfhq2xa $w $h 0"
253 root 1.52 . "| convert -depth 8 -size ".($w * 2)."x".($h * 2)." rgba:- $CROP $QUANTIZE -quality 00 png32:\Q$other\E~"
254     and die "convert/cfhq2xa pipeline error: status $? ($!)";
255 root 1.31 system $OPTIPNG, "-i0", "-q", "$other~";
256 root 1.19 die "$other~ has zero size, aborting." unless -s "$other~";
257     rename "$other~", $other;
258     };
259     }
260    
261 root 1.53 push @c_png, [$other, !$CACHE];
262 root 1.19 }
263    
264     # possibly scale down
265     if (0 > aio_stat "$stem.32x32.png") {
266     my $other = "$stem.32x32.png~";
267    
268     if (0 > aio_lstat $other or (-M _) > (-M $path)) {
269     fork_sub {
270     system "convert png:\Q$path\E -geometry 50% -filter lanczos $QUANTIZE -quality 00 png32:\Q$other\E~";
271 root 1.31 system $OPTIPNG, "-i0", "-q", "$other~";
272    
273     # reduce smoothfaces >10000 bytes
274     if ($stem =~ /_S\./ && (-s "$other~") > 10000) {
275     my $ncolor = 256;
276     while () {
277 root 1.33 system "<\Q$other~\E $PNGNQ -s1 -n$ncolor >\Q$other~~\E";
278 root 1.31 system $OPTIPNG, "-i0", "-q", "$other~~";
279     last if 10000 > -s "$other~~";
280     $ncolor = int $ncolor * 0.9;
281     $ncolor > 8 or die "cannot reduce filesize to < 10000 bytes";
282     }
283    
284     printf "reduced %s from %d to %d bytes using %d colours.\n",
285     $other, -s "$other~", -s "$other~~", $ncolor
286 root 1.32 if $VERBOSE >= 2;
287 root 1.31 rename "$other~~", "$other~";
288     }
289    
290 root 1.19 die "$other~ has zero size, aborting." unless -s "$other~";
291     rename "$other~", $other;
292     };
293     }
294    
295 root 1.21 #warn "scaled down $path to $other\n";#d#
296 root 1.53 push @c_png, [$other, !$CACHE];
297 root 1.19 }
298     }
299    
300     (my $face = $stem) =~ s/^.*\///;
301    
302 root 1.23 # split all bigfaces, but avoid smoothfaces (*_S)
303 root 1.3 if (($w > $T || $h > $T) && $face !~ /_S\./) {
304     # split
305     my @tile;
306     for my $x (0 .. (int $w / $T) - 1) {
307     for my $y (0 .. (int $h / $T) - 1) {
308     my $file = "$path+$x+$y~";
309     aio_lstat $file;
310     push @tile, [$x, $y, $file, (stat _)[9]];
311     }
312 root 1.2 }
313    
314 root 1.3 my $mtime = (lstat $path)[9];
315     my @todo = grep { $_->[3] <= $mtime } @tile;
316     if (@todo) {
317     fork_sub {
318     open my $convert, "|-", $CONVERT,
319     "png:-",
320     (map {
321     (
322     "(",
323     "+clone",
324     -crop => (sprintf "%dx%d+%d+%d", $T, $T, $_->[0] * $T, $_->[1] * $T),
325 root 1.7 "+repage",
326 root 1.19 -quality => "00",
327     -write => "png32:$_->[2]~",
328 root 1.3 "+delete",
329     ")",
330     )
331     } @todo),
332     "null:";
333    
334     binmode $convert;
335     print $convert $png;
336     close $convert;
337    
338     # pass 2, optimise, and rename
339     for (@todo) {
340     system $OPTIPNG, "-o5", "-i0", "-q", "$_->[2]~";
341 root 1.19 die "$_->[2]~ has zero size, aborting." unless -s "$_->[2]~";
342 root 1.3 rename "$_->[2]~", $_->[2];
343     }
344     };
345 root 1.2 }
346    
347 root 1.3 for (@tile) {
348     my ($x, $y, $file) = @$_;
349     my $tile;
350    
351     if (0 > aio_load $file, $tile) {
352     die "$path: unable to read tile +$x+$y, aborting.\n";
353     }
354     IO::AIO::aio_unlink $file unless $CACHE;
355 root 1.19 commit_png $x|$y ? "$face+$x+$y" : $face, $tile, $T;
356 root 1.2 }
357 root 1.3 } else {
358     # use as-is (either small, use smooth)
359 root 1.19 commit_png $face, $png, $T;
360 root 1.3 }
361 root 1.19
362     aio_unlink $path if $delete;
363 root 1.3 }
364 root 1.2 }
365    
366 root 1.3 sub process_arc {
367 root 1.53 while (my $job = $c_arc->get) {
368     my ($dir, $file) = @$job;
369 root 1.2
370 root 1.3 my $arc;
371     aio_load "$dir/$file", $arc; # simply pre-cache, as read_arch wants a file :/
372 root 1.4
373 root 1.2 my $arc = read_arch "$dir/$file";
374 root 1.4 for my $o (values %$arc) {
375 root 1.30 push @ARC, $o;
376     for (my $m = $o; $m; $m = $m->{more}) {
377     $ARC{$m->{_name}} = $m;
378     }
379 root 1.6
380 root 1.10 $o->{editor_folder} = $dir;
381    
382 root 1.6 my $visibility = delete $o->{visibility};
383     my $magicmap = delete $o->{magicmap};
384    
385     # find upper left corner :/
386     # omg, this is sooo broken
387 root 1.4 my ($dx, $dy);
388     for (my $o = $o; $o; $o = $o->{more}) {
389     $dx = $o->{x} if $o->{x} < $dx;
390     $dy = $o->{y} if $o->{y} < $dy;
391     }
392 root 1.6
393 root 1.4 for (my $o = $o; $o; $o = $o->{more}) {
394     my $x = $o->{x} - $dx;
395     my $y = $o->{y} - $dy;
396 root 1.6
397     my $ext = $x|$y ? "+$x+$y" : "";
398    
399 root 1.26 $o->{face} .= $ext unless /^blank.x11$|^empty.x11$/ || !$o->{face};
400 root 1.6
401 root 1.35 $visibility = delete $o->{visibility} if exists $o->{visibility};
402     $magicmap = delete $o->{magicmap} if exists $o->{magicmap};
403 root 1.6
404     my $anim = delete $o->{anim};
405    
406     if ($anim) {
407 root 1.24 # possibly add $ext to the animation name to avoid
408     # the need to specify archnames for all parts
409     # of a multipart archetype.
410 root 1.8 $o->{animation} = "$o->{_name}";
411 root 1.24 my $facings = 1;
412     my @frames;
413 root 1.6
414     for (@$anim) {
415 root 1.24 if (/^facings\s+(\d+)/) {
416     $facings = $1*1;
417     } elsif (/^blank.x11$|^empty.x11$/) {
418     push @frames, $_;
419     } else {
420     push @frames, "$_$ext";
421     }
422 root 1.6 }
423    
424 root 1.24 $ANIMINFO{$o->{animation}} = {
425     facings => $facings,
426     frames => \@frames,
427     };
428 root 1.6 }
429    
430 root 1.58 for ($o->{face} || (), @{$anim || []}) {
431     next if /^facings\s/;
432    
433     my $face = $_;
434     $face =~ s/\+\d+\+\d+$//; # remove tile offset coordinates
435 root 1.6
436 root 1.56 my $info = $FACEINFO{$face} ||= { };
437     $info->{arc} = $o;
438    
439     next if $face =~ /^blank.x11$|^empty.x11$/;
440 root 1.6
441     $info->{visibility} = $visibility if defined $visibility;
442     $info->{magicmap} = $magicmap if defined $magicmap;
443 root 1.4 }
444 root 1.12
445     if (my $smooth = delete $o->{smoothface}) {
446 root 1.54 my %kv = split /\s+/, $smooth;
447 root 1.23 my $level = $o->{smoothlevel}; #TODO: delete from $o if !gcfclient-support
448     while (my ($face, $smooth) = each %kv) {
449 root 1.54 $FACEINFO{$smooth}{arc} = $o;
450    
451 root 1.23 $FACEINFO{$face}{smooth} = $smooth;
452     $FACEINFO{$face}{smoothlevel} = $level;
453 root 1.14 }
454 root 1.12 }
455 root 1.4 }
456     }
457 root 1.3 }
458 root 1.2 }
459    
460 root 1.3 sub process_trs {
461 root 1.53 while (my $job = $c_trs->get) {
462     my ($dir, $file) = @$job;
463 root 1.12 my $path = "$dir/$file";
464    
465     my $trs;
466     if (0 > aio_load $path, $trs) {
467     warn "$path: $!, skipping.\n";
468     next;
469     }
470    
471     $TRS .= $trs;
472 root 1.3 }
473 root 1.2 }
474    
475 root 1.45 my %FILECACHE;
476    
477     sub load_cached($;$) {
478     unless (exists $FILECACHE{$_[0]}) {
479     my $data;
480     if (0 < aio_load $_[0], $data) {
481 root 1.49 if ($_[1]) {
482     $data = eval { $_[1]->($data) };
483     warn "$_[0]: $@" if $@;
484     }
485 root 1.45 }
486    
487     $FILECACHE{$_[0]} = $data;
488     }
489    
490     $FILECACHE{$_[0]}
491     }
492    
493 root 1.37 sub process_res {
494 root 1.53 while (my $job = $c_res->get) {
495     my ($dir, $file, $type) = @$job;
496 root 1.37
497     my $data;
498     aio_load "$dir/$file", $data;
499    
500 root 1.45 my $meta = load_cached "$dir/meta", sub { JSON::XS::from_json shift };
501 root 1.38
502 root 1.50 next if $meta && !exists $meta->{$file};
503 root 1.49
504 root 1.48 $meta = {
505     %{ $meta->{"" } || {} },
506     %{ $meta->{$file} || {} },
507     };
508    
509 root 1.49 if ($meta->{license} =~ s/^#//) {
510     $meta->{license} = ({
511     "pd" => "Public Domain",
512     "gpl" => "GNU General Public License, version 3.0 or any later",
513     "cc/by/2.0" => "Licensed under Creative Commons Attribution 2.0 http://creativecommons.org/licenses/by/2.0/",
514 root 1.53 "cc/by/2.5" => "Licensed under Creative Commons Attribution 2.5 http://creativecommons.org/licenses/by/2.5/",
515 root 1.49 "cc/by/3.0" => "Licensed under Creative Commons Attribution 3.0 http://creativecommons.org/licenses/by/3.0/",
516     })->{$meta->{license}}
517     || warn "$dir/$file: license tag '$meta->{license}' not found.";
518     }
519    
520 root 1.37 $file =~ s/\.res$//;
521 root 1.46 $file =~ s/\.(ogg|wav|jpg|png)$//;
522 root 1.37
523     substr $dir, 0, 1 + length $PATH, "";
524    
525     $RESOURCE{"$dir/$file"} = {
526 root 1.45 type => (delete $meta->{type}) || $type,
527     data => $data,
528     %$meta ? (meta => $meta) : (),
529 root 1.37 };
530     }
531     }
532    
533 root 1.3 sub find_files;
534     sub find_files {
535 root 1.2 my ($path) = @_;
536    
537 root 1.3 IO::AIO::aioreq_pri 4;
538     IO::AIO::aio_scandir $path, 4, sub {
539 root 1.2 my ($dirs, $nondirs) = @_;
540    
541 root 1.3 find_files "$path/$_"
542 root 1.2 for grep $_ !~ /^(?:CVS|dev)$/, @$dirs;
543    
544 root 1.43 my $dir = $path;
545     substr $dir, 0, 1 + length $PATH, "";
546    
547 root 1.2 for my $file (@$nondirs) {
548 root 1.44 if ($dir =~ /^music(?:\/|$)/) {
549 root 1.53 $c_res->put ([$path, $file, 3]) # FT_MUSIC
550 root 1.44 if $file =~ /\.(ogg)$/;
551    
552 root 1.47 } elsif ($dir =~ /^sound(?:\/|$)/) {
553 root 1.53 $c_res->put ([$path, $file, 5]) # FT_SOUND
554 root 1.44 if $file =~ /\.(wav|ogg)$/;
555    
556     } elsif ($dir =~ /^res(?:\/|$)/) {
557 root 1.53 $c_res->put ([$path, $file, 0]) # FT_FACE
558 root 1.44 if $file =~ /\.(jpg|png)$/;
559 root 1.53 $c_res->put ([$path, $file, 7]) # FT_RSRC
560 root 1.44 if $file =~ /\.(res)$/;
561    
562 root 1.43 } elsif ($file =~ /\.png$/) {
563 root 1.53 push @c_png, ["$path/$file", 0];
564 root 1.44
565 root 1.2 } elsif ($file =~ /\.trs$/) {
566 root 1.53 $c_trs->put ([$path, $file]);
567 root 1.44
568 root 1.2 } elsif ($file =~ /\.arc$/) {
569 root 1.53 $c_arc->put ([$path, $file]);
570 root 1.44
571 root 1.2 } else {
572 root 1.33 warn "ignoring $path/$file\n" if $VERBOSE >= 3;
573 root 1.2 }
574     }
575     };
576     }
577    
578     sub inst_arch($) {
579     my (undef, $path) = @_;
580    
581 root 1.37 $PATH = $path;
582    
583 root 1.29 print "\n",
584     "Installing '$path' to '$DATADIR'\n",
585 root 1.28 "\n",
586 root 1.27 "This can take a long time if you run this\n",
587     "for the first time or do not use --cache.\n",
588     "\n",
589     "Unless you run verbosely, all following warning\n",
590     "or error messages indicate serious problems.\n",
591     "\n";
592 root 1.2
593     if (!-d "$path/treasures") {
594     warn "'$path' does not look like an arch directory ('treasures' directory is missing).\n";
595     exit 1 unless $FORCE;
596     }
597    
598 root 1.53 print "scanning files...\n" if $VERBOSE;
599    
600 root 1.3 find_files $path;
601 root 1.53
602     my @a_arc = map +(async \&process_arc), 1..2;
603     my @a_res = map +(async \&process_res), 1..2;
604     my @a_trs = map +(async \&process_trs), 1..2;
605    
606 root 1.2 IO::AIO::flush;
607    
608 root 1.53 $c_res->put (undef) for @a_res;
609     $c_arc->put (undef) for @a_arc;
610     $c_trs->put (undef) for @a_trs;
611    
612     print "start file scan, arc, res processing...\n" if $VERBOSE;
613    
614     $_->join for @a_arc; # need to parse all archetypes before png processing
615 root 1.3
616 root 1.53 print "end arc, start png processing...\n" if $VERBOSE;
617    
618     # four png crunchers work fine for my 2x smp machine
619     my @a_png = map +(async \&process_png), 1..4;
620    
621     $_->join for (@a_trs, @a_res, @a_png);
622    
623     print "scanning done, processing results...\n" if $VERBOSE;
624 root 1.5 {
625 root 1.27 # remove path prefix from editor_folder
626     substr $_->{editor_folder}, 0, 1 + length $path, ""
627     for values %ARC;
628    
629 root 1.53 print "resolving inheritance tree...\n" if $VERBOSE;
630 root 1.27 # resolve inherit
631     while () {
632     my $progress;
633     my $loop;
634    
635     for my $o (values %ARC) {
636     if (my $other = $o->{inherit}) {
637     if (my $s = $ARC{$other}) {
638     if ($s->{inherit}) {
639     $loop = $s;
640     } else {
641     delete $o->{inherit};
642 root 1.30 my %s = %$s;
643     delete @s{qw(_name more name name_pl)};
644     %$o = ( %s, %$o );
645 root 1.27 ++$progress;
646     }
647     } else {
648     warn "'$o->{_name}' tries to inherit from undefined archetype '$other', skipping.\n";
649     delete $ARC{$o->{_name}};
650     }
651     }
652     }
653    
654     unless ($progress) {
655     die "inheritance loop detected starting at archetype '$loop->{_name}', aborting.\n"
656     if $loop;
657    
658     last;
659     }
660     }
661    
662 root 1.34 # remove base classes (by naming scheme, should use something like "baseclass xxx" to inherit
663     @ARC = grep $_->{_name} !~ /^(?:type|class)_/, @ARC;
664    
665 root 1.53 print "writing archetypes...\n" if $VERBOSE;
666 root 1.5 open my $fh, ">:utf8", "$DATADIR/archetypes~"
667     or die "$DATADIR/archetypes~: $!";
668 root 1.30 print $fh Crossfire::archlist_to_string [sort { $a->{_name} cmp $b->{_name} } @ARC];
669 root 1.5 }
670    
671 root 1.6 {
672 root 1.53 print "writing treasures...\n" if $VERBOSE;
673 root 1.12 open my $fh, ">:utf8", "$DATADIR/treasures~"
674     or die "$DATADIR/treasures~: $!";
675     print $fh $TRS;
676     }
677    
678     {
679 root 1.53 print "processing facedata...\n" if $VERBOSE;
680 root 1.6 while (my ($k, $v) = each %FACEINFO) {
681 root 1.12 length $v->{data32} or warn "$k: face has no png32. this will not work (shoddy gcfclient will crash of course).\n";
682 root 1.19 length $v->{data64} or warn "$k: face has no png64. this will not work very well.\n";
683 root 1.5
684 root 1.20 length $v->{data32} <= 10000 or warn "$k: face32 larger than 10000 bytes, will not work with crossfire client.\n";
685     #length $v->{data64} <= 10000 or warn "$k: face64 larger than 10000 bytes.\n";
686    
687 root 1.35 if (my $magicmap = $v->{magicmap}) {
688     $magicmap =~ y/A-Z_\-/a-z/d;
689     $v->{magicmap} = $COLOR{$magicmap};
690     }
691 root 1.53
692     delete $v->{arc};
693 root 1.6 }
694    
695 root 1.53 print "writing facedata...\n" if $VERBOSE;
696 root 1.19 open my $fh, ">:perlio", "$DATADIR/facedata~"
697     or die "$DATADIR/facedata~: $!";
698 root 1.6
699 root 1.25 print $fh freeze {
700 root 1.24 version => 2,
701     faceinfo => \%FACEINFO,
702     animinfo => \%ANIMINFO,
703 root 1.37 resource => \%RESOURCE,
704 root 1.24 };
705 root 1.5 }
706    
707 root 1.53 print "committing files...\n" if $VERBOSE;
708    
709 root 1.24 for (qw(archetypes facedata treasures)) {
710 root 1.6 chmod 0644, "$DATADIR/$_~";
711 root 1.13 rename "$DATADIR/$_~", "$DATADIR/$_"
712     or die "$DATADIR/$_: $!";
713 root 1.6 }
714 root 1.13
715     print "archetype data installed successfully.\n";
716 root 1.2 }
717     }
718    
719     Getopt::Long::Configure ("bundling", "no_ignore_case");
720     GetOptions (
721     "verbose|v:+" => \$VERBOSE,
722 root 1.3 "cache" => \$CACHE,
723 root 1.2 "quiet|q" => sub { $VERBOSE = 0 },
724     "force" => sub { $FORCE = 1 },
725     "install-arch=s" => \&inst_arch,
726     "install-maps=s" => \&inst_maps,
727     "print-statedir" => sub { print "@pkgstatedir@\n" },
728     "print-datadir" => sub { print "$DATADIR\n" },
729     "print-confdir" => sub { print "@pkgconfdir@\n" },
730     "print-libdir" => sub { print "@libdir@/@PACKAGE@\n" },
731     "print-bindir" => sub { print "@bindir@/@PACKAGE@\n" },
732     ) or usage;
733