/// Is this position next to a water tile? static bool isWater(int x, int y) { bool result = false; result = result || (tileOnMap(x ,y ) && mapTile(x ,y )->ground == waterGroundType); result = result || (tileOnMap(x+1,y ) && mapTile(x+1,y )->ground == waterGroundType); result = result || (tileOnMap(x ,y+1) && mapTile(x ,y+1)->ground == waterGroundType); result = result || (tileOnMap(x+1,y+1) && mapTile(x+1,y+1)->ground == waterGroundType); return result; }
/// 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; }
// 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; }
// /////////////////////////////////////////////////////////////// // receive splattered artifacts void recvMultiPlayerRandomArtifacts(NETQUEUE queue) { int count, i; uint8_t quantity, player; uint32_t tx,ty; uint32_t ref; FEATURE_TYPE type = FEAT_TREE; // Dummy initialisation. FEATURE *pF; NETbeginDecode(queue, GAME_ARTIFACTS); NETuint8_t(&quantity); NETenum(&type); debug(LOG_FEATURE, "receiving %u artifact(s) type: (%s)", quantity, feature_names[type]); for (i = 0; i < numFeatureStats && asFeatureStats[i].subType != type; i++) {} for (count = 0; count < quantity; count++) { MAPTILE *psTile; NETuint32_t(&tx); NETuint32_t(&ty); NETuint32_t(&ref); NETuint8_t(&player); if (tx == INVALID_XY) { continue; } else if (!tileOnMap(tx, ty)) { debug(LOG_ERROR, "Bad tile coordinates (%u,%u)", tx, ty); continue; } psTile = mapTile(tx, ty); if (!psTile || psTile->psObject != NULL) { debug(LOG_ERROR, "Already something at (%u,%u)!", tx, ty); continue; } pF = buildFeature((asFeatureStats + i), world_coord(tx), world_coord(ty), false); if (pF) { pF->id = ref; pF->player = player; syncDebugFeature(pF, '+'); } else { debug(LOG_ERROR, "Couldn't build feature %u for player %u ?", ref, player); } } NETend(); }
static QScriptValue js_orderDroidLoc(QScriptContext *context, QScriptEngine *) { QScriptValue droidVal = context->argument(0); int id = droidVal.property("id").toInt32(); int player = droidVal.property("player").toInt32(); QScriptValue orderVal = context->argument(1); int x = context->argument(2).toInt32(); int y = context->argument(3).toInt32(); DROID_ORDER order = (DROID_ORDER)orderVal.toInt32(); DROID *psDroid = IdToDroid(id, player); SCRIPT_ASSERT(context, psDroid, "Droid id %d not found belonging to player %d", id, player); SCRIPT_ASSERT(context, tileOnMap(x, y), "Outside map bounds (%d, %d)", x, y); orderDroidLoc(psDroid, order, world_coord(x), world_coord(y), ModeQueue); return QScriptValue(); }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { int mapX, mapY, width, breadth, player; MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); if(bMultiMessages && !ingame.localJoiningInProgress) { SendDestroyFeature(psDel); // inform other players of destruction return true; // Wait for our message before really destroying the feature. } //remove from the map data 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++) { if (tileOnMap(mapX + width, mapY + breadth)) { MAPTILE *psTile = mapTile(mapX + width, mapY + breadth); if (psTile->psObject == (BASE_OBJECT *)psDel) { psTile->psObject = NULL; auxClearBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } 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) { for (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); } } } killFeature(psDel); return true; }
ASR_RETVAL fpathAStarRoute(MOVE_CONTROL *psMove, PATHJOB *psJob) { ASR_RETVAL retval = ASR_OK; bool mustReverse = true; const PathCoord tileOrig(map_coord(psJob->origX), map_coord(psJob->origY)); const PathCoord tileDest(map_coord(psJob->destX), map_coord(psJob->destY)); PathCoord endCoord; // Either nearest coord (mustReverse = true) or orig (mustReverse = false). std::list<PathfindContext>::iterator contextIterator = fpathContexts.begin(); for (contextIterator = fpathContexts.begin(); contextIterator != fpathContexts.end(); ++contextIterator) { if (!contextIterator->matches(psJob->blockingMap, tileDest)) { // This context is not for the same droid type and same destination. continue; } // We have tried going to tileDest before. if (contextIterator->map[tileOrig.x + tileOrig.y*mapWidth].iteration == contextIterator->iteration && contextIterator->map[tileOrig.x + tileOrig.y*mapWidth].visited) { // Already know the path from orig to dest. endCoord = tileOrig; } else { // Need to find the path from orig to dest, continue previous exploration. fpathAStarReestimate(*contextIterator, tileOrig); endCoord = fpathAStarExplore(*contextIterator, tileOrig); } if (endCoord != tileOrig) { // orig turned out to be on a different island than what this context was used for, so can't use this context data after all. continue; } mustReverse = false; // We have the path from the nearest reachable tile to dest, to orig. break; // Found the path! Don't search more contexts. } if (contextIterator == fpathContexts.end()) { // We did not find an appropriate context. Make one. if (fpathContexts.size() < 30) { fpathContexts.push_back(PathfindContext()); } --contextIterator; // Init a new context, overwriting the oldest one if we are caching too many. // We will be searching from orig to dest, since we don't know where the nearest reachable tile to dest is. fpathInitContext(*contextIterator, psJob->blockingMap, tileOrig, tileOrig, tileDest); endCoord = fpathAStarExplore(*contextIterator, tileDest); contextIterator->nearestCoord = endCoord; } PathfindContext &context = *contextIterator; // return the nearest route if no actual route was found if (context.nearestCoord != tileDest) { retval = ASR_NEAREST; } // Get route, in reverse order. static std::vector<Vector2i> path; // Declared static to save allocations. path.clear(); PathCoord newP; for (PathCoord p = endCoord; p != context.tileS; p = newP) { ASSERT_OR_RETURN(ASR_FAILED, tileOnMap(p.x, p.y), "Assigned XY coordinates (%d, %d) not on map!", (int)p.x, (int)p.y); ASSERT_OR_RETURN(ASR_FAILED, path.size() < (unsigned)mapWidth*mapHeight, "Pathfinding got in a loop."); path.push_back(Vector2i(world_coord(p.x) + TILE_UNITS / 2, world_coord(p.y) + TILE_UNITS / 2)); PathExploredTile &tile = context.map[p.x + p.y*mapWidth]; newP = PathCoord(p.x - tile.dx, p.y - tile.dy); if (p == newP) { break; // We stopped moving, because we reached the closest reachable tile to context.tileS. Give up now. } } if (path.empty()) { // We are probably already in the destination tile. Go to the exact coordinates. path.push_back(Vector2i(psJob->destX, psJob->destY)); } else if (retval == ASR_OK) { // Found exact path, so use exact coordinates for last point, no reason to lose precision Vector2i v(psJob->destX, psJob->destY); if (mustReverse) { path.front() = v; } else { path.back() = v; } } // TODO FIXME once we can change numPoints to something larger than uint16_t psMove->numPoints = std::min<int>(UINT16_MAX, path.size()); // Allocate memory psMove->asPath = static_cast<Vector2i *>(malloc(sizeof(*psMove->asPath) * path.size())); ASSERT(psMove->asPath, "Out of memory"); if (!psMove->asPath) { fpathHardTableReset(); return ASR_FAILED; } // get the route in the correct order // If as I suspect this is to reverse the list, then it's my suspicion that // we could route from destination to source as opposed to source to // destination. We could then save the reversal. to risky to try now...Alex M // // The idea is impractical, because you can't guarentee that the target is // reachable. As I see it, this is the reason why psNearest got introduced. // -- Dennis L. // // If many droids are heading towards the same destination, then destination // to source would be faster if reusing the information in nodeArray. --Cyp if (mustReverse) { // Copy the list, in reverse. std::copy(path.rbegin(), path.rend(), psMove->asPath); if (!context.isBlocked(tileOrig.x, tileOrig.y)) // If blocked, searching from tileDest to tileOrig wouldn't find the tileOrig tile. { // Next time, search starting from nearest reachable tile to the destination. fpathInitContext(context, psJob->blockingMap, tileDest, context.nearestCoord, tileOrig); } } else { // Copy the list. std::copy(path.begin(), path.end(), psMove->asPath); } // Move context to beginning of last recently used list. if (contextIterator != fpathContexts.begin()) // Not sure whether or not the splice is a safe noop, if equal. { fpathContexts.splice(fpathContexts.begin(), fpathContexts, contextIterator); } psMove->destination = psMove->asPath[path.size() - 1]; return retval; }
// 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 end point of the route // is a blocking tile and find an alternative if it is if (psDroid->sMove.Status != MOVEWAITROUTE && fpathDroidBlockingTile(psDroid, map_coord(tX), map_coord(tY), moveType)) { // find the nearest non blocking tile to the DROID int minDist = SDWORD_MAX; int nearestDir = NUM_DIR; int dir; objTrace(psDroid->id, "BLOCKED(%d,%d) - trying workaround", map_coord(tX), map_coord(tY)); for (dir = 0; dir < NUM_DIR; dir++) { int x = map_coord(tX) + aDirOffset[dir].x; int y = map_coord(tY) + aDirOffset[dir].y; if (tileOnMap(x, y) && !fpathDroidBlockingTile(psDroid, x, y, moveType)) { // pick the adjacent tile closest to our starting point int tileDist = fpathDistToTile(x, y, psDroid->pos.x, psDroid->pos.y); if (tileDist < minDist) { minDist = tileDist; nearestDir = dir; } } if (dir == NUM_BASIC - 1 && nearestDir != NUM_DIR) { break; // found a solution without checking at greater distance } } if (nearestDir == NUM_DIR) { // surrounded by blocking tiles, give up objTrace(psDroid->id, "route to (%d, %d) failed - target blocked", map_coord(tX), map_coord(tY)); return FPR_FAILED; } else { tX = world_coord(map_coord(tX) + aDirOffset[nearestDir].x) + TILE_UNITS / 2; tY = world_coord(map_coord(tY) + aDirOffset[nearestDir].y) + TILE_UNITS / 2; objTrace(psDroid->id, "Workaround found at (%d, %d)", map_coord(tX), map_coord(tY)); } } switch (psDroid->order) { case DORDER_BUILD: case DORDER_HELPBUILD: // help to build a structure case DORDER_LINEBUILD: // 6 - build a number of structures in a row (walls + bridges) case DORDER_DEMOLISH: // demolish a structure case DORDER_REPAIR: acceptNearest = false; break; default: acceptNearest = true; break; } return fpathRoute(&psDroid->sMove, psDroid->id, psDroid->pos.x, psDroid->pos.y, tX, tY, psPropStats->propulsionType, psDroid->droidType, moveType, psDroid->player, acceptNearest); }