Petr Mrázek 2012-03-23 23:06:48 +01:00
commit 2f76a52959
10 changed files with 1231 additions and 0 deletions

@ -379,6 +379,16 @@ For more information, refer to the command's internal help.
Spawning and deleting liquids can F up pathing data and
temperatures (creating heat traps). You've been warned.
liquidsgo
=========
Allows adding magma, water and obsidian to the game. It replaces the normal dfhack command line and can't be used from a hotkey. Settings will be remembered as long as dfhack runs. Intended for use in combination with the command liquidsgo-here (which can be bound to a hotkey).
For more information, refer to the command's internal help.
liquidsgo-here
==============
Run the liquid spawner with the current/last settings made in liquidsgo (if no settings in liquidsgo were made it paints a point of 7/7 magma by default).
Intended to be used as keybinding. Requires an active in-game cursor.
mode
====
This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results.
@ -526,11 +536,43 @@ Contains various tweaks for minor bugs (currently just one).
Options
-------
:tweak clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures.
:tweak clear-ghostly: Remove the ghostly status from the selected unit and mark it as dead. This allows getting rid of bugged ghosts which do not show up in the engraving slab menu at all, even after using clear-missing. It works, but is potentially very dangerous - so use with care. Probably (almost certainly) it does not have the same effects like a proper burial. You've been warned.
tubefill
========
Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).
cursecheck
==========
Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).
With an active in-game cursor only the selected tile will be observed. Without a cursor the whole map will be checked.
By default cursed creatures will be only counted in case you just want to find out if you have any of them running around in your fort.
By default dead and passive creatures (ghosts who were put to rest, killed vampires, ...) are ignored.
Undead skeletons, corpses, bodyparts and the like are all thrown into the curse category "zombie".
Anonymous zombies and resurrected body parts will show as "unnamed creature".
Options
-------
:detail: Print full name, date of birth, date of curse and some status info (some vampires might use fake identities in-game, though).
:nick: Set the type of curse as nickname (does not always show up in-game, some vamps don't like nicknames).
:all: Include dead and passive cursed creatures (can result in a quite long list after having FUN with necromancers).
:verbose: Print all curse tags (if you really want to know it all).
Examples:
---------
Check one single map tile if one of the creatures on it is cursed (in-game cursor required):
* cursecheck
Count all active cursed creatures who roam around on your map (no in-game cursor) without giving more details:
* cursecheck
Give detailed info about all cursed creatures including deceased ones (no in-game cursor):
* cursecheck detail all
Give a nickname all living/active cursed creatures on the map(no in-game cursor):
* cursecheck nick
.. note::
* If you do a full search (with the option "all") former ghosts will show up with the cursetype "unknown" because their ghostly flag is not set anymore. But if you happen to find a living/active creature with cursetype "unknown" please report that in the dfhack thread on the modding forum or per irc. This is likely to happen with mods which introduce new types of curses, for example.
vdig
====
Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

@ -108,3 +108,15 @@ OPTION(BUILD_SKELETON "Build the skeleton plugin." OFF)
if(BUILD_SKELETON)
add_subdirectory(skeleton)
endif()
# this is a plugin which helps detect cursed creatures (vampires, necromancers, werebeasts, ...)
OPTION(BUILD_CURSECHECK "Build the cursecheck plugin." ON)
if(BUILD_CURSECHECK)
add_subdirectory(cursecheck)
endif()
# alternative version of liquids which can be used non-interactively after configuring it
OPTION(BUILD_LIQUIDSGO "Build the liquidsgo plugin." ON)
if(BUILD_LIQUIDSGO)
add_subdirectory(liquidsgo)
endif()

@ -0,0 +1,33 @@
PROJECT (cursecheck)
# A list of source files
SET(PROJECT_SRCS
cursecheck.cpp
)
# A list of headers
SET(PROJECT_HDRS
cursecheck.h
)
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(SKELETON_THREAD "Use threads in the skeleton 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(cursecheck ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -0,0 +1,372 @@
// cursecheck plugin
//
// check single tile or whole map/world for cursed creatures by checking if a valid curse date (!=-1) is set
// if a cursor is active only the selected tile will be observed
// without cursor the whole map will be checked
// by default cursed creatures will be only counted
//
// the tool was intended to help finding vampires but it will also list necromancers, werebeasts and zombies
//
// Options:
// detail - print full name, date of birth, date of curse (vamp might use fake identity, though)
// show if the creature is active or dead, missing, ghostly (ghost vampires should not exist in 34.05)
// identify type of curse (works fine for vanilla ghosts, vamps, werebeasts, zombies and necromancers)
// nick - set nickname to 'CURSED' (does not always show up ingame, some vamps don't like nicknames)
// all - don't ignore dead and inactive creatures (former ghosts, dead necromancers, ...)
// verbose - acts like detail but also lists all curse tags (if you want to know it all).
#include <iostream>
#include <iomanip>
#include <climits>
#include <vector>
#include <string>
#include <sstream>
#include <ctime>
#include <cstdio>
using namespace std;
#include "Core.h"
#include "Console.h"
#include "PluginManager.h"
#include "modules/Units.h"
#include <modules/Translation.h>
#include "modules/Gui.h"
#include "MiscUtils.h"
#include "df/unit.h"
#include "df/unit_soul.h"
#include "df/historical_entity.h"
#include "df/historical_figure.h"
#include "df/historical_figure_info.h"
#include "df/assumed_identity.h"
#include "df/language_name.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "df/death_info.h"
using std::vector;
using std::string;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::cursor;
command_result cursecheck (color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("cursecheck");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("cursecheck",
"Checks for cursed creatures (vampires, necromancers, zombies, ...).",
cursecheck, false ));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
// code for setting nicknames is copypasta from rename.cpp
// will not work in all cases, some vampires don't like to get nicks
static void set_nickname(df::language_name *name, std::string nick)
{
if (!name->has_name)
{
*name = df::language_name();
name->language = 0;
name->has_name = true;
}
name->nickname = nick;
}
void setUnitNickname(df::unit *unit, const std::string &nick)
{
// There are >=3 copies of the name, and the one
// in the unit is not the authoritative one.
// This is the reason why military units often
// lose nicknames set from Dwarf Therapist.
set_nickname(&unit->name, nick);
if (unit->status.current_soul)
set_nickname(&unit->status.current_soul->name, nick);
df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id);
if (figure)
{
set_nickname(&figure->name, nick);
// v0.34.01: added the vampire's assumed identity
if (figure->info && figure->info->reputation)
{
auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity);
if (identity)
{
auto id_hfig = df::historical_figure::find(identity->histfig_id);
if (id_hfig)
{
// Even DF doesn't do this bit, because it's apparently
// only used for demons masquerading as gods, so you
// can't ever change their nickname in-game.
set_nickname(&id_hfig->name, nick);
}
else
set_nickname(&identity->name, nick);
}
}
}
}
void cursedump (color_ostream &out, df::unit * unit);
std::string determineCurse(df::unit * unit)
{
string cursetype = "unknown";
// ghosts: ghostly, duh
if(unit->flags3.bits.ghostly)
cursetype = "ghost";
// zombies: undead or hate life (according to ag), not bloodsuckers
if( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING)
&& !unit->curse.add_tags1.bits.BLOODSUCKER )
cursetype = "zombie";
// necromancers: alive, don't eat, don't drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
&& unit->curse.add_tags1.bits.NO_EAT
&& unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING
)
cursetype = "necromancer";
// werecreatures: alive, DO eat, DO drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
&& !unit->curse.add_tags1.bits.NO_EAT
&& !unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING )
cursetype = "werebeast";
// vampires: bloodsucker (obvious enough)
if(unit->curse.add_tags1.bits.BLOODSUCKER)
cursetype = "vampire";
return cursetype;
}
command_result cursecheck (color_ostream &out, vector <string> & parameters)
{
CoreSuspender suspend;
int32_t cursorX, cursorY, cursorZ;
Gui::getCursorCoords(cursorX,cursorY,cursorZ);
bool giveDetails = false;
bool giveNick = false;
bool ignoreDead = true;
bool verbose = false;
size_t cursecount = 0;
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out.print( " Search for cursed creatures (ghosts, vampires, necromancers, zombies, werebeasts).\n"
" With map cursor active only the current tile will be checked.\n"
" Without an in-game cursor the whole map/world will be scanned.\n"
" By default cursed creatures are only counted to make it more interesting.\n"
" By default dead and passive creatures (aka really dead) are ignored.\n"
"Options:\n"
" detail - show details (name and age shown ingame might differ)\n"
" nick - try to set cursetype as nickname (does not always work)\n"
" all - include dead and passive creatures\n"
" verbose - show all curse tags (if you really want to know it all)\n"
);
return CR_OK;
}
if(parameters[i] == "detail")
giveDetails = true;
if(parameters[i] == "nick")
giveNick = true;
if(parameters[i] == "all")
ignoreDead = false;
if(parameters[i] == "verbose")
{
// verbose makes no sense without enabling details
giveDetails = true;
verbose = true;
}
}
// check whole map if no cursor is active
bool checkWholeMap = false;
if(cursorX == -30000)
{
out.print("No cursor; will check all units on the map.\n");
checkWholeMap = true;
}
for(size_t i = 0; i < world->units.all.size(); i++)
{
df::unit * unit = world->units.all[i];
// don't spam all completely dead creatures if not explicitly wanted
if(unit->flags1.bits.dead && ignoreDead)
{
continue;
}
// bail out if we have a map cursor and creature is not at that specific position
if ( !checkWholeMap && (unit->pos.x != cursorX || unit->pos.y != cursorY || unit->pos.z != cursorZ) )
{
continue;
}
// non-cursed creatures have curse_year == -1
if(unit->relations.curse_year != -1)
{
cursecount++;
string cursetype = determineCurse(unit);
if(giveNick)
{
setUnitNickname(unit, cursetype); //"CURSED");
}
if(giveDetails)
{
if(unit->name.has_name)
{
string firstname = unit->name.first_name;
string restofname = Translation::TranslateName(&unit->name, false);
firstname[0] = toupper(firstname[0]);
// if creature has no nickname, restofname will already contain firstname
// no need for double output
if(restofname.compare(0, firstname.length(),firstname) != 0)
out.print("%s ", firstname.c_str());
out.print("%s ", restofname.c_str());
}
else
{
// happens with unnamed zombies and resurrected body parts
out.print("Unnamed creature ");
}
auto death = df::death_info::find(unit->counters.death_id);
bool missing = false;
if (death && !death->flags.bits.discovered)
{
missing = true;
}
out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n",
unit->relations.birth_year,
unit->relations.curse_year,
cursetype.c_str(),
// technically most cursed creatures are undead,
// therefore output 'active' if they are not completely dead
unit->flags1.bits.dead ? "deceased" : "active",
unit->flags3.bits.ghostly ? "-ghostly" : "",
missing ? "-missing" : ""
);
if (missing)
{
out.print("- You can use 'tweak clear-missing' to allow engraving a memorial easier.\n");
}
// dump all curse flags on demand
if (verbose)
{
cursedump(out, unit);
}
}
}
}
if (checkWholeMap)
out.print("Number of cursed creatures on map: %d \n", cursecount);
else
out.print("Number of cursed creatures on tile: %d \n", cursecount);
return CR_OK;
}
void cursedump (color_ostream &out, df::unit * unit)
{
out << "Curse flags: ";
if(unit->curse.add_tags1.bits.BLOODSUCKER)
out << "bloodsucker ";
if(unit->curse.add_tags1.bits.EXTRAVISION)
out << "extravision ";
if(unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
out << "opposed_to_life ";
if(unit->curse.add_tags1.bits.NOT_LIVING)
out << "not_living ";
if(unit->curse.add_tags1.bits.NOEXERT)
out << "noexpert ";
if(unit->curse.add_tags1.bits.NOPAIN)
out << "nopain ";
if(unit->curse.add_tags1.bits.NOBREATHE)
out << "nobreathe ";
if(unit->curse.add_tags1.bits.HAS_BLOOD)
out << "has_blood ";
if(unit->curse.add_tags1.bits.NOSTUN)
out << "nostun ";
if(unit->curse.add_tags1.bits.NONAUSEA)
out << "nonausea ";
if(unit->curse.add_tags1.bits.NO_DIZZINESS)
out << "no_dizziness ";
if(unit->curse.add_tags1.bits.TRANCES)
out << "trances ";
if(unit->curse.add_tags1.bits.NOEMOTION)
out << "noemotion ";
if(unit->curse.add_tags1.bits.PARALYZEIMMUNE)
out << "paralyzeimmune ";
if(unit->curse.add_tags1.bits.NOFEAR)
out << "nofear ";
if(unit->curse.add_tags1.bits.NO_EAT)
out << "no_eat ";
if(unit->curse.add_tags1.bits.NO_DRINK)
out << "no_drink ";
if(unit->curse.add_tags1.bits.MISCHIEVOUS)
out << "mischievous ";
if(unit->curse.add_tags1.bits.NO_PHYS_ATT_GAIN)
out << "no_phys_att_gain ";
if(unit->curse.add_tags1.bits.NO_PHYS_ATT_RUST)
out << "no_phys_att_rust ";
if(unit->curse.add_tags1.bits.NOTHOUGHT)
out << "nothought ";
if(unit->curse.add_tags1.bits.NO_THOUGHT_CENTER_FOR_MOVEMENT)
out << "no_thought_center_for_movement ";
if(unit->curse.add_tags1.bits.CAN_SPEAK)
out << "can_speak ";
if(unit->curse.add_tags1.bits.CAN_LEARN)
out << "can_learn ";
if(unit->curse.add_tags1.bits.CRAZED)
out << "crazed ";
if(unit->curse.add_tags1.bits.BLOODSUCKER)
out << "bloodsucker ";
if(unit->curse.add_tags1.bits.SUPERNATURAL)
out << "supernatural ";
if(unit->curse.add_tags2.bits.NO_AGING)
out << "no_aging ";
if(unit->curse.add_tags2.bits.STERILE)
out << "sterile ";
if(unit->curse.add_tags2.bits.FIT_FOR_ANIMATION)
out << "fit_for_animation ";
if(unit->curse.add_tags2.bits.FIT_FOR_RESURRECTION)
out << "fit_for_resurrection ";
out << endl << endl;
}

@ -605,5 +605,9 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
out << command << " : unknown command." << endl;
}
}
//cleanup
delete brush;
return CR_OK;
}

@ -0,0 +1,33 @@
PROJECT (liquidsgo)
# A list of source files
SET(PROJECT_SRCS
liquidsgo.cpp
)
# A list of headers
SET(PROJECT_HDRS
liquidsgo.h
)
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(SKELETON_THREAD "Use threads in the skeleton 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(liquidsgo ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

@ -0,0 +1,709 @@
// plugin liquidsgo
//
// This is a rewrite of the liquids module which can also be used non-interactively (hotkey).
// First the user sets the mode and other parameters with the interactive command liqiudsgo
// just like in the original liquids module.
// They are stored in statics to allow being used after the interactive session was closed.
// After defining an action the non-interactive command liquidsgo-here can be used to call the
// execute method without the necessity to go back to the console. This allows convenient painting
// of liquids and obsidian using the ingame cursor and a hotkey.
//
// Commands:
// liquidsgo - basically the original liquids with the map changing stuff moved to an execute method
// liquidsgo-here - runs the execute method with the last settings from liquidsgo
// (intended to be mapped to a hotkey)
// Options:
// ?, help - print some help
//
// TODO:
// - maybe allow all parameters be passed as command line options? tedious parsing but might be useful
// - grab the code from digcircle to get a circle brush - could be nice when painting with obsidian
// - maybe store the last parameters in a file to make them persistent after dfhack is closed?
#include <iostream>
#include <vector>
#include <stack>
#include <map>
#include <set>
#include <cstdlib>
#include <sstream>
using std::vector;
using std::string;
using std::endl;
using std::set;
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Vegetation.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
#include "TileTypes.h"
#include "modules/MapCache.h"
using namespace MapExtras;
using namespace DFHack;
using namespace df::enums;
typedef vector <df::coord> coord_vec;
CommandHistory liquidsgo_hist;
command_result df_liquidsgo (color_ostream &out, vector <string> & parameters);
command_result df_liquidsgo_here (color_ostream &out, vector <string> & parameters);
command_result df_liquidsgo_execute (color_ostream &out);
DFHACK_PLUGIN("liquidsgo");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
liquidsgo_hist.load("liquidsgo.history");
commands.clear();
commands.push_back(PluginCommand(
"liquidsgo", "Place magma, water or obsidian.",
df_liquidsgo, true)); // interactive, needs console for prompt
commands.push_back(PluginCommand(
"liquidsgo-here", "Use settings from liquidsgo at cursor position.",
df_liquidsgo_here, Gui::cursor_hotkey, // non-interactive, needs ingame cursor
" Identical to pressing enter in liquidsgo, intended for use as keybinding.\n"
" Can (but doesn't need to) be called while liquidsgo is running in the console."));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
liquidsgo_hist.save("liquidsgo.history");
return CR_OK;
}
class Brush
{
public:
virtual ~Brush(){};
virtual coord_vec points(MapCache & mc,DFHack::DFCoord start) = 0;
};
/**
* generic 3D rectangle brush. you can specify the dimensions of
* the rectangle and optionally which tile is its 'center'
*/
class RectangleBrush : public Brush
{
public:
RectangleBrush(int x, int y, int z = 1, int centerx = -1, int centery = -1, int centerz = -1)
{
if(centerx == -1)
cx_ = x/2;
else
cx_ = centerx;
if(centery == -1)
cy_ = y/2;
else
cy_ = centery;
if(centerz == -1)
cz_ = z/2;
else
cz_ = centerz;
x_ = x;
y_ = y;
z_ = z;
};
coord_vec points(MapCache & mc, DFHack::DFCoord start)
{
coord_vec v;
DFHack::DFCoord iterstart(start.x - cx_, start.y - cy_, start.z - cz_);
DFHack::DFCoord iter = iterstart;
for(int xi = 0; xi < x_; xi++)
{
for(int yi = 0; yi < y_; yi++)
{
for(int zi = 0; zi < z_; zi++)
{
if(mc.testCoord(iter))
v.push_back(iter);
iter.z++;
}
iter.z = iterstart.z;
iter.y++;
}
iter.y = iterstart.y;
iter.x ++;
}
return v;
};
~RectangleBrush(){};
private:
int x_, y_, z_;
int cx_, cy_, cz_;
};
/**
* stupid block brush, legacy. use when you want to apply something to a whole DF map block.
*/
class BlockBrush : public Brush
{
public:
BlockBrush(){};
~BlockBrush(){};
coord_vec points(MapCache & mc, DFHack::DFCoord start)
{
coord_vec v;
DFHack::DFCoord blockc = start / 16;
DFHack::DFCoord iterc = blockc * 16;
if( !mc.testCoord(start) )
return v;
auto starty = iterc.y;
for(int xi = 0; xi < 16; xi++)
{
for(int yi = 0; yi < 16; yi++)
{
v.push_back(iterc);
iterc.y++;
}
iterc.y = starty;
iterc.x ++;
}
return v;
};
};
/**
* Column from a position through open space tiles
* example: create a column of magma
*/
class ColumnBrush : public Brush
{
public:
ColumnBrush(){};
~ColumnBrush(){};
coord_vec points(MapCache & mc, DFHack::DFCoord start)
{
coord_vec v;
bool juststarted = true;
while (mc.testCoord(start))
{
df::tiletype tt = mc.tiletypeAt(start);
if(DFHack::LowPassable(tt) || juststarted && DFHack::HighPassable(tt))
{
v.push_back(start);
juststarted = false;
start.z++;
}
else break;
}
return v;
};
};
/**
* Flood-fill water tiles from cursor (for wclean)
* example: remove salt flag from a river
*/
class FloodBrush : public Brush
{
public:
FloodBrush(Core *c){c_ = c;};
~FloodBrush(){};
coord_vec points(MapCache & mc, DFHack::DFCoord start)
{
coord_vec v;
std::stack<DFCoord> to_flood;
to_flood.push(start);
std::set<DFCoord> seen;
while (!to_flood.empty()) {
DFCoord xy = to_flood.top();
to_flood.pop();
df::tile_designation des = mc.designationAt(xy);
if (seen.find(xy) == seen.end()
&& des.bits.flow_size
&& des.bits.liquid_type == tile_liquid::Water) {
v.push_back(xy);
seen.insert(xy);
maybeFlood(DFCoord(xy.x - 1, xy.y, xy.z), to_flood, mc);
maybeFlood(DFCoord(xy.x + 1, xy.y, xy.z), to_flood, mc);
maybeFlood(DFCoord(xy.x, xy.y - 1, xy.z), to_flood, mc);
maybeFlood(DFCoord(xy.x, xy.y + 1, xy.z), to_flood, mc);
df::tiletype tt = mc.tiletypeAt(xy);
if (LowPassable(tt))
{
maybeFlood(DFCoord(xy.x, xy.y, xy.z - 1), to_flood, mc);
}
if (HighPassable(tt))
{
maybeFlood(DFCoord(xy.x, xy.y, xy.z + 1), to_flood, mc);
}
}
}
return v;
}
private:
void maybeFlood(DFCoord c, std::stack<DFCoord> &to_flood, MapCache &mc) {
if (mc.testCoord(c)) {
to_flood.push(c);
}
}
Core *c_;
};
// static stuff to be remembered between sessions
static string brushname = "point";
static string mode="magma";
static string flowmode="f+";
static string setmode ="s.";
static unsigned int amount = 7;
static int width = 1, height = 1, z_levels = 1;
command_result df_liquidsgo (color_ostream &out_, vector <string> & parameters)
{
assert(out_.is_console());
Console &out = static_cast<Console&>(out_);
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out.print( "This tool allows placing magma, water and other similar things.\n"
"It is interactive and further help is available when you run it.\n"
"The settings will be remembered until dfhack is closed and you can call\n"
"'liquidsgo-here' (mapped to a hotkey) to paint liquids at the cursor position\n"
"without the need to go back to the dfhack console.\n");
return CR_OK;
}
}
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
return CR_FAILURE;
}
bool end = false;
out << "Welcome to the liquid spawner.\nType 'help' or '?' for a list of available commands, 'q' to quit.\nPress return after a command to confirm." << std::endl;
while(!end)
{
string command = "";
std::stringstream str;
str <<"[" << mode << ":" << brushname;
if (brushname == "range")
str << "(w" << width << ":h" << height << ":z" << z_levels << ")";
str << ":" << amount << ":" << flowmode << ":" << setmode << "]#";
if(out.lineedit(str.str(),command,liquidsgo_hist) == -1)
return CR_FAILURE;
if(command=="help" || command == "?")
{
out << "Modes:" << endl
<< "m - switch to magma" << endl
<< "w - switch to water" << endl
<< "o - make obsidian wall instead" << endl
<< "of - make obsidian floors" << endl
<< "rs - make a river source" << endl
<< "f - flow bits only" << endl
<< "wclean - remove salt and stagnant flags from tiles" << endl
<< "Set-Modes (only for magma/water):" << endl
<< "s+ - only add" << endl
<< "s. - set" << endl
<< "s- - only remove" << endl
<< "Properties (only for magma/water):" << endl
<< "f+ - make the spawned liquid flow" << endl
<< "f. - don't change flow state (read state in flow mode)" << endl
<< "f- - make the spawned liquid static" << endl
<< "0-7 - set liquid amount" << endl
<< "Brush:" << endl
<< "point - single tile [p]" << endl
<< "range - block with cursor at bottom north-west [r]" << endl
<< " (any place, any size)" << endl
<< "block - DF map block with cursor in it" << endl
<< " (regular spaced 16x16x1 blocks)" << endl
<< "column - Column from cursor, up through free space" << endl
<< "flood - Flood-fill water tiles from cursor" << endl
<< " (only makes sense with wclean)" << endl
<< "Other:" << endl
<< "q - quit" << endl
<< "help or ? - print this list of commands" << endl
<< "empty line - put liquid" << endl
<< endl
<< "Usage: point the DF cursor at a tile you want to modify" << endl
<< "and use the commands available :)" << endl;
out << endl << "Settings will be remembered until you quit DF. You can call liquidsgo-here to execute the last configured action. Useful in combination with keybindings." << endl;
}
else if(command == "m")
{
mode = "magma";
}
else if(command == "o")
{
mode = "obsidian";
}
else if(command == "of")
{
mode = "obsidian_floor";
}
else if(command == "w")
{
mode = "water";
}
else if(command == "f")
{
mode = "flowbits";
}
else if(command == "rs")
{
mode = "riversource";
}
else if(command == "wclean")
{
mode = "wclean";
}
else if(command == "point" || command == "p")
{
brushname = "point";
}
else if(command == "range" || command == "r")
{
std::stringstream str;
CommandHistory range_hist;
str << " :set range width<" << width << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
width = command == "" ? width : atoi (command.c_str());
if(width < 1) width = 1;
str.str("");
str << " :set range height<" << height << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
height = command == "" ? height : atoi (command.c_str());
if(height < 1) height = 1;
str.str("");
str << " :set range z-levels<" << z_levels << "># ";
out.lineedit(str.str(),command,range_hist);
range_hist.add(command);
z_levels = command == "" ? z_levels : atoi (command.c_str());
if(z_levels < 1) z_levels = 1;
if(width == 1 && height == 1 && z_levels == 1)
{
brushname = "point";
}
else
{
brushname = "range";
}
}
else if(command == "block")
{
brushname = "block";
}
else if(command == "column")
{
brushname = "column";
}
else if(command == "flood")
{
brushname = "flood";
}
else if(command == "q")
{
end = true;
}
else if(command == "f+")
{
flowmode = "f+";
}
else if(command == "f-")
{
flowmode = "f-";
}
else if(command == "f.")
{
flowmode = "f.";
}
else if(command == "s+")
{
setmode = "s+";
}
else if(command == "s-")
{
setmode = "s-";
}
else if(command == "s.")
{
setmode = "s.";
}
// blah blah, bad code, bite me.
else if(command == "0")
amount = 0;
else if(command == "1")
amount = 1;
else if(command == "2")
amount = 2;
else if(command == "3")
amount = 3;
else if(command == "4")
amount = 4;
else if(command == "5")
amount = 5;
else if(command == "6")
amount = 6;
else if(command == "7")
amount = 7;
else if(command.empty())
{
df_liquidsgo_execute(out);
}
else
{
out << command << " : unknown command." << endl;
}
}
return CR_OK;
}
command_result df_liquidsgo_here (color_ostream &out, vector <string> & parameters)
{
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
{
out << "This command is supposed to be mapped to a hotkey." << endl;
out << "It will use the current/last parameters set in liquidsgo." << endl;
return CR_OK;
}
}
out.print("Run liquidsgo-here with these parameters: ");
out << "[" << mode << ":" << brushname;
if (brushname == "range")
out << "(w" << width << ":h" << height << ":z" << z_levels << ")";
out << ":" << amount << ":" << flowmode << ":" << setmode << "]\n";
return df_liquidsgo_execute(out);
}
command_result df_liquidsgo_execute(color_ostream &out)
{
// create brush type depending on old parameters
Brush * brush;
if (brushname == "point")
{
brush = new RectangleBrush(1,1,1,0,0,0);
//width = 1;
//height = 1;
//z_levels = 1;
}
else if (brushname == "range")
{
brush = new RectangleBrush(width,height,z_levels,0,0,0);
}
else if(brushname == "block")
{
brush = new BlockBrush();
}
else if(brushname == "column")
{
brush = new ColumnBrush();
}
else if(brushname == "flood")
{
brush = new FloodBrush(&Core::getInstance());
}
else
{
// this should never happen!
out << "Old brushtype is invalid! Resetting to point brush.\n";
brushname = "point";
width = 1;
height = 1;
z_levels = 1;
brush = new RectangleBrush(width,height,z_levels,0,0,0);
}
CoreSuspender suspend;
do
{
if (!Maps::IsValid())
{
out << "Can't see any DF map loaded." << endl;
break;;
}
int32_t x,y,z;
if(!Gui::getCursorCoords(x,y,z))
{
out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl;
break;
}
out << "cursor coords: " << x << "/" << y << "/" << z << endl;
MapCache mcache;
DFHack::DFCoord cursor(x,y,z);
coord_vec all_tiles = brush->points(mcache,cursor);
out << "working..." << endl;
if(mode == "obsidian")
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
{
mcache.setTiletypeAt(*iter, tiletype::LavaWall);
mcache.setTemp1At(*iter,10015);
mcache.setTemp2At(*iter,10015);
df::tile_designation des = mcache.designationAt(*iter);
des.bits.flow_size = 0;
mcache.setDesignationAt(*iter, des);
iter ++;
}
}
if(mode == "obsidian_floor")
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
{
mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1));
iter ++;
}
}
else if(mode == "riversource")
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
{
mcache.setTiletypeAt(*iter, tiletype::RiverSource);
df::tile_designation a = mcache.designationAt(*iter);
a.bits.liquid_type = tile_liquid::Water;
a.bits.liquid_static = false;
a.bits.flow_size = 7;
mcache.setTemp1At(*iter,10015);
mcache.setTemp2At(*iter,10015);
mcache.setDesignationAt(*iter,a);
Block * b = mcache.BlockAt((*iter)/16);
DFHack::t_blockflags bf = b->BlockFlags();
bf.bits.liquid_1 = true;
bf.bits.liquid_2 = true;
b->setBlockFlags(bf);
iter++;
}
}
else if(mode=="wclean")
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
{
DFHack::DFCoord current = *iter;
df::tile_designation des = mcache.designationAt(current);
des.bits.water_salt = false;
des.bits.water_stagnant = false;
mcache.setDesignationAt(current,des);
iter++;
}
}
else if(mode== "magma" || mode== "water" || mode == "flowbits")
{
set <Block *> seen_blocks;
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
{
DFHack::DFCoord current = *iter; // current tile coord
DFHack::DFCoord curblock = current /16; // current block coord
// check if the block is actually there
if(!mcache.BlockAt(curblock))
{
iter ++;
continue;
}
df::tile_designation des = mcache.designationAt(current);
df::tiletype tt = mcache.tiletypeAt(current);
// don't put liquids into places where they don't belong...
if(!DFHack::FlowPassable(tt))
{
iter++;
continue;
}
if(mode != "flowbits")
{
if(setmode == "s.")
{
des.bits.flow_size = amount;
}
else if(setmode == "s+")
{
if(des.bits.flow_size < amount)
des.bits.flow_size = amount;
}
else if(setmode == "s-")
{
if (des.bits.flow_size > amount)
des.bits.flow_size = amount;
}
if(amount != 0 && mode == "magma")
{
des.bits.liquid_type = tile_liquid::Magma;
mcache.setTemp1At(current,12000);
mcache.setTemp2At(current,12000);
}
else if(amount != 0 && mode == "water")
{
des.bits.liquid_type = tile_liquid::Water;
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
}
else if(amount == 0 && (mode == "water" || mode == "magma"))
{
// reset temperature to sane default
mcache.setTemp1At(current,10015);
mcache.setTemp2At(current,10015);
}
mcache.setDesignationAt(current,des);
}
seen_blocks.insert(mcache.BlockAt(current / 16));
iter++;
}
set <Block *>::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end())
{
DFHack::t_blockflags bflags = (*biter)->BlockFlags();
if(flowmode == "f+")
{
bflags.bits.liquid_1 = true;
bflags.bits.liquid_2 = true;
(*biter)->setBlockFlags(bflags);
}
else if(flowmode == "f-")
{
bflags.bits.liquid_1 = false;
bflags.bits.liquid_2 = false;
(*biter)->setBlockFlags(bflags);
}
else
{
out << "flow bit 1 = " << bflags.bits.liquid_1 << endl;
out << "flow bit 2 = " << bflags.bits.liquid_2 << endl;
}
biter ++;
}
}
if(mcache.WriteAll())
out << "OK" << endl;
else
out << "Something failed horribly! RUN!" << endl;
} while (0);
// cleanup
delete brush;
return CR_OK;
}

@ -46,6 +46,11 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
"tweak", "Various tweaks for minor bugs.", tweak, false,
" tweak clear-missing\n"
" Remove the missing status from the selected unit.\n"
" tweak clear-ghostly\n"
" Remove the ghostly status from the selected unit.\n"
" Intended to fix the case where you can't engrave memorials for ghosts.\n"
" Note that this is very dirty and possibly dangerous!\n"
" Most probably does not have the positive effect of a proper burial.\n"
));
return CR_OK;
}
@ -83,6 +88,25 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
crime->flags.bits.discovered = true;
}
}
else if (cmd == "clear-ghostly")
{
df::unit *unit = getSelectedUnit(out);
if (!unit)
return CR_FAILURE;
// don't accidentally kill non-ghosts!
if (unit->flags3.bits.ghostly)
{
// remove ghostly, set to dead instead
unit->flags3.bits.ghostly = 0;
unit->flags1.bits.dead = 1;
}
else
{
out.print("That's not a ghost!\n");
return CR_FAILURE;
}
}
else return CR_WRONG_USAGE;
return CR_OK;