// Wide-area traffic designation utility. // Flood-fill from cursor or fill entire map. #include //For toupper(). #include //for min(). #include #include #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/Gui.h" using std::stack; using MapExtras::MapCache; using namespace DFHack; using namespace df::enums; //Function pointer type for whole-map tile checks. typedef void (*checkTile)(DFCoord, MapExtras::MapCache &); //Forward Declarations for Commands command_result filltraffic(color_ostream &out, std::vector & params); command_result alltraffic(color_ostream &out, std::vector & params); command_result restrictLiquid(color_ostream &out, std::vector & params); command_result restrictIce(color_ostream &out, std::vector & params); //Forward Declarations for Utility Functions command_result setAllMatching(color_ostream &out, checkTile checkProc, DFCoord minCoord = DFCoord(0, 0, 0), DFCoord maxCoord = DFCoord(0xFFFF, 0xFFFF, 0xFFFF)); void allHigh(DFCoord coord, MapExtras::MapCache & map); void allNormal(DFCoord coord, MapExtras::MapCache & map); void allLow(DFCoord coord, MapExtras::MapCache & map); void allRestricted(DFCoord coord, MapExtras::MapCache & map); void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map); void restrictIceProc(DFCoord coord, MapExtras::MapCache &map); DFHACK_PLUGIN("filltraffic"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "filltraffic", "Flood-fill selected traffic designation from cursor.", filltraffic, Gui::cursor_hotkey)); commands.push_back(PluginCommand( "alltraffic", "Set traffic designation for the entire map.", alltraffic)); commands.push_back(PluginCommand( "restrictliquids", "Restrict traffic on every visible square with liquid.", restrictLiquid)); commands.push_back(PluginCommand( "restrictice", "Restrict traffic on squares above visible ice.", restrictIce)); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } command_result filltraffic(color_ostream &out, std::vector & params) { // HOTKEY COMMAND; CORE ALREADY SUSPENDED //Maximum map size. uint32_t x_max,y_max,z_max; //Source and target traffic types. df::tile_traffic source = tile_traffic::Normal; df::tile_traffic target = tile_traffic::Normal; //Option flags bool updown = false; bool checkpit = true; bool checkbuilding = true; //Loop through parameters for(size_t i = 0; i < params.size();i++) { if (params[i] == "help" || params[i] == "?" || params[i].size() != 1) return CR_WRONG_USAGE; switch (toupper(params[i][0])) { case 'H': target = tile_traffic::High; break; case 'N': target = tile_traffic::Normal; break; case 'L': target = tile_traffic::Low; break; case 'R': target = tile_traffic::Restricted; break; case 'X': updown = true; break; case 'B': checkbuilding = false; break; case 'P': checkpit = false; break; default: return CR_WRONG_USAGE; } } if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } int32_t cx, cy, cz; Maps::getSize(x_max,y_max,z_max); uint32_t tx_max = x_max * 16; uint32_t ty_max = y_max * 16; Gui::getCursorCoords(cx,cy,cz); while(cx == -30000) { out.printerr("Cursor is not active.\n"); return CR_FAILURE; } DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); MapExtras::MapCache MCache; df::tile_designation des = MCache.designationAt(xy); df::tiletype tt = MCache.tiletypeAt(xy); df::tile_occupancy oc; if (checkbuilding) oc = MCache.occupancyAt(xy); source = (df::tile_traffic)des.bits.traffic; if(source == target) { out.printerr("This tile is already set to the target traffic type.\n"); return CR_FAILURE; } if(isWallTerrain(tt)) { out.printerr("This tile is a wall. Please select a passable tile.\n"); return CR_FAILURE; } if(checkpit && isOpenTerrain(tt)) { out.printerr("This tile is a hole. Please select a passable tile.\n"); return CR_FAILURE; } if(checkbuilding && oc.bits.building) { out.printerr("This tile contains a building. Please select an empty tile.\n"); return CR_FAILURE; } out.print("%d/%d/%d ... FILLING!\n", cx,cy,cz); //Naive four-way or six-way flood fill with possible tiles on a stack. stack flood; flood.push(xy); while(!flood.empty()) { xy = flood.top(); flood.pop(); des = MCache.designationAt(xy); if (des.bits.traffic != source) continue; tt = MCache.tiletypeAt(xy); if(isWallTerrain(tt)) continue; if(checkpit && isOpenTerrain(tt)) continue; if (checkbuilding) { oc = MCache.occupancyAt(xy); if(oc.bits.building) continue; } //This tile is ready. Set its traffic level and add surrounding tiles. if (MCache.testCoord(xy)) { des.bits.traffic = target; MCache.setDesignationAt(xy,des); if (xy.x > 0) { flood.push(DFCoord(xy.x - 1, xy.y, xy.z)); } if (xy.x < int32_t(tx_max) - 1) { flood.push(DFCoord(xy.x + 1, xy.y, xy.z)); } if (xy.y > 0) { flood.push(DFCoord(xy.x, xy.y - 1, xy.z)); } if (xy.y < int32_t(ty_max) - 1) { flood.push(DFCoord(xy.x, xy.y + 1, xy.z)); } if (updown) { if (xy.z > 0 && LowPassable(tt)) { flood.push(DFCoord(xy.x, xy.y, xy.z - 1)); } if (xy.z < int32_t(z_max) && HighPassable(tt)) { flood.push(DFCoord(xy.x, xy.y, xy.z + 1)); } } } } MCache.WriteAll(); return CR_OK; } enum e_checktype {no_check, check_equal, check_nequal}; command_result alltraffic(color_ostream &out, std::vector & params) { void (*proc)(DFCoord, MapExtras::MapCache &) = allNormal; //Loop through parameters for(size_t i = 0; i < params.size();i++) { if (params[i] == "help" || params[i] == "?" || params[i].size() != 1) return CR_WRONG_USAGE; //Pick traffic type. Possibly set bounding rectangle later. switch (toupper(params[i][0])) { case 'H': proc = allHigh; break; case 'N': proc = allNormal; break; case 'L': proc = allLow; break; case 'R': proc = allRestricted; break; default: return CR_WRONG_USAGE; } } return setAllMatching(out, proc); } command_result restrictLiquid(color_ostream &out, std::vector & params) { return setAllMatching(out, restrictLiquidProc); } command_result restrictIce(color_ostream &out, std::vector & params) { return setAllMatching(out, restrictIceProc); } //Helper function for writing new functions that check every tile on the map. //newTraffic is the traffic designation to set. //check takes a coordinate and the map cache as arguments, and returns true if the criteria is met. //minCoord and maxCoord can be used to specify a bounding cube. command_result setAllMatching(color_ostream &out, checkTile checkProc, DFCoord minCoord, DFCoord maxCoord) { //Initialization. CoreSuspender suspend; if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } //Maximum map size. uint32_t x_max,y_max,z_max; Maps::getSize(x_max,y_max,z_max); uint32_t tx_max = x_max * 16; uint32_t ty_max = y_max * 16; //Ensure maximum coordinate is within map. Truncate to map edge. maxCoord.x = std::min((uint32_t) maxCoord.x, tx_max); maxCoord.y = std::min((uint32_t) maxCoord.y, ty_max); maxCoord.z = std::min((uint32_t) maxCoord.z, z_max); //Check minimum co-ordinates against maximum map size if (minCoord.x > maxCoord.x) { out.printerr("Minimum x coordinate is greater than maximum x coordinate.\n"); return CR_FAILURE; } if (minCoord.y > maxCoord.y) { out.printerr("Minimum y coordinate is greater than maximum y coordinate.\n"); return CR_FAILURE; } if (minCoord.z > maxCoord.y) { out.printerr("Minimum z coordinate is greater than maximum z coordinate.\n"); return CR_FAILURE; } MapExtras::MapCache MCache; out.print("Setting traffic...\n"); //Loop through every single tile for(int32_t x = minCoord.x; x <= maxCoord.x; x++) { for(int32_t y = minCoord.y; y <= maxCoord.y; y++) { for(int32_t z = minCoord.z; z <= maxCoord.z; z++) { DFCoord tile = DFCoord(x, y, z); checkProc(tile, MCache); } } } MCache.WriteAll(); out.print("Complete!\n"); return CR_OK; } //Unconditionally set map to target traffic type void allHigh(DFCoord coord, MapExtras::MapCache &map) { df::tile_designation des = map.designationAt(coord); des.bits.traffic = tile_traffic::High; map.setDesignationAt(coord, des); } void allNormal(DFCoord coord, MapExtras::MapCache &map) { df::tile_designation des = map.designationAt(coord); des.bits.traffic = tile_traffic::Normal; map.setDesignationAt(coord, des); } void allLow(DFCoord coord, MapExtras::MapCache &map) { df::tile_designation des = map.designationAt(coord); des.bits.traffic = tile_traffic::Low; map.setDesignationAt(coord, des); } void allRestricted(DFCoord coord, MapExtras::MapCache &map) { df::tile_designation des = map.designationAt(coord); des.bits.traffic = tile_traffic::Restricted; map.setDesignationAt(coord, des); } //Restrict traffic if tile is visible and liquid is present. void restrictLiquidProc(DFCoord coord, MapExtras::MapCache &map) { df::tile_designation des = map.designationAt(coord); if ((des.bits.hidden == 0) && (des.bits.flow_size != 0)) { des.bits.traffic = tile_traffic::Restricted; map.setDesignationAt(coord, des); } } //Restrict traffice if tile is above visible ice wall. void restrictIceProc(DFCoord coord, MapExtras::MapCache &map) { //There is no ice below the bottom of the map. if (coord.z == 0) return; DFCoord tile_below = DFCoord(coord.x, coord.y, coord.z - 1); df::tiletype tt = map.tiletypeAt(tile_below); df::tile_designation des = map.designationAt(tile_below); if ((des.bits.hidden == 0) && (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID)) { des = map.designationAt(coord); des.bits.traffic = tile_traffic::Restricted; map.setDesignationAt(coord, des); } }