Logo Search packages:      
Sourcecode: warzone2100 version File versions

map.c

Go to the documentation of this file.
/*
      This file is part of Warzone 2100.
      Copyright (C) 1999-2004  Eidos Interactive
      Copyright (C) 2005-2007  Warzone Resurrection Project

      Warzone 2100 is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation; either version 2 of the License, or
      (at your option) any later version.

      Warzone 2100 is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      GNU General Public License for more details.

      You should have received a copy of the GNU General Public License
      along with Warzone 2100; if not, write to the Free Software
      Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
 * @file map.c
 *
 * Utility functions for the map data structure.
 *
 */
#include <time.h>

#include "lib/framework/frame.h"
#include "lib/framework/tagfile.h"
#include "lib/framework/file.h"
#include "lib/framework/physfs_ext.h"
#include "lib/ivis_common/tex.h"

#include "map.h"
#include "hci.h"
#include "projectile.h"
#include "display3d.h"
#include "lighting.h"
#include "game.h"
#include "texture.h"
#include "environ.h"
#include "advvis.h"
#include "research.h"
#include "mission.h"
#include "formationdef.h"
#include "gateway.h"
#include "wrappers.h"
#include "mapgrid.h"
#include "astar.h"
#include "fpath.h"
#include "levels.h"

//scroll min and max values
SDWORD            scrollMinX, scrollMaxX, scrollMinY, scrollMaxY;

/* Structure definitions for loading and saving map data */
typedef struct _map_save_header
{
      char        aFileType[4];
      UDWORD            version;
      UDWORD            width;
      UDWORD            height;
} MAP_SAVEHEADER;


#define SAVE_MAP_V2 \
      UWORD       texture; \
      UBYTE       height

typedef struct _map_save_tilev2
{
      SAVE_MAP_V2;
} MAP_SAVETILEV2;

typedef struct _map_save_tile
{
      SAVE_MAP_V2;
} MAP_SAVETILE;

typedef struct _gateway_save_header
{
      UDWORD            version;
      UDWORD            numGateways;
} GATEWAY_SAVEHEADER;

typedef struct _gateway_save
{
      UBYTE x0,y0,x1,y1;
} GATEWAY_SAVE;

typedef struct _zonemap_save_header_v1 {
      UWORD version;
      UWORD numZones;
} ZONEMAP_SAVEHEADER_V1;

typedef struct _zonemap_save_header {
      UWORD version;
      UWORD numZones;
      UWORD numEquivZones;
      UWORD pad;
} ZONEMAP_SAVEHEADER;

/* Sanity check definitions for the save struct file sizes */
#define SAVE_HEADER_SIZE      16
#define SAVE_TILE_SIZE        3
#define SAVE_TILE_SIZEV1      6
#define SAVE_TILE_SIZEV2      3

// Maximum expected return value from get height
#define     MAX_HEIGHT              (256 * ELEVATION_SCALE)

/* Number of entries in the sqrt(1/(1+x*x)) table for aaLine */
#define     ROOT_TABLE_SIZE         1024

/* aaLine direction bits and tables */
#define DIR_STEEP             1  /* set when abs(dy) > abs(dx) */
#define DIR_NEGY              2  /* set whey dy < 0 */

/* The size and contents of the map */
UDWORD      mapWidth = 0, mapHeight = 0;
MAPTILE     *psMapTiles = NULL;

/* Look up table that returns the terrain type of a given tile texture */
UBYTE terrainTypes[MAX_TILE_TEXTURES];

/* Create a new map of a specified size */
BOOL mapNew(UDWORD width, UDWORD height)
{
      MAPTILE *psTile;
      UDWORD      i;

      /* See if a map has already been allocated */
      if (psMapTiles != NULL)
      {
            /* Clear all the objects off the map and free up the map memory */
            gwShutDown();
            releaseAllProxDisp();
            freeAllDroids();
            freeAllStructs();
            freeAllFeatures();
            freeAllFlagPositions();
            proj_FreeAllProjectiles();
            free(psMapTiles);
            psMapTiles = NULL;
            initStructLimits();
      }

      if (width*height > MAP_MAXAREA)
      {
            debug( LOG_ERROR, "mapNew: map too large : %d %d\n", width, height );
            abort();
            return false;
      }

      psMapTiles = calloc(width * height, sizeof(MAPTILE));
      ASSERT(psMapTiles != NULL, "mapNew: Out of memory")

      psTile = psMapTiles;
      for (i = 0; i < width * height; i++)
      {
            psTile->height = MAX_HEIGHT / 4;
            psTile->illumination = 255;
            psTile->level = psTile->illumination;
            psTile->bMaxed = true;
            psTile->colour= WZCOL_WHITE;
            psTile++;
      }

      mapWidth = width;
      mapHeight = height;

      intSetMapPos(mapWidth * TILE_UNITS/2, mapHeight * TILE_UNITS/2);

      environReset();

      /*set up the scroll mins and maxs - set values to valid ones for a new map*/
      scrollMinX = scrollMinY = 0;
      scrollMaxX = mapWidth;
      scrollMaxY = mapHeight;

      gridReset();

      return true;
}

/* load the map data - for version 3 */
static BOOL mapLoadV3(char *pFileData, UDWORD fileSize)
{
      UDWORD                        i,j;
      MAP_SAVETILEV2          *psTileData;
      GATEWAY_SAVEHEADER      *psGateHeader;
      GATEWAY_SAVE            *psGate;
      ZONEMAP_SAVEHEADER      *psZoneHeader;
      UWORD ZoneSize;
      UBYTE *pZone;
      UBYTE *pDestZone;

      /* Load in the map data */
      psTileData = (MAP_SAVETILEV2 *)(pFileData + SAVE_HEADER_SIZE);
      for(i=0; i< mapWidth * mapHeight; i++)
      {
            /* MAP_SAVETILEV2 */
            endian_uword(&psTileData->texture);

            psMapTiles[i].texture = psTileData->texture;
            psMapTiles[i].height = psTileData->height;
            for (j=0; j<MAX_PLAYERS; j++)
            {
                  psMapTiles[i].tileVisBits =(UBYTE)(( (psMapTiles[i].tileVisBits) &~ (UBYTE)(1<<j) ));
            }
            psTileData = (MAP_SAVETILEV2 *)(((UBYTE *)psTileData) + SAVE_TILE_SIZE);
      }

      psGateHeader = (GATEWAY_SAVEHEADER*)psTileData;
      psGate = (GATEWAY_SAVE*)(psGateHeader+1);

      /* GATEWAY_SAVEHEADER */
      endian_udword(&psGateHeader->version);
      endian_udword(&psGateHeader->numGateways);

      ASSERT( psGateHeader->version == 1,"Invalid gateway version" );

      for(i=0; i<psGateHeader->numGateways; i++) {
            if (!gwNewGateway(psGate->x0,psGate->y0, psGate->x1,psGate->y1)) {
                  debug( LOG_ERROR, "mapLoadV3: Unable to add gateway" );
                  abort();
                  return false;
            }
            psGate++;
      }

      psZoneHeader = (ZONEMAP_SAVEHEADER*)psGate;

      /* ZONEMAP_SAVEHEADER */
      endian_uword(&psZoneHeader->version);
      endian_uword(&psZoneHeader->numZones);
      endian_uword(&psZoneHeader->numEquivZones);
      endian_uword(&psZoneHeader->pad);

      ASSERT(psZoneHeader->version == 1
          || psZoneHeader->version == 2
          || psZoneHeader->version == 3, "Invalid zone map version: %u", psZoneHeader->version);

      /* Special case due to r4637: a version 2 with numZones and
       * numEquivZones set to zero is actually version 3.
       */
      if (psZoneHeader->version == 2
       && psZoneHeader->numZones == 0
       && psZoneHeader->numEquivZones == 0)
      {
            psZoneHeader->version = 3;
      }

      if(!gwNewZoneMap()) {
            return false;
      }

      // This is a bit nasty but should work fine.
      if      (psZoneHeader->version == 1)
      {
            // version 1 so add the size of a version 1 header.
            pZone = ((UBYTE*)psZoneHeader) + sizeof(ZONEMAP_SAVEHEADER_V1);
      }
      else if (psZoneHeader->version == 2)
      {
            // version 2 so add the size of a version 2 header.
            pZone = ((UBYTE*)psZoneHeader) + sizeof(ZONEMAP_SAVEHEADER);
      }
      /* Loading of savegames with ZONEMAP_SAVEHEADER structure versions of 3
       * or larger is not supported (and for version 3 cannot be supported as
       * long as we depend on zones).
       */
      else if (psZoneHeader->version == 3)
      {
            debug(LOG_ERROR, "Unsupported savegame (zone map version 3): this savegame cannot be loaded with this version of warzone because \"zone map\" version 3 fails to provide required data. NOTE: Warzone 2100 2.2 will most likely support this savegame anyway.");
            return false;
      }
      else
      {
            return false;
      }

      for(i=0; i<psZoneHeader->numZones; i++) {
            ZoneSize = *((UWORD*)(pZone));
            endian_uword(&ZoneSize);

            pDestZone = gwNewZoneLine(i,ZoneSize);

            if(pDestZone == NULL) {
                  return false;
            }

            for(j=0; j<ZoneSize; j++) {
                  pDestZone[j] = pZone[2+j];
            }

            pZone += ZoneSize+2;
      }

      // Version 2 has the zone equivelancy lists tacked on the end.
      if(psZoneHeader->version == 2) {

            if(psZoneHeader->numEquivZones > 0) {
                  // Load in the zone equivelance lists.
                  if(!gwNewEquivTable(psZoneHeader->numEquivZones)) {
                        debug( LOG_ERROR, "gwNewEquivTable failed" );
                        abort();
                        return false;
                  }

                  for(i=0; i<psZoneHeader->numEquivZones; i++) {
                        if(*pZone != 0) {
                              if(!gwSetZoneEquiv(i, (SDWORD)*pZone, pZone+1)) {
                                    debug( LOG_ERROR, "gwSetZoneEquiv failed" );
                                    abort();
                                    return false;
                              }
                        }
                        pZone += ((UDWORD)*pZone)+1;
                  }
            }
      }

      if ((char *)pZone - pFileData > fileSize)
      {
            debug( LOG_ERROR, "mapLoadV3: unexpected end of file" );
            abort();
            return false;
      }

      LOADBARCALLBACK();      //    loadingScreenCallback();

      if ((apEquivZones != NULL) &&
            !gwGenerateLinkGates())
      {
            return false;
      }

      LOADBARCALLBACK();      //    loadingScreenCallback();

      //add new map initialise
      if (!gwLinkGateways())
      {
            return false;
      }

      LOADBARCALLBACK();      //    loadingScreenCallback();

#if defined(DEBUG)
      gwCheckZoneSizes();
#endif

      return true;
}


/* Initialise the map structure */
BOOL mapLoad(char *pFileData, UDWORD fileSize)
{
      UDWORD                        width,height;
      MAP_SAVEHEADER          *psHeader;

      /* Check the file type */
      psHeader = (MAP_SAVEHEADER *)pFileData;
      if (psHeader->aFileType[0] != 'm' || psHeader->aFileType[1] != 'a' ||
            psHeader->aFileType[2] != 'p' || psHeader->aFileType[3] != ' ')
      {
            debug( LOG_ERROR, "mapLoad: Incorrect file type" );
            abort();
            free(pFileData);
            return false;
      }

      /* MAP_SAVEHEADER */
      endian_udword(&psHeader->version);
      endian_udword(&psHeader->width);
      endian_udword(&psHeader->height);

      /* Check the file version */
      if (psHeader->version <= VERSION_9)
      {
            ASSERT(false, "MapLoad: unsupported save format version %d", psHeader->version);
            free(pFileData);
            return false;
      }
      else if (psHeader->version > CURRENT_VERSION_NUM)
      {
            ASSERT(false, "MapLoad: undefined save format version %d", psHeader->version);
            free(pFileData);
            return false;
      }

      /* Get the width and height */
      width = psHeader->width;
      height = psHeader->height;

      if (width*height > MAP_MAXAREA)
      {
            debug( LOG_ERROR, "mapLoad: map too large : %d %d\n", width, height );
            abort();
            return false;
      }

      /* See if this is the first time a map has been loaded */
      ASSERT(psMapTiles == NULL, "Map has not been cleared before calling mapLoad()!");

      /* Allocate the memory for the map */
      psMapTiles = calloc(width * height, sizeof(MAPTILE));
      ASSERT(psMapTiles != NULL, "mapLoad: Out of memory" );

      mapWidth = width;
      mapHeight = height;

      //load in the map data itself
      if (!mapLoadV3(pFileData, fileSize))
            return false;

      environReset();

      /* set up the scroll mins and maxs - set values to valid ones for any new map */
      scrollMinX = scrollMinY = 0;
      scrollMaxX = mapWidth;
      scrollMaxY = mapHeight;

      return true;
}

// Object macro group
static void objectSaveTagged(BASE_OBJECT *psObj)
{
      uint16_t v[MAX_PLAYERS], i;

      // not written: sDisplay

      tagWriteEnter(0x01, 1);
      tagWrite(0x01, psObj->type);
      tagWrite(0x02, psObj->id);
      v[0] = psObj->pos.x;
      v[1] = psObj->pos.y;
      v[2] = psObj->pos.z;
      tagWrite16v(0x03, 3, v);
      tagWritef(0x04, psObj->direction);
      tagWrites(0x05, psObj->pitch);
      tagWrites(0x06, psObj->roll);
      tagWrite(0x07, psObj->player);
      tagWrite(0x08, psObj->group);
      tagWrite(0x09, psObj->selected);
      tagWrite(0x0a, psObj->cluster);
      for (i = 0; i < MAX_PLAYERS; i++)
      {
            v[i] = psObj->visible[i];
      }
      tagWrite16v(0x0b, MAX_PLAYERS, v);
      tagWrite(0x0c, psObj->died);
      tagWrite(0x0d, psObj->lastEmission);
      tagWriteBool(0x0e, psObj->inFire);
      tagWrite(0x0f, psObj->burnStart);
      tagWrite(0x10, psObj->burnDamage);
      tagWriteLeave(0x01);
}

static void objectSensorTagged(int sensorRange, int sensorPower, int ecmRange, int ecmPower)
{
      tagWriteEnter(0x02, 1);
      tagWrite(0x01, sensorRange);
      tagWrite(0x02, sensorPower);
      tagWrite(0x03, ecmRange);
      tagWrite(0x04, ecmPower);
      tagWriteLeave(0x02);
}

static void objectStatTagged(BASE_OBJECT *psObj, int body, int resistance)
{
      int i;

      tagWriteEnter(0x03, 1);
      tagWrite(0x01, body);
      tagWrite(0x02, NUM_WEAPON_CLASS);
      tagWriteEnter(0x03, NUM_HIT_SIDES);
      for (i = 0; i < NUM_HIT_SIDES; i++)
      {
            tagWrite(0x01, psObj->armour[i][WC_KINETIC]);
            tagWrite(0x02, psObj->armour[i][WC_HEAT]);
            tagWriteNext();
      }
      tagWriteLeave(0x03);
      tagWrite(0x04, resistance);
      tagWriteLeave(0x03);
}

static void objectWeaponTagged(int num, UWORD *rotation, UWORD *pitch, WEAPON *asWeaps, BASE_OBJECT **psTargets)
{
      int i;

      tagWriteEnter(0x04, num);
      for (i = 0; i < num; i++)
      {
            tagWrite(0x01, asWeaps[i].nStat);
            tagWrite(0x02, rotation[i]);
            tagWrite(0x03, pitch[i]);
            tagWrite(0x04, asWeaps[i].hitPoints);
            tagWrite(0x05, asWeaps[i].ammo);
            tagWrite(0x06, asWeaps[i].lastFired);
            tagWrite(0x07, asWeaps[i].recoilValue);
            if (psTargets[i] != NULL)
            {
                  tagWrites(0x08, psTargets[i]->id); // else default -1
            }
            tagWriteNext();
      }
      tagWriteLeave(0x04);
}

static void droidSaveTagged(DROID *psDroid)
{
      int plr = psDroid->player;
      uint16_t v[DROID_MAXCOMP], i, order[4], ammo[VTOL_MAXWEAPS];
      int32_t sv[2];
      float fv[3];

      /* common groups */

      objectSaveTagged((BASE_OBJECT *)psDroid); /* 0x01 */
      objectSensorTagged(psDroid->sensorRange, psDroid->sensorPower, 0, psDroid->ECMMod); /* 0x02 */
      objectStatTagged((BASE_OBJECT *)psDroid, psDroid->originalBody, psDroid->resistance); /* 0x03 */
      objectWeaponTagged(psDroid->numWeaps, psDroid->turretRotation, psDroid->turretPitch, psDroid->asWeaps, psDroid->psActionTarget);

      /* DROID GROUP */

      tagWriteEnter(0x0a, 1);
      tagWrite(0x01, psDroid->droidType);
      for (i = 0; i < DROID_MAXCOMP; i++)
      {
            v[i] = psDroid->asBits[i].nStat;
      }
      tagWrite16v(0x02, DROID_MAXCOMP, v);
      // transporter droid in the mission list
      if (psDroid->droidType == DROID_TRANSPORTER && apsDroidLists[plr] == mission.apsDroidLists[plr])
      {
            tagWriteBool(0x03, true);
      }
      tagWrite(0x07, psDroid->weight);
      tagWrite(0x08, psDroid->baseSpeed);
      tagWriteString(0x09, psDroid->aName);
      tagWrite(0x0a, psDroid->body);
      tagWritef(0x0b, psDroid->experience);
      tagWrite(0x0c, psDroid->NameVersion);
      if (psDroid->psTarget)
      {
            tagWrites(0x0e, psDroid->psTarget->id); // else -1
      }
      if (psDroid->psTarStats)
      {
            tagWrites(0x0f, psDroid->psTarStats->ref); // else -1
      }
      if (psDroid->psBaseStruct)
      {
            tagWrites(0x10, psDroid->psBaseStruct->id); // else -1
      }
      // current order
      tagWrite(0x11, psDroid->order);
      order[0] = psDroid->orderX;
      order[1] = psDroid->orderY;
      order[2] = psDroid->orderX2;
      order[3] = psDroid->orderX2;
      tagWrite16v(0x12, 4, order);
      // queued orders
      tagWriteEnter(0x13, psDroid->listSize);
      for (i = 0; i < psDroid->listSize; i++)
      {
            tagWrite(0x01, psDroid->asOrderList[i].order);
            order[0] = psDroid->asOrderList[i].x;
            order[1] = psDroid->asOrderList[i].y;
            order[2] = psDroid->asOrderList[i].x2;
            order[3] = psDroid->asOrderList[i].y2;
            tagWrite16v(0x02, 4, order);
            tagWriteNext();
      }
      tagWriteLeave(0x13);
      if (psDroid->sMove.psFormation != NULL)
      {
            tagWrites(0x14, psDroid->sMove.psFormation->dir);
            tagWrites(0x15, psDroid->sMove.psFormation->x);
            tagWrites(0x16, psDroid->sMove.psFormation->y);
      } // else these are zero as by default
      // vtol ammo
      for (i = 0; i < VTOL_MAXWEAPS; i++)
      {
            ammo[i] = psDroid->sMove.iAttackRuns[i];
      }
      tagWrite16v(0x17, VTOL_MAXWEAPS, ammo);
      // other movement related stuff
      sv[0] = psDroid->sMove.DestinationX;
      sv[1] = psDroid->sMove.DestinationY;
      tagWrites32v(0x18, 2, sv);
      sv[0] = psDroid->sMove.srcX;
      sv[1] = psDroid->sMove.srcY;
      tagWrites32v(0x19, 2, sv);
      sv[0] = psDroid->sMove.targetX;
      sv[1] = psDroid->sMove.targetY;
      tagWrites32v(0x1a, 2, sv);
      fv[0] = psDroid->sMove.fx;
      fv[1] = psDroid->sMove.fy;
      fv[2] = psDroid->sMove.fz;
      tagWritefv(0x1b, 3, fv);
      tagWritef(0x1c, psDroid->sMove.speed);
      sv[0] = psDroid->sMove.boundX;
      sv[1] = psDroid->sMove.boundY;
      tagWrites32v(0x1d, 2, sv);
      v[0] = psDroid->sMove.bumpX;
      v[1] = psDroid->sMove.bumpY;
      tagWrite16v(0x1e, 2, v);
      tagWrites(0x1f, psDroid->sMove.moveDir);
      tagWrites(0x20, psDroid->sMove.bumpDir);
      tagWrite(0x21, psDroid->sMove.bumpTime);
      tagWrite(0x22, psDroid->sMove.lastBump);
      tagWrite(0x23, psDroid->sMove.pauseTime);
      tagWrite(0x24, psDroid->sMove.iVertSpeed);
      tagWriteEnter(0x25, psDroid->sMove.numPoints);
      for (i = 0; i < psDroid->sMove.numPoints; i++)
      {
            v[0] = psDroid->sMove.asPath[i].x;
            v[1] = psDroid->sMove.asPath[i].y;
            tagWrite16v(0x01, 2, v);
            tagWriteNext();
      }
      tagWriteLeave(0x25);
      tagWrite(0x26, psDroid->sMove.Status);
      tagWrite(0x27, psDroid->sMove.Position);

      tagWriteLeave(0x0a);
}

static void structureSaveTagged(STRUCTURE *psStruct)
{
      int stype = NUM_DIFF_BUILDINGS;

      if (psStruct->pFunctionality)
      {
            stype = psStruct->pStructureType->type;
      }

      /* common groups */

      objectSaveTagged((BASE_OBJECT *)psStruct); /* 0x01 */
      objectSensorTagged(psStruct->sensorRange, psStruct->sensorPower, 0, psStruct->ECMMod); /* 0x02 */
      objectStatTagged((BASE_OBJECT *)psStruct, psStruct->pStructureType->bodyPoints, psStruct->resistance); /* 0x03 */
      objectWeaponTagged(psStruct->numWeaps, psStruct->turretRotation, psStruct->turretPitch, psStruct->asWeaps, psStruct->psTarget);

      /* STRUCTURE GROUP */

      tagWriteEnter(0x0b, 1);
      tagWrite(0x01, psStruct->pStructureType->type);
      tagWrites(0x02, psStruct->currentPowerAccrued);
      tagWrite(0x03, psStruct->lastResistance);
      tagWrite(0x04, psStruct->targetted);
      tagWrite(0x05, psStruct->timeLastHit);
      tagWrite(0x06, psStruct->lastHitWeapon);
      tagWrite(0x07, psStruct->status);
      tagWrites(0x08, psStruct->currentBuildPts);
      tagWriteLeave(0x0b);

      /* Functionality groups */

      switch (psStruct->pStructureType->type)
      {
            case REF_FACTORY:
            case REF_CYBORG_FACTORY:
            case REF_VTOL_FACTORY:
            {
                  FACTORY *psFactory = (FACTORY *)psStruct->pFunctionality;

                  tagWriteEnter(0x0d, 1); // FACTORY GROUP
                  tagWrite(0x01, psFactory->capacity);
                  tagWrite(0x02, psFactory->quantity);
                  tagWrite(0x03, psFactory->loopsPerformed);
                  //tagWrite(0x04, psFactory->productionOutput); // not used in original code, recalculated instead
                  tagWrite(0x05, psFactory->powerAccrued);
                  if (psFactory->psSubject)
                  {
                        tagWrites(0x06, ((DROID_TEMPLATE *)psFactory->psSubject)->multiPlayerID);
                  }
                  tagWrite(0x07, psFactory->timeStarted);
                  tagWrite(0x08, psFactory->timeToBuild);
                  tagWrite(0x09, psFactory->timeStartHold);
                  tagWrite(0x0a, psFactory->secondaryOrder);
                  if (psFactory->psAssemblyPoint)
                  {
                        // since we save our type, the combination of type and number is enough
                        // to find our flag back on load
                        tagWrites(0x0b, psFactory->psAssemblyPoint->factoryInc);
                  }
                  if (psFactory->psCommander)
                  {
                        tagWrites(0x0c, psFactory->psCommander->id);
                  }
                  tagWriteLeave(0x0d);
            } break;
            case REF_RESEARCH:
            {
                  RESEARCH_FACILITY *psResearch = (RESEARCH_FACILITY *)psStruct->pFunctionality;

                  tagWriteEnter(0x0e, 1);
                  tagWrite(0x01, psResearch->capacity); // number of upgrades it has
                  tagWrite(0x02, psResearch->powerAccrued);
                  tagWrite(0x03, psResearch->timeStartHold);
                  if (psResearch->psSubject)
                  {
                        tagWrite(0x04, psResearch->psSubject->ref - REF_RESEARCH_START);
                        tagWrite(0x05, psResearch->timeStarted);
                  }
                  tagWriteLeave(0x0e);
            } break;
            case REF_RESOURCE_EXTRACTOR:
            {
                  RES_EXTRACTOR *psExtractor = (RES_EXTRACTOR *)psStruct->pFunctionality;

                  tagWriteEnter(0x0f, 1);
                  tagWrite(0x01, psExtractor->power);
                  if (psExtractor->psPowerGen)
                  {
                        tagWrites(0x02, psExtractor->psPowerGen->id);
                  }
                  tagWriteLeave(0x0f);
            } break;
            case REF_POWER_GEN:
            {
                  POWER_GEN *psPower = (POWER_GEN *)psStruct->pFunctionality;

                  tagWriteEnter(0x10, 1);
                  tagWrite(0x01, psPower->capacity); // number of upgrades
                  tagWriteLeave(0x10);
            } break;
            case REF_REPAIR_FACILITY:
            {
                  REPAIR_FACILITY *psRepair = (REPAIR_FACILITY *)psStruct->pFunctionality;
                  FLAG_POSITION *psFlag = psRepair->psDeliveryPoint;

                  tagWriteEnter(0x11, 1);
                  tagWrite(0x01, psRepair->timeStarted);
                  tagWrite(0x02, psRepair->powerAccrued);
                  if (psRepair->psObj)
                  {
                        tagWrites(0x03, psRepair->psObj->id);
                  }
                  if (psFlag)
                  {
                        tagWrites(0x04, psFlag->factoryInc);
                  }
                  tagWriteLeave(0x11);
            } break;
            case REF_REARM_PAD:
            {
                  REARM_PAD *psRearm = (REARM_PAD *)psStruct->pFunctionality;

                  tagWriteEnter(0x12, 1);
                  tagWrite(0x01, psRearm->reArmPoints);
                  tagWrite(0x02, psRearm->timeStarted);
                  tagWrite(0x03, psRearm->currentPtsAdded);
                  if (psRearm->psObj)
                  {
                        tagWrites(0x04, psRearm->psObj->id);
                  }
                  tagWriteLeave(0x12);
            } break;
            case REF_HQ:
            case REF_FACTORY_MODULE:
            case REF_POWER_MODULE:
            case REF_DEFENSE:
            case REF_WALL:
            case REF_WALLCORNER:
            case REF_BLASTDOOR:
            case REF_RESEARCH_MODULE:
            case REF_COMMAND_CONTROL:
            case REF_BRIDGE:
            case REF_DEMOLISH:
            case REF_LAB:
            case REF_MISSILE_SILO:
            case REF_SAT_UPLINK:
            case NUM_DIFF_BUILDINGS:
            {
                  // nothing
            } break;
      }
}

static void featureSaveTagged(FEATURE *psFeat)
{
      /* common groups */

      objectSaveTagged((BASE_OBJECT *)psFeat); /* 0x01 */
      objectStatTagged((BASE_OBJECT *)psFeat, psFeat->psStats->body, 0); /* 0x03 */

      /* FEATURE GROUP */

      tagWriteEnter(0x0c, 1);
      tagWrite(0x01, psFeat->startTime);
      tagWriteLeave(0x0c);
}

// the maximum number of flags: each factory type can have two, one for itself, and one for a commander
#define MAX_FLAGS (NUM_FLAG_TYPES * MAX_FACTORY * 2)

BOOL mapSaveTagged(char *pFileName)
{
      MAPTILE *psTile;
      GATEWAY *psCurrGate;
      int numGateways = 0, i = 0, x = 0, y = 0, plr, droids, structures, features;
      float cam[3];
      const char *definition = "tagdefinitions/savegame/map.def";
      DROID *psDroid;
      FEATURE *psFeat;
      STRUCTURE *psStruct;

      // find the number of non water gateways
      for (psCurrGate = gwGetGateways(); psCurrGate; psCurrGate = psCurrGate->psNext)
      {
            if (!(psCurrGate->flags & GWR_WATERLINK))
            {
                  numGateways += 1;
            }
      }

      if (!tagOpenWrite(definition, pFileName))
      {
            debug(LOG_ERROR, "mapSaveTagged: Failed to create savegame %s", pFileName);
            return false;
      }
      debug(LOG_MAP, "Creating tagged savegame %s with definition %s:", pFileName, definition);

      tagWriteEnter(0x03, 1); // map info group
      tagWrite(0x01, mapWidth);
      tagWrite(0x02, mapHeight);
      tagWriteLeave(0x03);

      tagWriteEnter(0x04, 1); // camera info group
      cam[0] = player.p.x;
      cam[1] = player.p.y;
      cam[2] = player.p.z; /* Transform position to float */
      tagWritefv(0x01, 3, cam);
      cam[0] = player.r.x;
      cam[1] = player.r.y;
      cam[2] = player.r.z; /* Transform rotation to float */
      tagWritefv(0x02, 3, cam);
      debug(LOG_MAP, " * Writing player position(%d, %d, %d) and rotation(%d, %d, %d)",
            (int)player.p.x, (int)player.p.y, (int)player.p.z, (int)player.r.x,
            (int)player.r.y, (int)player.r.z);
      tagWriteLeave(0x04);

      tagWriteEnter(0x05, _TEX_INDEX - 1); // texture group
      for (i = 0; i < _TEX_INDEX - 1; i++)
      {
            tagWriteString(0x01, _TEX_PAGE[i].name);
            tagWriteNext(); // add repeating group separator
      }
      debug(LOG_MAP, " * Writing info about %d texture pages", (int)_TEX_INDEX - 1);
      tagWriteLeave(0x05);

      tagWriteEnter(0x0a, mapWidth * mapHeight); // tile group
      psTile = psMapTiles;
      for (i = 0, x = 0, y = 0; i < mapWidth * mapHeight; i++)
      {
            tagWrite(0x01, terrainType(psTile));
            tagWrite(0x02, TileNumber_tile(psTile->texture));
            tagWrite(0x03, TRI_FLIPPED(psTile));
            tagWrite(0x04, psTile->texture & TILE_XFLIP);
            tagWrite(0x05, psTile->texture & TILE_YFLIP);
            tagWrite(0x06, TILE_IS_NOTBLOCKING(psTile));
            tagWrite(0x08, psTile->height);
            tagWrite(0x09, psTile->tileVisBits);
            tagWrite(0x0a, psTile->tileInfoBits);

            psTile++;
            x++;
            if (x == mapWidth)
            {
                  x = 0; y++;
            }
            tagWriteNext();
      }
      debug(LOG_MAP, " * Writing info about %d tiles", (int)mapWidth * mapHeight);
      tagWriteLeave(0x0a);

      tagWriteEnter(0x0b, numGateways); // gateway group
      for (psCurrGate = gwGetGateways(); psCurrGate; psCurrGate = psCurrGate->psNext)
      {
            if (!(psCurrGate->flags & GWR_WATERLINK))
            {
                  uint16_t p[4];

                  p[0] = psCurrGate->x1;
                  p[1] = psCurrGate->y1;
                  p[2] = psCurrGate->x2;
                  p[3] = psCurrGate->y2;
                  tagWrite16v(0x01, 4, p);
                  tagWriteNext();
            }
      }
      debug(LOG_MAP, " * Writing info about %d gateways", (int)numGateways);
      tagWriteLeave(0x0b);

      tagWriteEnter(0x0c, MAX_TILE_TEXTURES); // terrain type <=> texture mapping group
      for (i = 0; i < MAX_TILE_TEXTURES; i++)
        {
                tagWrite(0x01, terrainTypes[i]);
            tagWriteNext();
        }
      debug(LOG_MAP, " * Writing info about %d textures' type", (int)MAX_TILE_TEXTURES);
      tagWriteLeave(0x0c);

      tagWriteEnter(0x0d, MAX_PLAYERS); // player info group
      for (plr = 0; plr < MAX_PLAYERS; plr++)
      {
            RESEARCH *psResearch = asResearch;
            STRUCTURE_STATS *psStructStats = asStructureStats;
            FLAG_POSITION *psFlag;
            FLAG_POSITION *flagList[MAX_FLAGS];
            MESSAGE *psMessage;

            memset(flagList, 0, sizeof(flagList));

            tagWriteEnter(0x01, numResearch); // research group
            for (i = 0; i < numResearch; i++, psResearch++)
            {
                  tagWrite(0x01, IsResearchPossible(&asPlayerResList[plr][i]));
                  tagWrite(0x02, asPlayerResList[plr][i].ResearchStatus & RESBITS);
                  tagWrite(0x03, asPlayerResList[plr][i].currentPoints);
                  tagWriteNext();
            }
            tagWriteLeave(0x01);

            tagWriteEnter(0x02, numStructureStats); // structure limits group
            for (i = 0; i < numStructureStats; i++, psStructStats++)
            {
                  tagWriteString(0x01, psStructStats->pName);
                  tagWrite(0x02, asStructLimits[plr][i].limit);
                  tagWriteNext();
            }
            tagWriteLeave(0x02);

            // count and list flags in list
            for (i = 0, psFlag = apsFlagPosLists[plr]; psFlag != NULL; psFlag = psFlag->psNext)
            {
                  ASSERT(i < MAX_FLAGS, "More flags than we can handle (1)!");
                  flagList[i] = psFlag;
                  i++;
            }
            y = i; // remember separation point between registered and unregistered flags
            // count flags not in list (commanders)
            for (psStruct = apsStructLists[plr]; psStruct != NULL; psStruct = psStruct->psNext)
            {
                  ASSERT(i < MAX_FLAGS, "More flags than we can handle (2)!");
                  if (psStruct->pFunctionality
                      && (psStruct->pStructureType->type == REF_FACTORY
                          || psStruct->pStructureType->type == REF_CYBORG_FACTORY
                          || psStruct->pStructureType->type == REF_VTOL_FACTORY))
                  {
                        FACTORY *psFactory = ((FACTORY *)psStruct->pFunctionality);

                        if (psFactory->psCommander && psFactory->psAssemblyPoint != NULL)
                        {
                              flagList[i] = psFactory->psAssemblyPoint;
                              i++;
                        }
                  }
            }
            tagWriteEnter(0x03, i); // flag group
            for (x = 0; x < i; x++)
            {
                  uint16_t p[3];

                  tagWrite(0x01, flagList[x]->type);
                  tagWrite(0x02, flagList[x]->frameNumber);
                  p[0] = flagList[x]->screenX;
                  p[1] = flagList[x]->screenY;
                  p[2] = flagList[x]->screenR;
                  tagWrite16v(0x03, 3, p);
                  tagWrite(0x04, flagList[x]->player);
                  tagWriteBool(0x05, flagList[x]->selected);
                  p[0] = flagList[x]->coords.x;
                  p[1] = flagList[x]->coords.y;
                  p[2] = flagList[x]->coords.z;
                  tagWrite16v(0x06, 3, p);
                  tagWrite(0x07, flagList[x]->factoryInc);
                  tagWrite(0x08, flagList[x]->factoryType);
                  if (flagList[x]->factoryType == REPAIR_FLAG)
                  {
                        tagWrite(0x09, getRepairIdFromFlag(flagList[x]));
                  }
                  if (x >= y)
                  {
                        tagWriteBool(0x0a, 1); // do not register this flag in flag list
                  }
                  tagWriteNext();
            }
            tagWriteLeave(0x03);

            // FIXME: Structured after old savegame logic, but surely it must be possible
            // to simplify the mess below. It refers to non-saved file containing messages.
            for (psMessage = apsMessages[plr], i = 0; psMessage != NULL; psMessage = psMessage->psNext, i++);
            tagWriteEnter(0x04, i); // message group
            for (psMessage = apsMessages[plr]; psMessage != NULL; psMessage = psMessage->psNext)
            {
                  if (psMessage->type == MSG_PROXIMITY)
                  {
                        PROXIMITY_DISPLAY *psProx = apsProxDisp[plr];

                        // find the matching proximity message
                        for (; psProx != NULL && psProx->psMessage != psMessage; psProx = psProx->psNext);

                        ASSERT(psProx != NULL, "Save message; proximity display not found for message");
                        if (psProx->type == POS_PROXDATA)
                        {
                              // message has viewdata so store the name
                              VIEWDATA *pViewData = (VIEWDATA*)psMessage->pViewData;

                              tagWriteString(0x02, pViewData->pName);
                        }
                        else
                        {
                              BASE_OBJECT *psObj = (BASE_OBJECT*)psMessage->pViewData;

                              tagWrites(0x01, psObj->id);
                        }
                  }
                  else
                  {
                        VIEWDATA *pViewData = (VIEWDATA*)psMessage->pViewData;

                        tagWriteString(0x2, pViewData->pName);
                  }
                  tagWriteBool(0x3, psMessage->read);
                  tagWriteNext();
            }
            tagWriteLeave(0x04);

            tagWriteNext();
      }
      debug(LOG_MAP, " * Writing info about %d players", (int)MAX_PLAYERS);
      tagWriteLeave(0x0d);

      tagWriteEnter(0x0e, NUM_FACTORY_TYPES); // production runs group
      for (i = 0; i < NUM_FACTORY_TYPES; i++)
      {
            int factoryNum, runNum;

            tagWriteEnter(0x01, MAX_FACTORY);
            for (factoryNum = 0; factoryNum < MAX_FACTORY; factoryNum++)
            {
                  tagWriteEnter(0x01, MAX_PROD_RUN);
                  for (runNum = 0; runNum < MAX_PROD_RUN; runNum++)
                  {
                        PRODUCTION_RUN *psCurrentProd = &asProductionRun[i][factoryNum][runNum];

                        tagWrite(0x01, psCurrentProd->quantity);
                        tagWrite(0x02, psCurrentProd->built);
                        if (psCurrentProd->psTemplate != NULL)
                                {
                              tagWrites(0x03, psCurrentProd->psTemplate->multiPlayerID); // -1 if none
                                }
                        tagWriteNext();
                  }
                  tagWriteLeave(0x01);
                  tagWriteNext();
            }
            tagWriteLeave(0x01);
            tagWriteNext();
      }
      tagWriteLeave(0x0e);

      objCount(&droids, &structures, &features);
      tagWriteEnter(0x0f, droids + structures + features); // object group
      for (plr = 0; plr < MAX_PLAYERS; plr++)
      {
            for (psDroid = apsDroidLists[plr]; psDroid != NULL; psDroid = psDroid->psNext)
            {
                  droidSaveTagged(psDroid);
                  tagWriteNext();
                  if (psDroid->droidType == DROID_TRANSPORTER)
                  {
                        DROID *psTrans = psDroid->psGroup->psList;
                        for(psTrans = psTrans->psGrpNext; psTrans != NULL; psTrans = psTrans->psGrpNext)
                        {
                              droidSaveTagged(psTrans);
                              tagWriteNext();
                        }
                  }
            }
            for (psStruct = apsStructLists[plr]; psStruct; psStruct = psStruct->psNext)
            {
                  structureSaveTagged(psStruct);
                  tagWriteNext();
            }
      }
      for (psFeat = apsFeatureLists[0]; psFeat; psFeat = psFeat->psNext)
      {
            featureSaveTagged(psFeat);
            tagWriteNext();
      }
      tagWriteLeave(0x0f);

      tagWriteEnter(0x10, gwNumZoneLines()); // gateway zone lines
      for (i = 0; i < gwNumZoneLines(); i++)
      {
            // binary blob for now... ugh, this is ugly, but no worse than it was
            tagWrite(0x01, gwZoneLineSize(i));
            tagWrite8v(0x02, gwZoneLineSize(i), apRLEZones[i]);
            tagWriteNext();
      }
      tagWriteLeave(0x10);

      tagWriteEnter(0x11, gwNumZones); // gateway equivalency zones
      for (i = 0; i < gwNumZones; i++)
      {
            tagWrite(0x01, aNumEquiv[i]);
            if (aNumEquiv[i])
            {
                  // another ugly blob job
                  tagWrite8v(0x02, aNumEquiv[i], apEquivZones[i]);
            }
            tagWriteNext();
      }
      tagWriteLeave(0x11);

      tagClose();
      return true;
}

BOOL mapLoadTagged(char *pFileName)
{
      int count, i, mapx, mapy;
      float cam[3];
      const char *definition = "tagdefinitions/savegame/map.def";
      MAPTILE     *psTile;

      if (!tagOpenRead(definition, pFileName))
      {
            debug(LOG_ERROR, "mapLoadTagged: Failed to open savegame %s", pFileName);
            return false;
      }
      debug(LOG_MAP, "Reading tagged savegame %s with definition %s:", pFileName, definition);

      tagReadEnter(0x03); // map info group
      mapx = tagRead(0x01);
      mapy = tagRead(0x02);
      debug(LOG_MAP, " * Map size: %d, %d", (int)mapx, (int)mapy);
      ASSERT(mapx == mapWidth && mapy == mapHeight, "mapLoadTagged: Wrong map size");
      ASSERT(mapWidth * mapHeight <= MAP_MAXAREA, "mapLoadTagged: Map too large");
      tagReadLeave(0x03);

      tagReadEnter(0x04); // camera info group
      tagReadfv(0x01, 3, cam); debug(LOG_MAP, " * Camera position: %f, %f, %f", cam[0], cam[1], cam[2]);
      tagReadfv(0x02, 3, cam); debug(LOG_MAP, " * Camera rotation: %f, %f, %f", cam[0], cam[1], cam[2]);
      tagReadLeave(0x04);

      count = tagReadEnter(0x05); // texture group
      for (i = 0; i < count; i++)
      {
            char mybuf[200];

            tagReadString(0x01, 200, mybuf);
            debug(LOG_MAP, " * Texture[%d]: %s", i, mybuf);
            tagReadNext();
      }
      tagReadLeave(0x05);

      i = tagReadEnter(0x0a); // tile group
      ASSERT(i == mapWidth * mapHeight, "Map size (%d, %d) is not equal to number of tiles (%d) in mapLoadTagged()!",
             (int)mapWidth, (int)mapHeight, i);
      psTile = psMapTiles;
      for (i = 0; i < mapWidth * mapHeight; i++)
      {
            BOOL triflip, notblock, xflip, yflip;
            int texture, height, terrain;

            terrain = tagRead(0x01); ASSERT(terrainType(psTile) == terrain, "Wrong terrain");
            texture = tagRead(0x02); ASSERT(TileNumber_tile(psTile->texture) == texture, "Wrong texture");
            triflip = tagRead(0x03);
            xflip = tagRead(0x04);
            yflip = tagRead(0x05);
            notblock = tagRead(0x06);
            height = tagRead(0x08); ASSERT(psTile->height == height, "Wrong height");

            psTile++;
            tagReadNext();
      }
      tagReadLeave(0x0a);

      tagClose();
      return true;
}

/* Save the map data */
BOOL mapSave(char **ppFileData, UDWORD *pFileSize)
{
      UDWORD      i;
      MAP_SAVEHEADER    *psHeader = NULL;
      MAP_SAVETILE      *psTileData = NULL;
      MAPTILE     *psTile = NULL;
      GATEWAY *psCurrGate = NULL;
      GATEWAY_SAVEHEADER *psGateHeader = NULL;
      GATEWAY_SAVE *psGate = NULL;
      ZONEMAP_SAVEHEADER *psZoneHeader = NULL;
      UBYTE *psZone = NULL;
      UBYTE *psLastZone = NULL;
      SDWORD      numGateways = 0;

      // find the number of non water gateways
      for(psCurrGate = gwGetGateways(); psCurrGate; psCurrGate = psCurrGate->psNext)
      {
            if (!(psCurrGate->flags & GWR_WATERLINK))
            {
                  numGateways += 1;
            }
      }

      /* Allocate the data buffer */
      *pFileSize = SAVE_HEADER_SIZE + mapWidth*mapHeight * SAVE_TILE_SIZE;
      // Add on the size of the gateway data.
      *pFileSize += sizeof(GATEWAY_SAVEHEADER) + sizeof(GATEWAY_SAVE)*numGateways;
      // Add on the size of the zone data header.
      *pFileSize += sizeof(ZONEMAP_SAVEHEADER);
      // Add on the size of the zone data.
      for(i=0; i<gwNumZoneLines(); i++) {
            *pFileSize += 2+gwZoneLineSize(i);
      }
      // Add on the size of the equivalency lists.
      for(i=0; i<(UDWORD)gwNumZones; i++) {
            *pFileSize += 1+aNumEquiv[i];
      }

      *ppFileData = (char*)malloc(*pFileSize);
      if (*ppFileData == NULL)
      {
            debug( LOG_ERROR, "Out of memory" );
            abort();
            return false;
      }

      /* Put the file header on the file */
      psHeader = (MAP_SAVEHEADER *)*ppFileData;
      psHeader->aFileType[0] = 'm';
      psHeader->aFileType[1] = 'a';
      psHeader->aFileType[2] = 'p';
      psHeader->aFileType[3] = ' ';
      psHeader->version = CURRENT_VERSION_NUM;
      psHeader->width = mapWidth;
      psHeader->height = mapHeight;

      /* MAP_SAVEHEADER */
      endian_udword(&psHeader->version);
      endian_udword(&psHeader->width);
      endian_udword(&psHeader->height);

      /* Put the map data into the buffer */
      psTileData = (MAP_SAVETILE *)(*ppFileData + SAVE_HEADER_SIZE);
      psTile = psMapTiles;
      for(i=0; i<mapWidth*mapHeight; i++)
      {

            // don't save the noblock flag as it gets set again when the objects are loaded
            psTileData->texture = (UWORD)(psTile->texture & (UWORD)~TILE_NOTBLOCKING);

            psTileData->height = psTile->height;

            /* MAP_SAVETILEV2 */
            endian_uword(&psTileData->texture);

            psTileData = (MAP_SAVETILE *)((UBYTE *)psTileData + SAVE_TILE_SIZE);
            psTile ++;
      }

      // Put the gateway header.
      psGateHeader = (GATEWAY_SAVEHEADER*)psTileData;
      psGateHeader->version = 1;
      psGateHeader->numGateways = numGateways;

      /* GATEWAY_SAVEHEADER */
      endian_udword(&psGateHeader->version);
      endian_udword(&psGateHeader->numGateways);

      psGate = (GATEWAY_SAVE*)(psGateHeader+1);

      i=0;
      // Put the gateway data.
      for(psCurrGate = gwGetGateways(); psCurrGate; psCurrGate = psCurrGate->psNext)
      {
            if (!(psCurrGate->flags & GWR_WATERLINK))
            {
                  psGate->x0 = psCurrGate->x1;
                  psGate->y0 = psCurrGate->y1;
                  psGate->x1 = psCurrGate->x2;
                  psGate->y1 = psCurrGate->y2;
                  psGate++;
                  i++;
            }
      }

      // Put the zone header.
      psZoneHeader = (ZONEMAP_SAVEHEADER*)psGate;
      psZoneHeader->version = 2;
      psZoneHeader->numZones =(UWORD)gwNumZoneLines();
      psZoneHeader->numEquivZones =(UWORD)gwNumZones;

      /* ZONEMAP_SAVEHEADER */
      endian_uword(&psZoneHeader->version);
      endian_uword(&psZoneHeader->numZones);
      endian_uword(&psZoneHeader->numEquivZones);
      endian_uword(&psZoneHeader->pad);

      // Put the zone data.
      psZone = (UBYTE*)(psZoneHeader+1);
      for(i=0; i<gwNumZoneLines(); i++) {
            psLastZone = psZone;
            *((UWORD*)psZone) = (UWORD)gwZoneLineSize(i);
            endian_uword(((UWORD *) psZone));

            psZone += sizeof(UWORD);
            memcpy(psZone,apRLEZones[i],gwZoneLineSize(i));
            psZone += gwZoneLineSize(i);
      }

      // Put the equivalency lists.
      if(gwNumZones > 0) {
            for(i=0; i<(UDWORD)gwNumZones; i++) {
                  psLastZone = psZone;
                  *psZone = aNumEquiv[i];
                  psZone ++;
                  if(aNumEquiv[i]) {
                        memcpy(psZone,apEquivZones[i],aNumEquiv[i]);
                        psZone += aNumEquiv[i];
                  }
            }
      }

      ASSERT( (intptr_t)psLastZone - (intptr_t)*ppFileData < (intptr_t)*pFileSize, "Buffer overflow saving map" );

      return true;
}

/* Shutdown the map module */
BOOL mapShutdown(void)
{
      if(psMapTiles)
      {
            free(psMapTiles);
      }
      psMapTiles = NULL;
      mapWidth = mapHeight = 0;

      return true;
}

/* Return linear interpolated height of x,y */
extern SWORD map_Height(int x, int y)
{
      int   retVal, tileX, tileY, tileYOffset, tileX2, tileY2Offset, dx, dy, ox, oy;
      int   h0, hx, hy, hxy, wTL = 0, wTR = 0, wBL = 0, wBR = 0;
      BOOL  bWaterTile = false;

      ASSERT(x >= 0, "map_Height: Negative x value");
      ASSERT(y >= 0, "map_Height: Negative y value");

      x = x >= world_coord(mapWidth) ? world_coord(mapWidth - 1) : x;
      y = y >= world_coord(mapHeight) ? world_coord(mapHeight - 1) : y;

      /* Turn into tile coordinates */
      tileX = map_coord(x);
      tileY = map_coord(y);

      /* Inter tile comp */
      ox = map_round(x);
      oy = map_round(y);

      if (terrainType(mapTile(tileX,tileY)) == TER_WATER)
      {
            bWaterTile = true;
            wTL = environGetValue(tileX,tileY)/2;
            wTR = environGetValue(tileX+1,tileY)/2;
            wBL = environGetValue(tileX,tileY+1)/2;
            wBR = environGetValue(tileX+1,tileY+1)/2;
      }

      // to account for the border of the map
      if(tileX + 1 < mapWidth)
      {
            tileX2 = tileX + 1;
      }
      else
      {
            tileX2 = tileX;
      }
      tileYOffset = (tileY * mapWidth);
      if(tileY + 1 < mapHeight)
      {
            tileY2Offset = tileYOffset + mapWidth;
      }
      else
      {
            tileY2Offset = tileYOffset;
      }

      ASSERT( ox < TILE_UNITS, "mapHeight: x offset too big" );
      ASSERT( oy < TILE_UNITS, "mapHeight: y offset too big" );
      ASSERT( ox >= 0, "mapHeight: x offset too small" );
      ASSERT( oy >= 0, "mapHeight: y offset too small" );

      //different code for 4 different triangle cases
      if (psMapTiles[tileX + tileYOffset].texture & TILE_TRIFLIP)
      {
            if ((ox + oy) > TILE_UNITS)//tile split top right to bottom left object if in bottom right half
            {
                  ox = TILE_UNITS - ox;
                  oy = TILE_UNITS - oy;
                  hy = psMapTiles[tileX + tileY2Offset].height;
                  hx = psMapTiles[tileX2 + tileYOffset].height;
                  hxy= psMapTiles[tileX2 + tileY2Offset].height;
                  if(bWaterTile)
                  {
                        hy+=wBL;
                        hx+=wTR;
                        hxy+=wBR;
                  }

                  dx = ((hy - hxy) * ox )/ TILE_UNITS;
                  dy = ((hx - hxy) * oy )/ TILE_UNITS;

                  retVal = (SDWORD)(((hxy + dx + dy)) * ELEVATION_SCALE);
                  ASSERT( retVal<MAX_HEIGHT,"Map height's gone weird!!!" );
                  return ((SWORD)retVal);
            }
            else //tile split top right to bottom left object if in top left half
            {
                  h0 = psMapTiles[tileX + tileYOffset].height;
                  hy = psMapTiles[tileX + tileY2Offset].height;
                  hx = psMapTiles[tileX2 + tileYOffset].height;

                  if(bWaterTile)
                  {
                        h0+=wTL;
                        hy+=wBL;
                        hx+=wTR;
                  }
                  dx = ((hx - h0) * ox )/ TILE_UNITS;
                  dy = ((hy - h0) * oy )/ TILE_UNITS;

                  retVal = (SDWORD)((h0 + dx + dy) * ELEVATION_SCALE);
                  ASSERT( retVal<MAX_HEIGHT,"Map height's gone weird!!!" );
                  return ((SWORD)retVal);
            }
      }
      else
      {
            if (ox > oy) //tile split topleft to bottom right object if in top right half
            {
                  h0 = psMapTiles[tileX + tileYOffset].height;
                  hx = psMapTiles[tileX2 + tileYOffset].height;
                  ASSERT( tileX2 + tileY2Offset < mapWidth*mapHeight, "array out of bounds");
                  hxy= psMapTiles[tileX2 + tileY2Offset].height;

                  if(bWaterTile)
                  {
                        h0+=wTL;
                        hx+=wTR;
                        hxy+=wBR;
                  }
                  dx = ((hx - h0) * ox )/ TILE_UNITS;
                  dy = ((hxy - hx) * oy )/ TILE_UNITS;
                  retVal = (SDWORD)(((h0 + dx + dy)) * ELEVATION_SCALE);
                  ASSERT( retVal<MAX_HEIGHT,"Map height's gone weird!!!" );
                  return ((SWORD)retVal);
            }
            else //tile split topleft to bottom right object if in bottom left half
            {
                  h0 = psMapTiles[tileX + tileYOffset].height;
                  hy = psMapTiles[tileX + tileY2Offset].height;
                  hxy = psMapTiles[tileX2 + tileY2Offset].height;

                  if(bWaterTile)
                  {
                        h0+=wTL;
                        hy+=wBL;
                        hxy+=wBR;
                  }
                  dx = ((hxy - hy) * ox )/ TILE_UNITS;
                  dy = ((hy - h0) * oy )/ TILE_UNITS;

                  retVal = (SDWORD)((h0 + dx + dy) * ELEVATION_SCALE);
                  ASSERT( retVal<MAX_HEIGHT,"Map height's gone weird!!!" );
                  return ((SWORD)retVal);
            }
      }
      return 0;
}

/* returns true if object is above ground */
extern BOOL mapObjIsAboveGround( BASE_OBJECT *psObj )
{
      // min is used to make sure we don't go over array bounds!
      SDWORD      iZ,
                  tileX = map_coord(psObj->pos.x),
                  tileY = map_coord(psObj->pos.y),
                  tileYOffset1 = (tileY * mapWidth),
                  tileYOffset2 = ((tileY+1) * mapWidth),
                  h1 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX)    ].height,
                  h2 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset1 + tileX + 1)].height,
                  h3 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX)    ].height,
                  h4 = psMapTiles[MIN(mapWidth * mapHeight, tileYOffset2 + tileX + 1)].height;

      /* trivial test above */
      if ( (psObj->pos.z > h1) && (psObj->pos.z > h2) &&
             (psObj->pos.z > h3) && (psObj->pos.z > h4)    )
      {
            return true;
      }

      /* trivial test below */
      if ( (psObj->pos.z <= h1) && (psObj->pos.z <= h2) &&
             (psObj->pos.z <= h3) && (psObj->pos.z <= h4)    )
      {
            return false;
      }

      /* exhaustive test */
      iZ = map_Height( psObj->pos.x, psObj->pos.y );
      if ( psObj->pos.z > iZ )
      {
            return true;
      }
      else
      {
            return false;
      }
}

/* returns the max and min height of a tile by looking at the four corners
   in tile coords */
void getTileMaxMin(UDWORD x, UDWORD y, UDWORD *pMax, UDWORD *pMin)
{
      UDWORD      height, i, j;

      *pMin = TILE_MAX_HEIGHT;
      *pMax = TILE_MIN_HEIGHT;

      for (j=0; j < 2; j++)
      {
            for (i=0; i < 2; i++)
            {
                  height = map_TileHeight(x+i, y+j);
                  if (*pMin > height)
                  {
                        *pMin = height;
                  }
                  if (*pMax < height)
                  {
                        *pMax = height;
                  }
            }
      }
}

UDWORD GetWidthOfMap(void)
{
      return mapWidth;
}



UDWORD GetHeightOfMap(void)
{
      return mapHeight;
}


// -----------------------------------------------------------------------------------
/* This will save out the visibility data */
bool writeVisibilityData(const char* fileName)
{
      unsigned int i;
      VIS_SAVEHEADER fileHeader;

      PHYSFS_file* fileHandle = openSaveFile(fileName);
      if (!fileHandle)
      {
            return false;
      }

      fileHeader.aFileType[0] = 'v';
      fileHeader.aFileType[1] = 'i';
      fileHeader.aFileType[2] = 's';
      fileHeader.aFileType[3] = 'd';

      fileHeader.version = CURRENT_VERSION_NUM;

      // Write out the current file header
      if (PHYSFS_write(fileHandle, fileHeader.aFileType, sizeof(fileHeader.aFileType), 1) != 1
       || !PHYSFS_writeUBE32(fileHandle, fileHeader.version))
      {
            debug(LOG_ERROR, "writeVisibilityData: could not write header to %s; PHYSFS error: %s", fileName, PHYSFS_getLastError());
            PHYSFS_close(fileHandle);
            return false;
      }

      for (i = 0; i < mapWidth * mapHeight; ++i)
      {
            if (!PHYSFS_writeUBE8(fileHandle, psMapTiles[i].tileVisBits))
            {
                  debug(LOG_ERROR, "writeVisibilityData: could not write to %s; PHYSFS error: %s", fileName, PHYSFS_getLastError());
                  PHYSFS_close(fileHandle);
                  return false;
            }
      }

      // Everything is just fine!
      PHYSFS_close(fileHandle);
      return true;
}

// -----------------------------------------------------------------------------------
/* This will read in the visibility data */
bool readVisibilityData(const char* fileName)
{
      VIS_SAVEHEADER fileHeader;
      unsigned int expectedFileSize, fileSize;
      unsigned int i;

      PHYSFS_file* fileHandle = openLoadFile(fileName, false);
      if (!fileHandle)
      {
            // Failure to open the file is no failure to read it
            return true;
      }

      // Read the header from the file
      if (PHYSFS_read(fileHandle, fileHeader.aFileType, sizeof(fileHeader.aFileType), 1) != 1
       || !PHYSFS_readUBE32(fileHandle, &fileHeader.version))
      {
            debug(LOG_ERROR, "readVisibilityData: error while reading header from file: %s", PHYSFS_getLastError());
            PHYSFS_close(fileHandle);
            return false;
      }

      // Check the header to see if we've been given a file of the right type
      if (fileHeader.aFileType[0] != 'v'
       || fileHeader.aFileType[1] != 'i'
       || fileHeader.aFileType[2] != 's'
       || fileHeader.aFileType[3] != 'd')
      {
            debug(LOG_ERROR, "readVisibilityData: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'v' 'i' 's' 'd')",
                  fileHeader.aFileType[0],
                  fileHeader.aFileType[1],
                  fileHeader.aFileType[2],
                  fileHeader.aFileType[3]);

            PHYSFS_close(fileHandle);
            return false;
      }

      // Validate the filesize
      expectedFileSize = sizeof(fileHeader.aFileType) + sizeof(fileHeader.version) + mapWidth * mapHeight * sizeof(uint8_t);
      fileSize = PHYSFS_fileLength(fileHandle);
      if (fileSize != expectedFileSize)
      {
            PHYSFS_close(fileHandle);
            ASSERT(!"readVisibilityData: unexpected filesize", "readVisibilityData: unexpected filesize; should be %u, but is %u", expectedFileSize, fileSize);
            abort();
            return false;
      }

      // For every tile...
      for(i=0; i<mapWidth*mapHeight; i++)
      {
            /* Get the visibility data */
            if (!PHYSFS_readUBE8(fileHandle, &psMapTiles[i].tileVisBits))
            {
                  debug(LOG_ERROR, "readVisibilityData: could not read from %s; PHYSFS error: %s", fileName, PHYSFS_getLastError());
                  PHYSFS_close(fileHandle);
                  return false;
            }
      }

      // Close the file
      PHYSFS_close(fileHandle);

      /* Hopefully everything's just fine by now */
      return true;
}

static void astarTest(const char *name, int x1, int y1, int x2, int y2)
{
      int         asret, i;
      ASTAR_ROUTE route;
      int         x = world_coord(x1);
      int         y = world_coord(y1);
      int         endx = world_coord(x2);
      int         endy = world_coord(y2);
      clock_t           stop;
      clock_t           start = clock();
      int         iterations;
      bool        retval;

      retval = levLoadData(name, NULL, 0);
      ASSERT(retval, "Could not load %s", name);
      fpathInitialise();
      for (i = 0; i < 100; i++)
      {
            iterations = 1;
            route.numPoints = 0;
            astarResetCounters();
            ASSERT(astarInner == 0, "astarInner not reset");
            asret = fpathAStarRoute(ASR_NEWROUTE, &route, x, y, endx, endy);
            while (asret == ASR_PARTIAL)
            {
                  astarResetCounters();
                  ASSERT(astarInner == 0, "astarInner not reset");
                  asret = fpathAStarRoute(ASR_CONTINUE, &route, x, y, endx, endy);
                  iterations++;
            }
      }
      stop = clock();
      fprintf(stdout, "\t\tPath-finding timing %s: %.02f (%d nodes, %d iterations)\n", name,
              (double)(stop - start) / (double)CLOCKS_PER_SEC, route.numPoints, iterations);
      levReleaseAll();
}

void mapTest()
{
      fprintf(stdout, "\tMap self-test...\n");

      astarTest("BeggarsKanyon-T1", 16, 5, 119, 182);
      astarTest("MizaMaze-T3", 5, 5, 108, 112);

      fprintf(stdout, "\tMap self-test: PASSED\n");
}

Generated by  Doxygen 1.6.0   Back to index