complete tile drawing support in Data::StarCraft::Tileset
[perl/schtarr.git] / Data-StarCraft / lib / Data / StarCraft / Tileset.pm
1 package Data::StarCraft::Tileset;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6
7 use List::Util qw(sum);
8
9 our $VERSION = '0.10';
10
11 sub open {
12         my ($class, $filename) = @_;
13         my $self = bless {}, $class;
14         open my $groupfile, '<', "$filename.cv5" or return;
15         $self->readgroups($groupfile);
16         if (open my $groupfile, '<', "$filename.vx4") {
17                 $self->readvx4($groupfile);
18         }
19         if (open my $groupfile, '<', "$filename.vf4") {
20                 $self->readvf4($groupfile);
21         }
22         if (open my $groupfile, '<', "$filename.vr4") {
23                 $self->readvr4($groupfile);
24         }
25         if (open my $groupfile, '<', "$filename.wpe") {
26                 $self->readwpe($groupfile);
27         }
28         return $self;
29 }
30
31 sub _read {
32         my $self = shift;
33         my ($fh, $size, $seek) = @_;
34
35         seek $fh, $seek, 0 if $seek;
36         read($fh, my $in, $size) eq $size or return undef;
37         return $in;
38 }
39
40 sub readwpe {
41         my $self = shift;
42         my ($fh) = @_;
43
44         # wpe = palette
45         while (defined (my $line = $self->_read($fh, 4))) {
46                 push @{ $self->{palette} }, [ unpack 'CCCX', $line ];
47         }
48         return;
49 }
50
51 sub readvx4 {
52         my $self = shift;
53         my ($fh) = @_;
54
55         # vx4 = minitile map (4x4 references per megatile)
56         while (defined (my $line = $self->_read($fh, 16*2))) {
57                 push @{ $self->{tileref} }, [ unpack 'v*', $line ];
58         }
59         return;
60 }
61
62 sub readvr4 {
63         my $self = shift;
64         my ($fh) = @_;
65
66         # vr4 = minitile sprites
67         while (defined (my $line = $self->_read($fh, 8**2))) {
68                 push @{ $self->{minibmp} }, [ unpack 'C*', $line ];
69         }
70         return;
71 }
72
73 sub readvf4 {
74         my $self = shift;
75         my ($fh) = @_;
76
77         # vf4 = minitile type
78         while (defined (my $line = $self->_read($fh, 2))) {
79                 push @{ $self->{minitype} }, unpack 'v', $line;
80         }
81         return;
82 }
83
84 sub col {
85         my $self = shift;
86         my ($minitile) = @_;
87
88         return $self->{bmp}->[$minitile] if defined $self->{bmp}->[$minitile];
89
90         my $pixels = $self->{minibmp}->[$minitile];
91         my $pal = $self->{palette};
92
93         my @rgb;
94         for my $color (@$pixels) {
95 #               $rgb[$_] += $self->{palette}->[$color]->[$_] for 0 .. 2;
96 #                       # ^ wow, this is really slow
97                 $rgb[0] += $pal->[$color]->[0];
98                 $rgb[1] += $pal->[$color]->[1];
99                 $rgb[2] += $pal->[$color]->[2];
100         }
101         return $self->{bmp}->[$minitile] = [map {$_ >> 6} @rgb];
102 }
103
104 =head2 col
105
106 Average color [r,g,b] for minitile.
107
108 For example, using the Jungle tileset:
109
110         $t->col(719)
111
112 is a water minitile, giving a blue color C<[38, 38, 57]>.
113
114 =cut
115
116 sub sprite {
117         my $self = shift;
118         my ($id) = @_;
119
120         my $minitiles = $self->{tileref}->[$id];
121         my @pix;
122         for my $tiley (0 .. 3) {
123                 for my $y (0 .. 7) {
124                         for my $tilex (0 .. 3) {
125                                 # minitile number is half of tileref
126                                 # if odd, the minitile is mirrored
127                                 my $minitile = $minitiles->[$tilex + $tiley*4];
128
129                                 for my $x ($minitile & 1 ? (reverse 0 .. 7) : (0 .. 7)) {
130                                         push @pix, $self->{minibmp}->[$minitile >> 1]->[$x + $y*8];
131                                 }
132                         }
133                 }
134         }
135         return \@pix;
136 }
137
138 =head2 sprite
139
140 Returns bitmap of an entire tile, as 32x32 color indexes.
141
142 For example, the green value of the bottom center pixel of tile #1 would be:
143
144         $t->{palette}->[ $t->sprite(1)->[31*32 + 15] ]->[2]
145
146 =cut
147
148 sub tile {
149         my $self = shift;
150         my ($id) = @_;
151
152         my $tile = $self->{map}->[$id];
153         my $minitiles = $self->{tileref}->[$tile] || [];
154         return {
155                 id => $tile,
156                 group   => $self->{group}->[$id >> 4],
157 #               subtype => [ map { $self->{minitype}->[$_] } @$minitiles ],
158                 subtype => [ map { $self->{minitype}->[$_] } $tile*16 .. $tile*16+15 ],
159                 subcol  => [ map { $self->col($_ >> 1) } @$minitiles ],
160 #               sprite  => $self->sprite($tile),
161         };
162 }
163
164 sub tileavg {
165         my $self = shift;
166         my $tile = $self->tile(shift);
167
168         my $info = $tile->{group};
169         $info->{walk} = sum(@{ $tile->{subtype} }) >> 4;
170         my @rgb;
171         for my $subcol (@{ $tile->{subcol} }) {
172                 $rgb[$_] += $subcol->[$_] for 0 .. 2;
173         }
174         $info->{col} = [ map {$_ >> 4} @rgb ];
175         return $info;
176 }
177
178 =head2 tile
179
180 Tile details. For example a water tile:
181
182         $t->tile(96)
183
184 would give:
185
186         group => {
187                 walk   => 0,
188                 height => 0,
189                 build  => 128,
190         },
191         subtype => [ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1 ],
192         subcol => [
193                 [39,40,59], [38,39,57], [40,41,60], [36,37,55],
194                 [37,38,56], [39,40,60], [38,39,57], [40,41,61],
195                 [37,38,56], [41,41,61], [40,40,60], [36,36,54],
196                 [36,36,54], [37,37,55], [35,35,53], [38,38,57]
197         ],
198
199 =cut
200
201 sub readgroups {
202         my $self = shift;
203         my ($fh) = @_;
204
205         # cv5 = tile groups
206         my @tilemap;
207         while (defined (my $line = $self->_read($fh, 52))) {
208                 my @data = unpack "vCC v24", $line;
209                 my %row = (
210                         build => $data[1] & 0xF0,
211                         height => $data[2] & 0xF,
212 #                       rawdata => [@data[0..10]],
213                 );
214                 push @{ $self->{group} }, \%row;
215                 push @{ $self->{map} }, $_ for @data[11..26];
216         }
217         return;
218         # index (ground_height unknown4 buildable unknown3) u6 u7 u8 u9 u10 u11 u12 u13
219         # 1 (ground_height unknown4 buildable unknown3) 35 0 doodad_group 0 58 6 6 0
220 }
221
222 1;
223