ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/deliantra/server/utils/cfutil.in
Revision: 1.64
Committed: Mon Dec 17 08:50:15 2007 UTC (16 years, 5 months ago) by root
Branch: MAIN
Changes since 1.63: +1 -0 lines
Log Message:
no wonder rsync took so long *g*

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