Support renaming some buildings, and arbitrary units, via gui script.

develop
Alexander Gavrilov 2012-09-05 21:27:42 +04:00
parent 57086ac56e
commit 8d876cc7d9
8 changed files with 296 additions and 7 deletions

@ -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 #
##############################

@ -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

@ -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);
}

@ -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)

@ -0,0 +1,13 @@
local _ENV = mkmodule('plugins.rename')
--[[
Native functions:
* canRenameBuilding(building)
* isRenamingBuilding(building)
* renameBuilding(building, name)
--]]
return _ENV

@ -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;
}

@ -3,11 +3,15 @@
#include "Export.h"
#include "PluginManager.h"
#include <Error.h>
#include <LuaTools.h>
#include "modules/Gui.h"
#include "modules/Translation.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "DataDefs.h"
#include <VTableInterpose.h>
#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 <string> & parameters);
DFHACK_PLUGIN("rename");
@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" rename unit \"nickname\"\n"
" rename unit-profession \"custom profession\"\n"
" (a unit must be highlighted in the ui)\n"
" rename building \"nickname\"\n"
" (a building must be highlighted via 'q')\n"
));
if (Core::getInstance().isMapLoaded())
plugin_onstatechange(out, SC_MAP_LOADED);
}
return CR_OK;
}
static void init_buildings(bool enable);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
init_buildings(true);
break;
case SC_MAP_UNLOADED:
init_buildings(false);
break;
default:
break;
}
return CR_OK;
}
@ -61,6 +96,133 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
/*
* Building renaming - it needs some per-type hacking.
*/
#define KNOWN_BUILDINGS \
BUILDING('p', building_stockpilest, "Stockpile") \
BUILDING('w', building_workshopst, NULL) \
BUILDING('e', building_furnacest, NULL) \
BUILDING('T', building_trapst, NULL) \
BUILDING('i', building_siegeenginest, NULL)
#define BUILDING(code, cname, tag) \
struct cname##_hook : df::cname { \
typedef df::cname interpose_base; \
DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf)) { \
if (!name.empty()) {\
buf->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<df::cname>(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 <string> &parameters)
{
CoreSuspender suspend;
@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
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 != "?")

@ -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