/// Is this position next to a water tile? static bool isWater(int x, int y) { bool result = false; result = result || (tileOnMap(x , y) && terrainType(mapTile(x , y)) == TER_WATER); result = result || (tileOnMap(x - 1, y) && terrainType(mapTile(x - 1, y)) == TER_WATER); result = result || (tileOnMap(x , y - 1) && terrainType(mapTile(x , y - 1)) == TER_WATER); result = result || (tileOnMap(x - 1, y - 1) && terrainType(mapTile(x - 1, y - 1)) == TER_WATER); return result; }
//By passing in params - it means that if the scroll limits are changed mid-mission //we can re-do over the area that hasn't been seen void initLighting(UDWORD x1, UDWORD y1, UDWORD x2, UDWORD y2) { UDWORD i, j; // quick check not trying to go off the map - don't need to check for < 0 since UWORD's!! if (x1 > mapWidth || x2 > mapWidth || y1 > mapHeight || y2 > mapHeight) { ASSERT( false, "initLighting: coords off edge of map" ); return; } for (i = x1; i < x2; i++) { for(j = y1; j < y2; j++) { MAPTILE *psTile = mapTile(i, j); // always make the edge tiles dark if (i==0 || j==0 || i >= mapWidth-1 || j >= mapHeight-1) { psTile->illumination = 16; // give water tiles at edge of map a border if (terrainType(psTile) == TER_WATER) { psTile->texture = 0; } } else { calcTileIllum(i,j); } // Basically darkens down the tiles that are outside the scroll // limits - thereby emphasising the cannot-go-there-ness of them if ((SDWORD)i < scrollMinX + 4 || (SDWORD)i > scrollMaxX - 4 || (SDWORD)j < scrollMinY + 4 || (SDWORD)j > scrollMaxY - 4) { psTile->illumination/=3; } } } }
/* Remove a Feature and free it's memory */ bool destroyFeature(FEATURE *psDel) { UDWORD widthScatter,breadthScatter,heightScatter, i; EFFECT_TYPE explosionSize; Vector3i pos; UDWORD width,breadth; UDWORD mapX,mapY; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); /* 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->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); 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 ); } } if (psDel->psStats->subType == FEAT_SKYSCRAPER) { // ----- 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/2; mapY = map_coord(psDel->pos.y) - psDel->psStats->baseBreadth/2; 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->texture = TileNumber_texture(psTile->texture) | RUBBLE_TILE; } else { /* This remains a blocking tile */ psTile->psObject = NULL; psTile->texture = TileNumber_texture(psTile->texture) | BLOCKING_RUBBLE_TILE; } } } } } removeFeature(psDel); return true; }
static PIELIGHT appliedRadarColour(RADAR_DRAW_MODE radarDrawMode, MAPTILE *WTile) { PIELIGHT WScr = WZCOL_BLACK; // squelch warning // draw radar on/off feature if (!getRevealStatus() && !TEST_TILE_VISIBLE(selectedPlayer, WTile)) { return WZCOL_RADAR_BACKGROUND; } switch (radarDrawMode) { case RADAR_MODE_TERRAIN: { // draw radar terrain on/off feature PIELIGHT col = tileColours[TileNumber_tile(WTile->texture)]; col.byte.r = sqrtf(col.byte.r * WTile->illumination); col.byte.b = sqrtf(col.byte.b * WTile->illumination); col.byte.g = sqrtf(col.byte.g * WTile->illumination); if (terrainType(WTile) == TER_CLIFFFACE) { col.byte.r /= 2; col.byte.g /= 2; col.byte.b /= 2; } if (!hasSensorOnTile(WTile, selectedPlayer)) { col.byte.r = col.byte.r * 2 / 3; col.byte.g = col.byte.g * 2 / 3; col.byte.b = col.byte.b * 2 / 3; } if (!TEST_TILE_VISIBLE(selectedPlayer, WTile)) { col.byte.r /= 2; col.byte.g /= 2; col.byte.b /= 2; } WScr = col; } break; case RADAR_MODE_COMBINED: { // draw radar terrain on/off feature PIELIGHT col = tileColours[TileNumber_tile(WTile->texture)]; col.byte.r = sqrtf(col.byte.r * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2); col.byte.b = sqrtf(col.byte.b * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2); col.byte.g = sqrtf(col.byte.g * (WTile->illumination + WTile->height / ELEVATION_SCALE) / 2); if (terrainType(WTile) == TER_CLIFFFACE) { col.byte.r /= 2; col.byte.g /= 2; col.byte.b /= 2; } if (!hasSensorOnTile(WTile, selectedPlayer)) { col.byte.r = col.byte.r * 2 / 3; col.byte.g = col.byte.g * 2 / 3; col.byte.b = col.byte.b * 2 / 3; } if (!TEST_TILE_VISIBLE(selectedPlayer, WTile)) { col.byte.r /= 2; col.byte.g /= 2; col.byte.b /= 2; } WScr = col; } break; case RADAR_MODE_HEIGHT_MAP: { WScr.byte.r = WScr.byte.g = WScr.byte.b = WTile->height / ELEVATION_SCALE; } break; case RADAR_MODE_NO_TERRAIN: { WScr = WZCOL_RADAR_BACKGROUND; } break; case NUM_RADAR_MODES: { assert(false); } break; } return WScr; }
// not a direct script function but a helper for scrSkDefenseLocation and scrSkDefenseLocationB static bool defenseLocation(bool variantB) { SDWORD *pX, *pY, statIndex, statIndex2; UDWORD x, y, gX, gY, dist, player, nearestSoFar, count; GATEWAY *psGate, *psChosenGate; DROID *psDroid; UDWORD x1, x2, x3, x4, y1, y2, y3, y4; bool noWater; UDWORD minCount; UDWORD offset; if (!stackPopParams(6, VAL_REF | VAL_INT, &pX, VAL_REF | VAL_INT, &pY, ST_STRUCTURESTAT, &statIndex, ST_STRUCTURESTAT, &statIndex2, ST_DROID, &psDroid, VAL_INT, &player)) { debug(LOG_ERROR, "defenseLocation: failed to pop"); return false; } if (player >= MAX_PLAYERS) { ASSERT(false, "defenseLocation:player number is too high"); return false; } ASSERT_OR_RETURN(false, statIndex < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex, numStructureStats); ASSERT_OR_RETURN(false, statIndex2 < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex2, numStructureStats); STRUCTURE_STATS *psWStats = (asStructureStats + statIndex2); // check for wacky coords. if (*pX < 0 || *pX > world_coord(mapWidth) || *pY < 0 || *pY > world_coord(mapHeight) ) { goto failed; } x = map_coord(*pX); // change to tile coords. y = map_coord(*pY); // go down the gateways, find the nearest gateway with >1 empty tiles nearestSoFar = UDWORD_MAX; psChosenGate = NULL; for (psGate = gwGetGateways(); psGate; psGate = psGate->psNext) { if (auxTile(psGate->x1, psGate->y1, player) & AUXBITS_THREAT) { continue; // enemy can shoot there, not safe to build } count = 0; noWater = true; // does it have >1 tile unoccupied. if (psGate->x1 == psGate->x2) { // vert //skip gates that are too short if (variantB && (psGate->y2 - psGate->y1) <= 2) { continue; } gX = psGate->x1; for (gY = psGate->y1; gY <= psGate->y2; gY++) { if (! TileIsOccupied(mapTile(gX, gY))) { count++; } if (terrainType(mapTile(gX, gY)) == TER_WATER) { noWater = false; } } } else { // horiz //skip gates that are too short if (variantB && (psGate->x2 - psGate->x1) <= 2) { continue; } gY = psGate->y1; for (gX = psGate->x1; gX <= psGate->x2; gX++) { if (! TileIsOccupied(mapTile(gX, gY))) { count++; } if (terrainType(mapTile(gX, gY)) == TER_WATER) { noWater = false; } } } if (variantB) { minCount = 2; } else { minCount = 1; } if (count > minCount && noWater) //<NEW> min 2 tiles { // ok it's free. Is it the nearest one yet? /* Get gateway midpoint */ gX = (psGate->x1 + psGate->x2) / 2; gY = (psGate->y1 + psGate->y2) / 2; /* Estimate the distance to it */ dist = iHypot(x - gX, y - gY); /* Is it best we've found? */ if (dist < nearestSoFar && dist < 30) { /* Yes, then keep a record of it */ nearestSoFar = dist; psChosenGate = psGate; } } } if (!psChosenGate) // we have a gateway. { goto failed; } // find an unnocupied tile on that gateway. if (psChosenGate->x1 == psChosenGate->x2) { // vert gX = psChosenGate->x1; for (gY = psChosenGate->y1; gY <= psChosenGate->y2; gY++) { if (! TileIsOccupied(mapTile(gX, gY))) { y = gY; x = gX; break; } } } else { // horiz gY = psChosenGate->y1; for (gX = psChosenGate->x1; gX <= psChosenGate->x2; gX++) { if (! TileIsOccupied(mapTile(gX, gY))) { y = gY; x = gX; break; } } } // back to world coords and store result. *pX = world_coord(x) + (TILE_UNITS / 2); // return centre of tile. *pY = world_coord(y) + (TILE_UNITS / 2); scrFunctionResult.v.bval = true; if (!stackPushResult(VAL_BOOL, &scrFunctionResult)) // success { return false; } // order the droid to build two walls, one either side of the gateway. // or one in the case of a 2 size gateway. //find center of the gateway x = (psChosenGate->x1 + psChosenGate->x2) / 2; y = (psChosenGate->y1 + psChosenGate->y2) / 2; //find start pos of the gateway x1 = world_coord(psChosenGate->x1) + (TILE_UNITS / 2); y1 = world_coord(psChosenGate->y1) + (TILE_UNITS / 2); if (variantB) { offset = 2; } else { offset = 1; } if (psChosenGate->x1 == psChosenGate->x2) //vert { x2 = x1; //vert: end x pos of the first section = start x pos y2 = world_coord(y - 1) + TILE_UNITS / 2; //start y loc of the first sec x3 = x1; y3 = world_coord(y + offset) + TILE_UNITS / 2; } else //hor { x2 = world_coord(x - 1) + TILE_UNITS / 2; y2 = y1; x3 = world_coord(x + offset) + TILE_UNITS / 2; y3 = y1; } //end coords of the second section x4 = world_coord(psChosenGate->x2) + TILE_UNITS / 2; y4 = world_coord(psChosenGate->y2) + TILE_UNITS / 2; // first section. if (x1 == x2 && y1 == y2) //first sec is 1 tile only: ((2 tile gate) or (3 tile gate and first sec)) { orderDroidStatsLocDir(psDroid, DORDER_BUILD, psWStats, x1, y1, 0, ModeQueue); } else { orderDroidStatsTwoLocDir(psDroid, DORDER_LINEBUILD, psWStats, x1, y1, x2, y2, 0, ModeQueue); } // second section if (x3 == x4 && y3 == y4) { orderDroidStatsLocDirAdd(psDroid, DORDER_BUILD, psWStats, x3, y3, 0); } else { orderDroidStatsTwoLocDirAdd(psDroid, DORDER_LINEBUILD, psWStats, x3, y3, x4, y4, 0); } return true; failed: scrFunctionResult.v.bval = false; if (!stackPushResult(VAL_BOOL, &scrFunctionResult)) // failed! { return false; } return true; }
/* Moves one of the particles */ static void processParticle(ATPART *psPart) { SDWORD groundHeight; Vector3i pos; UDWORD x, y; MAPTILE *psTile; /* Only move if the game isn't paused */ if (!gamePaused()) { /* Move the particle - frame rate controlled */ psPart->position.x += graphicsTimeAdjustedIncrement(psPart->velocity.x); psPart->position.y += graphicsTimeAdjustedIncrement(psPart->velocity.y); psPart->position.z += graphicsTimeAdjustedIncrement(psPart->velocity.z); /* Wrap it around if it's gone off grid... */ testParticleWrap(psPart); /* If it's gone off the WORLD... */ if (psPart->position.x < 0 || psPart->position.z < 0 || psPart->position.x > ((mapWidth - 1)*TILE_UNITS) || psPart->position.z > ((mapHeight - 1)*TILE_UNITS)) { /* The kill it */ psPart->status = APS_INACTIVE; return; } /* What height is the ground under it? Only do if low enough...*/ if (psPart->position.y < 255 * ELEVATION_SCALE) { /* Get ground height */ groundHeight = map_Height(psPart->position.x, psPart->position.z); /* Are we below ground? */ if ((int)psPart->position.y < groundHeight || psPart->position.y < 0.f) { /* Kill it and return */ psPart->status = APS_INACTIVE; if (psPart->type == AP_RAIN) { x = map_coord(psPart->position.x); y = map_coord(psPart->position.z); psTile = mapTile(x, y); if (terrainType(psTile) == TER_WATER && TEST_TILE_VISIBLE(selectedPlayer, psTile)) { pos.x = psPart->position.x; pos.z = psPart->position.z; pos.y = groundHeight; effectSetSize(60); addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_SPECIFIED, true, getImdFromIndex(MI_SPLASH), 0); } } return; } } if (psPart->type == AP_SNOW) { if (rand() % 30 == 1) { psPart->velocity.z = (float)SNOW_SPEED_DRIFT; } if (rand() % 30 == 1) { psPart->velocity.x = (float)SNOW_SPEED_DRIFT; } } } }
int main(int argc, char **argv) { char *filename, *p_filename; char *base, tmpFile[PATH_MAX]; GAMEMAP *map; MAPTILE *psTile; if (argc != 2) { printf("Usage: %s <map>\n", argv[0]); return -1; } physfs_init(argv[0]); filename = physfs_addmappath(argv[1]); p_filename = strrchr(filename, '/'); if (p_filename) { p_filename++; base = strdup(p_filename); } else { base = strdup(filename); } map = mapLoad(filename); free(filename); if (!map) { return EXIT_FAILURE; } const PreviewColors* tileColors = NULL; switch (map->tileset) { case TILESET_ARIZONA: tileColors = &pArizonaColors; break; case TILESET_URBAN: tileColors = &pUrbanColors; break; case TILESET_ROCKIES: tileColors = &pRockiesColors; break; default: fprintf(stderr, "Unknown tileset: %d\n", (int)map->tileset); mapFree(map); physfs_shutdown(); return EXIT_FAILURE; } const int mapWidth = (int) map->width; const int mapHeight = (int) map->height; int col; // RGB888 pixels uint8_t *pixels = (uint8_t*) malloc(sizeof(uint8_t) * mapWidth * mapHeight * 3); for (int y = 0; y < mapHeight; y++) { for (int x = 0; x < mapWidth; x++) { // We're placing the origin at the top for aesthetic reasons psTile = mapTile(map, x, mapHeight-1-y); col = psTile->height / 2; // 2 = ELEVATION_SCALE uint8_t * const p = &pixels[(y * map->width + x) * 3]; switch(terrainType(psTile)) { case TER_CLIFFFACE: p[0] = tileColors->cliffLow.x + (tileColors->cliffHigh.x - tileColors->cliffLow.x) * col / 256; p[1] = tileColors->cliffLow.y + (tileColors->cliffHigh.y - tileColors->cliffLow.y) * col / 256; p[2] = tileColors->cliffLow.z + (tileColors->cliffHigh.z - tileColors->cliffLow.z) * col / 256; break; case TER_WATER: p[0] = tileColors->water.x; p[1] = tileColors->water.y; p[2] = tileColors->water.z; break; case TER_ROAD: p[0] = tileColors->roadLow.x + (tileColors->roadHigh.x - tileColors->roadLow.x) * col / 256; p[1] = tileColors->roadLow.y + (tileColors->roadHigh.y - tileColors->roadLow.y) * col / 256; p[2] = tileColors->roadLow.z + (tileColors->roadHigh.z - tileColors->roadLow.z) * col / 256; break; default: p[0] = tileColors->groundLow.x + (tileColors->groundHigh.x - tileColors->groundLow.x) * col / 256; p[1] = tileColors->groundLow.y + (tileColors->groundHigh.y - tileColors->groundLow.y) * col / 256; p[2] = tileColors->groundLow.z + (tileColors->groundHigh.z - tileColors->groundLow.z) * col / 256; break; } } } paintStructureData(pixels, map); strcpy(tmpFile, base); strcat(tmpFile, ".png"); savePng(tmpFile, pixels, mapWidth, mapHeight); free(pixels); mapFree(map); physfs_shutdown(); return 0; }
// multiple turrets display removed the pointless mountRotation void displayComponentObject(DROID *psDroid) { Vector3i position, rotation; int32_t xShift,zShift; SDWORD frame; PROPULSION_STATS *psPropStats; UDWORD tileX,tileY; MAPTILE *psTile; SPACETIME st = interpolateObjectSpacetime((SIMPLE_OBJECT *)psDroid, graphicsTime); psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; leftFirst = angleDelta(player.r.y - st.rot.direction) <= 0; /* Push the matrix */ pie_MatBegin(); /* Get internal tile units coordinates */ xShift = map_round(player.p.x); zShift = map_round(player.p.z); /* Mask out to tile_units resolution */ pie_TRANSLATE(xShift,0,-zShift); /* Get the real position */ position.x = (st.pos.x - player.p.x) - terrainMidX*TILE_UNITS; position.z = terrainMidY*TILE_UNITS - (st.pos.y - player.p.z); position.y = st.pos.z; if(psDroid->droidType == DROID_TRANSPORTER) { position.y += bobTransporterHeight(); } /* Get all the pitch,roll,yaw info */ rotation.y = -st.rot.direction; rotation.x = st.rot.pitch; rotation.z = st.rot.roll; /* Translate origin */ pie_TRANSLATE(position.x,position.y,position.z); /* Rotate for droid */ pie_MatRotY(rotation.y); pie_MatRotX(rotation.x); pie_MatRotZ(rotation.z); if( (gameTime-psDroid->timeLastHit < GAME_TICKS_PER_SEC) && psDroid->lastHitWeapon == WSC_ELECTRONIC) { objectShimmy( (BASE_OBJECT*) psDroid ); } if (psDroid->lastHitWeapon == WSC_EMP && (gameTime - psDroid->timeLastHit < EMP_DISABLE_TIME)) { Vector3i position; //add an effect on the droid position.x = st.pos.x + DROID_EMP_SPREAD; position.y = st.pos.z + rand()%8; position.z = st.pos.y + DROID_EMP_SPREAD; effectGiveAuxVar(90+rand()%20); addEffect(&position,EFFECT_EXPLOSION,EXPLOSION_TYPE_PLASMA,false,NULL,0); } if ((psDroid->visible[selectedPlayer] == UBYTE_MAX) || demoGetStatus()) { //ingame not button object //should render 3 mounted weapons now displayCompObj(psDroid, false); } else { // make sure it's not over water. tileX = st.pos.x/TILE_UNITS; tileY = st.pos.y/TILE_UNITS; // double check it's on map if ( tileX < mapWidth && tileY < mapHeight ) { psTile = mapTile(tileX,tileY); if (terrainType(psTile) != TER_WATER) { frame = gameTime/BLIP_ANIM_DURATION + psDroid->id; //visible[selectedPlayer]; pie_Draw3DShape(getImdFromIndex(MI_BLIP), frame, 0, WZCOL_WHITE, WZCOL_BLACK, pie_ADDITIVE, psDroid->visible[selectedPlayer] / 2); /* set up all the screen coords stuff - need to REMOVE FROM THIS LOOP */ } } } pie_MatEnd(); }
/* Remove a Feature and free it's memory */ bool destroyFeature(FEATURE *psDel, unsigned impactTime) { UDWORD widthScatter, breadthScatter, heightScatter, i; EFFECT_TYPE explosionSize; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT(gameTime - deltaGameTime < impactTime, "Expected %u < %u, gameTime = %u, bad impactTime", gameTime - deltaGameTime, impactTime, gameTime); /* 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, impactTime); } 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, impactTime); initPerimeterSmoke(psDel->sDisplay.imd, pos); shakeStart(250); // small shake } /* 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, impactTime); //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); } } if (psDel->psStats->subType == FEAT_SKYSCRAPER) { // ----- Flip all the tiles under the skyscraper to a rubble tile // smoke effect should disguise this happening StructureBounds b = getStructureBounds(psDel); for (int breadth = 0; breadth < b.size.y; ++breadth) { for (int width = 0; width < b.size.x; ++width) { MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth); // stops water texture changing for underwater features if (terrainType(psTile) != TER_WATER) { if (terrainType(psTile) != TER_CLIFFFACE) { /* Clear feature bits */ psTile->texture = TileNumber_texture(psTile->texture) | RUBBLE_TILE; auxClearBlocking(b.map.x + width, b.map.y + breadth, AUXBITS_ALL); } else { /* This remains a blocking tile */ psTile->psObject = NULL; auxClearBlocking(b.map.x + width, b.map.y + breadth, AIR_BLOCKED); // Shouldn't remain blocking for air units, however. psTile->texture = TileNumber_texture(psTile->texture) | BLOCKING_RUBBLE_TILE; } } } } } removeFeature(psDel); psDel->died = impactTime; return true; }