From e63ba6f87de66fe039880be20aa57351b79538de Mon Sep 17 00:00:00 2001 From: Robert Heinrich Date: Mon, 26 Mar 2012 04:15:42 +0200 Subject: [PATCH] added changelayer plugin --- plugins/CMakeLists.txt | 6 + plugins/changelayer/CMakeLists.txt | 32 +++ plugins/changelayer/changelayer.cpp | 390 ++++++++++++++++++++++++++++ 3 files changed, 428 insertions(+) create mode 100644 plugins/changelayer/CMakeLists.txt create mode 100644 plugins/changelayer/changelayer.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4559c8c73..b2f700e41 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,3 +105,9 @@ if(BUILD_SKELETON) add_subdirectory(skeleton) endif() +# this is the changelayer plugin. +OPTION(BUILD_CHANGELAYER "Build the changelayer plugin." ON) +if(BUILD_CHANGELAYER) + add_subdirectory(changelayer) +endif() + diff --git a/plugins/changelayer/CMakeLists.txt b/plugins/changelayer/CMakeLists.txt new file mode 100644 index 000000000..147f8986b --- /dev/null +++ b/plugins/changelayer/CMakeLists.txt @@ -0,0 +1,32 @@ +PROJECT (changelayer) +# A list of source files +SET(PROJECT_SRCS + changelayer.cpp +) +# A list of headers +SET(PROJECT_HDRS +) +SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) + +# mash them together (headers are marked as headers and nothing will try to compile them) +LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS}) + +# option to use a thread for no particular reason +OPTION(CHANGELAYER_THREAD "Use threads in the changelayer plugin." ON) +#linux +IF(UNIX) + add_definitions(-DLINUX_BUILD) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + ) +# windows +ELSE(UNIX) + SET(PROJECT_LIBS + # add any extra linux libs here + ${PROJECT_LIBS} + $(NOINHERIT) + ) +ENDIF(UNIX) +# this makes sure all the stuff is put in proper places and linked to dfhack +DFHACK_PLUGIN(changelayer ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/changelayer/changelayer.cpp b/plugins/changelayer/changelayer.cpp new file mode 100644 index 000000000..757e68d25 --- /dev/null +++ b/plugins/changelayer/changelayer.cpp @@ -0,0 +1,390 @@ +// changelayer plugin +// allows changing the material type of geological layers + +// some headers required for a plugin. Nothing special, just the basics. +#include "Core.h" +#include +#include +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include "modules/Maps.h" +#include "modules/Materials.h" +#include "modules/MapCache.h" +#include "modules/Gui.h" + +#include "TileTypes.h" + +#include "df/world_data.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" + +using namespace DFHack; +using namespace df::enums; +using namespace std; + +using std::vector; +using std::string; + +using df::global::world; +using df::global::cursor; + +const string changelayer_help = + " Allows to change the material of whole geology layers.\n" + " Can have impact on all surrounding regions, not only your embark!\n" + " By default changing stone to soil and vice versa is not allowed.\n" + " By default changes only the layer at the cursor position.\n" + " Note that one layer can stretch across lots of z levels.\n" + " By default changes only the geology which is linked to the biome under the\n" + " cursor. That geology might be linked to other biomes as well, though.\n" + " Mineral veins and gem clusters will stay on the map.\n" + " Use 'changevein' for them.\n\n" + " tl;dr: You will end up with changing quite big areas in one go.\n\n" + "Options (first parameter MUST be the material id):\n" + " all_biomes - Change layer for all biomes on your map.\n" + " Result may be undesirable since the same layer\n" + " can AND WILL be on different z-levels for different biomes.\n" + " Use the tool 'probe' to get an idea how layers and biomes\n" + " are distributed on your map.\n" + " all_layers - Change all layers on your map.\n" + " Candy mountain, anyone?\n" + " Will make your map quite boring, but tidy.\n" + " force - Allow changing stone to soil and vice versa.\n" + " !!THIS CAN HAVE WEIRD EFFECTS, USE WITH CARE!!\n" + " Note that soil will not be magically replaced with stone.\n" + " You will, however, get a stone floor after digging so it\n" + " will allow the floor to be engraved.\n" + " Note that stone will not be magically replaced with soil.\n" + " You will, however, get a soil floor after digging so it\n" + " could be helpful for creating farm plots on maps with no soil.\n" + " verbose - Give some more details about what is being changed.\n" + " trouble - Give some advice for known problems.\n" + "Example:\n" + " changelayer GRANITE\n" + " Convert layer at cursor position into granite.\n" + " changelayer SILTY_CLAY force\n" + " Convert layer at cursor position into clay even if it's stone.\n" + " changelayer MARBLE allbiomes alllayers\n" + " Convert all layers of all biomes into marble.\n"; + +const string changelayer_trouble = + "Known problems with changelayer:\n\n" + " Nothing happens, the material stays the old.\n" + " Try pausing/unpausing the game or moving the cursor a bit.\n\n" + " Weird stuff happening after using the 'force' option.\n" + " Change former stone layers back to stone, soil back to soil.\n" + " If in doubt, use the 'probe' tool to find tiles with soil walls\n" + " and stone layer type or the other way round.\n"; + + +command_result changelayer (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("changelayer"); + +// Mandatory init function. If you have some global state, create it here. +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + // Fill the command list with your commands. + commands.clear(); + commands.push_back(PluginCommand( + "changelayer", "Change a whole geology layer.", + changelayer, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + changelayer_help.c_str() + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +bool conversionAllowed(color_ostream &out, MaterialInfo mi, MaterialInfo ml, bool force); + +// no need to spam the "stone to soil" warning more than once +// in case multiple biomes and/or layers are going to be changed +static bool warned = false; + +command_result changelayer (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + string material; + bool force = false; + bool all_biomes = false; + bool all_layers = false; + bool verbose = false; + warned = false; + + for(size_t i = 0; i < parameters.size();i++) + { + if(parameters[i] == "help" || parameters[i] == "?") + { + out.print(changelayer_help.c_str()); + return CR_OK; + } + if(parameters[i] == "trouble") + { + out.print(changelayer_trouble.c_str()); + return CR_OK; + } + if(parameters[i] == "force") + force = true; + if(parameters[i] == "all_biomes") + all_biomes = true; + if(parameters[i] == "all_layers") + all_layers = true; + if(parameters[i] == "verbose") + verbose = true; + } + + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + + if (parameters.empty()) + { + out.printerr("You need to specify a material!\n"); + return CR_WRONG_USAGE; + } + + material = parameters[0]; + + MaterialInfo mat_new; + if (!mat_new.findInorganic(material)) + { + out.printerr("No such material!\n"); + return CR_FAILURE; + } + + // check if specified material is stone or gem or soil + if (mat_new.inorganic->material.flags.is_set(material_flags::IS_METAL) || + mat_new.inorganic->material.flags.is_set(material_flags::NO_STONE_STOCKPILE)) + { + out.printerr("Invalid material - you must select a type of stone or gem or soil.\n"); + return CR_FAILURE; + } + + + MapExtras::MapCache mc; + + int32_t regionX, regionY, regionZ; + Maps::getPosition(regionX,regionY,regionZ); + + int32_t cursorX, cursorY, cursorZ; + Gui::getCursorCoords(cursorX,cursorY,cursorZ); + if(cursorX == -30000) + { + out.printerr("No cursor; place cursor over tile.\n"); + return CR_FAILURE; + } + DFCoord cursor (cursorX,cursorY,cursorZ); + + uint32_t blockX = cursorX / 16; + uint32_t tileX = cursorX % 16; + uint32_t blockY = cursorY / 16; + uint32_t tileY = cursorY % 16; + + MapExtras::Block * b = mc.BlockAt(cursor/16); + if(!b && !b->valid) + { + out.printerr("No data.\n"); + return CR_OK; + } + mapblock40d & block = b->raw; + df::tile_designation &des = block.designation[tileX][tileY]; + + // get biome and geolayer at cursor position + uint32_t biome = des.bits.biome; + uint32_t layer = des.bits.geolayer_index; + if(verbose) + { + out << "biome: " << biome << endl + << "geolayer: " << layer << endl; + } + + + // there is no Maps::WriteGeology or whatever, and I didn't want to mess with the library and add it + // so I copied the stuff which reads the geology information and modified it to be able to change it + + // no need to touch the same geology more than once + // though it wouldn't matter much since there is not much data to be processed + vector v_geoprocessed; + v_geoprocessed.clear(); + + // iterate over 8 surrounding regions + local region + for (int i = eNorthWest; i < eBiomeCount; i++) + { + if(verbose) + out << "---Biome: " << i; + if(!all_biomes && i!=biome) + { + if(verbose) + out << "-skipping" << endl; + continue; + } + else + { + if(verbose) + out << "-checking" << endl; + } + + // check against worldmap boundaries, fix if needed + // regionX is in embark squares + // regionX/16 is in 16x16 embark square regions + // i provides -1 .. +1 offset from the current region + int bioRX = world->map.region_x / 16 + ((i % 3) - 1); + if (bioRX < 0) bioRX = 0; + if (bioRX >= world->world_data->world_width) bioRX = world->world_data->world_width - 1; + int bioRY = world->map.region_y / 16 + ((i / 3) - 1); + if (bioRY < 0) bioRY = 0; + if (bioRY >= world->world_data->world_height) bioRY = world->world_data->world_height - 1; + + // get index into geoblock vector + uint16_t geoindex = world->world_data->region_map[bioRX][bioRY].geo_index; + + if(verbose) + out << "geoindex: " << geoindex << endl; + + bool skip = false; + for(int g=0; g &geolayers = geo_biome->layers; + + // complain if layer is out of range + // geology has up to 16 layers currently, but size can be < 15 sometimes! + if(layer >= geolayers.size() || layer < 0) + { + if(verbose) + out << "layer out of range!"; + continue; + } + + // now let's actually write the new mat id to the layer(s) + if(all_layers) + { + for (size_t j = 0; j < geolayers.size(); j++) + { + MaterialInfo mat_old; + mat_old.decode(0, geolayers[j]->mat_index); + if(conversionAllowed(out, mat_new, mat_old, force)) + { + if(verbose) + out << "changing geolayer " << j + << " from " << mat_old.getToken() + << " to " << mat_new.getToken() + << endl; + geolayers[j]->mat_index = mat_new.index; + } + } + } + else + { + MaterialInfo mat_old; + mat_old.decode(0, geolayers[layer]->mat_index); + if(conversionAllowed(out, mat_new, mat_old, force)) + { + if(verbose) + out << "changing geolayer " << layer + << " from " << mat_old.getToken() + << " to " << mat_new.getToken() + << endl; + geolayers[layer]->mat_index = mat_new.index; + } + } + } + + out.print("Done.\n"); + + // Give control back to DF. + return CR_OK; +} + +// check if user tries to convert soil <-> stone +// throw some warning if he does +bool conversionAllowed(color_ostream &out, MaterialInfo mat_new, MaterialInfo mat_old, bool force) +{ + // check if current layer mat is stone or soil: + // by default don't allow to turn stone to soil and vice versa + bool unsafe = false; + bool allowed = true; + + // throw warning if user wants to change soil to stone or vice versa + // while it does work it might be unsafe and can have weird results + // you can't simply convert stone to soil, the rock walls will remain (same the other way round) + // the floor will turn into soil, though, so it might be useful for creating farm plots + // therefore it's not completely forbidden and can be enabled by the 'force' option + + if ( mat_new.inorganic->flags.is_set(inorganic_flags::SOIL_ANY) + && !mat_old.inorganic->flags.is_set(inorganic_flags::SOIL_ANY)) + { + if(!warned) + { + out << "Changing a stone layer into soil is probably not wise." << endl + << "The stone will remain and you get a soil floor after digging." << endl; + } + unsafe = true; + } + else if ( !mat_new.inorganic->flags.is_set(inorganic_flags::SOIL_ANY) + && mat_old.inorganic->flags.is_set(inorganic_flags::SOIL_ANY)) + { + if(!warned) + { + out << "Changing a soil layer into stone is probably not wise." << endl + << "The soil will remain and you only get a stone floor after digging." << endl; + } + unsafe = true; + } + + if(unsafe) + { + if(force) + { + out << "You've been warned, good luck." << endl; + allowed = true; + } + else + { + if(!warned) + { + out << "Use the option 'force' if you REALLY want to do that." << endl + << "Weird things can happen with your map, so save your game before trying!" << endl + << "Example: 'changelayer GRANITE force'" << endl; + warned = true; + } + allowed = false; + } + } + return allowed; +} \ No newline at end of file