summaryrefslogtreecommitdiff
path: root/plugins/changelayer.cpp
diff options
context:
space:
mode:
authorRobert Heinrich2012-03-27 17:28:10 +0800
committerPetr Mrázek2012-03-28 17:00:33 +0800
commitcebdbff46dc5db9424a1ac4ba96738a2115f01fe (patch)
tree19dd8350608b1eae6c370681d051badedf4ccec0 /plugins/changelayer.cpp
parent515a4467e84943b28221b7830fd57dc996bef1c0 (diff)
downloaddfhack-cebdbff46dc5db9424a1ac4ba96738a2115f01fe.tar.gz
dfhack-cebdbff46dc5db9424a1ac4ba96738a2115f01fe.tar.bz2
dfhack-cebdbff46dc5db9424a1ac4ba96738a2115f01fe.tar.xz
minor updates in readme.rst; moved changelayer plugin to main plugin folder
Diffstat (limited to 'plugins/changelayer.cpp')
-rw-r--r--plugins/changelayer.cpp404
1 files changed, 404 insertions, 0 deletions
diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp
new file mode 100644
index 00000000..5f7d5cf0
--- /dev/null
+++ b/plugins/changelayer.cpp
@@ -0,0 +1,404 @@
+// 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 <Console.h>
+#include <Export.h>
+#include <PluginManager.h>
+
+// 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"
+ " Pause/unpause the game and/or move the cursor a bit. Then retry.\n"
+ " Try changing another layer, undo the changes and try again.\n"
+ " Try saving and loading the game.\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 <std::string> & 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 <PluginCommand> &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 <std::string> & 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
+ //
+ // a more elegant solution would probably look like this:
+ // 1) modify Maps::ReadGeology to accept and fill one more optional vector
+ // where the geolayer ids of the 9 biomes are stored
+ // 2) call ReadGeology here, modify the data in the vectors without having to do all that map stuff
+ // 3) write Maps::WriteGeology, pass the vectors, let it do it's work
+ // Step 1) is optional, but it would make implementing 3) easier.
+ // Otherwise that "check which geo_index is used by biome X" loop would need to be done again.
+
+ // 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<uint16_t> 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<v_geoprocessed.size(); g++)
+ {
+ if(v_geoprocessed.at(g)==geoindex)
+ {
+ if(verbose)
+ out << "already processed" << endl;
+ skip = true;
+ break;
+ }
+ }
+ if(skip)
+ continue;
+
+ v_geoprocessed.push_back(geoindex);
+
+ /// geology blocks have a vector of layer descriptors
+ // get the vector with pointer to layers
+ df::world_geo_biome *geo_biome = df::world_geo_biome::find(geoindex);
+ if (!geo_biome)
+ {
+ if(verbose)
+ out << "no geology found here." << endl;
+ continue;
+ }
+
+ vector <df::world_geo_layer*> &geolayers = geo_biome->layers;
+
+ // complain if layer is out of range
+ // geology has up to 16 layers currently, but can have less!
+ 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)
+ {
+ if(!warned)
+ {
+ 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;
+ }
+ allowed = false;
+ }
+ // avoid multiple warnings for the same stuff
+ warned = true;
+ }
+ return allowed;
+} \ No newline at end of file