diff --git a/CMakeLists.txt b/CMakeLists.txt index 46fd565b0..aedb6037a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,9 +105,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.42.06") -SET(DFHACK_RELEASE "r1") -SET(DFHACK_PRERELEASE FALSE) +set(DF_VERSION "0.43.03") +SET(DFHACK_RELEASE "alpha0") +SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/NEWS.rst b/NEWS.rst index 3bf4c4276..7571ec45a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -33,12 +33,33 @@ Changelog DFHack future ============= -DFHack 0.42.06-r1 -================= +Lua +--- +- Label widgets can now easily register handlers for mouse clicks + +New Features +------------ +- `gui/gm-editor` it's now possible to insert default types to containers. For primitive types leave the type entry empty, for references use ``*``. Fixes ----- +- `createitem`: Now moves multiple created items to cursor correctly - `exportlegends`: Improved handling of unknown enum items (fixes many errors) +- `gui/create-item`: Fixed quality when creating multiple items +- `gui/mod-manager`: Fixed error when mods folder doesn't exist +- `modtools/item-trigger`: Fixed handling of items with subtypes + +Misc Improvements +----------------- +- `fix/diplomats`: replaces ``fixdiplomats`` +- `fix/merchants`: replaces ``fixmerchants`` + +Removed +------- +- `tweak` manager-quantity: no longer needed + +DFHack 0.42.06-r1 +================= Internals --------- diff --git a/dfhack.init-example b/dfhack.init-example index a367d3441..4d2a1b2f3 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -128,10 +128,6 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job -# workflow front-end -keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow -keybinding add Alt-W@overallstatus "gui/workflow status" - # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" @@ -220,7 +216,6 @@ enable \ zone \ stocks \ autochop \ - stockflow \ stockpiles #end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. # Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. diff --git a/docs/Authors.rst b/docs/Authors.rst index cff3ab352..c4462a987 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -16,6 +16,7 @@ AndreasPK AndreasPK Angus Mezick amezick Antalia tamarakorr Anuradha Dissanayake falconne +AtomicChicken AtomicChicken belal jimhester Ben Lubar BenLubar Caldfir caldfir @@ -32,6 +33,7 @@ Erik Youngren Artanis Espen Wiborg expwnent expwnent Feng +gchristopher gchristopher Harlan Playford playfordh IndigoFenix James Logsdon jlogsdon @@ -83,6 +85,7 @@ root Roses Pheosics Ross M RossM rout +rubybrowncoat rubybrowncoat Rumrusher rumrusher RusAnon RusAnon sami diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 000884b19..2db98abad 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -44,7 +44,7 @@ also broadly maps to the ``df`` namespace in the headers generated for C++. The wrapper provides almost raw access to the memory of the game, so mistakes in manipulating objects are as likely to crash the game as equivalent plain C++ code would be. - + eg. NULL pointer access is safely detected, but dangling pointers aren't. Objects managed by the wrapper can be broadly classified into the following groups: @@ -180,7 +180,7 @@ They implement the following features: In case of inheritance, *superclass* fields have precedence over the subclass, but fields shadowed in this way can still be accessed as ``ref['subclasstype.field']``. - + This shadowing order is necessary because vtable-based classes are automatically exposed in their exact type, and the reverse rule would make access to superclass fields unreliable. @@ -3051,10 +3051,13 @@ It has the following attributes: :text_pen: Specifies the pen for active text. :text_dpen: Specifies the pen for disabled text. +:text_hpen: Specifies the pen for text hovered over by the mouse, if a click handler is registered. :disabled: Boolean or a callback; if true, the label is disabled. :enabled: Boolean or a callback; if false, the label is disabled. :auto_height: Sets self.frame.h from the text height. :auto_width: Sets self.frame.w from the text width. +:on_click: A callback called when the label is clicked (optional) +:on_rclick: A callback called when the label is right-clicked (optional) The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via @@ -3499,7 +3502,7 @@ Functions :name: custom workshop id e.g. ``SOAPMAKER`` - + .. note:: this is the only mandatory field. :fix_impassible: @@ -3678,15 +3681,15 @@ Note that this function lets errors propagate to the caller. Run an Lua script and return its environment. This command allows you to use scripts like modules for increased portability. It is highly recommended that if you are a modder you put your custom modules in ``raw/scripts`` and use ``script_environment`` instead of ``require`` so that saves with your mod installed will be self-contained and can be transferred to people who do have DFHack but do not have your mod installed. - + You can say ``dfhack.script_environment('add-thought').addEmotionToUnit([arguments go here])`` and it will have the desired effect. It will call the script in question with the global ``moduleMode`` set to ``true`` so that the script can return early. This is useful because if the script is called from the console it should deal with its console arguments and if it is called by ``script_environment`` it should only create its global functions and return. You can also access global variables with, for example ``print(dfhack.script_environment('add-thought').validArgs)`` - + The function ``script_environment`` is fast enough that it is recommended that you not store its result in a nonlocal variable, because your script might need to load a different version of that script if the save is unloaded and a save with a different mod that overrides the same script with a slightly different functionality is loaded. This will not be an issue in most cases. - + This function also permits circular dependencies of scripts. * ``dfhack.reqscript(name)`` or ``reqscript(name)`` diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 4e1886e8d..75d2d988d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -180,18 +180,6 @@ Bugfixes .. contents:: :local: -fixdiplomats -============ -Adds a Diplomat position to all Elven civilizations, allowing them to negotiate -tree cutting quotas - and you to violate them and start wars. -This was vanilla behaviour until ``0.31.12``, in which the "bug" was "fixed". - -fixmerchants -============ -Adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements. This was the default behaviour in -``0.28.181.40d`` and earlier. - .. _fix-unit-occupancy: fix-unit-occupancy @@ -302,7 +290,6 @@ Subcommands that persist until disabled or DF quits: :kitchen-keys: Fixes DF kitchen meal keybindings (:bug:`614`) :kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences :kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) -:manager-quantity: Removes the limit of 30 jobs per manager order :max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile :military-color-assigned: Color squad candidates already assigned to other squads in yellow/green diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 1184865ac..eb8dec861 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -24,6 +24,7 @@ distribution. #include "Internal.h" +#include #include #include #include @@ -414,7 +415,11 @@ bool VMethodInterposeLinkBase::apply(bool enable) if (is_applied()) return true; if (!host->vtable_ptr) + { + std::cerr << "VMethodInterposeLinkBase::apply(" << enable << "): " << name() + << ": no vtable pointer: " << host->getName() << endl; return false; + } // Retrieve the current vtable entry VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; @@ -440,6 +445,7 @@ bool VMethodInterposeLinkBase::apply(bool enable) } else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method)) { + std::cerr << "VMethodInterposeLinkBase::apply(" << enable << "): " << name() << ": set_vmethod_ptr failed" << endl; set_chain(NULL); return false; } diff --git a/library/include/df/custom/knowledge_scholar_category_flag.methods.inc b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc new file mode 100644 index 000000000..6fe896804 --- /dev/null +++ b/library/include/df/custom/knowledge_scholar_category_flag.methods.inc @@ -0,0 +1,13 @@ +df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() +{ + int32_t value = category * 32; + for (int32_t i = 0; i < 32; i++) + { + if (flags & (1 << i)) + { + value += i; + break; + } + } + return df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag(value); +} diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 6e789d35d..4bc48c1b5 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -330,15 +330,21 @@ Label = defclass(Label, Widget) Label.ATTRS{ text_pen = COLOR_WHITE, - text_dpen = COLOR_DARKGREY, + text_dpen = COLOR_DARKGREY, -- disabled + text_hpen = DEFAULT_NIL, -- highlight - default is text_pen with reversed brightness disabled = DEFAULT_NIL, enabled = DEFAULT_NIL, auto_height = true, auto_width = false, + on_click = DEFAULT_NIL, + on_rclick = DEFAULT_NIL, } function Label:init(args) self:setText(args.text) + if not self.text_hpen then + self.text_hpen = ((tonumber(self.text_pen) or tonumber(self.text_pen.fg) or 0) + 8) % 16 + end end function Label:setText(text) @@ -374,11 +380,21 @@ function Label:getTextWidth() end function Label:onRenderBody(dc) - render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self)) + local text_pen = self.text_pen + if self:getMousePos() and (self.on_click or self.on_rclick) then + text_pen = self.text_hpen + end + render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self)) end function Label:onInput(keys) if not is_disabled(self) then + if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then + self:on_click() + end + if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then + self:on_rclick() + end return check_text_keys(self, keys) end end diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index ae7c97236..2cc955d63 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -51,12 +51,12 @@ using namespace DFHack; #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dungeonmodest.h" #include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/viewscreen_jobst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_itemst.h" #include "df/viewscreen_layer.h" -#include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_assigntradest.h" @@ -66,6 +66,7 @@ using namespace DFHack; #include "df/viewscreen_petst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_workshop_profilest.h" #include "df/ui_unit_view_mode.h" #include "df/ui_sidebar_menus.h" #include "df/ui_look_list.h" @@ -429,15 +430,21 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military) } } -DEFINE_GET_FOCUS_STRING_HANDLER(layer_workshop_profile) +DEFINE_GET_FOCUS_STRING_HANDLER(workshop_profile) { - auto list1 = getLayerList(screen, 0); - if (!list1) return; - - if (vector_get(screen->workers, list1->cursor)) + typedef df::viewscreen_workshop_profilest::T_tab T_tab; + switch(screen->tab) + { + case T_tab::Workers: focus += "/Unit"; - else - focus += "/None"; + break; + case T_tab::Orders: + focus += "/Orders"; + break; + case T_tab::Restrictions: + focus += "/Restrictions"; + break; + } } DEFINE_GET_FOCUS_STRING_HANDLER(layer_noblelist) @@ -755,6 +762,10 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet) { df::viewscreen *top = Core::getTopViewscreen(); + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_jobst, top)) + { + return screen->job; + } if (VIRTUAL_CAST_VAR(joblist, df::viewscreen_joblistst, top)) { df::job *job = vector_get(joblist->jobs, joblist->cursor_pos); @@ -810,10 +821,10 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) return ref ? ref->getUnit() : NULL; } - if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_workshop_profilest, top)) + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_workshop_profilest, top)) { - if (auto list1 = getLayerList(screen, 0)) - return vector_get(screen->workers, list1->cursor); + if (screen->tab == df::viewscreen_workshop_profilest::Workers) + return vector_get(screen->workers, screen->worker_idx); return NULL; } @@ -1051,9 +1062,12 @@ df::building *Gui::getAnyBuilding(df::viewscreen *top) using df::global::world; using df::global::ui_sidebar_menus; - if (auto screen = strict_virtual_cast(top)) + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top)) return vector_get(screen->buildings, screen->cursor); + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_workshop_profilest, top)) + return df::building::find(screen->building_id); + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedBuilding(); diff --git a/library/xml b/library/xml index d3182b7f9..d48de9e90 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d3182b7f9a241d75e556937cad1f2c8824845b4f +Subproject commit d48de9e904150c4cd0e8502b6edf246737849607 diff --git a/package/darwin/dfhack b/package/darwin/dfhack index 5a04bd591..66b574cf8 100755 --- a/package/darwin/dfhack +++ b/package/darwin/dfhack @@ -14,4 +14,5 @@ fi old_tty_settings=$(stty -g) DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@" stty "$old_tty_settings" +tput sgr0 echo "" diff --git a/package/linux/dfhack b/package/linux/dfhack index 2b2d04465..b7ce1d812 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -95,6 +95,7 @@ esac # Restore previous terminal settings stty "$old_tty_settings" +tput sgr0 echo if [ -n "$DF_POST_CMD" ]; then diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 3e51e6582..0bd2cd6c1 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "Core.h" #include "Console.h" diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index dd1c6344f..cc0de019f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -99,7 +99,6 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(filltraffic filltraffic.cpp) DFHACK_PLUGIN(fix-armory fix-armory.cpp) - DFHACK_PLUGIN(fixpositions fixpositions.cpp) DFHACK_PLUGIN(fix-unit-occupancy fix-unit-occupancy.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(flows flows.cpp) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 9bdfcb02c..013038834 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -134,7 +134,7 @@ void create_jobs() { for (auto w = workshops->begin(); w != workshops->end(); ++w) { auto workshop = virtual_cast(*w); - auto links = workshop->links.take_from_pile; + auto links = workshop->profile.links.take_from_pile; if (workshop->construction_stage < 3) { // Construction in progress. diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index 8f7b47107..156176bbe 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -128,7 +128,7 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it } } if ((is_gloves || is_shoes) && !second_item) - return makeItem(prod, unit, true); + return makeItem(prod, unit, true, move_to_cursor); return true; } diff --git a/plugins/fixpositions.cpp b/plugins/fixpositions.cpp deleted file mode 100644 index e136ee0a9..000000000 --- a/plugins/fixpositions.cpp +++ /dev/null @@ -1,244 +0,0 @@ -// Fix Entity Positions - make sure Elves have diplomats and Humans have guild representatives - -#include "Core.h" -#include -#include -#include - -#include -#include "df/world.h" -#include "df/historical_entity.h" -#include "df/entity_raw.h" -#include "df/entity_position.h" -#include "df/entity_position_responsibility.h" -#include "df/entity_position_assignment.h" - -using std::string; -using std::vector; -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("fixpositions"); -REQUIRE_GLOBAL(world); - -command_result df_fixdiplomats (color_ostream &out, vector ¶meters) -{ - if (!parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - int checked = 0, fixed = 0; - for (int i = 0; i < world->entities.all.size(); i++) - { - df::historical_entity *ent = world->entities.all[i]; - // only work with civilizations - ignore groups and religions - if (ent->type != 0) - continue; - // only add diplomats for tree cap diplomacy - if (!ent->entity_raw->flags.is_set(entity_raw_flags::TREE_CAP_DIPLOMACY)) - continue; - checked++; - - bool update = true; - df::entity_position *pos = NULL; - // see if we need to add a new position or modify an existing one - for (int j = 0; j < ent->positions.own.size(); j++) - { - pos = ent->positions.own[j]; - if (pos->responsibilities[entity_position_responsibility::MAKE_INTRODUCTIONS] && - pos->responsibilities[entity_position_responsibility::MAKE_PEACE_AGREEMENTS] && - pos->responsibilities[entity_position_responsibility::MAKE_TOPIC_AGREEMENTS]) - { - // a diplomat position exists with the proper responsibilities - skip to the end - update = false; - break; - } - // Diplomat position already exists, but has the wrong options - modify it instead of creating a new one - if (pos->code == "DIPLOMAT") - break; - pos = NULL; - } - if (update) - { - // either there's no diplomat, or there is one and it's got the wrong responsibilities - if (!pos) - { - // there was no diplomat - create it - pos = new df::entity_position; - ent->positions.own.push_back(pos); - - pos->code = "DIPLOMAT"; - pos->id = ent->positions.next_position_id++; - pos->flags.set(entity_position_flags::DO_NOT_CULL); - pos->flags.set(entity_position_flags::MENIAL_WORK_EXEMPTION); - pos->flags.set(entity_position_flags::SLEEP_PRETENSION); - pos->flags.set(entity_position_flags::PUNISHMENT_EXEMPTION); - pos->flags.set(entity_position_flags::ACCOUNT_EXEMPT); - pos->flags.set(entity_position_flags::DUTY_BOUND); - pos->flags.set(entity_position_flags::COLOR); - pos->flags.set(entity_position_flags::HAS_RESPONSIBILITIES); - pos->flags.set(entity_position_flags::IS_DIPLOMAT); - pos->flags.set(entity_position_flags::IS_LEADER); - // not sure what these flags do, but the game sets them for a valid diplomat - pos->flags.set(entity_position_flags::unk_12); - pos->flags.set(entity_position_flags::unk_1a); - pos->flags.set(entity_position_flags::unk_1b); - pos->name[0] = "Diplomat"; - pos->name[1] = "Diplomats"; - pos->precedence = 70; - pos->color[0] = 7; - pos->color[1] = 0; - pos->color[2] = 1; - } - // assign responsibilities - pos->responsibilities[entity_position_responsibility::MAKE_INTRODUCTIONS] = true; - pos->responsibilities[entity_position_responsibility::MAKE_PEACE_AGREEMENTS] = true; - pos->responsibilities[entity_position_responsibility::MAKE_TOPIC_AGREEMENTS] = true; - } - - // make sure the diplomat position, whether we created it or not, is set up for proper assignment - bool assign = true; - for (int j = 0; j < ent->positions.assignments.size(); j++) - { - if (ent->positions.assignments[j]->position_id == pos->id) - { - // it is - nothing more to do here - assign = false; - break; - } - } - if (assign) - { - // it isn't - set it up - df::entity_position_assignment *asn = new df::entity_position_assignment; - ent->positions.assignments.push_back(asn); - - asn->id = ent->positions.next_assignment_id++; - asn->position_id = pos->id; - asn->flags.extend(0x1F); // make room for 32 flags - asn->flags.set(0); // and set the first one - } - if (update || assign) - fixed++; - } - out.print("Fixed %d of %d civilizations to enable tree cap diplomacy.\n", fixed, checked); - return CR_OK; -} - -command_result df_fixmerchants (color_ostream &out, vector ¶meters) -{ - if (!parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - int checked = 0, fixed = 0; - for (int i = 0; i < world->entities.all.size(); i++) - { - df::historical_entity *ent = world->entities.all[i]; - // only work with civilizations - ignore groups and religions - if (ent->type != 0) - continue; - // only add guild reps for merchant nobility - if (!ent->entity_raw->flags.is_set(entity_raw_flags::MERCHANT_NOBILITY)) - continue; - checked++; - - bool update = true; - df::entity_position *pos = NULL; - // see if we need to add a new position or modify an existing one - for (int j = 0; j < ent->positions.own.size(); j++) - { - pos = ent->positions.own[j]; - if (pos->responsibilities[entity_position_responsibility::TRADE]) - { - // a guild rep exists with the proper responsibilities - skip to the end - update = false; - break; - } - // Guild Representative position already exists, but has the wrong options - modify it instead of creating a new one - if (pos->code == "GUILD_REPRESENTATIVE") - break; - pos = NULL; - } - if (update) - { - // either there's no guild rep, or there is one and it's got the wrong responsibilities - if (!pos) - { - // there was no guild rep - create it - pos = new df::entity_position; - ent->positions.own.push_back(pos); - - pos->code = "GUILD_REPRESENTATIVE"; - pos->id = ent->positions.next_position_id++; - pos->flags.set(entity_position_flags::DO_NOT_CULL); - pos->flags.set(entity_position_flags::MENIAL_WORK_EXEMPTION); - pos->flags.set(entity_position_flags::SLEEP_PRETENSION); - pos->flags.set(entity_position_flags::PUNISHMENT_EXEMPTION); - pos->flags.set(entity_position_flags::ACCOUNT_EXEMPT); - pos->flags.set(entity_position_flags::DUTY_BOUND); - pos->flags.set(entity_position_flags::COLOR); - pos->flags.set(entity_position_flags::HAS_RESPONSIBILITIES); - pos->flags.set(entity_position_flags::IS_DIPLOMAT); - pos->flags.set(entity_position_flags::IS_LEADER); - // not sure what these flags do, but the game sets them for a valid guild rep - pos->flags.set(entity_position_flags::unk_12); - pos->flags.set(entity_position_flags::unk_1a); - pos->flags.set(entity_position_flags::unk_1b); - pos->name[0] = "Guild Representative"; - pos->name[1] = "Guild Representatives"; - pos->precedence = 40; - pos->color[0] = 7; - pos->color[1] = 0; - pos->color[2] = 1; - } - // assign responsibilities - pos->responsibilities[entity_position_responsibility::TRADE] = true; - } - - // make sure the guild rep position, whether we created it or not, is set up for proper assignment - bool assign = true; - for (int j = 0; j < ent->positions.assignments.size(); j++) - { - if (ent->positions.assignments[j]->position_id == pos->id) - { - // it is - nothing more to do here - assign = false; - break; - } - } - if (assign) - { - // it isn't - set it up - df::entity_position_assignment *asn = new df::entity_position_assignment; - ent->positions.assignments.push_back(asn); - - asn->id = ent->positions.next_assignment_id++; - asn->position_id = pos->id; - asn->flags.extend(0x1F); // make room for 32 flags - asn->flags.set(0); // and set the first one - } - if (update || assign) - fixed++; - } - out.print("Fixed %d of %d civilizations to enable merchant nobility.\n", fixed, checked); - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "fixdiplomats", "Add Diplomat position to Elven civilizations for tree cap diplomacy.", - df_fixdiplomats, false)); - commands.push_back(PluginCommand( - "fixmerchants", "Add Guild Representative position to Human civilizations for merchant nobility.", - df_fixmerchants, false)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} diff --git a/plugins/lua/stockflow.lua b/plugins/lua/stockflow.lua index 7b038d34c..4d7889843 100644 --- a/plugins/lua/stockflow.lua +++ b/plugins/lua/stockflow.lua @@ -24,9 +24,9 @@ entry_ints = { trigger_number = 3, } -PageSize = 16 -FirstRow = 4 +FirstRow = 3 CenterCol = 38 +ExtraLines = 9 -- Populate the reaction and stockpile order lists. -- To be called whenever a world is loaded. @@ -797,10 +797,30 @@ screen = gui.FramedScreen { function screen:onRenderBody(dc) -- Emulates the built-in manager screen. + + if not (self.page_size == self.frame_rect.height - ExtraLines) then + -- The screen size has changed. + self:refilter() + end + + -- Top instruction line. dc:seek(1, 1):string("Type in parts of the name to narrow your search. ", COLOR_WHITE) - dc:string(gui.getKeyDisplay("LEAVESCREEN"), COLOR_LIGHTGREEN) - dc:string(" to abort.", COLOR_WHITE) - dc:seek(1, PageSize + 5):string(self.search_string, COLOR_LIGHTCYAN) + dc:key("LEAVESCREEN"):string(" to abort.", COLOR_WHITE) + + -- Search term, if any. + dc:seek(1, FirstRow + self.page_size + 1):string(self.search_string, COLOR_LIGHTCYAN) + + -- Bottom instruction line. + dc:seek(1, FirstRow + self.page_size + 2) + dc:key("STANDARDSCROLL_UP"):key("STANDARDSCROLL_DOWN") + dc:key("STANDARDSCROLL_PAGEUP"):key("STANDARDSCROLL_PAGEDOWN") + dc:key("STANDARDSCROLL_LEFT"):key("STANDARDSCROLL_RIGHT") + dc:string(": Select", COLOR_WHITE) + + dc:seek(CenterCol, FirstRow + self.page_size + 2) + dc:key("SETUPGAME_SAVE_PROFILE_ABORT"):string(": No order", COLOR_WHITE) + + -- Reaction lines. for _, item in ipairs(self.displayed) do dc:seek(item.x, item.y):string(item.name, item.color) end @@ -815,28 +835,69 @@ function screen:onInput(keys) if selected then store_order(self.stockpile, selected.index) end + elseif keys.SETUPGAME_SAVE_PROFILE_ABORT then + self:dismiss() + clear_order(self.stockpile) elseif keys.STANDARDSCROLL_UP then self.position = self.position - 1 elseif keys.STANDARDSCROLL_DOWN then self.position = self.position + 1 elseif keys.STANDARDSCROLL_LEFT then - self.position = self.position - PageSize + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size then + -- On the first column, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous column. + self.position = self.position - self.page_size + end elseif keys.STANDARDSCROLL_RIGHT then - self.position = self.position + PageSize + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next column. + self.position = self.position + self.page_size + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STANDARDSCROLL_PAGEUP then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position - PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == 1 then + -- Move from the very first item to the very last item. + self.position = #self.reactions + elseif self.position < self.page_size*2 then + -- On the first page, move to the very first item. + self.position = 1 + else + -- Move to the same position on the previous page. + self.position = self.position - self.page_size*2 + end elseif keys.STANDARDSCROLL_PAGEDOWN then - -- Moves to the first item displayed on the new page, for some reason. - self.position = self.position + PageSize*2 - ((self.position-1) % (PageSize*2)) + if self.position == #self.reactions then + -- Move from the very last item to the very first item. + self.position = 1 + else + -- Move to the same position on the next page. + self.position = self.position + self.page_size*2 + if self.position > #self.reactions then + -- If that's past the end, move to the very last item. + self.position = #self.reactions + end + end elseif keys.STRING_A000 then -- This seems like an odd way to check for Backspace. self.search_string = string.sub(self.search_string, 1, -2) + self.position = 1 elseif keys._STRING and keys._STRING >= 32 then -- This interface only accepts letters and spaces. local char = string.char(keys._STRING) if char == " " or string.find(char, "^%a") then self.search_string = self.search_string .. string.upper(char) + self.position = 1 end end @@ -886,6 +947,13 @@ function splitstring(full, pattern) end function screen:refilter() + -- Determine which rows to show, and in which colors. + -- Todo: The official one now has three categories of search results: + -- * Cyan: Contains at least one exact word from the search terms + -- * Yellow: At least one word starts with at least one search term + -- * Grey: Each search term is found in the middle of a word + self.page_size = self.frame_rect.height - ExtraLines + local filtered = {} local needles = splitstring(self.search_string, " ") for key, value in ipairs(reaction_list) do @@ -904,12 +972,12 @@ function screen:refilter() end local start = 1 - while self.position >= start + PageSize*2 do - start = start + PageSize*2 + while self.position >= start + self.page_size*2 do + start = start + self.page_size*2 end local displayed = {} - for n = 0, PageSize*2 - 1 do + for n = 0, self.page_size*2 - 1 do local item = filtered[start + n] if not item then break @@ -918,9 +986,9 @@ function screen:refilter() local x = 1 local y = FirstRow + n - if n >= PageSize then + if n >= self.page_size then x = CenterCol - y = y - PageSize + y = y - self.page_size name = " "..name end @@ -941,6 +1009,14 @@ function screen:refilter() self.displayed = displayed end +function clear_order(stockpile) + local saved = saved_orders[stockpile.id] + if saved then + saved.entry:delete() + saved_orders[stockpile.id] = nil + end +end + function store_order(stockpile, order_number) local name = reaction_list[order_number].name -- print("Setting stockpile #"..stockpile.stockpile_number.." to "..name.." (#"..order_number..")") @@ -1011,12 +1087,6 @@ function order_quantity(order, quantity) end end - if amount > 30 then - -- Respect the quantity limit. - -- With this many in the queue, we can wait for the next cycle. - return 30 - end - return amount end @@ -1026,7 +1096,10 @@ function create_orders(order, amount) new_order.amount_left = amount new_order.amount_total = amount -- Todo: Create in a validated state if the fortress is small enough? - new_order.is_validated = 0 + new_order.status.validated = false + new_order.status.active = false + new_order.id = df.global.world.manager_order_next_id + df.global.world.manager_order_next_id = df.global.world.manager_order_next_id + 1 df.global.world.manager_orders:insert('#', new_order) end diff --git a/plugins/search.cpp b/plugins/search.cpp index 83d498367..80dcc0f61 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -15,7 +15,7 @@ #include "df/viewscreen_layer_stockpilest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_noblelistst.h" -#include "df/viewscreen_layer_workshop_profilest.h" +#include "df/viewscreen_workshop_profilest.h" #include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" @@ -1160,6 +1160,9 @@ private: if (!in_entry_mode()) { // Changing screens, reset search + int32_t *cursor_pos = get_viewscreen_cursor(); + if (cursor_pos && *cursor_pos < 0) + *cursor_pos = 0; clear_search(); reset_all(); } @@ -1654,11 +1657,16 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_noblelistst, nobles_search); // // START: Workshop profiles search list // -typedef layered_search profiles_search_base; +typedef search_generic profiles_search_base; class profiles_search : public profiles_search_base { public: + bool can_init (df::viewscreen_workshop_profilest *screen) + { + return screen->tab == df::viewscreen_workshop_profilest::T_tab::Workers; + } + string get_element_description(df::unit *element) const { return get_unit_description(element); @@ -1666,16 +1674,21 @@ public: void render() const { - print_search_option(2, 23); + print_search_option(2, gps->dimy - 5); } vector *get_primary_list() { return &viewscreen->workers; } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->worker_idx; + } }; -IMPLEMENT_HOOKS(df::viewscreen_layer_workshop_profilest, profiles_search); +IMPLEMENT_HOOKS(df::viewscreen_workshop_profilest, profiles_search); // // END: Workshop profiles search list diff --git a/plugins/sort.cpp b/plugins/sort.cpp index 7d80b4aa1..264da21db 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -16,7 +16,6 @@ #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_layer_militaryst.h" -#include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_assigntradest.h" @@ -24,6 +23,7 @@ #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_workshop_profilest.h" #include "df/layer_object_listst.h" #include "df/assign_trade_status.h" @@ -310,23 +310,6 @@ DEFINE_SORT_HANDLER(unit_sorters, layer_military, "/Positions/Candidates", milit } -/* - * Sort units in the workshop 'q'uery 'P'rofile modification screen. - */ - -DEFINE_SORT_HANDLER(unit_sorters, layer_workshop_profile, "/Unit", profile) -{ - auto list1 = getLayerList(profile, 0); - - PARSE_SPEC("units", parameters); - - if (compute_order(*pout, L, top, &order, profile->workers)) - { - reorder_cursor(&list1->cursor, order); - reorder_vector(&profile->workers, order); - } -} - DEFINE_SORT_HANDLER(unit_sorters, layer_noblelist, "/Appoint", nobles) { auto list2 = getLayerList(nobles, 1); @@ -443,6 +426,21 @@ DEFINE_SORT_HANDLER(unit_sorters, dwarfmode, "/QueryBuilding/Some/Assign", scree } } +/* + * Sort units in the workshop 'q'uery 'P'rofile modification screen. + */ + +DEFINE_SORT_HANDLER(unit_sorters, workshop_profile, "/Unit", profile) +{ + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, profile->workers)) + { + reorder_cursor(&profile->worker_idx, order); + reorder_vector(&profile->workers, order); + } +} + /* * Sort pen assignment candidate units in 'z'->'N'. */ diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 04c42f3bc..783f73d1f 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -885,10 +885,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::WEAVING: case job_skill::CLOTHESMAKING: filter = NULL; - // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC35] for item_type CLOTH, mat_type 0, flags2.deep_material, and min_dimension 10000 - for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC35].size(); i++) + // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC36] for item_type CLOTH, mat_type 0, flags2.deep_material, and min_dimension 10000 + for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC36].size(); i++) { - filter = world->items.other[items_other_id::ANY_GENERIC35][i]; + filter = world->items.other[items_other_id::ANY_GENERIC36][i]; if (filter->getType() != item_type::CLOTH) { filter = NULL; @@ -973,10 +973,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) case job_skill::FORGE_FURNITURE: case job_skill::METALCRAFT: filter = NULL; - // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC35] for item_type BAR, mat_type 0, and flags2.deep_material - for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC35].size(); i++) + // TODO: do proper search through world->items.other[items_other_id::ANY_GENERIC36] for item_type BAR, mat_type 0, and flags2.deep_material + for (size_t i = 0; i < world->items.other[items_other_id::ANY_GENERIC36].size(); i++) { - filter = world->items.other[items_other_id::ANY_GENERIC35][i]; + filter = world->items.other[items_other_id::ANY_GENERIC36][i]; if (filter->getType() != item_type::BAR) { filter = NULL; diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 3b17d9fa0..88c57fb62 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -93,7 +93,6 @@ #include "tweaks/kitchen-keys.h" #include "tweaks/kitchen-prefs-color.h" #include "tweaks/kitchen-prefs-empty.h" -#include "tweaks/manager-quantity.h" #include "tweaks/max-wheelbarrow.h" #include "tweaks/military-assign.h" #include "tweaks/nestbox-color.h" @@ -212,8 +211,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector - -#include "df/viewscreen_createquotast.h" -#include "df/manager_order.h" - -using df::global::world; - -struct manager_quantity_hook : df::viewscreen_createquotast { - typedef df::viewscreen_createquotast interpose_base; - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set* input)) - { - bool cancel = false; - bool wanted_quantity = want_quantity; - if (want_quantity) - { - for (auto it = input->begin(); it != input->end(); ++it) - { - char c = DFHack::Screen::keyToChar(*it); - if (c >= '0' && c <= '9') - { - cancel = true; - size_t len = strlen(str_filter); - if (len < 5) - { - str_filter[len] = c; - str_filter[len + 1] = '\0'; - } - } - } - } - if (cancel) - return; - // Native feed() adds manager order, updates want_quantity, and removes SELECT from input - int select = input->count(df::interface_key::SELECT); - INTERPOSE_NEXT(feed)(input); - if (wanted_quantity && select && strlen(str_filter) > 0) - { - df::manager_order* order = world->manager_orders[world->manager_orders.size() - 1]; - int16_t count = 1; - try - { - count = std::stoi(str_filter); - } - catch (...) { } - if (count < 1) - count = 1; - order->amount_total = count; - order->amount_left = count; - } - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(manager_quantity_hook, feed); diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor index 26c600132..704aed444 160000 --- a/scripts/3rdparty/lethosor +++ b/scripts/3rdparty/lethosor @@ -1 +1 @@ -Subproject commit 26c60013223e02b5558e31bed8e0625495430995 +Subproject commit 704aed4447f27ae802dae6994479ebc9c46568cc diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index eb34dadab..9cfacece0 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -1604,7 +1604,7 @@ local function find_process_jobs() if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then local cursor = df.global.T_cursor:new() addr = zone:find_interactive([[ -Searching for process_jobs. Please position the cursor to the right +Searching for process_jobs. Please position the cursor to the left of at least 10 vacant natural floor tiles.]], 'int8_t', function(idx) @@ -1660,7 +1660,7 @@ local function find_process_dig() if dwarfmode_to_top() and dfhack.internal.getAddress('cursor') then local cursor = df.global.T_cursor:new() addr = zone:find_interactive([[ -Searching for process_dig. Please position the cursor to the right +Searching for process_dig. Please position the cursor to the left of at least 10 unmined, unrevealed tiles.]], 'int8_t', function(idx) diff --git a/scripts/devel/save-version.lua b/scripts/devel/save-version.lua index 12761a14f..450aa04cf 100644 --- a/scripts/devel/save-version.lua +++ b/scripts/devel/save-version.lua @@ -47,6 +47,7 @@ versions = { [1360] = "0.31.23", [1361] = "0.31.24", [1362] = "0.31.25", + [1372] = "0.34.01", [1374] = "0.34.02", [1376] = "0.34.03", @@ -58,6 +59,7 @@ versions = { [1402] = "0.34.09", [1403] = "0.34.10", [1404] = "0.34.11", + [1441] = "0.40.01", [1442] = "0.40.02", [1443] = "0.40.03", @@ -82,12 +84,16 @@ versions = { [1479] = "0.40.22", [1480] = "0.40.23", [1481] = "0.40.24", + [1531] = "0.42.01", [1532] = "0.42.02", [1533] = "0.42.03", [1534] = "0.42.04", [1537] = "0.42.05", [1542] = "0.42.06", + + [1551] = "0.43.01", + [1552] = "0.43.02", } min_version = math.huge diff --git a/scripts/fix/diplomats.lua b/scripts/fix/diplomats.lua new file mode 100644 index 000000000..4e8c6daf7 --- /dev/null +++ b/scripts/fix/diplomats.lua @@ -0,0 +1,106 @@ +-- Add Elven diplomats to negotiate tree caps +--[[=begin + +fix/diplomats +============= +Adds a Diplomat position to all Elven civilizations, allowing them to negotiate +tree cutting quotas - and you to violate them and start wars. +This was vanilla behaviour until ``0.31.12``, in which the "bug" was "fixed". + +=end]] + + +function update_pos(ent) + local pos = df.entity_position:new() + ent.positions.own:insert('#', pos) + + pos.code = "DIPLOMAT" + pos.id = ent.positions.next_position_id + 1 + ent.positions.next_position_id = ent.positions.next_position_id + 1 + + pos.flags.DO_NOT_CULL = true + pos.flags.MENIAL_WORK_EXEMPTION = true + pos.flags.SLEEP_PRETENSION = true + pos.flags.PUNISHMENT_EXEMPTION = true + pos.flags.ACCOUNT_EXEMPT = true + pos.flags.DUTY_BOUND = true + pos.flags.COLOR = true + pos.flags.HAS_RESPONSIBILITIES = true + pos.flags.IS_DIPLOMAT = true + pos.flags.IS_LEADER = true + -- not sure what these flags do, but the game sets them for a valid diplomat + pos.flags.unk_12 = true + pos.flags.unk_1a = true + pos.flags.unk_1b = true + pos.name[0] = "Diplomat" + pos.name[1] = "Diplomats" + pos.precedence = 70 + pos.color[0] = 7 + pos.color[1] = 0 + pos.color[2] = 1 + + return pos +end + + + +local checked = 0 +local fixed = 0 + +for _,ent in pairs(df.global.world.entities.all) do + if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.TREE_CAP_DIPLOMACY then + checked = checked + 1 + + update = true + local found_position + -- see if we need to add a new position or modify an existing one + for _,pos in pairs(ent.positions.own) do + if pos.responsibilities.MAKE_INTRODUCTIONS and + pos.responsibilities.MAKE_PEACE_AGREEMENTS and + pos.responsibilities.MAKE_TOPIC_AGREEMENTS then + -- a diplomat position exists with the proper responsibilities - skip to the end + update = false + found_position=pos + break + end + -- Diplomat position already exists, but has the wrong options - modify it instead of creating a new one + if pos.code == "DIPLOMAT" then + found_position=pos + break + end + end + if update then + -- either there's no diplomat, or there is one and it's got the wrong responsibilities + if not found_position then + found_position = add_guild_rep( ent ) + end + -- assign responsibilities + found_position.responsibilities.MAKE_INTRODUCTIONS = true + found_position.responsibilities.MAKE_PEACE_AGREEMENTS = true + found_position.responsibilities.MAKE_TOPIC_AGREEMENTS = true + end + -- make sure the diplomat position, whether we created it or not, is set up for proper assignment + local assign = true + for _,p in pairs(ent.positions.assignments) do + if p.position_id == found_position.id then -- it is - nothing more to do here + assign = false + break + end + end + if assign then -- it isn't - set it up + local asn = df.entity_position_assignment:new() + ent.positions.assignments:insert('#', asn); + + asn.id = ent.positions.next_assignment_id + ent.positions.next_assignment_id = asn.id + 1 + asn.position_id = found_position.id + asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags + asn.flags[0] = true -- and set the first one + end + if update or assign then + fixed = fixed + 1 + end + end +end + +print("Enabled tree cap diplomacy for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/fix/merchants.lua b/scripts/fix/merchants.lua new file mode 100644 index 000000000..ae3ef4233 --- /dev/null +++ b/scripts/fix/merchants.lua @@ -0,0 +1,104 @@ +-- Allow humans to make trade agreements +--[[=begin + +fix/merchants +============= +Adds the Guild Representative position to all Human civilizations, +allowing them to make trade agreements. This was the default behaviour in +``0.28.181.40d`` and earlier. + +=end]] + + +function add_guild_rep(ent) + -- there was no guild rep - create it + local pos = df.entity_position:new() + ent.positions.own:insert('#', pos) + + pos.code = "GUILD_REPRESENTATIVE" + pos.id = ent.positions.next_position_id + 1 + ent.positions.next_position_id = ent.positions.next_position_id + 1 + + pos.flags.DO_NOT_CULL = true + pos.flags.MENIAL_WORK_EXEMPTION = true + pos.flags.SLEEP_PRETENSION = true + pos.flags.PUNISHMENT_EXEMPTION = true + pos.flags.ACCOUNT_EXEMPT = true + pos.flags.DUTY_BOUND = true + pos.flags.COLOR = true + pos.flags.HAS_RESPONSIBILITIES = true + pos.flags.IS_DIPLOMAT = true + pos.flags.IS_LEADER = true + -- not sure what these flags do, but the game sets them for a valid guild rep + pos.flags.unk_12 = true + pos.flags.unk_1a = true + pos.flags.unk_1b = true + pos.name[0] = "Guild Representative" + pos.name[1] = "Guild Representatives" + pos.precedence = 40 + pos.color[0] = 7 + pos.color[1] = 0 + pos.color[2] = 1 + + return pos +end + +local checked = 0 +local fixed = 0 + +for _,ent in pairs(df.global.world.entities.all) do + if ent.type == df.historical_entity_type.Civilization and ent.entity_raw.flags.MERCHANT_NOBILITY then + checked = checked + 1 + + update = true + -- see if we need to add a new position or modify an existing one + local found_position + for _,pos in pairs(ent.positions.own) do + if pos.responsibilities.TRADE and pos.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS then + -- a guild rep exists with the proper responsibilities - skip to the end + update = false + found_position=pos + break + end + -- Guild Representative position already exists, but has the wrong options - modify it instead of creating a new one + if pos.code == "GUILD_REPRESENTATIVE" then + found_position=pos + break + end + end + if update then + -- either there's no guild rep, or there is one and it's got the wrong responsibilities + if not found_position then + found_position = add_guild_rep(ent) + end + -- assign responsibilities + found_position.responsibilities.ESTABLISH_COLONY_TRADE_AGREEMENTS = true + found_position.responsibilities.TRADE=true + end + + -- make sure the guild rep position, whether we created it or not, is set up for proper assignment + local assign = true + for _,p in pairs(ent.positions.assignments) do + if p.position_id == found_position.id then -- it is - nothing more to do here + assign = false + break + end + end + if assign then + -- it isn't - set it up + local asn = df.entity_position_assignment:new() + ent.positions.assignments:insert('#', asn) + + asn.id = ent.positions.next_assignment_id + ent.positions.next_assignment_id = asn.id + 1 + asn.position_id = found_position.id + asn.flags:resize(math.max(32, #asn.flags)) -- make room for 32 flags + asn.flags[0] = true -- and set the first one + end + if update or assign then + fixed = fixed + 1 + end + end +end + +print("Added merchant nobility for "..fixed.." of "..checked.." civilizations.") diff --git a/scripts/gui/create-item.lua b/scripts/gui/create-item.lua index d22f68904..a2c38247a 100644 --- a/scripts/gui/create-item.lua +++ b/scripts/gui/create-item.lua @@ -117,7 +117,7 @@ local function getMatFilter(itemtype) return itemTypes[df.item_type[itemtype]] or getRestrictiveMatFilter(itemtype) end -local function createItem(mat,itemType,quality,creator,description) +local function createItem(mat,itemType,quality,creator,description,amount) local item=df.item.find(dfhack.items.createItem(itemType[1], itemType[2], mat[1], mat[2], creator)) assert(item, 'failed to create item') quality = math.max(0, math.min(5, quality - 1)) @@ -125,6 +125,9 @@ local function createItem(mat,itemType,quality,creator,description) if df.item_type[itemType[1]]=='SLAB' then item.description=description end + if tonumber(amount) > 1 then + item:setStackSize(amount) + end end local function qualityTable() @@ -207,11 +210,10 @@ function hackWish(unit) if not amountok then return end if mattype and itemtype then if df.item_type.attrs[itemtype].is_stackable then - local proper_item=df.item.find(dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit)) - proper_item:setStackSize(amount) + createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,amount) else for i=1,amount do - dfhack.items.createItem(itemtype, itemsubtype, mattype, matindex, unit) + createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) end end return true @@ -219,7 +221,7 @@ function hackWish(unit) return false else if mattype and itemtype then - createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description) + createItem({mattype,matindex},{itemtype,itemsubtype},quality,unit,description,1) return true end return false diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 9012cbd55..38394ec36 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -136,7 +136,8 @@ function GmEditorUi:init(args) subviews={ mainList, widgets.Label{text={{text="",id="name"},{gap=1,text="Help",key=keybindings.help.key,key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, - widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}}, + widgets.Label{text={{text="Search",key=keybindings.start_filter.key,key_sep = '()'},{text=": "}},frame={l=1,t=2}, + on_click=function() self:enable_input(true) end}, widgets.EditField{frame={l=12,t=2},active=false,on_change=self:callback('text_input'),on_submit=self:callback("enable_input",false),view_id="filter_input"}, --widgets.Label{text="BLAH2"} } @@ -187,7 +188,7 @@ function GmEditorUi:find(test) end end function GmEditorUi:find_id() - local key = self:getSelectedKey() + local key = tostring(self:getSelectedKey()) local id = tonumber(self:getSelectedValue()) if not id then return end local opts = {} @@ -211,22 +212,26 @@ function GmEditorUi:find_id() end function GmEditorUi:insertNew(typename) local tp=typename - if typename== nil then - dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",self:callback("insertNew")) - return - end - local ntype=df[tp] - if ntype== nil then - dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_LIGHTRED) + if typename == nil then + dialog.showInputPrompt("Class type","You can:\n * Enter type name (without 'df.')\n * Leave empty for default type and 'nil' value\n * Enter '*' for default type and 'new' constructed pointer value",COLOR_WHITE,"",self:callback("insertNew")) return end local trg=self:currentTarget() if trg.target and trg.target._kind and trg.target._kind=="container" then - local thing=ntype:new() - dfhack.call_with_finalizer(1,false,df.delete,thing,function (tscreen,target,to_insert) - target:insert("#",to_insert); tscreen:updateTarget(true,true);end,self,trg.target,thing) - + if tp == "" then + trg.target:resize(#trg.target+1) + elseif tp== "*" then + trg.target:insert("#",{new=true}) + else + local ntype=df[tp] + if ntype== nil then + dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) + return + end + trg.target:insert("#",{new=ntype}) + end + self:updateTarget(true,true) end end function GmEditorUi:deleteSelected(key) diff --git a/scripts/gui/mod-manager.lua b/scripts/gui/mod-manager.lua index 692deeb62..5c3b39375 100644 --- a/scripts/gui/mod-manager.lua +++ b/scripts/gui/mod-manager.lua @@ -159,7 +159,7 @@ function manager:init(args) local mods=self.mods local mlist=dfhack.internal.getDir(mod_dir) - if #mlist==0 then + if mlist==nil or #mlist==0 then qerror("Mod directory not found! Are you sure it is in:"..mod_dir) end for k,v in ipairs(mlist) do diff --git a/scripts/item-descriptions.lua b/scripts/item-descriptions.lua index fe7e7ac00..b2c57d2e4 100644 --- a/scripts/item-descriptions.lua +++ b/scripts/item-descriptions.lua @@ -360,8 +360,12 @@ descriptions = { ITEM_TOOL_FORK_CARVING = { "A carving fork typically has only two prongs and is exceptionally long.", "It is used to hold down a piece of cooked meat while using a knife."}, + ITEM_TOOL_HELVE = { + "A helve is the handle of a tool such as an axe.", + "It is not useful in this state - but adding a rock makes a stone axe,", + "which can be used for woodcutting in Adventure mode."}, ITEM_TOOL_HIVE = { - " Hives are structures that house colonies of honey bees. To be", + "Hives are structures that house colonies of honey bees. To be", "productive, they need to be constructed on an above-ground tile with", "an accessible honey bee colony somewhere on the map. Some time after", "bees are 'installed' by a beekeeper, the hive will be ready to harvest", @@ -417,6 +421,9 @@ descriptions = { ITEM_TOOL_STEPLADDER = { "A small stepladder. If you have one of these, you can use zones to", "assign your dwarves to pick fruit from certain trees."}, + ITEM_TOOL_STONE_AXE = { + "This tool can be made in Adventure mode, and is used to cut", + "trees for building or carpentry."}, ITEM_TOOL_WHEELBARROW = { "A small hand-cart with long handles and a single wheel, this", "wheelbarrow makes heavy hauling jobs much more manageable."}, diff --git a/scripts/modtools/item-trigger.lua b/scripts/modtools/item-trigger.lua index 7a3f84bf0..ee44fd447 100644 --- a/scripts/modtools/item-trigger.lua +++ b/scripts/modtools/item-trigger.lua @@ -64,10 +64,19 @@ function processTrigger(command) dfhack.run_command(table.unpack(command2)) end +function getitemType(item) + if item:getSubtype() ~= -1 then + itemType = dfhack.items.getSubtypeDef(item:getType(),item:getSubtype()).id + else + itemType = df.item_type[item:getType()] + end + return itemType +end + function handler(table) local itemMat = dfhack.matinfo.decode(table.item) local itemMatStr = itemMat:getToken() - local itemType = dfhack.items.getSubtypeDef(table.item:getType(),table.item:getSubtype()).id + local itemType = getitemType(table.item) table.itemMat = itemMat table.itemType = itemType @@ -177,6 +186,7 @@ arguments: trigger the command for items of this type examples: ITEM_WEAPON_PICK + RING -onStrike trigger the command when someone strikes someone with an appropriate weapon -onEquip @@ -243,6 +253,7 @@ if not args.command then end if args.itemType then + if dfhack.items.findType(args.itemType) == -1 then local temp for _,itemdef in ipairs(df.global.world.raws.itemdefs.all) do if itemdef.id == args.itemType then @@ -254,6 +265,7 @@ if args.itemType then error 'Could not find item type.' end args.itemType = temp + end end local numConditions = (args.material and 1 or 0) + (args.itemType and 1 or 0) + (args.contaminant and 1 or 0)