StructureBounds getStructureBounds(BASE_STATS const *stats, Vector2i pos, uint16_t direction) { if (StatIsStructure(stats)) { return getStructureBounds(static_cast<STRUCTURE_STATS const *>(stats), pos, direction); } else if (StatIsFeature(stats)) { return getStructureBounds(static_cast<FEATURE_STATS const *>(stats), pos); } return StructureBounds(map_coord(pos), Vector2i(1, 1)); // Default to a 1×1 tile. }
StructureBounds getStructureBounds(BASE_OBJECT const *object) { STRUCTURE const *psStructure = castStructure(object); FEATURE const *psFeature = castFeature(object); if (psStructure != nullptr) { return getStructureBounds(psStructure); } else if (psFeature != nullptr) { return getStructureBounds(psFeature); } return StructureBounds(Vector2i(32767, 32767), Vector2i(-65535, -65535)); // Default to an invalid area. }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); //remove from the map data StructureBounds b = getStructureBounds(psDel); for (int breadth = 0; breadth < b.size.y; ++breadth) { for (int width = 0; width < b.size.x; ++width) { if (tileOnMap(b.map.x + width, b.map.y + breadth)) { MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth); if (psTile->psObject == psDel) { psTile->psObject = NULL; auxClearBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_DRUM) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x, pos.z) + 30; addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_DISCOVERY, false, NULL, 0, gameTime - deltaGameTime + 1); if (psDel->psStats->subType == FEAT_GEN_ARTE) { scoreUpdateVar(WD_ARTEFACTS_FOUND); intRefreshScreen(); } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_RESOURCE) { for (unsigned player = 0; player < MAX_PLAYERS; ++player) { psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); while (psMessage) { removeMessage(psMessage, player); psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); } } } debug(LOG_DEATH, "Killing off feature %s id %d (%p)", objInfo(psDel), psDel->id, psDel); killFeature(psDel); return true; }
// Find a route for an DROID to a location in world coordinates FPATH_RETVAL fpathDroidRoute(DROID *psDroid, SDWORD tX, SDWORD tY, FPATH_MOVETYPE moveType) { bool acceptNearest; PROPULSION_STATS *psPropStats = getPropulsionStats(psDroid); // override for AI to blast our way through stuff if (!isHumanPlayer(psDroid->player) && moveType == FMT_MOVE) { moveType = (psDroid->asWeaps[0].nStat == 0) ? FMT_MOVE : FMT_ATTACK; } ASSERT_OR_RETURN(FPR_FAILED, psPropStats != NULL, "invalid propulsion stats pointer"); ASSERT_OR_RETURN(FPR_FAILED, psDroid->type == OBJ_DROID, "We got passed an object that isn't a DROID!"); // Check whether the start and end points of the route are blocking tiles and find an alternative if they are. Position startPos = psDroid->pos; Position endPos = Position(tX, tY, 0); StructureBounds dstStructure = getStructureBounds(worldTile(endPos.xy)->psObject); startPos = findNonblockingPosition(startPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType); if (!dstStructure.valid()) // If there's a structure over the destination, ignore it, otherwise pathfind from somewhere around the obstruction. { endPos = findNonblockingPosition(endPos, getPropulsionStats(psDroid)->propulsionType, psDroid->player, moveType); } objTrace(psDroid->id, "Want to go to (%d, %d) -> (%d, %d), going (%d, %d) -> (%d, %d)", map_coord(psDroid->pos.x), map_coord(psDroid->pos.y), map_coord(tX), map_coord(tY), map_coord(startPos.x), map_coord(startPos.y), map_coord(endPos.x), map_coord(endPos.y)); switch (psDroid->order.type) { case DORDER_BUILD: case DORDER_LINEBUILD: // build a number of structures in a row (walls + bridges) dstStructure = getStructureBounds(psDroid->order.psStats, psDroid->order.pos, psDroid->order.direction); // Just need to get close enough to build (can be diagonally), do not need to reach the destination tile. // fallthrough case DORDER_HELPBUILD: // help to build a structure case DORDER_DEMOLISH: // demolish a structure case DORDER_REPAIR: acceptNearest = false; break; default: acceptNearest = true; break; } return fpathRoute(&psDroid->sMove, psDroid->id, startPos.x, startPos.y, endPos.x, endPos.y, psPropStats->propulsionType, psDroid->droidType, moveType, psDroid->player, acceptNearest, dstStructure); }
// Only used by fpathTest. static FPATH_RETVAL fpathSimpleRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY) { return fpathRoute(psMove, id, startX, startY, tX, tY, PROPULSION_TYPE_WHEELED, DROID_WEAPON, FMT_BLOCK, 0, true, getStructureBounds((BASE_OBJECT *)NULL)); }
/* 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"); /* 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; }
/* Create a feature on the map */ FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,bool FromSave) { //try and create the Feature FEATURE *psFeature = new FEATURE(generateSynchronisedObjectId(), psStats); if (psFeature == NULL) { debug(LOG_WARNING, "Feature couldn't be built."); 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); // snap the coords to a tile if (!FromSave) { x = (x & ~TILE_MASK) + psStats->baseWidth %2 * TILE_UNITS/2; y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2; } else { if ((x & TILE_MASK) != psStats->baseWidth %2 * TILE_UNITS/2 || (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2) { debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth); } } psFeature->pos.x = x; psFeature->pos.y = y; StructureBounds b = getStructureBounds(psFeature); // get the terrain average height int foundationMin = INT32_MAX; int foundationMax = INT32_MIN; for (int breadth = 0; breadth <= b.size.y; ++breadth) { for (int width = 0; width <= b.size.x; ++width) { int h = map_TileHeight(b.map.x + width, b.map.y + breadth); foundationMin = std::min(foundationMin, h); foundationMax = std::max(foundationMax, h); } } //return the average of max/min height int height = (foundationMin + foundationMax) / 2; if (psStats->subType == FEAT_TREE) { psFeature->rot.direction = gameRand(DEG_360); } else { psFeature->rot.direction = 0; } psFeature->body = psStats->body; psFeature->periodicalDamageStart = 0; psFeature->periodicalDamage = 0; // it has never been drawn psFeature->sDisplay.frameNumber = 0; memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick)); memset(psFeature->visible, 0, sizeof(psFeature->visible)); // set up the imd for the feature psFeature->sDisplay.imd = psStats->psImd; ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature"); // make sure we have an imd. 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); //check not outside of map - for load save game ASSERT_OR_RETURN(NULL, b.map.x + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats), psFeature->id); ASSERT_OR_RETURN(NULL, b.map.y + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats), 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), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y), psBlock->psStats->baseBreadth, getName(psFeature->psStats), psFeature->id, b.map.x, b.size.x, b.map.y, b.size.y); removeFeature(psBlock); } psTile->psObject = (BASE_OBJECT*)psFeature; // if it's a tall feature then flag it in the map. if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX) { auxSetBlocking(b.map.x + width, b.map.y + breadth, AIR_BLOCKED); } if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM) { auxSetBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED); } } if( (!psStats->tileDraw) && (FromSave == false) ) { psTile->height = height; } } } psFeature->pos.z = map_TileHeight(b.map.x, b.map.y);//jps 18july97 return psFeature; }