support -scale flags
[xonotic/xonotic.git] / misc / tools / xonotic-map-compiler
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use POSIX;
6 use File::Temp;
7
8 # change these to match your system, or define them in ~/.xonotic-map-compiler
9 # (just copy paste this part to the file ~/.xonotic-map-compiler)
10
11         # Path to Xonotic (where the data directory is in)
12         our $XONOTICDIR   = '/home/rpolzer/Games/Xonotic';
13
14         # Path to your q3map2 program. You find it in your GtkRadiant/install
15         # directory.
16         our $Q3MAP2      = '/home/rpolzer/Games/Xonotic/netradiant/install/q3map2.x86';
17
18         # General flags for q3map2 (for example -threads 4)
19         our $Q3MAP2FLAGS = '';
20
21         # Default flags for the -bsp stage
22         our $BSPFLAGS    = '-meta -samplesize 8 -minsamplesize 4 -mv 1000000 -mi 6000000';
23
24         # Default flags for the -vis stage
25         our $VISFLAGS    = '';
26
27         # Default flags for the -light stage
28         our $LIGHTFLAGS  = '-deluxe -patchshadows -samples 3 -lightmapsize 512';
29
30         # Default flags for the -minimap stage
31         our $MINIMAPFLAGS = '';
32
33         # Default order of commands
34         our $ORDER = 'light,vis,minimap';
35
36 # end of user changable part
37
38 do "$ENV{HOME}/.xonotic-map-compiler";
39
40 sub Usage()
41 {
42         print <<EOF;
43 Usage:
44 $0 mapname [-bsp bspflags...] [-vis visflags...] [-light lightflags...] [-minimap minimapflags]
45 EOF
46         exit 1;
47 }
48
49 my $options =
50 {
51         bsp => [split /\s+/, $BSPFLAGS],
52         vis => [split /\s+/, $VISFLAGS],
53         light => [split /\s+/, $LIGHTFLAGS],
54         minimap => [split /\s+/, $MINIMAPFLAGS],
55         scale => [], # can't have defaults atm
56         order => [split /\s*,\s*/, $ORDER],
57         maps => [],
58         scale => 1
59 };
60
61 my $curmode = 'maps';
62
63 while(@ARGV)
64 {
65         $_ = shift @ARGV;
66         my $enterflags = undef;
67         if($_ eq '-bsp')
68         {
69                 $enterflags = 'bsp';
70         }
71         elsif($_ eq '-vis')
72         {
73                 $enterflags = 'vis';
74         }
75         elsif($_ eq '-light')
76         {
77                 $enterflags = 'light';
78         }
79         elsif($_ eq '-minimap')
80         {
81                 $enterflags = 'minimap';
82         }
83         elsif($_ eq '-map')
84         {
85                 $curmode = 'maps';
86         }
87         elsif($_ eq '-scale')
88         {
89                 $options->{scale} = (shift @ARGV) || 1;
90                 $enterflags = 'scale';
91         }
92         elsif($_ eq '-novis')
93         {
94                 $options->{vis} = undef;
95         }
96         elsif($_ eq '-nolight')
97         {
98                 $options->{light} = undef;
99         }
100         elsif($_ eq '-nominimap')
101         {
102                 $options->{minimap} = undef;
103         }
104         elsif($_ eq '-noshaderlist')
105         {
106                 $options->{noshaderlist} = 1;
107         }
108         elsif($_ eq '-order')
109         {
110                 $options->{order} = [split /\s*,\s*/, shift @ARGV];
111         }
112         elsif($_ =~ /^-(-.*)/)
113         {
114                 if($curmode eq 'maps')
115                 {
116                         $curmode = 'bsp';
117                 }
118                 push @{$options->{$curmode}}, $1;
119         }
120         elsif($_ =~ /^-/ and $curmode eq 'maps')
121         {
122                 $curmode = 'bsp';
123                 push @{$options->{$curmode}}, $_;
124         }
125         else
126         {
127                 push @{$options->{$curmode}}, $_;
128         }
129         if(defined $enterflags)
130         {
131                 $curmode = $enterflags;
132                 if($ARGV[0] eq '+')
133                 {
134                         shift @ARGV;
135                 }
136                 else
137                 {
138                         $options->{$curmode} = [];
139                 }
140         }
141 }
142
143 my $linkdir = File::Temp::tempdir("xonotic-map-compiler.XXXXXX", TMPDIR => 1, CLEANUP => 1);
144
145 sub q3map2(@)
146 {
147         my @args = ($Q3MAP2, split(/\s+/, $Q3MAP2FLAGS), '-game', 'xonotic', '-fs_basepath', $XONOTICDIR, '-fs_basepath', $linkdir, '-v', @_);
148         print "\$ @args\n";
149         return !system @args;
150 }
151
152 (my $mapdir = getcwd()) =~ s!/[^/]*(?:$)!!;
153 $mapdir = "/" if $mapdir eq "";
154 symlink "$mapdir", "$linkdir/data";
155
156 my ($prescale, $postscale) = ($options->{scale} =~ /^([0-9.]+)(?::([0-9.]+))?$/);
157 $postscale = 1 if not defined $postscale;
158
159 for my $m(@{$options->{maps}})
160 {
161         $m =~ s/\.(?:map|bsp)$//;
162         if($prescale != 1)
163         {
164                 open my $checkfh, "<", "$m.map"
165                         or die "open $m.map: $!";
166                 my $keeplights = 0;
167                 while(<$checkfh>)
168                 {
169                         /^\s*"_keeplights"\s+"1"\s*$/
170                                 or next;
171                         $keeplights = 1;
172                 }
173                 close $checkfh;
174                 die "$m does not define _keeplights to 1"
175                         unless $keeplights;
176         }
177
178         my %shaders = map { m!/([^/.]*)\.shader(?:$)! ? ($1 => 1) : () } glob "../scripts/*.shader";
179
180         my $restore_shaderlist = sub { };
181         if(!$options->{noshaderlist})
182         {
183                 my $previous_shaderlist = undef;
184                 my $shaderlist = "";
185                 if(open my $fh, "<", "$XONOTICDIR/data/scripts/shaderlist.txt")
186                 {
187                         while(<$fh>)
188                         {
189                                 $shaderlist .= $_;
190                         }
191
192                         # we may have to restore the file on exit
193                         $previous_shaderlist = $shaderlist
194                                 if "$XONOTICDIR/data" eq $mapdir;
195                 }
196                 else
197                 {
198                         # possibly extract the shader list from a pk3?
199                         local $ENV{N} = $XONOTICDIR;
200                         $shaderlist = `cd "\$N" && for X in "\$N"/data/data*.pk3; do Y=\$X; done; unzip -p "\$Y" scripts/shaderlist.txt`;
201                 }
202
203                 my $shaderlist_new = "";
204                 for(split /\r?\n|\r/, $shaderlist)
205                 {
206                         delete $shaders{$_};
207                         $shaderlist_new .= "$_\n";
208                 }
209                 if(%shaders)
210                 {
211                         for(sort keys %shaders)
212                         {
213                                 $shaderlist_new .= "$_\n";
214                         }
215                 }
216                 else
217                 {
218                         $shaderlist_new = undef;
219                 }
220
221                 $restore_shaderlist = sub
222                 {
223                         if(defined $shaderlist_new)
224                         {
225                                 if(defined $previous_shaderlist)
226                                 {
227                                         open my $fh, ">", "$mapdir/scripts/shaderlist.txt";
228                                         print $fh $previous_shaderlist;
229                                         close $fh;
230                                 }
231                                 else
232                                 {
233                                         unlink "$mapdir/scripts/shaderlist.txt";
234                                 }
235                         }
236                 };
237
238                 if(defined $shaderlist_new)
239                 {
240                         mkdir "$mapdir/scripts";
241                         open my $fh, ">", "$mapdir/scripts/shaderlist.txt";
242                         print $fh $shaderlist_new;
243                         close $fh;
244                 }
245         }
246
247         local $SIG{INT} = sub
248         {
249                 print "SIGINT caught, cleaning up...\n";
250                 $restore_shaderlist->();
251                 exit 0;
252         };
253
254         eval
255         {
256                 unlink <$m/lm_*>; # delete old external lightmaps
257                 q3map2 '-bsp', @{$options->{bsp}},   "$m.map"
258                         or die "-bsp: $?";
259                 if($prescale != 1)
260                 {
261                         q3map2 '-scale', @{$options->{scale}}, $prescale, "$m.bsp"
262                                 or die "-scale: $?";
263                         rename "${m}_s.bsp", "$m.bsp"
264                                 or die "rename ${m}_s.bsp $m.bsp: $!";
265                 }
266                 my @o = @{$options->{order}};
267                 push @o, qw/light vis minimap/;
268                 my %o = ();
269
270                 for(@o)
271                 {
272                         next if $o{$_}++;
273                         if($_ eq 'light')
274                         {
275                                 if(defined $options->{light})
276                                 {
277                                         q3map2 '-light',        @{$options->{light}}, "$m.map"
278                                                 or die "-light: $?";
279                                 }
280                         }
281                         if($_ eq 'vis')
282                         {
283                                 if(defined $options->{vis})
284                                 {
285                                         q3map2 '-vis',          @{$options->{vis}},   "$m.map"
286                                                 or die "-vis: $?";
287                                 }
288                         }
289                         if($_ eq 'minimap')
290                         {
291                                 if(defined $options->{minimap})
292                                 {
293                                         q3map2 '-minimap',      @{$options->{minimap}}, "$m.map"
294                                                 or die "-minimap: $?";
295                                 }
296                         }
297                 }
298
299                 if($postscale != 1)
300                 {
301                         q3map2 '-scale', @{$options->{scale}}, $postscale, "$m.bsp"
302                                 or die "-scale: $?";
303                         rename "${m}_s.bsp", "$m.bsp"
304                                 or die "rename ${m}_s.bsp $m.bsp: $!";
305                 }
306
307                 unlink "$m.srf";
308                 unlink "$m.prt";
309
310                 $restore_shaderlist->();
311                 1;
312         }
313         or do
314         {
315                 $restore_shaderlist->();
316                 die $@;
317         };
318 }