Logo Search packages:      
Sourcecode: warzone2100 version File versions

feature.c

/*
      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
*/
/*
 * Feature.c
 *
 * Load feature stats
 */
#include "lib/framework/frame.h"
#include "lib/framework/frameresource.h"
#include "lib/framework/strres.h"

#include "lib/gamelib/gtime.h"
#include "lib/sound/audio.h"
#include "lib/sound/audio_id.h"

#include "feature.h"
#include "map.h"
#include "hci.h"
#include "power.h"
#include "objects.h"
#include "display.h"
#include "console.h"
#include "order.h"
#include "structure.h"
#include "miscimd.h"
#include "anim_id.h"
#include "visibility.h"
#include "text.h"
#include "effects.h"
#include "geometry.h"
#include "scores.h"
#include "combat.h"
#include "multiplay.h"
#include "advvis.h"

#include "mapgrid.h"
#include "display3d.h"
#include "gateway.h"


/* The statistics for the features */
FEATURE_STATS     *asFeatureStats;
UDWORD                  numFeatureStats;

//Value is stored for easy access to this feature in destroyDroid()/destroyStruct()
UDWORD                  oilResFeature;

/* other house droid to add */
#define DROID_TEMPLINDEX      0
#define DROID_X                     (TILE_UNITS * 37 + TILE_UNITS/2)
#define DROID_Y                     (TILE_UNITS + TILE_UNITS/2)
#define DROID_TARX                  37
#define DROID_TARY                  42

//specifies how far round (in tiles) a constructor droid sound look for more wreckage
#define WRECK_SEARCH 3

struct featureTypeMap
{
      const char *typeStr;
      FEATURE_TYPE type;
};

const static struct featureTypeMap map[] =
{
      { "HOVER WRECK", FEAT_HOVER },
      { "TANK WRECK", FEAT_TANK },
      { "GENERIC ARTEFACT", FEAT_GEN_ARTE },
      { "OIL RESOURCE", FEAT_OIL_RESOURCE },
      { "BOULDER", FEAT_BOULDER },
      { "VEHICLE", FEAT_VEHICLE },
      { "DROID WRECK", FEAT_DROID },
      { "BUILDING WRECK", FEAT_BUILD_WRECK },
      { "BUILDING", FEAT_BUILDING },
      { "OIL DRUM", FEAT_OIL_DRUM },
      { "TREE", FEAT_TREE },
      { "SKYSCRAPER", FEAT_SKYSCRAPER }
};


void featureInitVars(void)
{
      asFeatureStats = NULL;
      numFeatureStats = 0;
      oilResFeature = 0;
}

static void featureType(FEATURE_STATS* psFeature, const char *pType)
{
      int i;
      
      for (i = 0; i < sizeof(map) / sizeof(map[0]); i++)
      {
            if (strcmp(pType, map[i].typeStr) == 0)
            {
                  psFeature->subType = map[i].type;
                  return;
            }
      }

      ASSERT(false, "featureType: Unknown feature type");
}

/* Load the feature stats */
BOOL loadFeatureStats(const char *pFeatureData, UDWORD bufferSize)
{
      FEATURE_STATS           *psFeature;
      unsigned int            i;
      char                    featureName[MAX_STR_LENGTH], GfxFile[MAX_STR_LENGTH],
                                    type[MAX_STR_LENGTH];

      numFeatureStats = numCR(pFeatureData, bufferSize);

      asFeatureStats = (FEATURE_STATS*)malloc(sizeof(FEATURE_STATS) * numFeatureStats);

      if (asFeatureStats == NULL)
      {
            debug( LOG_ERROR, "Feature Stats - Out of memory" );
            abort();
            return false;
      }

      psFeature = asFeatureStats;
      for (i = 0; i < numFeatureStats; i++)
      {
            UDWORD Width, Breadth;

            memset(psFeature, 0, sizeof(FEATURE_STATS));

            featureName[0] = '\0';
            GfxFile[0] = '\0';
            type[0] = '\0';

            //read the data into the storage - the data is delimeted using comma's
            sscanf(pFeatureData,"%[^','],%d,%d,%d,%d,%d,%[^','],%[^','],%d,%d,%d",
                  featureName, &Width, &Breadth,
                  &psFeature->damageable, &psFeature->armourValue, &psFeature->body,
                  GfxFile, type, &psFeature->tileDraw, &psFeature->allowLOS,
                  &psFeature->visibleAtStart);

            // These are now only 16 bits wide - so we need to copy them
            psFeature->baseWidth = (UWORD)Width;
            psFeature->baseBreadth = (UWORD)Breadth;

            if (!allocateName(&psFeature->pName, featureName))
            {
                  return false;
            }

            //determine the feature type
            featureType(psFeature, type);

            //and the oil resource - assumes only one!
            if (psFeature->subType == FEAT_OIL_RESOURCE)
            {
                  oilResFeature = i;
            }

            //get the IMD for the feature
            psFeature->psImd = (iIMDShape *) resGetData("IMD", GfxFile);
            if (psFeature->psImd == NULL)
            {
                  debug( LOG_ERROR, "Cannot find the feature PIE for record %s",  getName( psFeature->pName ) );
                  abort();
                  return false;
            }

            psFeature->ref = REF_FEATURE_START + i;

            //increment the pointer to the start of the next record
            pFeatureData = strchr(pFeatureData,'\n') + 1;
            //increment the list to the start of the next storage block
            psFeature++;
      }

      return true;
}

/* Release the feature stats memory */
void featureStatsShutDown(void)
{
      if(numFeatureStats)
      {
            free(asFeatureStats);
            asFeatureStats = NULL;
      }
}

/** Deals with damage to a feature
 *  \param psFeature feature to deal damage to
 *  \param damage amount of damage to deal
 *  \param weaponClass,weaponSubClass the class and subclass of the weapon that deals the damage
 *  \param impactSide the side/directon on which the feature is hit
 *  \return < 0 when the dealt damage destroys the feature, > 0 when the feature survives
 */
00213 float featureDamage(FEATURE *psFeature, UDWORD damage, UDWORD weaponClass, UDWORD weaponSubClass, HIT_SIDE impactSide)
{
      float       relativeDamage;

      ASSERT(psFeature != NULL, "featureDamage: Invalid feature pointer");

      debug(LOG_ATTACK, "featureDamage(%d): body %d armour %d damage: %d",
            psFeature->id, psFeature->body, psFeature->armour[impactSide][weaponClass], damage);

      relativeDamage = objDamage((BASE_OBJECT *)psFeature, damage, psFeature->psStats->body, weaponClass, weaponSubClass, impactSide);

      // If the shell did sufficient damage to destroy the feature
      if (relativeDamage < 0.0f)
      {
            destroyFeature(psFeature);
            return relativeDamage * -1.0f;
      }
      else
      {
            return relativeDamage;
      }
}


/* Create a feature on the map */
FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,BOOL FromSave)
{
      UDWORD            mapX, mapY;
      UDWORD            width,breadth, foundationMin,foundationMax, height;
      UDWORD            startX,startY,max,min;
      SDWORD            i;
      UBYTE       vis;

      //try and create the Feature
      FEATURE* psFeature = createFeature();
      if (psFeature == NULL)
      {
            return NULL;
      }
      // features are not in the cluster system
      // this will cause an assert when they still end up there
      psFeature->cluster = ~0;
      //add the feature to the list - this enables it to be drawn whilst being built
      addFeature(psFeature);

      // get the terrain average height
      startX = map_coord(x);
      startY = map_coord(y);
      foundationMin = TILE_MAX_HEIGHT;
      foundationMax = TILE_MIN_HEIGHT;
      for (breadth = 0; breadth < psStats->baseBreadth; breadth++)
      {
            for (width = 0; width < psStats->baseWidth; width++)
            {
                  getTileMaxMin(startX + width, startY + breadth, &max, &min);
                  if (foundationMin > min)
                  {
                        foundationMin = min;
                  }
                  if (foundationMax < max)
                  {
                        foundationMax = max;
                  }
            }
      }
      //return the average of max/min height
      height = (foundationMin + foundationMax) / 2;

      // check you can reach an oil resource
      if ((psStats->subType == FEAT_OIL_RESOURCE) &&
            !gwZoneReachable(gwGetZone(startX,startY)))
      {
            debug( LOG_NEVER, "Oil resource at (%d,%d) is unreachable", startX, startY );
      }

      if(FromSave == true) {
            psFeature->pos.x = (UWORD)x;
            psFeature->pos.y = (UWORD)y;
      } else {
            psFeature->pos.x = (UWORD)((x & (~TILE_MASK)) + psStats->baseWidth * TILE_UNITS / 2);
            psFeature->pos.y = (UWORD)((y & (~TILE_MASK)) + psStats->baseBreadth * TILE_UNITS / 2);
      }

      /* Dump down the building wrecks at random angles - still looks shit though */
      if(psStats->subType == FEAT_BUILD_WRECK)
      {
            psFeature->direction = rand() % 360;
      }
      else if(psStats->subType == FEAT_TREE)
      {
            psFeature->direction = rand() % 360;
      }
      else
      {
            psFeature->direction = 0;
      }
      //psFeature->damage = featureDamage;
      psFeature->selected = false;
      psFeature->psStats = psStats;
      //psFeature->subType = psStats->subType;
      psFeature->body = psStats->body;
      psFeature->player = MAX_PLAYERS+1;  //set the player out of range to avoid targeting confusions
      psFeature->sensorRange = 0;
      psFeature->sensorPower = 0;
      psFeature->ECMMod = 0;
      psFeature->bTargetted = false;
      psFeature->timeLastHit = 0;

      // it has never been drawn
      psFeature->sDisplay.frameNumber = 0;

      if(getRevealStatus())
      {
            vis = 0;
      }
      else
      {
            if(psStats->visibleAtStart)
            {
                  vis = UBYTE_MAX;
            }
            else
            {
                  vis = 0;
            }
      }

      // note that the advanced armour system current unused for features
      for (i = 0; i < NUM_HIT_SIDES; i++)
      {
            int j;

            for (j = 0; j < NUM_WEAPON_CLASS; j++)
            {
                  psFeature->armour[i][j] = psFeature->psStats->armourValue;
            }
      }

      for(i=0; i<MAX_PLAYERS; i++)
      {
            psFeature->visible[i] = 0;//vis;
      }
      //load into the map data

      if(FromSave) {
            mapX = map_coord(x) - psStats->baseWidth / 2;
            mapY = map_coord(y) - psStats->baseBreadth / 2;
      } else {
            mapX = map_coord(x);
            mapY = map_coord(y);
      }

      // set up the imd for the feature
      if(psFeature->psStats->subType==FEAT_BUILD_WRECK)
      {

            psFeature->sDisplay.imd = getRandomWreckageImd();

      }
      else
      {
            psFeature->sDisplay.imd = psStats->psImd;
      }


      assert(psFeature->sDisplay.imd);          // make sure we have an imd.

      for (width = 0; width <= psStats->baseWidth; width++)
      {
            for (breadth = 0; breadth <= psStats->baseBreadth; breadth++)
            {
                  MAPTILE *psTile = mapTile(mapX + width, mapY + breadth);

                  //check not outside of map - for load save game
                  ASSERT( (mapX+width) < mapWidth,
                        "x coord bigger than map width - %s, id = %d",
                        getName(psFeature->psStats->pName), psFeature->id );
                  ASSERT( (mapY+breadth) < mapHeight,
                        "y coord bigger than map height - %s, id = %d",
                        getName(psFeature->psStats->pName), psFeature->id );

                  if (width != psStats->baseWidth && breadth != psStats->baseBreadth)
                  {
                        if (TileHasFeature(psTile))
                        {
                              FEATURE *psBlock = (FEATURE *)psTile->psObject;

                              debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it",
                                    getName(psBlock->psStats->pName), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y), 
                                    psBlock->psStats->baseBreadth, getName(psFeature->psStats->pName), psFeature->id, mapX, psStats->baseWidth, mapY, psStats->baseBreadth);

                              removeFeature(psBlock);
                        }

                        psTile->psObject = (BASE_OBJECT*)psFeature;

                        if (psStats->subType == FEAT_GEN_ARTE || psStats->subType == FEAT_OIL_DRUM || psStats->subType == FEAT_BUILD_WRECK)// they're there - just can see me
                        {
                              SET_TILE_NOTBLOCKING(psTile);
                        }
                  }

                  if( (!psStats->tileDraw) && (FromSave == false) )
                  {
                        psTile->height = (UBYTE)(height / ELEVATION_SCALE);
                  }
            }
      }
      psFeature->pos.z = map_TileHeight(mapX,mapY);//jps 18july97

      //store the time it was built for removing wrecked droids/structures
      psFeature->startTime = gameTime;

//    // set up the imd for the feature
//    if(psFeature->psStats->subType==FEAT_BUILD_WRECK)
//    {
//          psFeature->sDisplay.imd = wreckageImds[rand()%MAX_WRECKAGE];
//    }
//    else
//    {
//          psFeature->sDisplay.imd = psStats->psImd;
//    }

      // add the feature to the grid
      gridAddObject((BASE_OBJECT *)psFeature);

      return psFeature;
}


/* Release the resources associated with a feature */
void featureRelease(FEATURE *psFeature)
{
      gridRemoveObject((BASE_OBJECT *)psFeature);
}


/* Update routine for features */
void featureUpdate(FEATURE *psFeat)
{
   // if(getRevealStatus())
   // {
            // update the visibility for the feature
            processVisibility((BASE_OBJECT *)psFeat);
   // }

      switch (psFeat->psStats->subType)
      {
      case FEAT_DROID:
      case FEAT_BUILD_WRECK:
//          //kill off wrecked droids and structures after 'so' long
//          if ((gameTime - psFeat->startTime) > WRECK_LIFETIME)
//          {
                  destroyFeature(psFeat); // get rid of the now!!!
//          }
            break;
      default:
            break;
      }
}


// free up a feature with no visual effects
void removeFeature(FEATURE *psDel)
{
      UDWORD            mapX, mapY, width,breadth, player;
      MESSAGE           *psMessage;
      Vector3i pos;

      ASSERT( psDel != NULL,
            "removeFeature: invalid feature pointer\n" );

      if (psDel->died)
      {
            // feature has already been killed, quit
            ASSERT( false,
                  "removeFeature: feature already dead" );
            return;
      }


      if(bMultiPlayer && !ingame.localJoiningInProgress)
      {
            SendDestroyFeature(psDel);    // inform other players of destruction
      }


      //remove from the map data
      mapX = map_coord(psDel->pos.x - psDel->psStats->baseWidth * TILE_UNITS / 2);
      mapY = map_coord(psDel->pos.y - psDel->psStats->baseBreadth * TILE_UNITS / 2);
      for (width = 0; width < psDel->psStats->baseWidth; width++)
      {
            for (breadth = 0; breadth < psDel->psStats->baseBreadth; breadth++)
            {
                  MAPTILE *psTile = mapTile(mapX + width, mapY + breadth);

                  psTile->psObject = NULL;

                  CLEAR_TILE_NOTBLOCKING(psTile);
            }
      }

      if(psDel->psStats->subType == FEAT_GEN_ARTE)
      {
            pos.x = psDel->pos.x;
            pos.z = psDel->pos.y;
            pos.y = map_Height(pos.x,pos.z);
            addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_DISCOVERY,false,NULL,0);
            scoreUpdateVar(WD_ARTEFACTS_FOUND);
            intRefreshScreen();
      }

      if (  (psDel->psStats->subType == FEAT_GEN_ARTE)
            ||(psDel->psStats->subType == FEAT_OIL_RESOURCE))
      {
            // have to check all players cos if you cheat you'll get em.
            for (player=0; player<MAX_PLAYERS; player ++)
            {
                  //see if there is a proximity message FOR THE SELECTED PLAYER at this location
                  psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, selectedPlayer);
                  while (psMessage)
                  {
                        removeMessage(psMessage, selectedPlayer);
                        psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player);
                  }
            }
      }

      // remove the feature from the grid
      gridRemoveObject((BASE_OBJECT *)psDel);

      killFeature(psDel);
}

/* Remove a Feature and free it's memory */
void destroyFeature(FEATURE *psDel)
{
      UDWORD                  widthScatter,breadthScatter,heightScatter, i;
      EFFECT_TYPE       explosionSize;
      Vector3i pos;
      UDWORD                  width,breadth;
      UDWORD                  mapX,mapY;
      UDWORD                  texture;

      ASSERT( psDel != NULL,
            "destroyFeature: invalid feature pointer\n" );

      /* Only add if visible and damageable*/
      if(psDel->visible[selectedPlayer] && psDel->psStats->damageable)
      {
            /* Set off a destruction effect */
            /* First Explosions */
            widthScatter = TILE_UNITS/2;
            breadthScatter = TILE_UNITS/2;
            heightScatter = TILE_UNITS/4;
            //set which explosion to use based on size of feature
            if (psDel->psStats->baseWidth < 2 && psDel->psStats->baseBreadth < 2)
            {
                  explosionSize = EXPLOSION_TYPE_SMALL;
            }
            else if (psDel->psStats->baseWidth < 3 && psDel->psStats->baseBreadth < 3)
            {
                  explosionSize = EXPLOSION_TYPE_MEDIUM;
            }
            else
            {
                  explosionSize = EXPLOSION_TYPE_LARGE;
            }
            for(i=0; i<4; i++)
            {
                  pos.x = psDel->pos.x + widthScatter - rand()%(2*widthScatter);
                  pos.z = psDel->pos.y + breadthScatter - rand()%(2*breadthScatter);
                  pos.y = psDel->pos.z + 32 + rand()%heightScatter;
                  addEffect(&pos,EFFECT_EXPLOSION,explosionSize,false,NULL,0);
            }

//          if(psDel->sDisplay.imd->pos.ymax>300)     // WARNING - STATS CHANGE NEEDED!!!!!!!!!!!
            if(psDel->psStats->subType == FEAT_SKYSCRAPER)
            {
                  pos.x = psDel->pos.x;
                  pos.z = psDel->pos.y;
                  pos.y = psDel->pos.z;
                  addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_SKYSCRAPER,true,psDel->sDisplay.imd,0);
                  initPerimeterSmoke(psDel->sDisplay.imd,pos.x,pos.y,pos.z);

                  // ----- Flip all the tiles under the skyscraper to a rubble tile
                  // smoke effect should disguise this happening
                  mapX = map_coord(psDel->pos.x - psDel->psStats->baseWidth * TILE_UNITS / 2);
                  mapY = map_coord(psDel->pos.y - psDel->psStats->baseBreadth * TILE_UNITS / 2);
//                if(psDel->sDisplay.imd->pos.ymax>300)
                  if (psDel->psStats->subType == FEAT_SKYSCRAPER)
                  {
                        for (width = 0; width < psDel->psStats->baseWidth; width++)
                        {
                              for (breadth = 0; breadth < psDel->psStats->baseBreadth; breadth++)
                              {
                                    MAPTILE *psTile = mapTile(mapX+width,mapY+breadth);
                                    // stops water texture chnaging for underwateer festures
                                    if (terrainType(psTile) != TER_WATER)
                                    {
                                          if (terrainType(psTile) != TER_CLIFFFACE)
                                          {
                                                /* Clear feature bits */
                                                psTile->psObject = NULL;
                                                texture = TileNumber_texture(psTile->texture) | RUBBLE_TILE;
                                                psTile->texture = (UWORD)texture;
                                          }
                                          else
                                          {
                                             /* This remains a blocking tile */
                                                psTile->psObject = NULL;
                                                texture = TileNumber_texture(psTile->texture) | BLOCKING_RUBBLE_TILE;
                                                psTile->texture = (UWORD)texture;

                                          }
                                    }
                              }
                        }
                  // -------
                  shakeStart();
                  }
            }

            /* Then a sequence of effects */
            pos.x = psDel->pos.x;
            pos.z = psDel->pos.y;
            pos.y = map_Height(pos.x,pos.z);
            addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_FEATURE,false,NULL,0);

            //play sound
            // ffs gj
            if(psDel->psStats->subType == FEAT_SKYSCRAPER)
            {
                  audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_BUILDING_FALL );
            }
            else

            {
                  audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION );
            }
      }
//---------------------------------------------------------------------------------------

      removeFeature(psDel);
}


SDWORD getFeatureStatFromName( const char *pName )
{
      unsigned int inc;
      FEATURE_STATS *psStat;

      for (inc = 0; inc < numFeatureStats; inc++)
      {
            psStat = &asFeatureStats[inc];
            if (!strcmp(psStat->pName, pName))
            {
                  return inc;
            }
      }
      return -1;
}


/*looks around the given droid to see if there is any building
wreckage to clear*/
FEATURE     * checkForWreckage(DROID *psDroid)
{
      FEATURE           *psFeature;
      UDWORD            startX, startY, incX, incY;
      SDWORD            x=0, y=0;

      startX = map_coord(psDroid->pos.x);
      startY = map_coord(psDroid->pos.y);

      //look around the droid - max 2 tiles distance
      for (incX = 1, incY = 1; incX < WRECK_SEARCH; incX++, incY++)
      {
            /* across the top */
            y = startY - incY;
            for(x = startX - incX; x < (SDWORD)(startX + incX); x++)
            {
                  if(TileHasFeature(mapTile(x,y)))
                  {
                        psFeature = getTileFeature(x, y);
                        if(psFeature && psFeature->psStats->subType == FEAT_BUILD_WRECK)
                        {
                              return psFeature;
                        }
                  }
            }
            /* the right */
            x = startX + incX;
            for(y = startY - incY; y < (SDWORD)(startY + incY); y++)
            {
                  if(TileHasFeature(mapTile(x,y)))
                  {
                        psFeature = getTileFeature(x, y);
                        if(psFeature && psFeature->psStats->subType == FEAT_BUILD_WRECK)
                        {
                              return psFeature;
                        }
                  }
            }
            /* across the bottom*/
            y = startY + incY;
            for(x = startX + incX; x > (SDWORD)(startX - incX); x--)
            {
                  if(TileHasFeature(mapTile(x,y)))
                  {
                        psFeature = getTileFeature(x, y);
                        if(psFeature && psFeature->psStats->subType == FEAT_BUILD_WRECK)
                        {
                              return psFeature;
                        }
                  }
            }

            /* the left */
            x = startX - incX;
            for(y = startY + incY; y > (SDWORD)(startY - incY); y--)
            {
                  if(TileHasFeature(mapTile(x,y)))
                  {
                        psFeature = getTileFeature(x, y);
                        if(psFeature && psFeature->psStats->subType == FEAT_BUILD_WRECK)
                        {
                              return psFeature;
                        }
                  }
            }
      }
      return NULL;
}

Generated by  Doxygen 1.6.0   Back to index