complete tile drawing support in Data::StarCraft::Tileset
[perl/schtarr.git] / Data-StarCraft / lib / Data / StarCraft / Tileset.pm
index 0b17b07aca12eb5dd00c7a83877dafab8da0a492..891c12b93c5ae96f48792f06ae6b27ea17f81018 100644 (file)
@@ -4,42 +4,216 @@ use strict;
 use warnings;
 use Data::Dumper;
 
+use List::Util qw(sum);
+
+our $VERSION = '0.10';
+
 sub open {
        my ($class, $filename) = @_;
        my $self = bless {}, $class;
        open my $groupfile, '<', "$filename.cv5" or return;
        $self->readgroups($groupfile);
-       # vf4 = minitile type
-       # vr4 = minitile sprites
-       # vx4 = minitile map?
+       if (open my $groupfile, '<', "$filename.vx4") {
+               $self->readvx4($groupfile);
+       }
+       if (open my $groupfile, '<', "$filename.vf4") {
+               $self->readvf4($groupfile);
+       }
+       if (open my $groupfile, '<', "$filename.vr4") {
+               $self->readvr4($groupfile);
+       }
+       if (open my $groupfile, '<', "$filename.wpe") {
+               $self->readwpe($groupfile);
+       }
        return $self;
 }
 
 sub _read {
        my $self = shift;
        my ($fh, $size, $seek) = @_;
+
        seek $fh, $seek, 0 if $seek;
        read($fh, my $in, $size) eq $size or return undef;
        return $in;
 }
 
+sub readwpe {
+       my $self = shift;
+       my ($fh) = @_;
+
+       # wpe = palette
+       while (defined (my $line = $self->_read($fh, 4))) {
+               push @{ $self->{palette} }, [ unpack 'CCCX', $line ];
+       }
+       return;
+}
+
+sub readvx4 {
+       my $self = shift;
+       my ($fh) = @_;
+
+       # vx4 = minitile map (4x4 references per megatile)
+       while (defined (my $line = $self->_read($fh, 16*2))) {
+               push @{ $self->{tileref} }, [ unpack 'v*', $line ];
+       }
+       return;
+}
+
+sub readvr4 {
+       my $self = shift;
+       my ($fh) = @_;
+
+       # vr4 = minitile sprites
+       while (defined (my $line = $self->_read($fh, 8**2))) {
+               push @{ $self->{minibmp} }, [ unpack 'C*', $line ];
+       }
+       return;
+}
+
+sub readvf4 {
+       my $self = shift;
+       my ($fh) = @_;
+
+       # vf4 = minitile type
+       while (defined (my $line = $self->_read($fh, 2))) {
+               push @{ $self->{minitype} }, unpack 'v', $line;
+       }
+       return;
+}
+
+sub col {
+       my $self = shift;
+       my ($minitile) = @_;
+
+       return $self->{bmp}->[$minitile] if defined $self->{bmp}->[$minitile];
+
+       my $pixels = $self->{minibmp}->[$minitile];
+       my $pal = $self->{palette};
+
+       my @rgb;
+       for my $color (@$pixels) {
+#              $rgb[$_] += $self->{palette}->[$color]->[$_] for 0 .. 2;
+#                      # ^ wow, this is really slow
+               $rgb[0] += $pal->[$color]->[0];
+               $rgb[1] += $pal->[$color]->[1];
+               $rgb[2] += $pal->[$color]->[2];
+       }
+       return $self->{bmp}->[$minitile] = [map {$_ >> 6} @rgb];
+}
+
+=head2 col
+
+Average color [r,g,b] for minitile.
+
+For example, using the Jungle tileset:
+
+       $t->col(719)
+
+is a water minitile, giving a blue color C<[38, 38, 57]>.
+
+=cut
+
+sub sprite {
+       my $self = shift;
+       my ($id) = @_;
+
+       my $minitiles = $self->{tileref}->[$id];
+       my @pix;
+       for my $tiley (0 .. 3) {
+               for my $y (0 .. 7) {
+                       for my $tilex (0 .. 3) {
+                               # minitile number is half of tileref
+                               # if odd, the minitile is mirrored
+                               my $minitile = $minitiles->[$tilex + $tiley*4];
+
+                               for my $x ($minitile & 1 ? (reverse 0 .. 7) : (0 .. 7)) {
+                                       push @pix, $self->{minibmp}->[$minitile >> 1]->[$x + $y*8];
+                               }
+                       }
+               }
+       }
+       return \@pix;
+}
+
+=head2 sprite
+
+Returns bitmap of an entire tile, as 32x32 color indexes.
+
+For example, the green value of the bottom center pixel of tile #1 would be:
+
+       $t->{palette}->[ $t->sprite(1)->[31*32 + 15] ]->[2]
+
+=cut
+
+sub tile {
+       my $self = shift;
+       my ($id) = @_;
+
+       my $tile = $self->{map}->[$id];
+       my $minitiles = $self->{tileref}->[$tile] || [];
+       return {
+               id => $tile,
+               group   => $self->{group}->[$id >> 4],
+#              subtype => [ map { $self->{minitype}->[$_] } @$minitiles ],
+               subtype => [ map { $self->{minitype}->[$_] } $tile*16 .. $tile*16+15 ],
+               subcol  => [ map { $self->col($_ >> 1) } @$minitiles ],
+#              sprite  => $self->sprite($tile),
+       };
+}
+
+sub tileavg {
+       my $self = shift;
+       my $tile = $self->tile(shift);
+
+       my $info = $tile->{group};
+       $info->{walk} = sum(@{ $tile->{subtype} }) >> 4;
+       my @rgb;
+       for my $subcol (@{ $tile->{subcol} }) {
+               $rgb[$_] += $subcol->[$_] for 0 .. 2;
+       }
+       $info->{col} = [ map {$_ >> 4} @rgb ];
+       return $info;
+}
+
+=head2 tile
+
+Tile details. For example a water tile:
+
+       $t->tile(96)
+
+would give:
+
+       group => {
+               walk   => 0,
+               height => 0,
+               build  => 128,
+       },
+       subtype => [ 1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1 ],
+       subcol => [
+               [39,40,59], [38,39,57], [40,41,60], [36,37,55],
+               [37,38,56], [39,40,60], [38,39,57], [40,41,61],
+               [37,38,56], [41,41,61], [40,40,60], [36,36,54],
+               [36,36,54], [37,37,55], [35,35,53], [38,38,57]
+       ],
+
+=cut
+
 sub readgroups {
        my $self = shift;
        my ($fh) = @_;
-       my $id = 0;
+
+       # cv5 = tile groups
        my @tilemap;
        while (defined (my $line = $self->_read($fh, 52))) {
                my @data = unpack "vCC v24", $line;
                my %row = (
                        build => $data[1] & 0xF0,
                        height => $data[2] & 0xF,
-                       walk => undef,
+#                      rawdata => [@data[0..10]],
                );
-               $self->{group}->[$id] = \%row;
-               push @tilemap, $_ for @data[11..26];
-               $id++;
+               push @{ $self->{group} }, \%row;
+               push @{ $self->{map} }, $_ for @data[11..26];
        }
-       $self->{tile} = \@tilemap;
        return;
        # index (ground_height unknown4 buildable unknown3) u6 u7 u8 u9 u10 u11 u12 u13
        # 1 (ground_height unknown4 buildable unknown3) 35 0 doodad_group 0 58 6 6 0