summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dfhack.init-example7
m---------library/xml0
-rw-r--r--plugins/CMakeLists.txt1
-rw-r--r--plugins/advtools.cpp332
4 files changed, 340 insertions, 0 deletions
diff --git a/dfhack.init-example b/dfhack.init-example
index 4a4c0437..e4d1d3d3 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -13,6 +13,13 @@ keybinding add Ctrl-Shift-K autodump-destroy-here
# any item:
keybinding add Ctrl-K autodump-destroy-item
+##############################
+# Generic adv mode bindings #
+##############################
+
+keybinding add Ctrl-B adv-bodyswap
+keybinding add Ctrl-Shift-B "adv-bodyswap force"
+
#############################
# Context-specific bindings #
#############################
diff --git a/library/xml b/library/xml
-Subproject 05bb1b96da5d53451782d32bf74755d8513d0c7
+Subproject 62371227a8cbcc6f1807b714d7f0722803c5e67
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 453a95e9..8d3fa645 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -80,6 +80,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(fixpositions fixpositions.cpp)
DFHACK_PLUGIN(follow follow.cpp)
DFHACK_PLUGIN(changevein changevein.cpp)
+ DFHACK_PLUGIN(advtools advtools.cpp)
#DFHACK_PLUGIN(versionosd versionosd.cpp)
endif()
diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp
new file mode 100644
index 00000000..d4aa9421
--- /dev/null
+++ b/plugins/advtools.cpp
@@ -0,0 +1,332 @@
+#include "Core.h"
+#include "Console.h"
+#include "Export.h"
+#include "PluginManager.h"
+#include "MiscUtils.h"
+#include "modules/World.h"
+#include "modules/Translation.h"
+
+#include "DataDefs.h"
+#include "df/world.h"
+#include "df/ui_advmode.h"
+#include "df/unit.h"
+#include "df/nemesis_record.h"
+#include "df/general_ref_is_nemesisst.h"
+#include "df/viewscreen_optionst.h"
+#include "df/viewscreen_dungeonmodest.h"
+#include "df/viewscreen_dungeon_monsterstatusst.h"
+
+
+using namespace DFHack;
+using namespace df::enums;
+
+using df::global::world;
+using df::global::ui_advmode;
+
+using namespace DFHack::Simple::Translation;
+
+static bool bodyswap_hotkey(Core *c, df::viewscreen *top);
+
+command_result adv_bodyswap (Core * c, std::vector <std::string> & parameters);
+
+DFHACK_PLUGIN("advtools");
+
+DFhackCExport command_result plugin_init ( Core * c, std::vector <PluginCommand> &commands)
+{
+ commands.clear();
+
+ if (!ui_advmode)
+ return CR_OK;
+
+ commands.push_back(PluginCommand(
+ "adv-bodyswap", "Change the adventurer unit.",
+ adv_bodyswap, bodyswap_hotkey,
+ " - When viewing unit details, body-swaps into that unit.\n"
+ " - In the main adventure mode screen, reverts transient swap.\n"
+ "Options:\n"
+ " force\n"
+ " Allow swapping into non-companion units.\n"
+ " permanent\n"
+ " Permanently change the unit to be the adventurer.\n"
+ " Otherwise it will revert if adv-bodyswap is called\n"
+ " in the main screen, or if the main menu, Fast Travel\n"
+ " or Sleep/Wait screen is opened.\n"
+ " no-make-leader\n"
+ " In permanent mode, don't swap companions to the new unit.\n"
+ ));
+
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_shutdown ( Core * c )
+{
+ return CR_OK;
+}
+
+df::nemesis_record *getPlayerNemesis(Core *c, bool restore_swap);
+
+static bool in_transient_swap = false;
+
+DFhackCExport command_result plugin_onstatechange(Core* c, state_change_event event)
+{
+ switch (event) {
+ case SC_GAME_LOADED:
+ case SC_GAME_UNLOADED:
+ in_transient_swap = false;
+ break;
+ default:
+ break;
+ }
+ return CR_OK;
+}
+
+DFhackCExport command_result plugin_onupdate ( Core * c )
+{
+ // Revert transient swaps before trouble happens
+ if (in_transient_swap)
+ {
+ auto screen = c->getTopViewscreen();
+ bool revert = false;
+
+ if (strict_virtual_cast<df::viewscreen_dungeonmodest>(screen))
+ {
+ using namespace df::enums::ui_advmode_menu;
+
+ switch (ui_advmode->menu)
+ {
+ case Travel:
+ case Sleep:
+ revert = true;
+ break;
+ default:
+ break;
+ }
+ }
+ else if (strict_virtual_cast<df::viewscreen_optionst>(screen))
+ {
+ // Options may mean save game
+ revert = true;
+ }
+
+ if (revert)
+ {
+ getPlayerNemesis(c, true);
+ in_transient_swap = false;
+ }
+ }
+
+ return CR_OK;
+}
+
+static bool bodyswap_hotkey(Core *c, df::viewscreen *top)
+{
+ return !!virtual_cast<df::viewscreen_dungeonmodest>(top) ||
+ !!virtual_cast<df::viewscreen_dungeon_monsterstatusst>(top);
+}
+
+df::unit *getCurUnit(Core *c)
+{
+ auto top = c->getTopViewscreen();
+
+ if (VIRTUAL_CAST_VAR(ms, df::viewscreen_dungeon_monsterstatusst, top))
+ return ms->unit;
+
+ return NULL;
+}
+
+df::nemesis_record *getNemesis(df::unit *unit)
+{
+ if (!unit)
+ return NULL;
+
+ for (unsigned i = 0; i < unit->refs.size(); i++)
+ {
+ df::nemesis_record *rv = unit->refs[i]->getNemesis();
+ if (rv && rv->unit == unit)
+ return rv;
+ }
+
+ return NULL;
+}
+
+bool bodySwap(Core *c, df::unit *player)
+{
+ if (!player)
+ {
+ c->con.printerr("Unit to swap is NULL\n");
+ return false;
+ }
+
+ auto &vec = world->units.other[0];
+
+ int idx = linear_index(vec, player);
+ if (idx < 0)
+ {
+ c->con.printerr("Unit to swap not found: %d\n", player->id);
+ return false;
+ }
+
+ if (idx != 0)
+ std::swap(vec[0], vec[idx]);
+
+ return true;
+}
+
+df::nemesis_record *getPlayerNemesis(Core *c, bool restore_swap)
+{
+ auto real_nemesis = df::nemesis_record::find(ui_advmode->player_id);
+ if (!real_nemesis || !real_nemesis->unit)
+ {
+ c->con.printerr("Invalid player nemesis id: %d\n", ui_advmode->player_id);
+ return NULL;
+ }
+
+ if (restore_swap)
+ {
+ df::unit *ctl = world->units.other[0][0];
+ auto ctl_nemesis = getNemesis(ctl);
+
+ if (ctl_nemesis != real_nemesis)
+ {
+ if (!bodySwap(c, real_nemesis->unit))
+ return NULL;
+
+ auto name = TranslateName(&real_nemesis->unit->name, false);
+ c->con.print("Returned into the body of %s.\n", name.c_str());
+ }
+
+ real_nemesis->unit->relations.group_leader_id = -1;
+ in_transient_swap = false;
+ }
+
+ return real_nemesis;
+}
+
+void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis)
+{
+ auto &cvec = new_nemesis->companions;
+
+ // Swap companions
+ cvec.swap(old_nemesis->companions);
+
+ vector_erase_at(cvec, linear_index(cvec, new_nemesis->id));
+ insert_into_vector(cvec, old_nemesis->id);
+
+ // Update follow
+ new_nemesis->group_leader_id = -1;
+ new_nemesis->unit->relations.group_leader_id = -1;
+
+ for (unsigned i = 0; i < cvec.size(); i++)
+ {
+ auto nm = df::nemesis_record::find(cvec[i]);
+ if (!nm)
+ continue;
+
+ nm->group_leader_id = new_nemesis->id;
+ if (nm->unit)
+ nm->unit->relations.group_leader_id = new_nemesis->unit_id;
+ }
+}
+
+void copyAcquaintances(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis)
+{
+ auto &svec = old_nemesis->unit->adventurer_knows;
+ auto &tvec = new_nemesis->unit->adventurer_knows;
+
+ for (unsigned i = 0; i < svec.size(); i++)
+ insert_into_vector(tvec, svec[i]);
+
+ insert_into_vector(tvec, old_nemesis->unit_id);
+}
+
+command_result adv_bodyswap (Core * c, std::vector <std::string> & parameters)
+{
+ // HOTKEY COMMAND; CORE IS SUSPENDED
+ bool force = false;
+ bool permanent = false;
+ bool no_make_leader = false;
+
+ for (unsigned i = 0; i < parameters.size(); i++)
+ {
+ auto &item = parameters[i];
+
+ if (item == "force")
+ force = true;
+ else if (item == "permanent")
+ permanent = true;
+ else if (item == "no-make-leader")
+ no_make_leader = true;
+ else
+ return CR_WRONG_USAGE;
+ }
+
+ // Get the real player; undo previous transient swap
+ auto real_nemesis = getPlayerNemesis(c, true);
+ if (!real_nemesis)
+ return CR_FAILURE;
+
+ // Get the unit to swap to
+ auto new_unit = getCurUnit(c);
+ auto new_nemesis = getNemesis(new_unit);
+
+ if (!new_nemesis)
+ {
+ if (new_unit)
+ {
+ c->con.printerr("Cannot swap into a non-historical unit.\n");
+ return CR_FAILURE;
+ }
+
+ return CR_OK;
+ }
+
+ if (new_nemesis == real_nemesis)
+ return CR_OK;
+
+ // Verify it's a companion
+ if (!force && linear_index(real_nemesis->companions, new_nemesis->id) < 0)
+ {
+ c->con.printerr("This is not your companion - use force to bodyswap.\n");
+ return CR_FAILURE;
+ }
+
+ // Swap
+ if (!bodySwap(c, new_nemesis->unit))
+ return CR_FAILURE;
+
+ auto name = TranslateName(&new_nemesis->unit->name, false);
+ c->con.print("Swapped into the body of %s.\n", name.c_str());
+
+ // Permanently re-link everything
+ if (permanent)
+ {
+ ui_advmode->player_id = new_nemesis->id;
+
+ // Flag 0 appears to be the 'active adventurer' flag, and
+ // the player_id field above seems to be computed using it
+ // when a savegame is loaded.
+ // Also, unless this is set, it is impossible to retire.
+ real_nemesis->flags.set(0, false);
+ new_nemesis->flags.set(0, true);
+
+ real_nemesis->flags.set(1, true); // former retired adventurer
+ new_nemesis->flags.set(2, true); // blue color in legends
+
+ // Reassign companions and acquaintances
+ if (!no_make_leader)
+ {
+ changeGroupLeader(new_nemesis, real_nemesis);
+ copyAcquaintances(new_nemesis, real_nemesis);
+ }
+ }
+ else
+ {
+ in_transient_swap = true;
+
+ // Make the player unit follow around to avoid bad consequences
+ // if it is unloaded before the transient swap is reverted.
+ real_nemesis->unit->relations.group_leader_id = new_nemesis->unit_id;
+ }
+
+ return CR_OK;
+}