diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 6db9af322..8af3f4d5c 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2450,40 +2450,10 @@ Example: fortplan ======== -Usage: ``fortplan [filename]`` - -**Fortplan is deprecated.** Please use DFHack's more powerful `quickfort` -command instead. You can use your existing .csv files. Just move them to the -``blueprints`` folder in your DF installation, and instead of ``fortplan file.csv`` run ``quickfort run file.csv``. - -Designates furniture for building according to a ``.csv`` file with -quickfort-style syntax. - -The first line of the file must contain the following:: - - #build start(X; Y; ) - -...where X and Y are the offset from the top-left corner of the file's area -where the in-game cursor should be located, and ```` -is an optional description of where that is. You may also leave a description -of the contents of the file itself following the closing parenthesis on the -same line. - -The syntax of the file itself is similar to `digfort` or :forums:`quickfort <35931>`. -At present, only buildings constructed of an item with the same name as the building -are supported. All other characters are ignored. For example:: - - `,`,d,`,` - `,f,`,t,` - `,s,b,c,` - -This section of a file would designate for construction a door and some -furniture inside a bedroom: specifically, clockwise from top left, a cabinet, -a table, a chair, a bed, and a statue. - -All of the building designation uses `buildingplan`, so you do not need to -have the items available to construct all the buildings when you run -fortplan with the .csv file. +**Fortplan has been removed.** Please use DFHack's more powerful `quickfort` +script instead. You can use your existing .csv files. Just move them to the +``blueprints`` folder in your DF installation, and instead of +``fortplan file.csv`` run ``quickfort run file.csv``. .. _getplants: diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 25ea7d150..4fb844e6a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,7 +127,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(flows flows.cpp) dfhack_plugin(follow follow.cpp) dfhack_plugin(forceequip forceequip.cpp) - dfhack_plugin(fortplan fortplan.cpp LINK_LIBRARIES buildingplan-lib lua) dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) dfhack_plugin(getplants getplants.cpp) dfhack_plugin(hotkeys hotkeys.cpp) diff --git a/plugins/fortplan.cpp b/plugins/fortplan.cpp deleted file mode 100644 index b346a49f3..000000000 --- a/plugins/fortplan.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Fortplan is deprecated. All functionality has moved to the DFHack quickfort - * script. Fortplan will be removed in a future DFHack release. - */ - -#include -#include - -#include "df/job_item.h" -#include "df/trap_type.h" -#include "df/world.h" - -#include "modules/Buildings.h" -#include "modules/Filesystem.h" -#include "modules/Gui.h" -#include "modules/Maps.h" -#include "modules/World.h" - -#include "LuaTools.h" -#include "PluginManager.h" - -#include "buildingplan-lib.h" -#include "uicommon.h" - -DFHACK_PLUGIN("fortplan"); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(world); - -#define PLUGIN_VERSION 0.15 - -using namespace std; -using namespace DFHack; - -command_result fortplan(color_ostream &out, vector & params); - -struct BuildingInfo { - std::string code; - df::building_type type; - df::furnace_type furnaceType; - df::workshop_type workshopType; - df::trap_type trapType; - std::string name; - bool variableSize; - int defaultHeight; - int defaultWidth; - bool hasCustomOptions; - - BuildingInfo(std::string theCode, df::building_type theType, std::string theName, int height, int width) { - code = theCode; - type = theType; - name = theName; - variableSize = false; - defaultHeight = height; - defaultWidth = width; - hasCustomOptions = false; - } - - bool allocate(coord32_t cursor) { - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - - CoreSuspendClaimer suspend; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 5) || - !Lua::PushModulePublic(out, L, "plugins.fortplan", - "construct_building_from_params")) - { - return false; - } - - Lua::Push(L, type); - Lua::Push(L, cursor.x); - Lua::Push(L, cursor.y); - Lua::Push(L, cursor.z); - - if (!Lua::SafeCall(out, L, 4, 1)) - return false; - - auto bld = Lua::GetDFObject(L, -1); - lua_pop(L, 1); - - if (!bld) - { - out.printerr("fortplan: construct_building_from_params() failed\n"); - return false; - } - - planner.addPlannedBuilding(bld); - - return true; - } -}; - -class MatchesCode -{ - std::string _code; - -public: - MatchesCode(const std::string &code) : _code(code) {} - - bool operator()(const BuildingInfo &check) const - { - return check.code == _code; - } -}; - -std::vector buildings; - -DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - commands.push_back(PluginCommand("fortplan","Lay out buildings from a Quickfort-style CSV file.",fortplan,false, - "Lay out buildings in your fortress based on a Quickfort-style CSV input file.\n" - "Usage: fortplan [filename]\n")); - - buildings.push_back(BuildingInfo("c",df::building_type::Chair,"Chair",1,1)); - buildings.push_back(BuildingInfo("b",df::building_type::Bed,"Bed",1,1)); - buildings.push_back(BuildingInfo("t",df::building_type::Table,"Table",1,1)); - buildings.push_back(BuildingInfo("n",df::building_type::Coffin,"Coffin",1,1)); - buildings.push_back(BuildingInfo("d",df::building_type::Door,"Door",1,1)); - buildings.push_back(BuildingInfo("x",df::building_type::Floodgate,"Floodgate",1,1)); - buildings.push_back(BuildingInfo("h",df::building_type::Box,"Box",1,1)); - buildings.push_back(BuildingInfo("r",df::building_type::Weaponrack,"Weapon Rack",1,1)); - buildings.push_back(BuildingInfo("a",df::building_type::Armorstand,"Armor Stand",1,1)); - buildings.push_back(BuildingInfo("f",df::building_type::Cabinet,"Cabinet",1,1)); - buildings.push_back(BuildingInfo("s",df::building_type::Statue,"Statue",1,1)); - buildings.push_back(BuildingInfo("y",df::building_type::WindowGlass,"Glass Window",1,1)); - buildings.push_back(BuildingInfo("m",df::building_type::AnimalTrap,"Animal Trap",1,1)); - buildings.push_back(BuildingInfo("v",df::building_type::Chain,"Chain",1,1)); - buildings.push_back(BuildingInfo("j",df::building_type::Cage,"Cage",1,1)); - buildings.push_back(BuildingInfo("H",df::building_type::Hatch,"Floor Hatch",1,1)); - buildings.push_back(BuildingInfo("W",df::building_type::GrateWall,"Wall Grate",1,1)); - buildings.push_back(BuildingInfo("G",df::building_type::GrateFloor,"Floor Grate",1,1)); - buildings.push_back(BuildingInfo("B",df::building_type::BarsVertical,"Vertical Bars",1,1)); - buildings.push_back(BuildingInfo("~b",df::building_type::BarsFloor,"Floor Bars",1,1)); - buildings.push_back(BuildingInfo("R",df::building_type::TractionBench,"Traction Bench",1,1)); - buildings.push_back(BuildingInfo("~s",df::building_type::Slab,"Slab",1,1)); - buildings.push_back(BuildingInfo("N",df::building_type::NestBox,"Nest Box",1,1)); - buildings.push_back(BuildingInfo("~h",df::building_type::Hive,"Hive",1,1)); - - return CR_OK; -} - -#define DAY_TICKS 1200 -DFhackCExport command_result plugin_onupdate(color_ostream &out) -{ - if (Maps::IsValid() && !World::ReadPauseState() && df::global::world->frame_counter % (DAY_TICKS/2) == 0) - { - planner.doCycle(); - } - - return CR_OK; -} - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if (!df::global::gps ) - return CR_FAILURE; - - if (enable != is_enabled) - { - planner.reset(); - - is_enabled = enable; - } - - return CR_OK; -} - -std::vector> tokenizeFile(std::string filename) { - std::ifstream infile(filename.c_str()); - std::vector> fileTokens(128, std::vector(128)); - std::vector>::size_type x, y; - if (!infile.good()) { - throw -1; - } - std::string line; - y = 0; - while (std::getline(infile, line)) { - x = 0; - if (strcmp(line.substr(0,1).c_str(),"#")==0) { - fileTokens[y++][0] = line; - continue; - } - int start = 0; - auto nextInd = line.find(','); - std::string curCell = line.substr(start,nextInd-start); - do { - fileTokens[y][x] = curCell; - start = nextInd+1; - nextInd = line.find(',',start); - curCell = line.substr(start,nextInd-start); - x++; - } while (nextInd != line.npos); - y++; - } - return fileTokens; -} - -command_result fortplan(color_ostream &out, vector & params) { - auto & con = out; - con.print("Fortplan is deprecated. Please move your blueprints to the" - " 'blueprints' folder (under your DF installation directory) and use" - " DFHack's quickfort command instead:\n quickfort run example.csv\n" - " Fortplan will be removed in a future DFHack release.\n"); - - std::vector> layout(128, std::vector(128)); - if (params.size()) { - coord32_t cursor; - coord32_t userCursor; - coord32_t startCursor; - if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z)) { - con.print("You must have an active in-game cursor.\n"); - return CR_FAILURE; - } - DFHack::Gui::getCursorCoords(startCursor.x, startCursor.y, startCursor.z); - userCursor = startCursor; - - std::string cwd = Filesystem::getcwd(); - std::string filename = cwd+"/"+params[0]; - con.print("Loading file '%s'...\n",filename.c_str()); - try { - layout = tokenizeFile(filename); - } catch (int) { - con.print("Could not open the file.\n"); - return CR_FAILURE; - } - if (!is_enabled) { - plugin_enable(out, true); - } - con.print("Loaded.\n"); - std::vector>::size_type x, y; - bool started = false; - for (y = 0; y < layout.size(); y++) { - x = 0; - auto hashBuild = layout[y][x].find("#build"); - if (hashBuild != layout[y][x].npos) { - auto startLoc = layout[y][x].find("start("); - if (startLoc != layout[y][x].npos) { - startLoc += 6; - auto nextDelimiter = layout[y][x].find(";",startLoc); - std::string startXStr = layout[y][x].substr(startLoc,nextDelimiter-startLoc); - int startXOffset = std::stoi(startXStr); - startLoc = nextDelimiter+1; - nextDelimiter = layout[y][x].find(";",startLoc); - std::string startYStr = layout[y][x].substr(startLoc,nextDelimiter-startLoc); - int startYOffset = std::stoi(startYStr); - startCursor.x -= startXOffset; - startCursor.y -= startYOffset; - DFHack::Gui::setCursorCoords(startCursor.x,startCursor.y,startCursor.z); - started = true; - - auto startEnd = layout[y][x].find(")",nextDelimiter); - - con.print("Starting at (%d,%d,%d) which is described as: %s\n",startCursor.x,startCursor.y,startCursor.z,layout[y][x].substr(nextDelimiter+1,startEnd-nextDelimiter).c_str()); - std::string desc = layout[y][x].substr(startEnd+1); - if (desc.size()>0) { - con.print("Description of this plan: %s\n",desc.c_str()); - } - continue; - } else { - con.print("No start location found for this block\n"); - } - } else if (!started) { - con.print("Not a build file: %s\n",layout[y][x].c_str()); - break; - } - - for (x = 0; x < layout[y].size(); x++) { - if (strcmp(layout[y][x].substr(0,1).c_str(),"#")==0) { - continue; - } - - if (strcmp(layout[y][x].c_str(),"`")!=0) { - auto dataIndex = layout[y][x].find("("); - std::string curCode; - std::vector curData; - if (dataIndex != layout[y][x].npos) { - curCode = layout[y][x].substr(0,dataIndex); - int dataStart = dataIndex+1; - auto nextDataStart = layout[y][x].find(",",dataStart); - while (nextDataStart!=layout[y][x].npos) { - std::string nextData = layout[y][x].substr(dataStart,nextDataStart); - if (strcmp(nextData.substr(nextData.size()-1,1).c_str(),")")==0) { - nextData = nextData.substr(0,nextData.size()-1); - } - curData.push_back(nextData); - dataStart = nextDataStart+1; - nextDataStart = layout[y][x].find(",",dataStart); - } - } else { - curCode = layout[y][x]; - } - //con.print("Found a cell with '%s' in it (layout[y][x] %d:%d-%d)\n",layout[y][x].c_str(),lineNum,start,nextInd); - auto buildingIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(curCode.c_str())); - - // = std::find(validInstructions.begin(), validInstructions.end(), layout[y][x]); - if(buildingIndex == buildings.end()) { - //con.print("That is not a valid code.\n"); - } else { - //con.print("I can build that!\n"); - BuildingInfo buildingInfo = *buildingIndex; - if (buildingInfo.variableSize || buildingInfo.defaultHeight > 1 || buildingInfo.defaultWidth > 1) { - //con.print("Found a building at (%d,%d) called %s, which has a size %dx%d or variable size\n",x,y,buildingInfo.name.c_str(),buildingInfo.defaultWidth, buildingInfo.defaultHeight); - // TODO: Make this function smarter, able to determine the exact shape - // and location of the building - // For now, we just assume that we are always looking at the top left - // corner of where it is located in the input file - coord32_t buildingSize; - if (!buildingInfo.variableSize) { - bool single = true; - bool block = true; - std::vector>::size_type checkX, checkY; - int yOffset = ((buildingInfo.defaultHeight-1)/2); - int xOffset = ((buildingInfo.defaultWidth-1)/2); - //con.print(" - Checking to see if it's a single, with %d<=x<%d and %d<=y<%d...\n",x-xOffset,x+xOffset,y-yOffset,y+yOffset); - // First, check to see if this is in the center of an empty square of its default size - for (checkY = y-yOffset; checkY <= (y+yOffset); checkY++) { - for (checkX = x-xOffset; checkX <= (x+xOffset); checkX++) { - if (checkX==x && checkY==y) { - continue; - } - auto checkDataIndex = layout[checkY][checkX].find("("); - std::string checkCode; - if (checkDataIndex != layout[checkY][checkX].npos) { - checkCode = layout[checkY][checkX].substr(0,checkDataIndex); - } else { - checkCode = layout[checkY][checkX]; - } - - con.print(" - Code at (%zu,%zu) is '%s': ",checkX,checkY,checkCode.c_str()); - auto checkIndex = std::find_if(buildings.begin(), buildings.end(), MatchesCode(checkCode.c_str())); - //if (checkIndex == buildings.end()) { - // con.print("this is not a valid code, so we keep going.\n"); - // continue; - //} - //if (curCode.compare(layout[checkY][checkX]) != 0) { - if (checkIndex != buildings.end()) { - //con.print("this is a building, so we break.\n"); - single = false; - break; - } else { - //con.print("this is not a building, so we keep going.\n"); - } - } - if (!single) { - //con.print("Not a single. Moving forward.\n"); - break; - } - } - if (!single) { - // If that's not the case, check to see if this is the top-left corner of - // a square of its default size - //con.print(" - It's not single; checking to see if it's a block...\n"); - for (checkY = y; checkY < (y+buildingInfo.defaultHeight); checkY++) { - for (checkX = x; checkX < (x+buildingInfo.defaultWidth); checkX++) { - if (checkX==x && checkY==y) { - continue; - } - auto checkDataIndex = layout[checkY][checkX].find("("); - std::string checkCode; - if (checkDataIndex != layout[checkY][checkX].npos) { - checkCode = layout[checkY][checkX].substr(0,checkDataIndex); - } else { - checkCode = layout[checkY][checkX]; - } - - //con.print(" - Code at (%d,%d) is '%s': ",checkX,checkY,checkCode.c_str()); - if (curCode.compare(checkCode) != 0) { - //con.print("this is not the same as '%s', so we break.\n",curCode.c_str()); - block = false; - break; - } else { - //con.print("this is the same as '%s', so we erase it and move on.\n",curCode.c_str()); - layout[checkY][checkX] = "``"; - } - } - if (!block) { - //con.print("Not a block. Moving forward.\n"); - break; - } - } - } - - if (single) { - //con.print("Placing a building with code '%s' centered at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); - coord32_t offsetCursor = cursor; - offsetCursor.x -= xOffset; - offsetCursor.y -= yOffset; - DFHack::Gui::setCursorCoords(offsetCursor.x, offsetCursor.y, offsetCursor.z); - if (!buildingInfo.allocate(offsetCursor)) { - con.print("*** There was an error placing building with code '%s' centered at (%zu,%zu).\n",curCode.c_str(),x,y); - } - DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); - } else if (block) { - //con.print("Placing a building with code '%s' with corner at (%d,%d) and default size %dx%d.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); - if (!buildingInfo.allocate(cursor)) { - con.print("*** There was an error placing building with code '%s' with corner at (%zu,%zu).\n",curCode.c_str(),x,y); - } - } else { - con.print("*** Found a code '%s' at (%zu,%zu) for a building with default size %dx%d with an invalid size designation.\n",curCode.c_str(),x,y,buildingInfo.defaultWidth,buildingInfo.defaultHeight); - } - } else { - //buildingSize = findBuildingExtent(layout, x, y, -1, -1, out); - //con.print(" - The building has variable size %dx%d\n",buildingSize.x, buildingSize.y); - //con.print(" - The building has a variable size, which is not yet handled.\n"); - } - } else { - //con.print("Building a(n) %s.\n",buildingInfo.name.c_str()); - if (!buildingInfo.allocate(cursor)) { - con.print("*** There was an error placing the %s at (%zu,%zu).\n",buildingInfo.name.c_str(),x,y); - } - } - } - } - cursor.x++; - DFHack::Gui::setCursorCoords(cursor.x, cursor.y, cursor.z); - } - - cursor.y++; - cursor.x = startCursor.x; - - } - DFHack::Gui::setCursorCoords(userCursor.x, userCursor.y, userCursor.z); - con.print("Done with file.\n"); - } else { - con.print("You must supply a filename to read.\n"); - } - - return CR_OK; -}