MHeasell/rwe

Extra tree on Show Down

Opened this issue · 2 comments

The map "Show Down" from The Core Contingency contains an extra tree next to a metal spot just south-east of the west side start position. Here it is when viewed in the map editor:

image

However it does not actually appear inside of TA:

image

Likely what happens is that the metal spot's collision box is overlapping the tree, so it overwrites the tree when features are populated onto the map.

Currently the extra tree shows up in RWE. This prevents the metal spot from having a mex placed on it.

Since this affects gameplay, we need to make the RWE behaviour match TA here.

I've now spent some time on a map decoder for Total Annihilation and I think I can describe how feature overlap is handled by the engine. The important points are: the engine treats "indestructible" (indestructible=1; in the .tdf) vs. "destructible" objects differently, and there is a particular order to feature placement that affects how they are tested.

There will be Perl code here but hopefully it is readable as pseudocode.

First, you build a list of all features in the map. Objects in the .tnt file are read left-to-right, then top-to-bottom, and then once that's done the objects from the .ota file are parsed in order (they are numbered in the file).

my @features;
# first gather all features from the tnt file
for (my $y = 0; $y < $tnt->{height} - 1; $y ++) {
  for (my $x = 0; $x < $tnt->{width} - 1; $x ++) {
    my $anim = $tnt->{anims}[ $tnt->{mapFeature}[$y][$x] ];
    if (defined $anim) {
      push @features, { y => $y, x => $x, anim => $anim };
    }
  }
}

# Next collect OTA mission features into the map.
for my $key (sort keys %{$ota->{globalheader}{'schema 0'}{features}}) {
  my $val = $ota->{globalheader}{'schema 0'}{features}{$key};
  push @features, { y => $val->{zpos}, x => $val->{xpos}, anim => lc($val->{featurename}) };
}

Next, attempt to place the features on a "footprint" map. There are a bunch of edge cases because of how it's done but I am pretty certain the rules work like this:

  • work on one cell at a time, from x, y, and advance horizontally then next row
  • if the cell you want to claim is already owned:
    • by an indestructible feature: this new feature cannot be added, erase it, and go to the next
    • by a destructible feature: the new feature can replace it, erase the existing feature and continue
  • mark the cell as owned by the current feature
  my @footprints;
FEATURE:
  for my $i (0 .. $#features) {
    my $feature = $features[$i];

    # test if it can be placed OK
    for my $t (0 .. $tdf{$feature->{anim}}{footprintz} - 1) {
      for my $s (0 .. $tdf{$feature->{anim}}{footprintx} - 1) {

        # retrieve any existing ID from this cell
        my $old_id = $footprints[$feature->{y}+$t][$feature->{x}+$s];

        if (defined $old_id && defined $features[$old_id]) {
          # found an ID, and that feature hasn't been deleted...
          if ($tdf{$features[$old_id]{anim}}{indestructible}) {
            # and it was indestructible, darn.  Erase self and go to the next.
            $features[$i] = undef; # mark self as deleted
            next FEATURE;
          } else {
            # it was destructible.  Erase it, instead.
            $features[$old_id] = undef; # this deletes OLD id
          }
        }

        # still kickin, now write to the footprints array
        $footprints[$feature->{y}+$t][$feature->{x}+$s] = $i;
      }
    }
  }

Once finished, the @features list has been pruned of overlaps!

I ran an "audit" of all official Cavedog maps from TA and expansions to see how common feature overlaps are. Turns out there are quite a few. I've only checked a handful by using Mappy, my renderer, and official TA but it seems to check out. Here are the results:
ta_map_audit.txt

One more thing I noticed in testing - and this may not affect any official TA maps, I haven't checked - is about footprints and the map edges. When you're placing an object, it first tests if the footprint would go outside the map, and if so it isn't placed at all. Then it does the cell-by-cell testing.

This screenshot of my test map in TAE can help illustrate.
image
Green objects are 8x1 indestructible, yellow are 5x2 destructible, and red is 1x1 indestructible.

In-game, what happens is:

  • Object 1 is drawn.
  • Object 2 is drawn.
  • Object 3's footprint extends off the map, it is skipped before marking any cells.
  • Object 4 is drawn (because 3 was deleted).
  • Object 5 would be drawn, but...
  • Object 6 is "drawn" and deletes object 5 - even though it is at the very edge of the map and actually off-screen in the overhang area.