#include "Core.h" #include "Console.h" #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 "modules/Screen.h" #include #include "df/plotinfost.h" #include "df/gamest.h" #include "df/world.h" #include "df/squad.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/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 "df/building_civzonest.h" #include "df/viewscreen_dwarfmodest.h" #include "RemoteServer.h" #include "rename.pb.h" #include "MiscUtils.h" #include using std::vector; using std::string; using std::endl; using namespace DFHack; using namespace df::enums; using namespace dfproto; DFHACK_PLUGIN("rename"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(game); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); static command_result rename(color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (world && plotinfo) { commands.push_back(PluginCommand( "rename", "Easily rename things.", rename)); if (Core::getInstance().isWorldLoaded()) plugin_onstatechange(out, SC_WORLD_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_WORLD_LOADED: init_buildings(true); break; case SC_WORLD_UNLOADED: init_buildings(false); break; default: break; } return CR_OK; } 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) \ BUILDING('Z', building_civzonest, "Zone") #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_PRIO(cname##_hook, getName, 100); KNOWN_BUILDINGS #undef BUILDING struct dwarf_render_zone_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; DEFINE_VMETHOD_INTERPOSE(void, render, ()) { INTERPOSE_NEXT(render)(); if (plotinfo->main.mode == ui_sidebar_mode::Zones && game && game->zone.selected && !game->zone.selected->name.empty()) { auto dims = Gui::getDwarfmodeViewDims(); int width = dims.menu_x2 - dims.menu_x1 - 1; Screen::Pen pen(' ',COLOR_WHITE); Screen::fillRect(pen, dims.menu_x1, dims.y1+1, dims.menu_x2, dims.y1+1); std::string name; game->zone.selected->getName(&name); Screen::paintString(pen, dims.menu_x1+1, dims.y1+1, name.substr(0, width)); } } }; IMPLEMENT_VMETHOD_INTERPOSE(dwarf_render_zone_hook, render); 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) { if (enable) is_enabled = true; if (code == 'Z') INTERPOSE_HOOK(dwarf_render_zone_hook, render).apply(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() { is_enabled = false; INTERPOSE_HOOK(dwarf_render_zone_hook, render).remove(); #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 entry = World::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 entry = World::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(plotinfo->group_id); if (!entity) return NULL; if (idx >= entity->squads.size()) return NULL; return df::squad::find(entity->squads[idx]); } static command_result RenameSquad(color_ostream &stream, const RenameSquadIn *in) { df::squad *squad = df::squad::find(in->squad_id()); if (!squad) return CR_NOT_FOUND; if (in->has_nickname()) Translation::setNickname(&squad->name, UTF2DF(in->nickname())); if (in->has_alias()) squad->alias = UTF2DF(in->alias()); return CR_OK; } static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) { df::unit *unit = df::unit::find(in->unit_id()); if (!unit) return CR_NOT_FOUND; if (in->has_nickname()) Units::setNickname(unit, UTF2DF(in->nickname())); if (in->has_profession()) unit->custom_profession = UTF2DF(in->profession()); 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; string cmd; if (!parameters.empty()) cmd = parameters[0]; if (cmd == "squad") { if (parameters.size() != 3) return CR_WRONG_USAGE; int id = atoi(parameters[1].c_str()); df::squad *squad = getSquadByIndex(id-1); if (!squad) { out.printerr("Couldn't find squad with index %d.\n", id); return CR_WRONG_USAGE; } squad->alias = parameters[2]; } else if (cmd == "hotkey") { if (parameters.size() != 3) return CR_WRONG_USAGE; int id = atoi(parameters[1].c_str()); if (id < 1 || id > 16) { out.printerr("Invalid hotkey index\n"); return CR_WRONG_USAGE; } plotinfo->main.hotkeys[id-1].name = parameters[2]; } else if (cmd == "unit") { if (parameters.size() != 2) return CR_WRONG_USAGE; df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) return CR_WRONG_USAGE; Units::setNickname(unit, parameters[1]); } else if (cmd == "unit-profession") { if (parameters.size() != 2) return CR_WRONG_USAGE; df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) return CR_WRONG_USAGE; unit->custom_profession = parameters[1]; } else if (cmd == "building") { if (parameters.size() != 2) return CR_WRONG_USAGE; df::building *bld = Gui::getSelectedBuilding(out, true); if (!bld) return CR_WRONG_USAGE; if (!renameBuilding(bld, parameters[1])) { out.printerr("This type of building is not supported.\n"); return CR_FAILURE; } } else { if (!parameters.empty() && cmd != "?") out.printerr("Invalid command: %s\n", cmd.c_str()); return CR_WRONG_USAGE; } return CR_OK; }