-
+
This plugin makes reactions with names starting with SPATTER_ADD_
produce contaminants on the items instead of improvements. The produced
contaminants are immune to being washed away by water or destroyed by
diff --git a/Readme.rst b/Readme.rst
index 99a852cd6..fde2b5c04 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -1397,6 +1397,8 @@ Subcommands that persist until disabled or DF quit:
:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (bug 6481).
+:confirm-embark: Adds a prompt before embarking (on the "prepare carefully" screen).
+
fix-armory
----------
@@ -2053,6 +2055,35 @@ See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic
* Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
* The game will be suspended while you're using dfusion. Don't panic when it doesn't respond.
+embark-tools
+------------
+A collection of embark-related tools.
+
+Usage::
+
+ embark-tools enable/disable tool [tool]...
+
+Tools:
+
+* ``anywhere``: Allows embarking anywhere (including sites, mountain-only biomes, and oceans). Use with caution.
+* ``nano``: An implementation of nano embark - allows resizing below 2x2 when enabled.
+* ``sand``: Displays an indicator when sand is present in the currently-selected area, similar to the default clay/stone indicators.
+* ``sticky``: Maintains the selected local area while navigating the world map
+
+petcapRemover
+-------------
+
+This plugin allows you to remove or raise the pet population cap. In vanilla DF, pets will not reproduce unless the population is below 50 and the number of children of that species is below a certain percentage. This plugin allows removing the second restriction and removing or raising the first. Pets still require PET or PET_EXOTIC tags in order to reproduce. Type help petcapRemover for exact usage. In order to make population more stable and avoid sudden population booms as you go below the raised population cap, this plugin counts pregnancies toward the new population cap. It can still go over, but only in the case of multiple births.
+
+`petcapRemover`
+ cause pregnancies now and schedule the next check
+`petcapRemover every n`
+ set how often in ticks the plugin checks for possible pregnancies
+`petcapRemover cap n`
+ set the new cap to n. if n = 0, no cap
+`petcapRemover pregtime n`
+ sets the pregnancy duration to n ticks. natural pregnancies are 300000 ticks for the current race and 200000 for everyone else
+
misery
------
When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).
@@ -2523,15 +2554,19 @@ To activate, open the unit screen and press 'l'.
This tool implements a Dwarf Therapist-like interface within the game UI. The
far left column displays the unit's Happiness (color-coded based on its
-value), and the right half of the screen displays each dwarf's labor settings
-and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand
-Master, and U-Z for Legendary thru Legendary+5).
+value), Name, Profession/Squad, and the right half of the screen displays each
+dwarf's labor settings and skill levels (0-9 for Dabbling thru Professional, A-E for
+Great thru Grand Master, and U-Z for Legendary thru Legendary+5).
Cells with teal backgrounds denote skills not controlled by labors, e.g.
military and social skills.
.. image:: images/manipulator2.png
+Press ``t`` to toggle between Profession and Squad view.
+
+.. image:: images/manipulator3.png
+
Use the arrow keys or number pad to move the cursor around, holding Shift to
move 10 tiles at a time.
@@ -2543,7 +2578,7 @@ Press Enter to toggle the selected labor for the selected unit, or Shift+Enter
to toggle all labors within the selected category.
Press the ``+-`` keys to sort the unit list according to the currently selected
-skill/labor, and press the ``*/`` keys to sort the unit list by Name, Profession,
+skill/labor, and press the ``*/`` keys to sort the unit list by Name, Profession/Squad,
Happiness, or Arrival order (using Tab to select which sort method to use here).
With a unit selected, you can press the "v" key to view its properties (and
@@ -2553,12 +2588,12 @@ Manipulator and zoom to its position within your fortress.
The following mouse shortcuts are also available:
* Click on a column header to sort the unit list. Left-click to sort it in one
- direction (descending for happiness or labors/skills, ascending for name or
- profession) and right-click to sort it in the opposite direction.
+ direction (descending for happiness or labors/skills, ascending for name,
+ profession or squad) and right-click to sort it in the opposite direction.
* Left-click on a labor cell to toggle that labor. Right-click to move the
cursor onto that cell instead of toggling it.
-* Left-click on a unit's name or profession to view its properties.
-* Right-click on a unit's name or profession to zoom to it.
+* Left-click on a unit's name, profession or squad to view its properties.
+* Right-click on a unit's name, profession or squad to zoom to it.
Pressing ESC normally returns to the unit screen, but Shift-ESC would exit
directly to the main dwarf mode screen.
diff --git a/images/manipulator.png b/images/manipulator.png
index 1f452549f..59e7226a6 100644
Binary files a/images/manipulator.png and b/images/manipulator.png differ
diff --git a/images/manipulator2.png b/images/manipulator2.png
index 38c6c3cfb..e415200a6 100644
Binary files a/images/manipulator2.png and b/images/manipulator2.png differ
diff --git a/images/manipulator3.png b/images/manipulator3.png
new file mode 100644
index 000000000..0ee952306
Binary files /dev/null and b/images/manipulator3.png differ
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index 578ce9264..4121ff9e3 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -127,6 +127,7 @@ include/modules/Vermin.h
include/modules/World.h
include/modules/Graphic.h
include/modules/Once.h
+include/modules/Filesystem.h
)
SET( MODULE_SOURCES
@@ -152,6 +153,7 @@ modules/World.cpp
modules/Graphic.cpp
modules/Windows.cpp
modules/Once.cpp
+modules/Filesystem.cpp
)
IF(WIN32)
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index 3a374445f..e830189c5 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -52,6 +52,7 @@ distribution.
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/Random.h"
+#include "modules/Filesystem.h"
#include "LuaWrapper.h"
#include "LuaTools.h"
@@ -1923,6 +1924,20 @@ static const luaL_Reg dfhack_screen_funcs[] = {
{ NULL, NULL }
};
+/***** Filesystem module *****/
+
+static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = {
+ WRAPM(Filesystem, getcwd),
+ WRAPM(Filesystem, chdir),
+ WRAPM(Filesystem, mkdir),
+ WRAPM(Filesystem, rmdir),
+ WRAPM(Filesystem, exists),
+ WRAPM(Filesystem, isfile),
+ WRAPM(Filesystem, isdir),
+ {NULL, NULL}
+};
+
+
/***** Internal module *****/
static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
@@ -2227,6 +2242,64 @@ static int internal_getDir(lua_State *L)
}
return 1;
}
+
+static int internal_runCommand(lua_State *L)
+{
+ buffered_color_ostream out;
+ command_result res;
+ if (lua_gettop(L) == 0)
+ {
+ lua_pushstring(L, "");
+ }
+ int type_1 = lua_type(L, 1);
+ if (type_1 == LUA_TTABLE)
+ {
+ std::string command = "";
+ std::vector args;
+ lua_pushnil(L); // first key
+ while (lua_next(L, 1) != 0)
+ {
+ if (command == "")
+ command = lua_tostring(L, -1);
+ else
+ args.push_back(lua_tostring(L, -1));
+ lua_pop(L, 1); // remove value, leave key
+ }
+ CoreSuspender suspend;
+ res = Core::getInstance().runCommand(out, command, args);
+ }
+ else if (type_1 == LUA_TSTRING)
+ {
+ std::string command = lua_tostring(L, 1);
+ CoreSuspender suspend;
+ res = Core::getInstance().runCommand(out, command);
+ }
+ else
+ {
+ lua_pushnil(L);
+ lua_pushfstring(L, "Expected table, got %s", lua_typename(L, type_1));
+ return 2;
+ }
+ auto fragments = out.fragments();
+ lua_newtable(L);
+ lua_pushinteger(L, (int)res);
+ lua_setfield(L, -2, "status");
+ int i = 1;
+ for (auto iter = fragments.begin(); iter != fragments.end(); iter++, i++)
+ {
+ int color = iter->first;
+ std::string output = iter->second;
+ lua_createtable(L, 2, 0);
+ lua_pushinteger(L, color);
+ lua_rawseti(L, -2, 1);
+ lua_pushstring(L, output.c_str());
+ lua_rawseti(L, -2, 2);
+ lua_rawseti(L, -2, i);
+ }
+ lua_pushvalue(L, -1);
+ return 1;
+}
+
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
@@ -2240,6 +2313,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "memscan", internal_memscan },
{ "diffscan", internal_diffscan },
{ "getDir", internal_getDir },
+ { "runCommand", internal_runCommand },
{ NULL, NULL }
};
@@ -2265,5 +2339,6 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs);
OpenModule(state, "constructions", dfhack_constructions_module);
OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs);
+ OpenModule(state, "filesystem", dfhack_filesystem_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}
diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h
new file mode 100644
index 000000000..3798bbeb2
--- /dev/null
+++ b/library/include/modules/Filesystem.h
@@ -0,0 +1,194 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+/* Based on luafilesystem
+Copyright © 2003-2014 Kepler Project.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#pragma once
+#include "Export.h"
+
+#ifndef _WIN32
+#ifndef _AIX
+#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */
+#else
+#define _LARGE_FILES 1 /* AIX */
+#endif
+#endif
+
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef _WIN32
+#include
+#include
+#include
+#include
+#ifdef __BORLANDC__
+ #include
+#else
+ #include
+#endif
+#include
+#else
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#define LFS_VERSION "1.6.2"
+#define LFS_LIBNAME "lfs"
+
+#if LUA_VERSION_NUM < 502
+# define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l))
+#endif
+
+/* Define 'strerror' for systems that do not implement it */
+#ifdef NO_STRERROR
+#define strerror(_) "System unable to describe the error"
+#endif
+
+/* Define 'getcwd' for systems that do not implement it */
+#ifdef NO_GETCWD
+#define getcwd(p,s) NULL
+#define getcwd_error "Function 'getcwd' not provided by system"
+#else
+#define getcwd_error strerror(errno)
+ #ifdef _WIN32
+ /* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */
+ #define LFS_MAXPATHLEN MAX_PATH
+ #else
+ /* For MAXPATHLEN: */
+ #include
+ #define LFS_MAXPATHLEN MAXPATHLEN
+ #endif
+#endif
+
+typedef struct dir_data {
+ int closed;
+#ifdef _WIN32
+ intptr_t hFile;
+ char pattern[MAX_PATH+1];
+#else
+ DIR *dir;
+#endif
+} dir_data;
+
+#ifdef _WIN32
+ #ifdef __BORLANDC__
+ #define lfs_setmode(L,file,m) ((void)L, setmode(_fileno(file), m))
+ #define STAT_STRUCT struct stati64
+ #else
+ #define lfs_setmode(L,file,m) ((void)L, _setmode(_fileno(file), m))
+ #define STAT_STRUCT struct _stati64
+ #endif
+#define STAT_FUNC _stati64
+#define LSTAT_FUNC STAT_FUNC
+#else
+#define _O_TEXT 0
+#define _O_BINARY 0
+#define lfs_setmode(L,file,m) ((void)L, (void)file, (void)m, 0)
+#define STAT_STRUCT struct stat
+#define STAT_FUNC stat
+#define LSTAT_FUNC lstat
+#endif
+
+#ifdef _WIN32
+ #ifndef S_ISDIR
+ #define S_ISDIR(mode) (mode&_S_IFDIR)
+ #endif
+ #ifndef S_ISREG
+ #define S_ISREG(mode) (mode&_S_IFREG)
+ #endif
+ #ifndef S_ISLNK
+ #define S_ISLNK(mode) (0)
+ #endif
+ #ifndef S_ISSOCK
+ #define S_ISSOCK(mode) (0)
+ #endif
+ #ifndef S_ISFIFO
+ #define S_ISFIFO(mode) (0)
+ #endif
+ #ifndef S_ISCHR
+ #define S_ISCHR(mode) (mode&_S_IFCHR)
+ #endif
+ #ifndef S_ISBLK
+ #define S_ISBLK(mode) (0)
+ #endif
+#endif
+
+enum _filetype {
+ FILETYPE_NONE = -2,
+ FILETYPE_UNKNOWN = -1,
+ FILETYPE_FILE = 1,
+ FILETYPE_DIRECTORY,
+ FILETYPE_LINK,
+ FILETYPE_SOCKET,
+ FILETYPE_NAMEDPIPE,
+ FILETYPE_CHAR_DEVICE,
+ FILETYPE_BLOCK_DEVICE
+};
+
+namespace DFHack {
+ namespace Filesystem {
+ DFHACK_EXPORT bool chdir (std::string path);
+ DFHACK_EXPORT std::string getcwd ();
+ DFHACK_EXPORT bool mkdir (std::string path);
+ DFHACK_EXPORT bool rmdir (std::string path);
+ DFHACK_EXPORT bool stat (std::string path, STAT_STRUCT &info);
+ DFHACK_EXPORT bool exists (std::string path);
+ DFHACK_EXPORT _filetype filetype (std::string path);
+ DFHACK_EXPORT bool isfile (std::string path);
+ DFHACK_EXPORT bool isdir (std::string path);
+ }
+}
diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h
index b51183d6b..5380d82aa 100644
--- a/library/include/modules/Units.h
+++ b/library/include/modules/Units.h
@@ -256,6 +256,8 @@ DFHACK_EXPORT std::string getCasteProfessionName(int race, int caste, df::profes
DFHACK_EXPORT int8_t getProfessionColor(df::unit *unit, bool ignore_noble = false);
DFHACK_EXPORT int8_t getCasteProfessionColor(int race, int caste, df::profession pid);
+
+DFHACK_EXPORT std::string getSquadName(df::unit *unit);
}
}
#endif
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index 77c62311a..3cc98e882 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -10,6 +10,14 @@ local dfhack = dfhack
local base_env = dfhack.BASE_G
local _ENV = base_env
+CR_LINK_FAILURE = -3
+CR_NEEDS_CONSOLE = -2
+CR_NOT_IMPLEMENTED = -1
+CR_OK = 0
+CR_FAILURE = 1
+CR_WRONG_USAGE = 2
+CR_NOT_FOUND = 3
+
-- Console color constants
COLOR_RESET = -1
@@ -256,7 +264,8 @@ function dfhack.interpreter(prompt,hfile,env)
print("Shortcuts:\n"..
" '= foo' => '_1,_2,... = foo'\n"..
" '! foo' => 'print(foo)'\n"..
- "Both save the first result as '_'.")
+ " '~ foo' => 'printall(foo)'\n"..
+ "All of these save the first result as '_'.")
print_banner = false
end
@@ -357,6 +366,31 @@ function dfhack.run_script(name,...)
return f(...)
end
+function dfhack.run_command(...)
+ args = {...}
+ if type(args[1]) == 'table' then
+ command = args[1]
+ elseif #args > 1 and type(args[2]) == 'table' then
+ -- {args[1]} + args[2]
+ command = args[2]
+ table.insert(command, 1, args[1])
+ elseif #args == 1 and type(args[1]) == 'string' then
+ command = args[1]
+ elseif #args > 1 and type(args[1]) == 'string' then
+ command = args
+ else
+ error('Invalid arguments')
+ end
+ result = internal.runCommand(command)
+ output = ""
+ for i, f in pairs(result) do
+ if type(f) == 'table' then
+ output = output .. f[2]
+ end
+ end
+ return output, result.status
+end
+
-- Per-save init file
function dfhack.getSavePath()
diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp
new file mode 100644
index 000000000..be1668bcc
--- /dev/null
+++ b/library/modules/Filesystem.cpp
@@ -0,0 +1,145 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+/* Based on luafilesystem
+Copyright © 2003-2014 Kepler Project.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include
+
+#include "modules/Filesystem.h"
+
+bool DFHack::Filesystem::chdir (std::string path)
+{
+ return !(bool)::chdir(path.c_str());
+}
+
+std::string DFHack::Filesystem::getcwd ()
+{
+ char *path;
+ char buf[LFS_MAXPATHLEN];
+ std::string result = "";
+#ifdef _WIN32
+ if ((path = ::_getcwd(buf, LFS_MAXPATHLEN)) != NULL)
+#else
+ if ((path = ::getcwd(buf, LFS_MAXPATHLEN)) != NULL)
+#endif
+ result = buf;
+ return result;
+}
+
+bool DFHack::Filesystem::mkdir (std::string path)
+{
+ int fail;
+#ifdef _WIN32
+ fail = ::_mkdir(path.c_str());
+#else
+ fail = ::mkdir(path.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
+ S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
+#endif
+ return !(bool)fail;
+}
+
+bool DFHack::Filesystem::rmdir (std::string path)
+{
+ int fail;
+#ifdef _WIN32
+ fail = ::_rmdir(path.c_str());
+#else
+ fail = ::rmdir(path.c_str());
+#endif
+ return !(bool)fail;
+}
+
+#ifdef _WIN32
+_filetype mode2type (unsigned short mode) {
+#else
+_filetype mode2type (mode_t mode) {
+#endif
+ if (S_ISREG(mode))
+ return FILETYPE_FILE;
+ else if (S_ISDIR(mode))
+ return FILETYPE_DIRECTORY;
+ else if (S_ISLNK(mode))
+ return FILETYPE_LINK;
+ else if (S_ISSOCK(mode))
+ return FILETYPE_SOCKET;
+ else if (S_ISFIFO(mode))
+ return FILETYPE_NAMEDPIPE;
+ else if (S_ISCHR(mode))
+ return FILETYPE_CHAR_DEVICE;
+ else if (S_ISBLK(mode))
+ return FILETYPE_BLOCK_DEVICE;
+ else
+ return FILETYPE_UNKNOWN;
+}
+
+bool DFHack::Filesystem::stat (std::string path, STAT_STRUCT &info)
+{
+ return !(bool)(STAT_FUNC(path.c_str(), &info));
+}
+
+bool DFHack::Filesystem::exists (std::string path)
+{
+ STAT_STRUCT info;
+ return (bool)DFHack::Filesystem::stat(path.c_str(), info);
+}
+
+#include
+_filetype DFHack::Filesystem::filetype (std::string path)
+{
+ STAT_STRUCT info;
+ DFHack::Filesystem::stat(path, info);
+ std::cout << info.st_mode << std::endl;
+ return mode2type(info.st_mode);
+}
+
+bool DFHack::Filesystem::isfile (std::string path)
+{
+ return DFHack::Filesystem::filetype(path) == FILETYPE_FILE;
+}
+
+bool DFHack::Filesystem::isdir (std::string path)
+{
+ return DFHack::Filesystem::filetype(path) == FILETYPE_DIRECTORY;
+}
diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp
index 20c536cd1..af4384b79 100644
--- a/library/modules/Units.cpp
+++ b/library/modules/Units.cpp
@@ -69,6 +69,7 @@ using namespace std;
#include "df/unit_misc_trait.h"
#include "df/unit_skill.h"
#include "df/curse_attr_change.h"
+#include "df/squad.h"
using namespace DFHack;
using namespace df::enums;
@@ -1552,3 +1553,15 @@ int8_t DFHack::Units::getCasteProfessionColor(int race, int casteid, df::profess
// default to dwarven peasant color
return 3;
}
+
+std::string DFHack::Units::getSquadName(df::unit *unit)
+{
+ if (unit->military.squad_id == -1)
+ return "";
+ df::squad *squad = df::squad::find(unit->military.squad_id);
+ if (!squad)
+ return "";
+ if (squad->alias.size() > 0)
+ return squad->alias;
+ return Translation::TranslateName(&squad->name, true);
+}
diff --git a/package/darwin/dfhack b/package/darwin/dfhack
index 40fc083ab..55aa81a98 100755
--- a/package/darwin/dfhack
+++ b/package/darwin/dfhack
@@ -12,6 +12,6 @@ fi
old_tty_settings=$(stty -g)
cd "${PWD}"
-DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe
+DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@"
stty "$old_tty_settings"
echo ""
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 84aa36199..8436f2fe1 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -167,6 +167,8 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(command-prompt command-prompt.cpp)
DFHACK_PLUGIN(remotefortressreader remotefortressreader.cpp PROTOBUFS RemoteFortressReader)
DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua)
+ DFHACK_PLUGIN(embark-tools embark-tools.cpp)
+ DFHACK_PLUGIN(petcapRemover petcapRemover.cpp)
endif()
# this is the skeleton plugin. If you want to make your own, make a copy and then change it
diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp
index 3cdd83ab3..f9a3f80bf 100644
--- a/plugins/command-prompt.cpp
+++ b/plugins/command-prompt.cpp
@@ -12,11 +12,13 @@
#include
#include
#include
+#include
#include "df/interface_key.h"
#include "df/ui.h"
#include "df/graphic.h"
#include "df/enabler.h"
+
using namespace DFHack;
using namespace df::enums;
@@ -24,6 +26,8 @@ using df::global::ui;
using df::global::gps;
using df::global::enabler;
+std::vector command_history;
+
class viewscreen_commandpromptst;
class prompt_ostream:public buffered_color_ostream
{
@@ -44,12 +48,25 @@ public:
void render();
void help() { }
+ int8_t movies_okay() { return 0; }
std::string getFocusString() { return "commandprompt"; }
- viewscreen_commandpromptst(std::string entry):is_response(false),entry(entry)
+ viewscreen_commandpromptst(std::string entry):is_response(false)
{
show_fps=df::global::gps->display_frames;
df::global::gps->display_frames=0;
+ cursor_pos = 0;
+ frame = 0;
+ history_idx = command_history.size();
+ if (history_idx > 0)
+ {
+ if (command_history[history_idx - 1] == "")
+ {
+ command_history.pop_back();
+ history_idx--;
+ }
+ }
+ command_history.push_back(entry);
}
~viewscreen_commandpromptst()
{
@@ -65,12 +82,50 @@ public:
responses.push_back(std::make_pair(v, part + '\n'));
}
}
+ std::string get_entry()
+ {
+ return command_history[history_idx];
+ }
+ void set_entry(std::string entry)
+ {
+ command_history[history_idx] = entry;
+ }
+ void back_word()
+ {
+ std::string entry = get_entry();
+ if (cursor_pos == 0)
+ return;
+ cursor_pos--;
+ while (cursor_pos > 0 && !isalnum(entry[cursor_pos]))
+ cursor_pos--;
+ while (cursor_pos > 0 && isalnum(entry[cursor_pos]))
+ cursor_pos--;
+ if (!isalnum(entry[cursor_pos]) && cursor_pos != 0)
+ cursor_pos++;
+ }
+ void forward_word()
+ {
+ std::string entry = get_entry();
+ int len = entry.size();
+ if (cursor_pos == len)
+ return;
+ cursor_pos++;
+ while (cursor_pos <= len && !isalnum(entry[cursor_pos]))
+ cursor_pos++;
+ while (cursor_pos <= len && isalnum(entry[cursor_pos]))
+ cursor_pos++;
+ if (cursor_pos > len)
+ cursor_pos = len;
+ }
+
protected:
std::list > responses;
+ int cursor_pos;
+ int history_idx;
bool is_response;
bool show_fps;
+ int frame;
void submit();
- std::string entry;
};
void prompt_ostream::flush_proxy()
{
@@ -82,6 +137,9 @@ void prompt_ostream::flush_proxy()
}
void viewscreen_commandpromptst::render()
{
+ ++frame;
+ if (frame >= df::global::enabler->gfps)
+ frame = 0;
if (Screen::isDismissed(this))
return;
@@ -101,14 +159,25 @@ void viewscreen_commandpromptst::render()
}
else
{
+ std::string entry = get_entry();
Screen::fillRect(Screen::Pen(' ', 7, 0),0,0,dim.x,0);
Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0,"[DFHack]#");
- if(entry.size()gfps / 2) ? "_" : " ";
+ if(cursor_pos < (dim.x - 10))
+ {
Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry);
+ if (entry.size() > dim.x - 10)
+ Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0);
+ if (cursor != " ")
+ Screen::paintString(Screen::Pen(' ', 10, 0), 10 + cursor_pos, 0, cursor);
+ }
else
{
- Screen::paintTile(Screen::Pen('>', 7, 0), 9, 0);
- Screen::paintString(Screen::Pen(' ', 7, 0), 10, 0, entry.substr(entry.size()-dim.x));
+ size_t start = cursor_pos - dim.x + 10 + 1;
+ Screen::paintTile(Screen::Pen('\033', 7, 0), 9, 0);
+ Screen::paintString(Screen::Pen(' ', 7, 0), 10, 0, entry.substr(start));
+ if (cursor != " ")
+ Screen::paintString(Screen::Pen(' ', 10, 0), dim.x - 1, 0, cursor);
}
}
}
@@ -120,9 +189,8 @@ void viewscreen_commandpromptst::submit()
Screen::dismiss(this);
return;
}
- //color_ostream_proxy out(Core::getInstance().getConsole());
prompt_ostream out(this);
- Core::getInstance().runCommand(out, entry);
+ Core::getInstance().runCommand(out, get_entry());
if(out.empty() && responses.empty())
Screen::dismiss(this);
else
@@ -132,7 +200,8 @@ void viewscreen_commandpromptst::submit()
}
void viewscreen_commandpromptst::feed(std::set *events)
{
-
+ int old_pos = cursor_pos;
+ std::string entry = get_entry();
bool leave_all = events->count(interface_key::LEAVESCREEN_ALL);
if (leave_all || events->count(interface_key::LEAVESCREEN))
{
@@ -144,30 +213,91 @@ void viewscreen_commandpromptst::feed(std::set *events)
parent->feed(events);
events->clear();
}
+ //if (command_history.size() && !entry.size())
+ // command_history.pop_back();
return;
}
- if(events->count(interface_key::SELECT))
+ if (events->count(interface_key::SELECT))
{
submit();
return;
}
- if(is_response)
+ if (is_response)
return;
for (auto it = events->begin(); it != events->end(); ++it)
{
auto key = *it;
if (key==interface_key::STRING_A000) //delete?
{
- if(entry.size())
- entry.resize(entry.size()-1);
+ if(entry.size() && cursor_pos > 0)
+ {
+ entry.erase(cursor_pos - 1, 1);
+ cursor_pos--;
+ }
+ if(cursor_pos > entry.size())
+ cursor_pos = entry.size();
continue;
}
if (key >= interface_key::STRING_A000 &&
key <= interface_key::STRING_A255)
{
- entry.push_back(char(key - interface_key::STRING_A000));
+ entry.insert(cursor_pos, 1, char(key - interface_key::STRING_A000));
+ cursor_pos++;
+ set_entry(entry);
+ return;
+ }
+ }
+ // Prevent number keys from moving cursor
+ if(events->count(interface_key::CURSOR_RIGHT))
+ {
+ cursor_pos++;
+ if (cursor_pos > entry.size())
+ cursor_pos = entry.size();
+ }
+ else if(events->count(interface_key::CURSOR_LEFT))
+ {
+ cursor_pos--;
+ if (cursor_pos < 0) cursor_pos = 0;
+ }
+ else if(events->count(interface_key::CURSOR_RIGHT_FAST))
+ {
+ forward_word();
+ }
+ else if(events->count(interface_key::CURSOR_LEFT_FAST))
+ {
+ back_word();
+ }
+ else if(events->count(interface_key::CUSTOM_CTRL_A))
+ {
+ cursor_pos = 0;
+ }
+ else if(events->count(interface_key::CUSTOM_CTRL_E))
+ {
+ cursor_pos = entry.size();
+ }
+ else if(events->count(interface_key::CURSOR_UP))
+ {
+ history_idx--;
+ if (history_idx < 0)
+ history_idx = 0;
+ entry = get_entry();
+ cursor_pos = entry.size();
+ }
+ else if(events->count(interface_key::CURSOR_DOWN))
+ {
+ if (history_idx < command_history.size() - 1)
+ {
+ history_idx++;
+ if (history_idx >= command_history.size())
+ history_idx = command_history.size() - 1;
+ entry = get_entry();
+ cursor_pos = entry.size();
}
}
+ set_entry(entry);
+ if (old_pos != cursor_pos)
+ frame = 0;
+
}
DFHACK_PLUGIN("command-prompt");
command_result show_prompt(color_ostream &out, std::vector & parameters)
diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp
new file mode 100644
index 000000000..2953f83f3
--- /dev/null
+++ b/plugins/embark-tools.cpp
@@ -0,0 +1,513 @@
+#include "Console.h"
+#include "Core.h"
+#include "DataDefs.h"
+#include "Export.h"
+#include "PluginManager.h"
+
+#include "modules/Screen.h"
+#include "modules/Gui.h"
+#include
+#include
+
+#include
+#include "ColorText.h"
+#include "df/viewscreen_choose_start_sitest.h"
+#include "df/interface_key.h"
+
+using namespace DFHack;
+
+struct EmbarkTool
+{
+ std::string id;
+ std::string name;
+ std::string desc;
+ bool enabled;
+ df::interface_key toggle_key;
+};
+
+EmbarkTool embark_tools[] = {
+ {"anywhere", "Embark anywhere", "Allows embarking anywhere on the world map",
+ false, df::interface_key::CUSTOM_A},
+ {"nano", "Nano embark", "Allows the embark size to be decreased below 2x2",
+ false, df::interface_key::CUSTOM_N},
+ {"sand", "Sand indicator", "Displays an indicator when sand is present on the given embark site",
+ false, df::interface_key::CUSTOM_S},
+ {"sticky", "Stable position", "Maintains the selected local area while navigating the world map",
+ false, df::interface_key::CUSTOM_P},
+};
+#define NUM_TOOLS int(sizeof(embark_tools) / sizeof(EmbarkTool))
+
+command_result embark_tools_cmd (color_ostream &out, std::vector & parameters);
+
+void OutputString (int8_t color, int &x, int y, const std::string &text);
+
+bool tool_exists (std::string tool_name);
+bool tool_enabled (std::string tool_name);
+bool tool_enable (std::string tool_name, bool enable_state);
+void tool_update (std::string tool_name);
+
+class embark_tools_settings : public dfhack_viewscreen
+{
+public:
+ embark_tools_settings () { };
+ ~embark_tools_settings () { };
+ void help () { };
+ std::string getFocusString () { return "embark-tools/options"; };
+ void render ()
+ {
+ int x;
+ auto dim = Screen::getWindowSize();
+ int width = 50,
+ height = 4 + 1 + NUM_TOOLS, // Padding + lower row
+ min_x = (dim.x - width) / 2,
+ max_x = (dim.x + width) / 2,
+ min_y = (dim.y - height) / 2,
+ max_y = min_y + height;
+ Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_DARKGREY), min_x, min_y, max_x, max_y);
+ Screen::fillRect(Screen::Pen(' ', COLOR_BLACK, COLOR_BLACK), min_x + 1, min_y + 1, max_x - 1, max_y - 1);
+ x = min_x + 2;
+ OutputString(COLOR_LIGHTRED, x, max_y - 2, Screen::getKeyDisplay(df::interface_key::SELECT));
+ OutputString(COLOR_WHITE, x, max_y - 2, "/");
+ OutputString(COLOR_LIGHTRED, x, max_y - 2, Screen::getKeyDisplay(df::interface_key::LEAVESCREEN));
+ OutputString(COLOR_WHITE, x, max_y - 2, ": Done");
+ for (int i = 0, y = min_y + 2; i < NUM_TOOLS; i++, y++)
+ {
+ EmbarkTool t = embark_tools[i];
+ x = min_x + 2;
+ OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(t.toggle_key));
+ OutputString(COLOR_WHITE, x, y, ": " + t.name + (t.enabled ? ": Enabled" : ": Disabled"));
+ }
+ };
+ void feed (std::set * input)
+ {
+ if (input->count(df::interface_key::SELECT) || input->count(df::interface_key::LEAVESCREEN))
+ {
+ Screen::dismiss(this);
+ return;
+ }
+ for (auto iter = input->begin(); iter != input->end(); iter++)
+ {
+ df::interface_key key = *iter;
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ if (embark_tools[i].toggle_key == key)
+ {
+ embark_tools[i].enabled = !embark_tools[i].enabled;
+ }
+ }
+ }
+ };
+};
+
+/*
+ * Logic
+ */
+
+void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen)
+{
+ bool is_top = false;
+ if (screen->embark_pos_min.y == 0)
+ is_top = true;
+ std::set keys;
+ keys.insert(df::interface_key::SETUP_LOCAL_Y_MUP);
+ screen->feed(&keys);
+ if (!is_top)
+ {
+ keys.insert(df::interface_key::SETUP_LOCAL_Y_MDOWN);
+ screen->feed(&keys);
+ }
+}
+
+void resize_embark (df::viewscreen_choose_start_sitest * screen, int dx, int dy)
+{
+ /* Reproduces DF's embark resizing functionality
+ * Local area resizes up and to the right, unless it's already touching the edge
+ */
+ int x1 = screen->embark_pos_min.x,
+ x2 = screen->embark_pos_max.x,
+ y1 = screen->embark_pos_min.y,
+ y2 = screen->embark_pos_max.y,
+ width = x2 - x1 + dx,
+ height = y2 - y1 + dy;
+ if (x1 == x2 && dx == -1)
+ dx = 0;
+ if (y1 == y2 && dy == -1)
+ dy = 0;
+
+ x2 += dx; // Resize right
+ while (x2 > 15)
+ {
+ x2--;
+ x1--;
+ }
+ x1 = std::max(0, x1);
+
+ y1 -= dy; // Resize up
+ while (y1 < 0)
+ {
+ y1++;
+ y2++;
+ }
+ y2 = std::min(15, y2);
+
+ screen->embark_pos_min.x = x1;
+ screen->embark_pos_max.x = x2;
+ screen->embark_pos_min.y = y1;
+ screen->embark_pos_max.y = y2;
+
+ update_embark_sidebar(screen);
+}
+
+std::string sand_indicator = "";
+bool sand_dirty = true; // Flag set when update is needed
+void sand_update (df::viewscreen_choose_start_sitest * screen)
+{
+ CoreSuspendClaimer suspend;
+ buffered_color_ostream out;
+ Core::getInstance().runCommand(out, "prospect");
+ auto fragments = out.fragments();
+ sand_indicator = "";
+ for (auto iter = fragments.begin(); iter != fragments.end(); iter++)
+ {
+ std::string fragment = iter->second;
+ if (fragment.find("SAND_") != std::string::npos)
+ {
+ sand_indicator = "Sand";
+ break;
+ }
+ }
+ sand_dirty = false;
+}
+
+int sticky_pos[] = {0, 0, 3, 3};
+bool sticky_moved = false;
+void sticky_save (df::viewscreen_choose_start_sitest * screen)
+{
+ sticky_pos[0] = screen->embark_pos_min.x;
+ sticky_pos[1] = screen->embark_pos_max.x;
+ sticky_pos[2] = screen->embark_pos_min.y;
+ sticky_pos[3] = screen->embark_pos_max.y;
+}
+
+void sticky_apply (df::viewscreen_choose_start_sitest * screen)
+{
+ screen->embark_pos_min.x = sticky_pos[0];
+ screen->embark_pos_max.x = sticky_pos[1];
+ screen->embark_pos_min.y = sticky_pos[2];
+ screen->embark_pos_max.y = sticky_pos[3];
+ update_embark_sidebar(screen);
+}
+
+/*
+ * Viewscreen hooks
+ */
+
+void OutputString (int8_t color, int &x, int y, const std::string &text)
+{
+ Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
+ x += text.length();
+}
+
+struct choose_start_site_hook : df::viewscreen_choose_start_sitest
+{
+ typedef df::viewscreen_choose_start_sitest interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input))
+ {
+ bool prevent_default = false;
+ if (tool_enabled("anywhere"))
+ {
+ for (auto iter = input->begin(); iter != input->end(); iter++)
+ {
+ df::interface_key key = *iter;
+ if (key == df::interface_key::SETUP_EMBARK)
+ {
+ prevent_default = true;
+ this->in_embark_normal = 1;
+ }
+ }
+ }
+
+ if (input->count(df::interface_key::CUSTOM_S))
+ {
+ Screen::show(new embark_tools_settings);
+ return;
+ }
+
+ if (tool_enabled("nano"))
+ {
+ for (auto iter = input->begin(); iter != input->end(); iter++)
+ {
+ df::interface_key key = *iter;
+ bool is_resize = true;
+ int dx = 0, dy = 0;
+ switch (key)
+ {
+ case df::interface_key::SETUP_LOCAL_Y_UP:
+ dy = 1;
+ break;
+ case df::interface_key::SETUP_LOCAL_Y_DOWN:
+ dy = -1;
+ break;
+ case df::interface_key::SETUP_LOCAL_X_UP:
+ dx = 1;
+ break;
+ case df::interface_key::SETUP_LOCAL_X_DOWN:
+ dx = -1;
+ break;
+ default:
+ is_resize = false;
+ }
+ if (is_resize)
+ {
+ prevent_default = true;
+ resize_embark(this, dx, dy);
+ }
+ }
+ }
+
+ if (tool_enabled("sticky"))
+ {
+ for (auto iter = input->begin(); iter != input->end(); iter++)
+ {
+ df::interface_key key = *iter;
+ bool is_motion = false;
+ int dx = 0, dy = 0;
+ switch (key)
+ {
+ case df::interface_key::CURSOR_UP:
+ case df::interface_key::CURSOR_DOWN:
+ case df::interface_key::CURSOR_LEFT:
+ case df::interface_key::CURSOR_RIGHT:
+ case df::interface_key::CURSOR_UPLEFT:
+ case df::interface_key::CURSOR_UPRIGHT:
+ case df::interface_key::CURSOR_DOWNLEFT:
+ case df::interface_key::CURSOR_DOWNRIGHT:
+ case df::interface_key::CURSOR_UP_FAST:
+ case df::interface_key::CURSOR_DOWN_FAST:
+ case df::interface_key::CURSOR_LEFT_FAST:
+ case df::interface_key::CURSOR_RIGHT_FAST:
+ case df::interface_key::CURSOR_UPLEFT_FAST:
+ case df::interface_key::CURSOR_UPRIGHT_FAST:
+ case df::interface_key::CURSOR_DOWNLEFT_FAST:
+ case df::interface_key::CURSOR_DOWNRIGHT_FAST:
+ is_motion = true;
+ break;
+ default:
+ is_motion = false;
+ }
+ if (is_motion && !sticky_moved)
+ {
+ sticky_save(this);
+ sticky_moved = true;
+ }
+ }
+ }
+
+ if (tool_enabled("sand"))
+ {
+ sand_dirty = true;
+ }
+ if (!prevent_default)
+ INTERPOSE_NEXT(feed)(input);
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(void, render, ())
+ {
+ if (tool_enabled("sticky") && sticky_moved)
+ {
+ sticky_apply(this);
+ sticky_moved = false;
+ }
+
+ INTERPOSE_NEXT(render)();
+
+ auto dim = Screen::getWindowSize();
+ int x = 1,
+ y = dim.y - 5;
+ OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_S));
+ OutputString(COLOR_WHITE, x, y, ": Enabled: ");
+ std::list parts;
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ if (embark_tools[i].enabled)
+ {
+ parts.push_back(embark_tools[i].name);
+ parts.push_back(", ");
+ }
+ }
+ if (parts.size())
+ {
+ parts.pop_back(); // Remove trailing comma
+ for (auto iter = parts.begin(); iter != parts.end(); iter++)
+ {
+ OutputString(COLOR_LIGHTMAGENTA, x, y, *iter);
+ }
+ }
+ else
+ {
+ OutputString(COLOR_LIGHTMAGENTA, x, y, "(none)");
+ }
+
+ if (tool_enabled("anywhere"))
+ {
+ x = 20; y = dim.y - 2;
+ if (this->page >= 0 && this->page <= 4)
+ {
+ // Only display on five map pages, not on site finder or notes
+ OutputString(COLOR_WHITE, x, y, ": Embark!");
+ }
+ }
+ if (tool_enabled("sand"))
+ {
+ if (sand_dirty)
+ {
+ sand_update(this);
+ }
+ x = dim.x - 28; y = 13;
+ if (this->page == 0)
+ {
+ OutputString(COLOR_YELLOW, x, y, sand_indicator);
+ }
+ }
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed);
+IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render);
+
+/*
+ * Tool management
+ */
+
+bool tool_exists (std::string tool_name)
+{
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ if (embark_tools[i].id == tool_name)
+ return true;
+ }
+ return false;
+}
+
+bool tool_enabled (std::string tool_name)
+{
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ if (embark_tools[i].id == tool_name)
+ return embark_tools[i].enabled;
+ }
+ return false;
+}
+
+bool tool_enable (std::string tool_name, bool enable_state)
+{
+ int n = 0;
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ if (embark_tools[i].id == tool_name || tool_name == "all")
+ {
+ embark_tools[i].enabled = enable_state;
+ tool_update(tool_name);
+ n++;
+ }
+ }
+ return (bool)n;
+}
+
+void tool_update (std::string tool_name)
+{
+ // Called whenever a tool is enabled/disabled
+ if (tool_name == "sand")
+ {
+ sand_dirty = true;
+ }
+}
+
+/*
+ * Plugin management
+ */
+
+DFHACK_PLUGIN("embark-tools");
+DFHACK_PLUGIN_IS_ENABLED(is_enabled);
+
+DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands)
+{
+ std::string help = "";
+ help += "embark-tools (enable/disable) tool [tool...]\n"
+ "Tools:\n";
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ help += (" " + embark_tools[i].id + ": " + embark_tools[i].desc + "\n");
+ }
+ commands.push_back(PluginCommand(
+ "embark-tools",
+ "A collection of embark tools",
+ embark_tools_cmd,
+ false,
+ help.c_str()
+ ));
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_shutdown (color_ostream &out)
+{
+ INTERPOSE_HOOK(choose_start_site_hook, feed).remove();
+ INTERPOSE_HOOK(choose_start_site_hook, render).remove();
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
+{
+ if (is_enabled != enable)
+ {
+ if (!INTERPOSE_HOOK(choose_start_site_hook, feed).apply(enable) ||
+ !INTERPOSE_HOOK(choose_start_site_hook, render).apply(enable))
+ return CR_FAILURE;
+ is_enabled = enable;
+ }
+ return CR_OK;
+}
+
+command_result embark_tools_cmd (color_ostream &out, std::vector & parameters)
+{
+ CoreSuspender suspend;
+ if (parameters.size())
+ {
+ // Set by "enable"/"disable" - allows for multiple commands, e.g. "enable nano disable anywhere"
+ bool enable_state = true;
+ for (size_t i = 0; i < parameters.size(); i++)
+ {
+ if (parameters[i] == "enable")
+ {
+ enable_state = true;
+ plugin_enable(out, true); // Enable plugin
+ }
+ else if (parameters[i] == "disable")
+ enable_state = false;
+ else if (tool_exists(parameters[i]) || parameters[i] == "all")
+ {
+ tool_enable(parameters[i], enable_state);
+ }
+ else
+ return CR_WRONG_USAGE;
+ }
+ }
+ else
+ {
+ if (is_enabled)
+ {
+ out << "Tool status:" << std::endl;
+ for (int i = 0; i < NUM_TOOLS; i++)
+ {
+ EmbarkTool t = embark_tools[i];
+ out << t.name << " (" << t.id << "): " << (t.enabled ? "Enabled" : "Disabled") << std::endl;
+ }
+ }
+ else
+ {
+ out << "Plugin not enabled" << std::endl;
+ }
+ }
+ return CR_OK;
+}
diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp
index 57f134447..4f22a44be 100644
--- a/plugins/manipulator.cpp
+++ b/plugins/manipulator.cpp
@@ -248,11 +248,13 @@ struct UnitInfo
string profession;
int8_t color;
int active_index;
+ string squad_effective_name;
+ string squad_info;
};
enum altsort_mode {
ALTSORT_NAME,
- ALTSORT_PROFESSION,
+ ALTSORT_PROFESSION_OR_SQUAD,
ALTSORT_HAPPINESS,
ALTSORT_ARRIVAL,
ALTSORT_MAX
@@ -278,6 +280,22 @@ bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
return (d1->profession < d2->profession);
}
+bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2)
+{
+ bool gt = false;
+ if (d1->unit->military.squad_id == -1 && d2->unit->military.squad_id == -1)
+ gt = d1->name > d2->name;
+ else if (d1->unit->military.squad_id == -1)
+ gt = true;
+ else if (d2->unit->military.squad_id == -1)
+ gt = false;
+ else if (d1->unit->military.squad_id != d2->unit->military.squad_id)
+ gt = d1->squad_effective_name > d2->squad_effective_name;
+ else
+ gt = d1->unit->military.squad_position > d2->unit->military.squad_position;
+ return descending ? gt : !gt;
+}
+
bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2)
{
if (descending)
@@ -336,7 +354,7 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
enum display_columns {
DISP_COLUMN_HAPPINESS,
DISP_COLUMN_NAME,
- DISP_COLUMN_PROFESSION,
+ DISP_COLUMN_PROFESSION_OR_SQUAD,
DISP_COLUMN_LABORS,
DISP_COLUMN_MAX,
};
@@ -366,6 +384,7 @@ public:
protected:
vector units;
altsort_mode altsort;
+ bool show_squad;
bool do_refresh_names;
int first_row, sel_row, num_rows;
@@ -418,6 +437,7 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur
units.push_back(cur);
}
altsort = ALTSORT_NAME;
+ show_squad = false;
first_column = sel_column = 0;
refreshNames();
@@ -450,6 +470,13 @@ void viewscreen_unitlaborsst::refreshNames()
cur->name = Translation::TranslateName(Units::getVisibleName(unit), false);
cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true);
cur->profession = Units::getProfessionName(unit);
+ if (unit->military.squad_id > -1) {
+ cur->squad_effective_name = Units::getSquadName(unit);
+ cur->squad_info = stl_sprintf("%i", unit->military.squad_position + 1) + "." + cur->squad_effective_name;
+ } else {
+ cur->squad_effective_name = "";
+ cur->squad_info = "";
+ }
}
calcSize();
}
@@ -458,7 +485,7 @@ void viewscreen_unitlaborsst::calcSize()
{
auto dim = Screen::getWindowSize();
- num_rows = dim.y - 10;
+ num_rows = dim.y - 11;
if (num_rows > units.size())
num_rows = units.size();
@@ -471,8 +498,8 @@ void viewscreen_unitlaborsst::calcSize()
col_maxwidth[DISP_COLUMN_HAPPINESS] = 4;
col_minwidth[DISP_COLUMN_NAME] = 16;
col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below
- col_minwidth[DISP_COLUMN_PROFESSION] = 10;
- col_maxwidth[DISP_COLUMN_PROFESSION] = 10; // adjusted in the loop below
+ col_minwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10;
+ col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = 10; // adjusted in the loop below
col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60%
col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS;
@@ -481,8 +508,13 @@ void viewscreen_unitlaborsst::calcSize()
{
if (col_maxwidth[DISP_COLUMN_NAME] < units[i]->name.size())
col_maxwidth[DISP_COLUMN_NAME] = units[i]->name.size();
- if (col_maxwidth[DISP_COLUMN_PROFESSION] < units[i]->profession.size())
- col_maxwidth[DISP_COLUMN_PROFESSION] = units[i]->profession.size();
+ if (show_squad) {
+ if (col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] < units[i]->squad_info.size())
+ col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = units[i]->squad_info.size();
+ } else {
+ if (col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] < units[i]->profession.size())
+ col_maxwidth[DISP_COLUMN_PROFESSION_OR_SQUAD] = units[i]->profession.size();
+ }
}
// check how much room we have
@@ -753,10 +785,10 @@ void viewscreen_unitlaborsst::feed(set *events)
}
break;
- case DISP_COLUMN_PROFESSION:
+ case DISP_COLUMN_PROFESSION_OR_SQUAD:
if (enabler->mouse_lbut || enabler->mouse_rbut)
{
- input_sort = ALTSORT_PROFESSION;
+ input_sort = ALTSORT_PROFESSION_OR_SQUAD;
if (enabler->mouse_lbut)
events->insert(interface_key::SECONDSCROLL_PAGEDOWN);
if (enabler->mouse_rbut)
@@ -783,7 +815,7 @@ void viewscreen_unitlaborsst::feed(set *events)
break;
case DISP_COLUMN_NAME:
- case DISP_COLUMN_PROFESSION:
+ case DISP_COLUMN_PROFESSION_OR_SQUAD:
// left-click to view, right-click to zoom
if (enabler->mouse_lbut)
{
@@ -879,8 +911,8 @@ void viewscreen_unitlaborsst::feed(set *events)
case ALTSORT_NAME:
std::sort(units.begin(), units.end(), sortByName);
break;
- case ALTSORT_PROFESSION:
- std::sort(units.begin(), units.end(), sortByProfession);
+ case ALTSORT_PROFESSION_OR_SQUAD:
+ std::sort(units.begin(), units.end(), show_squad ? sortBySquad : sortByProfession);
break;
case ALTSORT_HAPPINESS:
std::sort(units.begin(), units.end(), sortByHappiness);
@@ -895,9 +927,9 @@ void viewscreen_unitlaborsst::feed(set *events)
switch (altsort)
{
case ALTSORT_NAME:
- altsort = ALTSORT_PROFESSION;
+ altsort = ALTSORT_PROFESSION_OR_SQUAD;
break;
- case ALTSORT_PROFESSION:
+ case ALTSORT_PROFESSION_OR_SQUAD:
altsort = ALTSORT_HAPPINESS;
break;
case ALTSORT_HAPPINESS:
@@ -908,6 +940,10 @@ void viewscreen_unitlaborsst::feed(set *events)
break;
}
}
+ if (events->count(interface_key::OPTION20))
+ {
+ show_squad = !show_squad;
+ }
if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent))
{
@@ -949,7 +985,7 @@ void viewscreen_unitlaborsst::render()
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap.");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name");
- Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession");
+ Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 2, show_squad ? "Squad" : "Profession");
for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{
@@ -1019,12 +1055,17 @@ void viewscreen_unitlaborsst::render()
name.resize(col_widths[DISP_COLUMN_NAME]);
Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_NAME], 4 + row, name);
- string profession = cur->profession;
- profession.resize(col_widths[DISP_COLUMN_PROFESSION]);
- fg = cur->color;
bg = 0;
-
- Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION], 4 + row, profession);
+ string profession_or_squad;
+ if (show_squad) {
+ fg = 11;
+ profession_or_squad = cur->squad_info;
+ } else {
+ fg = cur->color;
+ profession_or_squad = cur->profession;
+ }
+ profession_or_squad.resize(col_widths[DISP_COLUMN_PROFESSION_OR_SQUAD]);
+ Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION_OR_SQUAD], 4 + row, profession_or_squad);
// Print unit's skills and labor assignments
for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
@@ -1114,7 +1155,25 @@ void viewscreen_unitlaborsst::render()
else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
}
- Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + num_rows + 2, str);
+ Screen::paintString(Screen::Pen(' ', 9, 0), x, y, str);
+
+ if (cur->unit->military.squad_id > -1) {
+
+ x = 1;
+ y++;
+
+ string squadLabel = "Squad: ";
+ Screen::paintString(white_pen, x, y, squadLabel);
+ x += squadLabel.size();
+
+ string squad = cur->squad_effective_name;
+ Screen::paintString(Screen::Pen(' ', 11, 0), x, y, squad);
+ x += squad.size();
+
+ string pos = stl_sprintf(" Pos %i", cur->unit->military.squad_position + 1);
+ Screen::paintString(Screen::Pen(' ', 9, 0), x, y, pos);
+
+ }
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
}
@@ -1136,6 +1195,9 @@ void viewscreen_unitlaborsst::render()
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN));
OutputString(15, x, y, ": Done, ");
+ OutputString(10, x, y, Screen::getKeyDisplay(interface_key::OPTION20));
+ OutputString(15, x, y, ": Toggle View, ");
+
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN));
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP));
OutputString(15, x, y, ": Sort by Skill, ");
@@ -1150,8 +1212,8 @@ void viewscreen_unitlaborsst::render()
case ALTSORT_NAME:
OutputString(15, x, y, "Name");
break;
- case ALTSORT_PROFESSION:
- OutputString(15, x, y, "Profession");
+ case ALTSORT_PROFESSION_OR_SQUAD:
+ OutputString(15, x, y, show_squad ? "Squad" : "Profession");
break;
case ALTSORT_HAPPINESS:
OutputString(15, x, y, "Happiness");
diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp
new file mode 100644
index 000000000..f0116131e
--- /dev/null
+++ b/plugins/petcapRemover.cpp
@@ -0,0 +1,213 @@
+
+#include "Console.h"
+#include "Core.h"
+#include "DataDefs.h"
+#include "Export.h"
+#include "PluginManager.h"
+#include "modules/EventManager.h"
+#include "modules/Maps.h"
+
+#include "df/caste_raw.h"
+#include "df/caste_raw_flags.h"
+#include "df/creature_raw.h"
+#include "df/profession.h"
+#include "df/unit.h"
+#include "df/world.h"
+
+#include