From 326c58f7935cb39206c52646a38ff19502ebb966 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 2 Jan 2012 18:46:24 +0400 Subject: [PATCH] Add a plugin to tweak inorganic materials in jobs. It also auto-seeks in the build item list if used in that context. --- library/MiscUtils.cpp | 27 ++- library/include/DataDefs.h | 4 + library/include/MiscUtils.h | 3 + plugins/CMakeLists.txt | 1 + plugins/jobutils.cpp | 402 ++++++++++++++++++++++++++++++++++++ 5 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 plugins/jobutils.cpp diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 47deea04c..72e3eae28 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -27,8 +27,6 @@ distribution. #include "Core.h" #include "MiscUtils.h" - - #ifndef LINUX_BUILD #include #else @@ -36,6 +34,31 @@ distribution. #include #endif +#include + +std::string stl_sprintf(const char *fmt, ...) { + va_list lst; + va_start(lst, fmt); + std::string rv = stl_vsprintf(fmt, lst); + va_end(lst); + return rv; +} + +std::string stl_vsprintf(const char *fmt, va_list args) { + std::vector buf; + buf.resize(4096); + for (;;) { + int rsz = vsnprintf(&buf[0], buf.size(), fmt, args); + + if (rsz < 0) + buf.resize(buf.size()*2); + else if (unsigned(rsz) > buf.size()) + buf.resize(rsz+1); + else + return std::string(&buf[0], rsz); + } +} + #ifdef LINUX_BUILD // Linux uint64_t GetTimeMs64() { diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 9f3bcf34d..d869f12aa 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -111,11 +111,15 @@ namespace DFHack return T::_identity.is_instance(ptr) ? static_cast(ptr) : NULL; } +#define VIRTUAL_CAST_VAR(var,type,input) type *var = virtual_cast(input) + template inline T *strict_virtual_cast(virtual_ptr ptr) { return T::_identity.is_direct_instance(ptr) ? static_cast(ptr) : NULL; } +#define STRICT_VIRTUAL_CAST_VAR(var,type,input) type *var = strict_virtual_cast(input) + void InitDataDefGlobals(Core *core); template diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 833e74104..8959a766b 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -102,3 +102,6 @@ CT *binsearch_in_vector(std::vector &vec, FT CT::*field, AT value) * source: http://stackoverflow.com/questions/1861294/how-to-calculate-execution-time-of-a-code-snippet-in-c */ DFHACK_EXPORT uint64_t GetTimeMs64(); + +DFHACK_EXPORT std::string stl_sprintf(const char *fmt, ...); +DFHACK_EXPORT std::string stl_vsprintf(const char *fmt, va_list args); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 133905727..d28baeb6f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -61,6 +61,7 @@ DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp) DFHACK_PLUGIN(rename rename.cpp) DFHACK_PLUGIN(fixwagons fixwagons.cpp) +DFHACK_PLUGIN(jobutils jobutils.cpp) #DFHACK_PLUGIN(versionosd versionosd.cpp) # this is the skeleton plugin. If you want to make your own, make a copy and then change it diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp new file mode 100644 index 000000000..1e0971dff --- /dev/null +++ b/plugins/jobutils.cpp @@ -0,0 +1,402 @@ +#include "Core.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +/* + * TODO: This should be in core: + */ + +struct MaterialInfo { + int16_t type; + int32_t index; + + df::material *material; + + enum Mode { + Builtin, + Inorganic, + Creature, + Plant + }; + Mode mode; + + int16_t subtype; + df::inorganic_raw *inorganic; + df::creature_raw *creature; + df::plant_raw *plant; + + df::historical_figure *figure; + + MaterialInfo(int16_t type = -1, int32_t index = -1) { + decode(type, index); + } + + bool isValid() const { return material != NULL; } + bool decode(int16_t type, int32_t index = -1); + + bool findInorganic(const std::string &token); + + std::string toString(uint16_t temp = 10015, bool named = true); +}; + +inline bool operator== (const MaterialInfo &a, const MaterialInfo &b) { + return a.type == b.type && a.index == b.index; +} +inline bool operator!= (const MaterialInfo &a, const MaterialInfo &b) { + return a.type != b.type || a.index != b.index; +} + +bool MaterialInfo::decode(int16_t type, int32_t index) +{ + this->type = type; + this->index = index; + + material = NULL; + mode = Builtin; subtype = 0; + inorganic = NULL; plant = NULL; creature = NULL; + figure = NULL; + + if (type < 0 || type >= 659) + return false; + + df::world_raws &raws = df::global::world->raws; + + if (index < 0) + { + material = raws.mat_table.builtin[type]; + } + else if (type == 0) + { + mode = Inorganic; + inorganic = df::inorganic_raw::find(index); + if (!inorganic) + return false; + material = &inorganic->material; + } + else if (type < 19) + { + material = raws.mat_table.builtin[type]; + } + else if (type < 219) + { + mode = Creature; + subtype = type-19; + creature = df::creature_raw::find(index); + if (!creature || subtype >= creature->material.size()) + return false; + material = creature->material[subtype]; + } + else if (type < 419) + { + mode = Creature; + subtype = type-219; + figure = df::historical_figure::find(index); + if (!figure) + return false; + creature = df::creature_raw::find(figure->race); + if (!creature || subtype >= creature->material.size()) + return false; + material = creature->material[subtype]; + } + else if (type < 619) + { + mode = Plant; + subtype = type-419; + plant = df::plant_raw::find(index); + if (!plant || subtype >= plant->material.size()) + return false; + material = plant->material[subtype]; + } + else + { + material = raws.mat_table.builtin[type]; + } + + return (material != NULL); +} + +bool MaterialInfo::findInorganic(const std::string &token) +{ + df::world_raws &raws = df::global::world->raws; + for (unsigned i = 0; i < raws.inorganics.size(); i++) + { + df::inorganic_raw *p = raws.inorganics[i]; + if (p->id == token) + return decode(0, i); + } + return decode(-1); +} + +std::string MaterialInfo::toString(uint16_t temp, bool named) +{ + if (type == -1) + return "NONE"; + if (!material) + return stl_sprintf("INVALID %d:%d", type, index); + + int idx = 0; + if (temp >= material->heat.melting_point) + idx = 1; + if (temp >= material->heat.boiling_point) + idx = 2; + std::string name = material->state_name[idx]; + if (!material->prefix.empty()) + name = material->prefix + " " + name; + + if (named && figure) + name += stl_sprintf(" of HF %d", index); + return name; +} + +/* + * Plugin-specific code starts here. + */ + +using df::global::world; +using df::global::ui; +using df::global::ui_build_selector; +using df::global::ui_workshop_job_cursor; + +static bool job_material_hotkey(Core *c, df::viewscreen *top); +static command_result job_material(Core *c, vector & parameters); + +DFhackCExport const char * plugin_name ( void ) +{ + return "jobutils"; +} + +DFhackCExport command_result plugin_init (Core *c, std::vector &commands) +{ + commands.clear(); + if (world && ui && (ui_workshop_job_cursor || ui_build_selector)) { + commands.push_back( + PluginCommand( + "job-material", "Alter the material of the selected job.", + job_material, job_material_hotkey, + " job-material \n" + "Intended to be used as a keybinding:\n" + " - In 'q' mode, when a job is highlighted within a workshop\n" + " or furnace, changes the material of the job.\n" + " - In 'b' mode, during selection of building components\n" + " positions the cursor over the first available choice\n" + " with the matching material.\n" + ) + ); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( Core * c ) +{ + return CR_OK; +} + +static bool job_material_hotkey(Core *c, df::viewscreen *top) +{ + using namespace ui_sidebar_mode; + + if (!dwarfmode_hotkey(c,top)) + return false; + + switch (ui->main.mode) { + case QueryBuilding: + { + if (!ui_workshop_job_cursor) // allow missing + return false; + + df::building *selected = world->selected_building; + if (!virtual_cast(selected) && + !virtual_cast(selected)) + return false; + + // No jobs? + if (selected->jobs.empty() || + selected->jobs[0]->job_id == job_type::DestroyBuilding) + return false; + + // Add job gui activated? + if (df::global::ui_workshop_in_add && // allow missing + *df::global::ui_workshop_in_add) + return false; + + return true; + }; + case Build: + { + if (!ui_build_selector) // allow missing + return false; + + // Not selecting, or no choices? + if (ui_build_selector->building_type < 0 || + ui_build_selector->stage != 2 || + ui_build_selector->choices.empty()) + return false; + + return true; + }; + default: + return false; + } +} + +static command_result job_material_in_job(Core *c, MaterialInfo &new_mat) +{ + df::building *selected = world->selected_building; + int idx = *ui_workshop_job_cursor; + + if (idx < 0 || idx >= selected->jobs.size()) + { + c->con.printerr("Invalid job cursor index: %d\n", idx); + return CR_FAILURE; + } + + df::job *job = selected->jobs[idx]; + MaterialInfo cur_mat(job->matType, job->matIndex); + + if (!cur_mat.isValid() || cur_mat.type != 0) + { + c->con.printerr("Current job material isn't inorganic: %s\n", + cur_mat.toString().c_str()); + return CR_FAILURE; + } + + // Verify that the new material matches the old one + df::material_flags rq_flag = material_flags::IS_STONE; + if (cur_mat.mode != MaterialInfo::Builtin) + { + if (cur_mat.material->flags.is_set(material_flags::IS_GEM)) + rq_flag = material_flags::IS_GEM; + else if (cur_mat.material->flags.is_set(material_flags::IS_METAL)) + rq_flag = material_flags::IS_METAL; + else if (!cur_mat.material->flags.is_set(rq_flag)) + { + c->con.printerr("Unexpected current material type: %s\n", + cur_mat.toString().c_str()); + return CR_FAILURE; + } + } + + if (!new_mat.material->flags.is_set(rq_flag)) + { + c->con.printerr("New material %s does not satisfy requirement: %s\n", + new_mat.toString().c_str(), ENUM_KEY_STR(material_flags, rq_flag)); + return CR_FAILURE; + } + + for (unsigned i = 0; i < job->job_items.size(); i++) + { + df::job_item *item = job->job_items[i]; + MaterialInfo item_mat(item->matType, item->matIndex); + + if (item_mat != cur_mat) + { + c->con.printerr("Job item %d has different material: %s\n", + i, item_mat.toString().c_str()); + return CR_FAILURE; + } + } + + // Apply the substitution + job->matType = new_mat.type; + job->matIndex = new_mat.index; + + for (unsigned i = 0; i < job->job_items.size(); i++) + { + df::job_item *item = job->job_items[i]; + item->matType = new_mat.type; + item->matIndex = new_mat.index; + } + + c->con << "Applied material '" << new_mat.toString() + << " jo job " << ENUM_KEY_STR(job_type,job->job_id) << endl; + return CR_OK; +} + +static command_result job_material_in_build(Core *c, MaterialInfo &new_mat) +{ + df::ui_build_item_req *req = ui_build_selector->requirements[ui_build_selector->req_index]; + + for (unsigned i = 0; i < ui_build_selector->choices.size(); i++) + { + df::build_req_choicest *choice = ui_build_selector->choices[i]; + bool found = false; + + if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, choice)) + { + if (gen->matType == new_mat.type && + gen->matIndex == new_mat.index && + gen->used_count < gen->candidates.size()) + { + found = true; + } + } + else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, choice)) + { + if (spec->candidate && + spec->candidate->getActualMaterial() == new_mat.type && + spec->candidate->getActualMaterialIndex() == new_mat.index && + !req->candidate_selected[spec->candidate_id]) + { + found = true; + } + } + + if (found) + { + ui_build_selector->sel_index = i; + return CR_OK; + } + } + + c->con.printerr("Could not find material in list: %s\n", new_mat.toString().c_str()); + return CR_FAILURE; +} + +static command_result job_material(Core * c, vector & parameters) +{ + // HOTKEY COMMAND: CORE ALREADY SUSPENDED + + MaterialInfo new_mat; + if (parameters.size() == 1) + { + if (!new_mat.findInorganic(parameters[0])) { + c->con.printerr("Could not find inorganic material: %s\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + } + else + return CR_WRONG_USAGE; + + if (ui->main.mode == ui_sidebar_mode::QueryBuilding) + return job_material_in_job(c, new_mat); + if (ui->main.mode == ui_sidebar_mode::Build) + return job_material_in_build(c, new_mat); + + return CR_WRONG_USAGE; +}