diff --git a/dfhack.init-example b/dfhack.init-example index 39c0e61df..af8b17f0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# gui/rename script +keybinding add Ctrl-Shift-N gui/rename +keybinding add Ctrl-Shift-P "gui/rename unit-profession" + ############################## # Generic adv mode bindings # ############################## diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index e6d30c970..35720f871 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -9,6 +9,7 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) +MessageBox.focus_path = 'MessageBox' MessageBox.frame_style = gui.GREY_LINE_FRAME function MessageBox:init(info) @@ -31,7 +32,7 @@ end function MessageBox:getWantedFrameSize() local text = self.text - local w = #(self.frame_title or '') + 2 + local w = #(self.frame_title or '') + 4 w = math.max(w, 20) w = math.max(self.frame_width or w, w) for _, l in ipairs(text) do @@ -41,7 +42,7 @@ function MessageBox:getWantedFrameSize() if h > 1 then h = h+1 end - return w, #text+2 + return w+2, #text+2 end function MessageBox:onRenderBody(dc) @@ -102,6 +103,8 @@ end InputBox = defclass(InputBox, MessageBox) +InputBox.focus_path = 'InputBox' + function InputBox:init(info) info = info or {} self:init_fields{ @@ -127,7 +130,7 @@ function InputBox:onRenderBody(dc) dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) local cursor = '_' - if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + if math.floor(dfhack.getTickCount()/300) % 2 == 0 then cursor = ' ' end local txt = self.input .. cursor diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 393e7cbfe..e14aa02a0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) { - *added = false; + if (added) *added = false; PersistentDataItem rv = GetPersistentData(key); if (!rv.isValid()) { - *added = true; + if (added) *added = true; rv = AddPersistentData(key); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a2e520178..04da3e6c8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,7 +92,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(seedwatch seedwatch.cpp) DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp) - DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename) + DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(workflow workflow.cpp) DFHACK_PLUGIN(showmood showmood.cpp) diff --git a/plugins/lua/rename.lua b/plugins/lua/rename.lua new file mode 100644 index 000000000..0e7128f57 --- /dev/null +++ b/plugins/lua/rename.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.rename') + +--[[ + + Native functions: + + * canRenameBuilding(building) + * isRenamingBuilding(building) + * renameBuilding(building, name) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index aa1e95f48..810091fc7 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -17,3 +17,10 @@ message RenameUnitIn { optional string nickname = 2; optional string profession = 3; } + +// RPC RenameBuilding : RenameBuildingIn -> EmptyMessage +message RenameBuildingIn { + required int32 building_id = 1; + + optional string name = 2; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1871d0f73..99dc6848a 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -3,11 +3,15 @@ #include "Export.h" #include "PluginManager.h" +#include +#include + #include "modules/Gui.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/World.h" -#include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -18,6 +22,11 @@ #include "df/historical_figure_info.h" #include "df/assumed_identity.h" #include "df/language_name.h" +#include "df/building_stockpilest.h" +#include "df/building_workshopst.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_siegeenginest.h" #include "RemoteServer.h" #include "rename.pb.h" @@ -36,6 +45,8 @@ using namespace dfproto; using df::global::ui; using df::global::world; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + static command_result rename(color_ostream &out, vector & parameters); DFHACK_PLUGIN("rename"); @@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector clear(); \ + *buf += name; \ + *buf += " ("; \ + if (tag) *buf += (const char*)tag; \ + else { std::string tmp; INTERPOSE_NEXT(getName)(&tmp); *buf += tmp; } \ + *buf += ")"; \ + return; \ + } \ + else \ + INTERPOSE_NEXT(getName)(buf); \ + } \ + }; \ + IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); +KNOWN_BUILDINGS +#undef BUILDING + +static char getBuildingCode(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + +#define BUILDING(code, cname, tag) \ + if (strict_virtual_cast(bld)) return code; +KNOWN_BUILDINGS +#undef BUILDING + + return 0; +} + +static bool enable_building_rename(char code, bool enable) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void disable_building_rename() +{ +#define BUILDING(code, cname, tag) \ + INTERPOSE_HOOK(cname##_hook, getName).remove(); +KNOWN_BUILDINGS +#undef BUILDING +} + +static bool is_enabled_building(char code) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).is_applied(); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void init_buildings(bool enable) +{ + disable_building_rename(); + + if (enable) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types"); + + if (entry.isValid()) + { + std::string val = entry.val(); + for (size_t i = 0; i < val.size(); i++) + enable_building_rename(val[i], true); + } + } +} + +static bool canRenameBuilding(df::building *bld) +{ + return getBuildingCode(bld) != 0; +} + +static bool isRenamingBuilding(df::building *bld) +{ + return is_enabled_building(getBuildingCode(bld)); +} + +static bool renameBuilding(df::building *bld, std::string name) +{ + char code = getBuildingCode(bld); + if (code == 0 && !name.empty()) + return false; + + if (!name.empty() && !is_enabled_building(code)) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types", NULL); + if (!entry.isValid()) + return false; + + if (!enable_building_rename(code, true)) + return false; + + entry.val().push_back(code); + } + + bld->name = name; + return true; +} + static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -101,14 +263,37 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_OK; } +static command_result RenameBuilding(color_ostream &stream, const RenameBuildingIn *in) +{ + auto building = df::building::find(in->building_id()); + if (!building) + return CR_NOT_FOUND; + + if (in->has_name()) + { + if (!renameBuilding(building, in->name())) + return CR_FAILURE; + } + + return CR_OK; +} + DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) { RPCService *svc = new RPCService(); svc->addFunction("RenameSquad", RenameSquad); svc->addFunction("RenameUnit", RenameUnit); + svc->addFunction("RenameBuilding", RenameBuilding); return svc; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(canRenameBuilding), + DFHACK_LUA_FUNCTION(isRenamingBuilding), + DFHACK_LUA_FUNCTION(renameBuilding), + DFHACK_LUA_END +}; + static command_result rename(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector ¶meters) unit->custom_profession = parameters[1]; } + else if (cmd == "building") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + return CR_WRONG_USAGE; + + if (!renameBuilding(world->selected_building, parameters[1])) + { + out.printerr("This type of building is not supported.\n"); + return CR_FAILURE; + } + } else { if (!parameters.empty() && cmd != "?") diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua new file mode 100644 index 000000000..a457a0bfd --- /dev/null +++ b/scripts/gui/rename.lua @@ -0,0 +1,63 @@ +-- Rename various objects via gui. + +local gui = require 'gui' +local dlg = require 'gui.dialogs' +local plugin = require 'plugins.rename' + +local mode = ... +local focus = dfhack.gui.getCurFocus() + +local function verify_mode(expected) + if mode ~= nil and mode ~= expected then + qerror('Invalid UI state for mode '..mode) + end +end + +if string.match(focus, '^dwarfmode/QueryBuilding/Some') then + verify_mode('building') + + local building = df.global.world.selected_building + if plugin.canRenameBuilding(building) then + dlg.showInputPrompt( + 'Rename Building', + 'Enter a new name for the building:', COLOR_GREEN, + building.name, + curry(plugin.renameBuilding, building) + ) + else + dlg.showMessage( + 'Rename Building', + 'Cannot rename this type of building.', COLOR_LIGHTRED + ) + end +elseif dfhack.gui.getSelectedUnit(true) then + local unit = dfhack.gui.getSelectedUnit(true) + + if mode == 'unit-profession' then + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new profession for the unit:', COLOR_GREEN, + unit.custom_profession, + function(newval) + unit.custom_profession = newval + end + ) + else + verify_mode('unit') + + local vname = dfhack.units.getVisibleName(unit) + local vnick = '' + if vname and vname.has_name then + vnick = vname.nickname + end + + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new nickname for the unit:', COLOR_GREEN, + vnick, + curry(dfhack.units.setNickname, unit) + ) + end +elseif mode then + verify_mode(nil) +end