From aa22917aeba47ffcf581f56afeb73c2f289d0194 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 31 Jan 2023 23:52:30 -0800 Subject: [PATCH 01/10] add new control panel textures --- data/art/control-panel.png | Bin 0 -> 639 bytes library/include/modules/Textures.h | 5 +++++ library/modules/Textures.cpp | 7 +++++++ 3 files changed, 12 insertions(+) create mode 100644 data/art/control-panel.png diff --git a/data/art/control-panel.png b/data/art/control-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..2f5e9468af89d99e93137a1b95babf4479dae8bd GIT binary patch literal 639 zcmV-_0)YLAP)u4(3A3g<=htBMUaLt3`NV13#62n`I8G)`W z_&$#$%@e+90!i7xmv9k?If5q3NHXXBPDM0xAif2H7l&7)Ddyo6gfJj}Hqtf%k{fqy zge&m(Km_-j51)>n*bih_TR4gc@F8Lt;#+5}7$AG^wIxJ4)b)VIZigGlA`ssyX{nRs zVdFxEv;Ds?#W8&g1Yf?thx-`7fgNr7m7b_o~%u_MWvUY0|bj-U%3tmRHzOPX`%2R}hG z2U6Lxa|!~>WK6$dO|)o98o7K%7J=5okOl)8LAz+rH_V0y<>ml^oryEnG8qCMJ-q{A z$Vsb^)9@H=S`Lb)C9c{SnBa$P(-K!(8s4-d)x(3jX$b_sZ(Kq5FlxIJqJa3eG}B;i zTH{&xf`zp$jp=_(A7T|B?41W(z9qZ!01#jRdw6w(;>%@%0000EWmrjOO-%qQ00008 Z000000002eQ Date: Tue, 31 Jan 2023 23:54:27 -0800 Subject: [PATCH 02/10] allow tile in Label tokens to be either pen or id --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 3 ++- library/lua/gui/widgets.lua | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..7fac2030c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -68,6 +68,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `helpdb`: changed from auto-refreshing every 60 seconds to only refreshing on explicit call to ``helpdb.refresh()``. docs very rarely change during a play session, and the automatic database refreshes were slowing down the startup of `gui/launcher` - ``widgets.Label``: ``label.scroll()`` now understands ``home`` and ``end`` keywords for scrolling to the top or bottom - ``dfhack.units.getCitizens()``: gets a list of citizens +- ``Label``: token ``tile`` properties can now be either pens or numeric texture ids ## Removed - `autohauler`: no plans to port to v50, as it just doesn't make sense with the new work detail system diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 658893ec1..78b324f63 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -4658,7 +4658,8 @@ containing newlines, or a table with the following possible fields: * ``token.tile = pen`` - Specifies a pen to paint as one tile before the main part of the token. + Specifies a pen or texture index to paint as one tile before the main part of + the token. * ``token.width = ...`` diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0696459cd..4228bc9ad 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1110,7 +1110,9 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if token.tile then x = x + 1 if dc then - dc:tile(nil, token.tile) + local tile_pen = tonumber(token.tile) and + to_pen{tile=token.tile} or token.tile + dc:char(nil, token.tile) if token.width then dc:advance(token.width-1) end From 5127f0657193766a8e96d983f76f19acd393cd84 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 31 Jan 2023 23:55:30 -0800 Subject: [PATCH 03/10] expose new tiles to Lua --- library/LuaApi.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 699c8aef7..bb2392546 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1694,6 +1694,7 @@ static const LuaWrapper::FunctionReg dfhack_textures_module[] = { WRAPM(Textures, getRedPinTexposStart), WRAPM(Textures, getIconsTexposStart), WRAPM(Textures, getOnOffTexposStart), + WRAPM(Textures, getControlPanelTexposStart), WRAPM(Textures, getThinBordersTexposStart), WRAPM(Textures, getMediumBordersTexposStart), WRAPM(Textures, getPanelBordersTexposStart), From be40d55e64e2a5fd1f211e845a2a81bdbc0c6184 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Mon, 9 Jan 2023 17:31:49 -0800 Subject: [PATCH 04/10] update seedwatch --- docs/plugins/seedwatch.rst | 41 ++- library/include/LuaTools.h | 8 + library/include/modules/Kitchen.h | 30 +- library/modules/Kitchen.cpp | 78 ---- plugins/CMakeLists.txt | 2 +- plugins/lua/seedwatch.lua | 67 ++++ plugins/seedwatch.cpp | 588 ++++++++++++++++++------------ 7 files changed, 452 insertions(+), 362 deletions(-) create mode 100644 plugins/lua/seedwatch.lua diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst index b41f3a977..12a728c51 100644 --- a/docs/plugins/seedwatch.rst +++ b/docs/plugins/seedwatch.rst @@ -5,42 +5,47 @@ seedwatch :summary: Manages seed and plant cooking based on seed stock levels. :tags: fort auto plants -Each seed type can be assigned a target. If the number of seeds of that type -falls below that target, then the plants and seeds of that type will be excluded -from cookery. If the number rises above the target + 20, then cooking will be -allowed. +Unlike brewing and other kinds of processing, cooking plants does not produce +a usable seed. By default, all plants are allowed to be cooked. This often leads +to the situation where dwarves have no seeds left to plant crops with because +they cooked all the relevant plants. Seedwatch protects you from this problem. -The plugin needs a fortress to be loaded and will deactivate automatically -otherwise. You have to reactivate with ``enable seedwatch`` after you load a -fort. +Each seed type can be assigned a target stock amount. If the number of seeds of +that type falls below that target, then the plants and seeds of that type will +be protected from cookery. If the number rises above the target + 20, then +cooking will be allowed again. Usage ----- ``enable seedwatch`` - Start managing seed and plant cooking. By default, no types are watched. - You have to add them with further ``seedwatch`` commands. + Start managing seed and plant cooking. By default, all types are watched + with a target of ``30``, but you can adjust the list or even + ``seedwatch clear`` it and start your own if you like. +``seedwatch [status]`` + Display whether seedwatch is enabled and prints out the watch list, along + with the current seed counts. ``seedwatch `` Adds the specified type to the watchlist (if it's not already there) and sets the target number of seeds to the specified number. You can pass the keyword ``all`` instead of a specific type to set the target for all types. -``seedwatch `` - Removes the specified type from the watch list. + If you set the target to ``0``, it removes the specified type from the + watch list. ``seedwatch clear`` - Clears all types from the watch list. -``seedwatch info`` - Display whether seedwatch is enabled and prints out the watch list. + Clears all types from the watch list. Same as ``seedwatch all 0``. -To print out a list of all plant types, you can run this command:: +To see a list of all plant types that you might want to set targets for, you can +run this command:: devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 Examples -------- -``seedwatch all 30`` - Adds all seeds to the watch list and sets the targets to 30. +``enable seedwatch`` + Adds all seeds to the watch list, sets the targets to 30, and starts + monitoring your seed stock levels. ``seedwatch MUSHROOM_HELMET_PLUMP 50`` Add Plump Helmets to the watch list and sets the target to 50. -``seedwatch MUSHROOM_HELMET_PLUMP`` +``seedwatch MUSHROOM_HELMET_PLUMP 0`` removes Plump Helmets from the watch list. diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 9e1901f03..ca9aac788 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -30,6 +30,7 @@ distribution. #include #include #include +#include #include "df/interfacest.h" @@ -378,6 +379,13 @@ namespace DFHack {namespace Lua { TableInsert(L, entry.first, entry.second); } + template + void Push(lua_State *L, const std::unordered_map &pmap) { + lua_createtable(L, 0, pmap.size()); + for (auto &entry : pmap) + TableInsert(L, entry.first, entry.second); + } + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); DFHACK_EXPORT bool IsCoreContext(lua_State *state); diff --git a/library/include/modules/Kitchen.h b/library/include/modules/Kitchen.h index 3fde8edf8..f2708c844 100644 --- a/library/include/modules/Kitchen.h +++ b/library/include/modules/Kitchen.h @@ -27,11 +27,6 @@ distribution. * kitchen settings */ #include "Export.h" -#include "Module.h" -#include "Types.h" -#include "VersionInfo.h" -#include "Core.h" -#include "modules/Items.h" #include "df/kitchen_exc_type.h" /** @@ -43,38 +38,21 @@ namespace DFHack { namespace Kitchen { +// print the exclusion list, with the material index also translated into its token (for organics) - for debug really +DFHACK_EXPORT void debug_print(color_ostream &out); + /** * Kitchen exclusions manipulator. Currently geared towards plants and seeds. * \ingroup grp_modules * \ingroup grp_kitchen */ -// print the exclusion list, with the material index also translated into its token (for organics) - for debug really -DFHACK_EXPORT void debug_print(color_ostream &out); - // remove this plant from the exclusion list if it is in it DFHACK_EXPORT void allowPlantSeedCookery(int32_t plant_id); // add this plant to the exclusion list, if it is not already in it DFHACK_EXPORT void denyPlantSeedCookery(int32_t plant_id); -// fills a map with info from the limit info storage entries in the exclusion list -DFHACK_EXPORT void fillWatchMap(std::map& watchMap); - -// Finds the index of a limit info storage entry. Returns -1 if not found. -DFHACK_EXPORT int findLimit(int32_t plant_id); - -// removes a limit info storage entry from the exclusion list if it's present -DFHACK_EXPORT bool removeLimit(int32_t plant_id); - -// add a limit info storage item to the exclusion list, or alters an existing one -DFHACK_EXPORT bool setLimit(int32_t plant_id, int16_t limit); - -// clears all limit info storage items from the exclusion list -DFHACK_EXPORT void clearLimits(); - -DFHACK_EXPORT std::size_t size(); - // Finds the index of a kitchen exclusion in plotinfo.kitchen.exc_types. Returns -1 if not found. DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, @@ -90,5 +68,7 @@ DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index); +DFHACK_EXPORT std::size_t size(); + } } diff --git a/library/modules/Kitchen.cpp b/library/modules/Kitchen.cpp index 65e47f528..5bbc2ee7c 100644 --- a/library/modules/Kitchen.cpp +++ b/library/modules/Kitchen.cpp @@ -78,84 +78,6 @@ void Kitchen::denyPlantSeedCookery(int32_t plant_id) type->material_defs.idx[plant_material_def::basic_mat]); } -void Kitchen::fillWatchMap(std::map& watchMap) -{ - watchMap.clear(); - for (std::size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - watchMap[plotinfo->kitchen.mat_indices[i]] = plotinfo->kitchen.mat_types[i]; - } - } -} - -int Kitchen::findLimit(int32_t plant_id) -{ - for (size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_types[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.mat_indices[i] == plant_id && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - return int(i); - } - } - return -1; -} - -bool Kitchen::removeLimit(int32_t plant_id) -{ - int i = findLimit(plant_id); - if (i < 0) - return false; - - plotinfo->kitchen.item_types.erase(plotinfo->kitchen.item_types.begin() + i); - plotinfo->kitchen.item_subtypes.erase(plotinfo->kitchen.item_subtypes.begin() + i); - plotinfo->kitchen.mat_types.erase(plotinfo->kitchen.mat_types.begin() + i); - plotinfo->kitchen.mat_indices.erase(plotinfo->kitchen.mat_indices.begin() + i); - plotinfo->kitchen.exc_types.erase(plotinfo->kitchen.exc_types.begin() + i); - return true; -} - -bool Kitchen::setLimit(int32_t plant_id, int16_t limit) -{ - if (limit > SEEDLIMIT_MAX) - limit = SEEDLIMIT_MAX; - - int i = findLimit(plant_id); - if (i < 0) - { - plotinfo->kitchen.item_types.push_back(SEEDLIMIT_ITEMTYPE); - plotinfo->kitchen.item_subtypes.push_back(SEEDLIMIT_ITEMSUBTYPE); - plotinfo->kitchen.mat_types.push_back(limit); - plotinfo->kitchen.mat_indices.push_back(plant_id); - plotinfo->kitchen.exc_types.push_back(SEEDLIMIT_EXCTYPE); - } - else - { - plotinfo->kitchen.mat_types[i] = limit; - } - return true; -} - -void Kitchen::clearLimits() -{ - for (size_t i = 0; i < size(); ++i) - { - if (plotinfo->kitchen.item_types[i] == SEEDLIMIT_ITEMTYPE && - plotinfo->kitchen.item_subtypes[i] == SEEDLIMIT_ITEMSUBTYPE && - plotinfo->kitchen.exc_types[i] == SEEDLIMIT_EXCTYPE) - { - removeLimit(plotinfo->kitchen.mat_indices[i]); - --i; - } - } -} - size_t Kitchen::size() { return plotinfo->kitchen.item_types.size(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 41b3ad542..d5f35fb21 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -149,7 +149,7 @@ add_subdirectory(remotefortressreader) #add_subdirectory(rendermax) dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua) #dfhack_plugin(search search.cpp) -dfhack_plugin(seedwatch seedwatch.cpp) +dfhack_plugin(seedwatch seedwatch.cpp LINK_LIBRARIES lua) dfhack_plugin(showmood showmood.cpp) #dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) #dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) diff --git a/plugins/lua/seedwatch.lua b/plugins/lua/seedwatch.lua new file mode 100644 index 000000000..a72c65d47 --- /dev/null +++ b/plugins/lua/seedwatch.lua @@ -0,0 +1,67 @@ +local _ENV = mkmodule('plugins.seedwatch') + +local argparse = require('argparse') + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +local function print_status() + print(('seedwatch is %s'):format(isEnabled() and "enabled" or "disabled")) + print() + print('usable seed counts and current targets:') + local watch_map, seed_counts = seedwatch_getData() + local sum = 0 + local plants = df.global.world.raws.plants.all + for k,v in pairs(seed_counts) do + print((' %4d/%d %s'):format(v, watch_map[k] or 0, plants[k].id)) + sum = sum + v + end + print() + print(('total usable seeds: %d'):format(sum)) +end + +local function set_target(name, num) + if not name or #name == 0 then + qerror('must specify "all" or plant name') + end + + num = tonumber(num) + num = num and math.floor(num) or nil + if not num or num < 0 then + qerror('target must be a non-negative integer') + end + + seedwatch_setTarget(name, num) +end + +function parse_commandline(...) + local args, opts = {...}, {} + local positionals = process_args(opts, args) + + if opts.help then + return false + end + + local command = positionals[1] + if not command or command == 'status' then + print_status() + elseif command == 'clear' then + set_target('all', 0) + elseif positionals[2] and positionals[3] then + set_target(positionals[2], positionals[3]) + else + return false + end + + return true +end + +return _ENV diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 13ed8e66e..c6482b1ac 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -4,218 +4,113 @@ // With thanks to peterix for DFHack and Quietust for information // http://www.bay12forums.com/smf/index.php?topic=91166.msg2605147#msg2605147 -#include -#include -#include -#include "Console.h" -#include "Core.h" -#include "Export.h" +#include "Debug.h" +#include "LuaTools.h" #include "PluginManager.h" -#include "modules/World.h" -#include "modules/Materials.h" +#include "TileTypes.h" + #include "modules/Kitchen.h" -#include "VersionInfo.h" -#include "df/world.h" -#include "df/plant_raw.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + #include "df/item_flags.h" #include "df/items_other_id.h" +#include "df/plant_raw.h" +#include "df/world.h" + +#include -using namespace std; using namespace DFHack; using namespace df::enums; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; + DFHACK_PLUGIN("seedwatch"); -DFHACK_PLUGIN_IS_ENABLED(running); // whether seedwatch is counting the seeds or not +DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); -const int buffer = 20; // seed number buffer - 20 is reasonable +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(seedwatch, config, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(seedwatch, cycle, DebugCategory::LINFO); +} // abbreviations for the standard plants -map abbreviations; - -bool ignoreSeeds(df::item_flags& f) // seeds with the following flags should not be counted -{ - return - f.bits.dump || - f.bits.forbid || - f.bits.garbage_collect || - f.bits.hidden || - f.bits.hostile || - f.bits.on_fire || - f.bits.rotten || - f.bits.trader || - f.bits.in_building || - f.bits.in_job; -}; +static unordered_map abbreviations; +static map world_plant_ids; +static const int DEFAULT_TARGET = 30; +static const int TARGET_BUFFER = 20; // seed number buffer; 20 is reasonable -// searches abbreviations, returns expansion if so, returns original if not -string searchAbbreviations(string in) -{ - if(abbreviations.count(in) > 0) - { - return abbreviations[in]; - } - else - { - return in; - } +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string SEED_CONFIG_KEY_PREFIX = string(plugin_name) + "/seed/"; +static PersistentDataItem config; +static unordered_map watched_seeds; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, }; -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if(enable == true) - { - if(Core::getInstance().isWorldLoaded()) - { - running = true; - out.print("seedwatch supervision started.\n"); - } else { - out.printerr( - "This plugin needs a fortress to be loaded and will deactivate automatically otherwise.\n" - "Activate with 'seedwatch start' after you load the game.\n" - ); - } - } else { - running = false; - out.print("seedwatch supervision stopped.\n"); - } +enum SeedConfigValues { + SEED_CONFIG_ID = 0, + SEED_CONFIG_TARGET = 1, +}; - return CR_OK; +static int get_config_val(PersistentDataItem &c, int index) { + if (!c.isValid()) + return -1; + return c.ival(index); +} +static bool get_config_bool(PersistentDataItem &c, int index) { + return get_config_val(c, index) == 1; +} +static void set_config_val(PersistentDataItem &c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; +} +static void set_config_bool(PersistentDataItem &c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); } -command_result df_seedwatch(color_ostream &out, vector& parameters) -{ - CoreSuspender suspend; - - map plantIDs; - for(size_t i = 0; i < world->raws.plants.all.size(); ++i) - { - auto & plant = world->raws.plants.all[i]; - if (plant->material_defs.type[plant_material_def::seed] != -1) - plantIDs[plant->id] = i; - } - - t_gamemodes gm; - World::ReadGameMode(gm);// FIXME: check return value - - // if game mode isn't fortress mode - if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type)) - { - // just print the help - return CR_WRONG_USAGE; - } +static PersistentDataItem & ensure_seed_config(color_ostream &out, int id) { + if (watched_seeds.count(id)) + return watched_seeds[id]; + string keyname = SEED_CONFIG_KEY_PREFIX + int_to_string(id); + DEBUG(config,out).print("creating new persistent key for seed type %d\n", id); + watched_seeds.emplace(id, World::GetPersistentData(keyname, NULL)); + return watched_seeds[id]; +} +static void remove_seed_config(color_ostream &out, int id) { + if (!watched_seeds.count(id)) + return; + DEBUG(config,out).print("removing persistent key for seed type %d\n", id); + World::DeletePersistentData(watched_seeds[id]); + watched_seeds.erase(id); +} - string par; - int limit; - switch(parameters.size()) - { - case 0: - return CR_WRONG_USAGE; - - case 1: - par = parameters[0]; - if ((par == "help") || (par == "?")) - { - return CR_WRONG_USAGE; - } - else if(par == "start") - { - plugin_enable(out, true); +static const int32_t CYCLE_TICKS = 1200; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle - } - else if(par == "stop") - { - plugin_enable(out, false); - } - else if(par == "clear") - { - Kitchen::clearLimits(); - out.print("seedwatch watchlist cleared\n"); - } - else if(par == "info") - { - out.print("seedwatch Info:\n"); - if(running) - { - out.print("seedwatch is supervising. Use 'disable seedwatch' to stop supervision.\n"); - } - else - { - out.print("seedwatch is not supervising. Use 'enable seedwatch' to start supervision.\n"); - } - map watchMap; - Kitchen::fillWatchMap(watchMap); - if(watchMap.empty()) - { - out.print("The watch list is empty.\n"); - } - else - { - out.print("The watch list is:\n"); - for(auto i = watchMap.begin(); i != watchMap.end(); ++i) - { - out.print("%s : %u\n", world->raws.plants.all[i->first]->id.c_str(), i->second); - } - } - } - else if(par == "debug") - { - map watchMap; - Kitchen::fillWatchMap(watchMap); - Kitchen::debug_print(out); - } - else - { - string token = searchAbbreviations(par); - if(plantIDs.count(token) > 0) - { - Kitchen::removeLimit(plantIDs[token]); - out.print("%s is not being watched\n", token.c_str()); - } - else - { - out.print("%s has not been found as a material.\n", token.c_str()); - } - } - break; - case 2: - limit = atoi(parameters[1].c_str()); - if(limit < 0) limit = 0; - if(parameters[0] == "all") - { - for(auto & entry : plantIDs) - Kitchen::setLimit(entry.second, limit); - } - else - { - string token = searchAbbreviations(parameters[0]); - if(plantIDs.count(token) > 0) - { - Kitchen::setLimit(plantIDs[token], limit); - out.print("%s is being watched.\n", token.c_str()); - } - else - { - out.print("%s has not been found as a material.\n", token.c_str()); - } - } - break; - default: - return CR_WRONG_USAGE; - break; - } +static command_result do_command(color_ostream &out, vector ¶meters); +static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); +static void seedwatch_setTarget(color_ostream &out, string name, int32_t num); - return CR_OK; -} +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(config,out).print("initializing %s\n", plugin_name); -DFhackCExport command_result plugin_init(color_ostream &out, vector& commands) -{ + // provide a configuration interface for the plugin commands.push_back(PluginCommand( - "seedwatch", - "Toggles seed cooking based on quantity available.", - df_seedwatch)); - // fill in the abbreviations map, with abbreviations for the standard plants + plugin_name, + "Automatically toggle seed cooking based on quantity available.", + do_command)); + + // fill in the abbreviations map abbreviations["bs"] = "SLIVER_BARB"; abbreviations["bt"] = "TUBER_BLOATED"; abbreviations["bw"] = "WEED_BLADE"; @@ -237,72 +132,285 @@ DFhackCExport command_result plugin_init(color_ostream &out, vector seedCount; // the number of seeds - - // count all seeds and plants by RAW material - for(size_t i = 0; i < world->items.other[items_other_id::SEEDS].size(); ++i) - { - df::item *item = world->items.other[items_other_id::SEEDS][i]; - MaterialInfo mat(item); - if (!mat.isPlant()) - continue; - if (!ignoreSeeds(item->flags)) - ++seedCount[mat.plant->index]; - } +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(config,out).print("shutting down %s\n", plugin_name); + + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + world_plant_ids.clear(); + for (size_t i = 0; i < world->raws.plants.all.size(); ++i) { + auto & plant = world->raws.plants.all[i]; + if (plant->material_defs.type[plant_material_def::seed] != -1) + world_plant_ids[plant->id] = i; + } + + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(config,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } + + is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); + DEBUG(config,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + watched_seeds.clear(); + vector seed_configs; + World::GetPersistentData(&seed_configs, SEED_CONFIG_KEY_PREFIX, true); + const size_t num_seed_configs = seed_configs.size(); + for (size_t idx = 0; idx < num_seed_configs; ++idx) { + auto &c = seed_configs[idx]; + watched_seeds.emplace(get_config_val(c, SEED_CONFIG_ID), c); + } - map watchMap; - Kitchen::fillWatchMap(watchMap); - for(auto i = watchMap.begin(); i != watchMap.end(); ++i) - { - if(seedCount[i->first] <= i->second) - { - Kitchen::denyPlantSeedCookery(i->first); - } - else if(i->second + buffer < seedCount[i->first]) - { - Kitchen::allowPlantSeedCookery(i->first); - } + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(config,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; } } return CR_OK; } -DFhackCExport command_result plugin_shutdown(Core* pCore) -{ +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { + int32_t num_enabled_seeds, num_disabled_seeds; + do_cycle(out, &num_enabled_seeds, &num_disabled_seeds); + if (0 < num_enabled_seeds) + out.print("%s: enabled %d seed types for cooking\n", + plugin_name, num_enabled_seeds); + if (0 < num_disabled_seeds) + out.print("%s: protected %d seed types from cooking\n", + plugin_name, num_disabled_seeds); + } return CR_OK; } + +static bool call_seedwatch_lua(color_ostream *out, const char *fn_name, + int nargs = 0, int nres = 0, + Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA, + Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) { + CoreSuspender guard; + + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!out) + out = &Core::getInstance().getConsole(); + + DEBUG(config,*out).print("calling %s lua function: '%s'\n", plugin_name, fn_name); + + return Lua::CallLuaModuleFunction(*out, L, "plugins.seedwatch", fn_name, + nargs, nres, + std::forward(args_lambda), + std::forward(res_lambda)); +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + bool show_help = false; + if (!call_seedwatch_lua(&out, "parse_commandline", parameters.size(), 1, + [&](lua_State *L) { + for (const string ¶m : parameters) + Lua::Push(L, param); + }, + [&](lua_State *L) { + show_help = !lua_toboolean(L, -1); + })) { + return CR_FAILURE; + } + + return show_help ? CR_WRONG_USAGE : CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +struct BadFlags { + uint32_t whole; + + // TODO: maybe don't filter out seeds that are in_building. that would + // allow us to count seeds that are in workshops. are there any negative + // consequences? + BadFlags() { + df::item_flags flags; + #define F(x) flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + F(in_job); F(owned); F(in_chest); F(removed); + F(encased); F(spider_web); + #undef F + whole = flags.whole; + } +}; + +static bool is_accessible_item(const df::coord &pos, const vector &citizens) { + for (auto &unit : citizens) { + if (Maps::canWalkBetween(unit->pos, pos)) + return true; + } + return false; +} + +static void scan_seeds(color_ostream &out, unordered_map *accessible_counts, + unordered_map *inaccessible_counts = NULL) { + static const BadFlags bad_flags; + + vector citizens; + Units::getCitizens(citizens); + + for (auto &item : world->items.other[items_other_id::SEEDS]) { + MaterialInfo mat(item); + if (!mat.isPlant()) + continue; + if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item->pos, citizens)) { + if (inaccessible_counts) + ++(*inaccessible_counts)[mat.plant->index]; + } else { + if (accessible_counts) + ++(*accessible_counts)[mat.plant->index]; + } + } +} + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + if (num_enabled_seed_types) + *num_enabled_seed_types = 0; + if (num_disabled_seed_types) + *num_disabled_seed_types = 0; + + unordered_map accessible_counts; + scan_seeds(out, &accessible_counts); + + for (auto &entry : watched_seeds) { + int32_t id = entry.first; + int32_t target = get_config_val(entry.second, SEED_CONFIG_TARGET); + if (accessible_counts[id] <= target) { + DEBUG(cycle,out).print("disabling seed mat: %d\n", id); + if (num_disabled_seed_types) + ++*num_disabled_seed_types; + Kitchen::denyPlantSeedCookery(id); + } else if (target + TARGET_BUFFER < accessible_counts[id]) { + DEBUG(cycle,out).print("enabling seed mat: %d\n", id); + if (num_enabled_seed_types) + ++*num_enabled_seed_types; + Kitchen::allowPlantSeedCookery(id); + } + } +} + +///////////////////////////////////////////////////// +// Lua API +// core will already be suspended when coming in through here +// + +static void set_target(color_ostream &out, int32_t id, int32_t target) { + if (target == 0) + remove_seed_config(out, id); + else { + PersistentDataItem &c = ensure_seed_config(out, id); + set_config_val(c, SEED_CONFIG_TARGET, target); + } +} + +// searches abbreviations, returns expansion if so, returns original if not +static string searchAbbreviations(string in) { + if(abbreviations.count(in) > 0) + return abbreviations[in]; + return in; +}; + +static void seedwatch_setTarget(color_ostream &out, string name, int32_t num) { + DEBUG(config,out).print("entering seedwatch_setTarget\n"); + + if (num < 0) { + out.printerr("target must be at least 0\n"); + return; + } + + if (name == "all") { + for (auto &entry : world_plant_ids) { + set_target(out, entry.second, num); + } + return; + } + + string token = searchAbbreviations(name); + if (!world_plant_ids.count(token)) { + out.printerr("%s has not been found as a material.\n", token.c_str()); + return; + } + + set_target(out, world_plant_ids[token], num); +} + +static int seedwatch_getData(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + DEBUG(config,*out).print("entering seedwatch_getData\n"); + unordered_map watch_map, accessible_counts; + scan_seeds(*out, &accessible_counts); + for (auto &entry : watched_seeds) { + watch_map.emplace(entry.first, get_config_val(entry.second, SEED_CONFIG_TARGET)); + } + Lua::Push(L, watch_map); + Lua::Push(L, accessible_counts); + return 2; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(seedwatch_setTarget), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(seedwatch_getData), + DFHACK_LUA_END +}; From febb2bf03052a5d811bf2d766437de36bbd17817 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:24:42 -0800 Subject: [PATCH 05/10] use actual item and unit positions --- plugins/autochop.cpp | 8 +++++--- plugins/seedwatch.cpp | 11 +++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index e44b4f804..81c6196a1 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -7,6 +7,7 @@ #include "modules/Burrows.h" #include "modules/Designations.h" +#include "modules/Items.h" #include "modules/Maps.h" #include "modules/Persistence.h" #include "modules/Units.h" @@ -253,9 +254,10 @@ static command_result do_command(color_ostream &out, vector ¶meters) // cycle logic // -static bool is_accessible_item(const df::coord &pos, const vector &citizens) { +static bool is_accessible_item(df::item *item, const vector &citizens) { + const df::coord pos = Items::getPosition(item); for (auto &unit : citizens) { - if (Maps::canWalkBetween(unit->pos, pos)) + if (Maps::canWalkBetween(Units::getPosition(unit), pos)) return true; } return false; @@ -518,7 +520,7 @@ static void scan_logs(int32_t *usable_logs, const vector &citizens, if (!is_valid_item(item)) continue; - if (!is_accessible_item(item->pos, citizens)) { + if (!is_accessible_item(item, citizens)) { if (inaccessible_logs) ++*inaccessible_logs; } else if (usable_logs) { diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index c6482b1ac..03ea4f268 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -9,6 +9,7 @@ #include "PluginManager.h" #include "TileTypes.h" +#include "modules/Items.h" #include "modules/Kitchen.h" #include "modules/Maps.h" #include "modules/Persistence.h" @@ -269,9 +270,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) struct BadFlags { uint32_t whole; - // TODO: maybe don't filter out seeds that are in_building. that would - // allow us to count seeds that are in workshops. are there any negative - // consequences? BadFlags() { df::item_flags flags; #define F(x) flags.bits.x = true; @@ -285,9 +283,10 @@ struct BadFlags { } }; -static bool is_accessible_item(const df::coord &pos, const vector &citizens) { +static bool is_accessible_item(df::item *item, const vector &citizens) { + const df::coord pos = Items::getPosition(item); for (auto &unit : citizens) { - if (Maps::canWalkBetween(unit->pos, pos)) + if (Maps::canWalkBetween(Units::getPosition(unit), pos)) return true; } return false; @@ -304,7 +303,7 @@ static void scan_seeds(color_ostream &out, unordered_map *acce MaterialInfo mat(item); if (!mat.isPlant()) continue; - if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item->pos, citizens)) { + if ((bad_flags.whole & item->flags.whole) || !is_accessible_item(item, citizens)) { if (inaccessible_counts) ++(*inaccessible_counts)[mat.plant->index]; } else { From aae5d5f41157fe31cb7247c3b1a36c8949a983e5 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:28:07 -0800 Subject: [PATCH 06/10] reduce diff in Kitchen.h --- library/include/modules/Kitchen.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/include/modules/Kitchen.h b/library/include/modules/Kitchen.h index f2708c844..ed8eb95b1 100644 --- a/library/include/modules/Kitchen.h +++ b/library/include/modules/Kitchen.h @@ -38,21 +38,23 @@ namespace DFHack { namespace Kitchen { -// print the exclusion list, with the material index also translated into its token (for organics) - for debug really -DFHACK_EXPORT void debug_print(color_ostream &out); - /** * Kitchen exclusions manipulator. Currently geared towards plants and seeds. * \ingroup grp_modules * \ingroup grp_kitchen */ +// print the exclusion list, with the material index also translated into its token (for organics) - for debug really +DFHACK_EXPORT void debug_print(color_ostream &out); + // remove this plant from the exclusion list if it is in it DFHACK_EXPORT void allowPlantSeedCookery(int32_t plant_id); // add this plant to the exclusion list, if it is not already in it DFHACK_EXPORT void denyPlantSeedCookery(int32_t plant_id); +DFHACK_EXPORT std::size_t size(); + // Finds the index of a kitchen exclusion in plotinfo.kitchen.exc_types. Returns -1 if not found. DFHACK_EXPORT int findExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, @@ -68,7 +70,5 @@ DFHACK_EXPORT bool removeExclusion(df::kitchen_exc_type type, df::item_type item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_index); -DFHACK_EXPORT std::size_t size(); - } } From b02405ea97556286571eb4846451ed83e0cddeee Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:30:45 -0800 Subject: [PATCH 07/10] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..bf64efb88 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -55,6 +55,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `getplants`: ID values will now be accepted regardless of case -@ New borders for DFHack tool windows -- tell us what you think! - `gui/control-panel`: new global hotkey: tilde (Shift-backtick on most keyboards) +- `seedwatch`: now persists enabled state in the savegame, automatically loads useful defaults, and respects reachability when counting available seeds ## Documentation -@ Quickstart guide has been updated with info on new window behavior and how to use the control panel From fd4b0d72330add10222fa8ef4a51ad2b9b3baf24 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 04:56:25 -0800 Subject: [PATCH 08/10] update and simplify nestboxes; persist state --- docs/changelog.txt | 1 + plugins/nestboxes.cpp | 201 ++++++++++++++++++++++++------------------ 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..4b578f1da 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - A new cross-compile build script was added for building DFHack for Windows from a Linux Docker builder (see the `compile` instructions in the docs) - You can now configure whether DFHack tool windows should pause the game by default - `hotkeys`: clicking on the DFHack logo no longer closes the popup menu +- `nestboxes`: now saves enabled state in your savegame - `gui/launcher`: sped up initialization time for faster load of the UI - `orders`: orders plugin functionality is now offered via an overlay widget when the manager orders screen is open - `gui/quickcmd`: now has its own global keybinding for your convenience: Ctrl-Shift-A diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 1908684d4..8feee65f7 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -1,120 +1,153 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" +#include "Debug.h" #include "PluginManager.h" -#include "DataDefs.h" +#include "modules/Persistence.h" +#include "modules/World.h" + #include "df/world.h" -#include "df/plotinfost.h" #include "df/building_nest_boxst.h" -#include "df/building_type.h" -#include "df/buildings_other_id.h" -#include "df/global_objects.h" #include "df/item.h" #include "df/unit.h" -#include "df/building.h" -#include "df/items_other_id.h" -#include "df/creature_raw.h" -#include "modules/MapCache.h" -#include "modules/Items.h" - -using std::vector; using std::string; -using std::endl; using namespace DFHack; using namespace df::enums; -using df::global::world; -using df::global::plotinfo; +DFHACK_PLUGIN("nestboxes"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); -static command_result nestboxes(color_ostream &out, vector & parameters); +REQUIRE_GLOBAL(world); -DFHACK_PLUGIN("nestboxes"); +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(nestboxes, config, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(nestboxes, cycle, DebugCategory::LINFO); +} -DFHACK_PLUGIN_IS_ENABLED(enabled); - -static void eggscan(color_ostream &out) -{ - CoreSuspender suspend; - - for (df::building *build : world->buildings.other[df::buildings_other_id::NEST_BOX]) - { - auto type = build->getType(); - if (df::enums::building_type::NestBox == type) - { - bool fertile = false; - df::building_nest_boxst *nb = virtual_cast(build); - if (nb->claimed_by != -1) - { - df::unit* u = df::unit::find(nb->claimed_by); - if (u && u->pregnancy_timer > 0) - fertile = true; - } - for (size_t j = 1; j < nb->contained_items.size(); j++) - { - df::item* item = nb->contained_items[j]->item; - if (item->flags.bits.forbid != fertile) - { - item->flags.bits.forbid = fertile; - out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; - } - } - } - } +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, +}; + +static int get_config_val(PersistentDataItem &c, int index) { + if (!c.isValid()) + return -1; + return c.ival(index); +} +static bool get_config_bool(PersistentDataItem &c, int index) { + return get_config_val(c, index) == 1; +} +static void set_config_val(PersistentDataItem &c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; +} +static void set_config_bool(PersistentDataItem &c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); +} + +static const int32_t CYCLE_TICKS = 100; // need to react quickly if eggs are unforbidden +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seeds, int32_t *num_disabled_seeds); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(config,out).print("initializing %s\n", plugin_name); + + return CR_OK; } +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (world && plotinfo) { - commands.push_back( - PluginCommand( - "nestboxes", - "Protect fertile eggs incubating in a nestbox.", - nestboxes)); + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(config,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(config,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); } return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(config,out).print("shutting down %s\n", plugin_name); + return CR_OK; } -DFhackCExport command_result plugin_onupdate(color_ostream &out) -{ - if (!enabled) - return CR_OK; +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); - static unsigned cnt = 0; - if ((++cnt % 5) != 0) - return CR_OK; + if (!config.isValid()) { + DEBUG(config,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(config, CONFIG_IS_ENABLED, is_enabled); + } - eggscan(out); + is_enabled = get_config_bool(config, CONFIG_IS_ENABLED); + DEBUG(config,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); return CR_OK; } -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - enabled = enable; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(config,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } + } return CR_OK; } -static command_result nestboxes(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - if (parameters.size() == 1) { - if (parameters[0] == "enable") - enabled = true; - else if (parameters[0] == "disable") - enabled = false; - else - return CR_WRONG_USAGE; - } else { - out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS) { + int32_t num_enabled_seeds, num_disabled_seeds; + do_cycle(out, &num_enabled_seeds, &num_disabled_seeds); + if (0 < num_enabled_seeds) + out.print("%s: enabled %d seed types for cooking\n", + plugin_name, num_enabled_seeds); + if (0 < num_disabled_seeds) + out.print("%s: protected %d seed types from cooking\n", + plugin_name, num_disabled_seeds); } return CR_OK; } + +///////////////////////////////////////////////////// +// cycle logic +// + +static void do_cycle(color_ostream &out, int32_t *num_enabled_seed_types, int32_t *num_disabled_seed_types) { + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + for (df::building_nest_boxst *nb : world->buildings.other.NEST_BOX) { + bool fertile = false; + if (nb->claimed_by != -1) { + df::unit *u = df::unit::find(nb->claimed_by); + if (u && u->pregnancy_timer > 0) + fertile = true; + } + for (auto &contained_item : nb->contained_items) { + df::item *item = contained_item->item; + if (item->flags.bits.forbid != fertile) { + item->flags.bits.forbid = fertile; + out.print("%d eggs %s.\n", item->getStackSize(), fertile ? "forbidden" : "unforbidden"); + } + } + } +} From 55d07a8cae86da7c05472ebb52ba5712e5f68b8a Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 14:51:46 -0800 Subject: [PATCH 09/10] actually use the tile --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 4228bc9ad..21143f053 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1112,7 +1112,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) if dc then local tile_pen = tonumber(token.tile) and to_pen{tile=token.tile} or token.tile - dc:char(nil, token.tile) + dc:char(nil, tile_pen) if token.width then dc:advance(token.width-1) end From 58be8cfd69b63b883889651578fa1a07df698003 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 1 Feb 2023 17:39:32 -0800 Subject: [PATCH 10/10] support offset text in graphics mode for pens --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 6 ++++++ library/LuaApi.cpp | 2 ++ library/include/modules/Screen.h | 2 ++ library/modules/Screen.cpp | 20 ++++++++++++++++---- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a78b14fc7..396bf89d2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,6 +62,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Buildings::containsTile()``: no longer takes a ``room`` parameter since that's not how rooms work anymore. If the building has extents, the extents will be checked. otherwise, the result just depends on whether the tile is within the building's bounding box. - ``Units::getCitizens()``: gets a list of citizens, which otherwise you'd have to iterate over all units the world to discover +- ``Screen::Pen``: now accepts ``top_of_text`` and ``bottom_of_text`` properties to support offset text in graphics mode ## Lua - `helpdb`: new function: ``helpdb.refresh()`` to force a refresh of the database. Call if you are a developer adding new scripts, loading new plugins, or changing help text during play diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 658893ec1..9e8604b15 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -2292,6 +2292,12 @@ a table with the following possible fields: ``write_to_lower`` If set to true, the specified ``tile`` will be written to the background instead of the foreground. + ``top_of_text`` + If set to true, the specified ``tile`` will have the top half of the specified + ``ch`` character superimposed over the lower half of the tile. + ``bottom_of_text`` + If set to true, the specified ``tile`` will have the bottom half of the specified + ``ch`` character superimposed over the top half of the tile. Alternatively, it may be a pre-parsed native object with the following API: diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 699c8aef7..1b674e0e8 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -222,6 +222,8 @@ static void decode_pen(lua_State *L, Pen &pen, int idx) get_bool_field(L, &pen.keep_lower, idx, "keep_lower", false); get_bool_field(L, &pen.write_to_lower, idx, "write_to_lower", false); + get_bool_field(L, &pen.top_of_text, idx, "top_of_text", false); + get_bool_field(L, &pen.bottom_of_text, idx, "bottom_of_text", false); } /************************************************** diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 48f34888f..0f0afd6e2 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -86,6 +86,8 @@ namespace DFHack bool write_to_lower = false; bool keep_lower = false; + bool top_of_text = false; + bool bottom_of_text = false; bool valid() const { return tile >= 0; } bool empty() const { return ch == 0 && tile == 0; } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 5627c08da..13dbcb204 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -155,14 +155,18 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) long *texpos_lower = &gps->screentexpos_lower[index]; uint32_t *flag = &gps->screentexpos_flag[index]; + // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE so occluded anchored textures + // don't appear corrupted + uint32_t flag_mask = 0x4; + if (pen.write_to_lower) + flag_mask |= 0x18; + *screen = 0; *texpos = 0; if (!pen.keep_lower) *texpos_lower = 0; gps->screentexpos_anchored[index] = 0; - // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE so occluded anchored textures - // don't appear corrupted - *flag &= 4; + *flag &= flag_mask; if (gps->top_in_use) { screen = &gps->screen_top[index * 8]; @@ -175,7 +179,7 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) if (!pen.keep_lower) *texpos_lower = 0; gps->screentexpos_top_anchored[index] = 0; - *flag &= 4; // keep SCREENTEXPOS_FLAG_ANCHOR_SUBORDINATE + *flag &= flag_mask; } uint8_t fg = pen.fg | (pen.bold << 3); @@ -196,6 +200,14 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) *texpos_lower = pen.tile; else *texpos = pen.tile; + + if (pen.top_of_text || pen.bottom_of_text) { + screen[0] = uint8_t(pen.ch); + if (pen.top_of_text) + *flag |= 0x8; + if (pen.bottom_of_text) + *flag |= 0x10; + } } else if (pen.ch) { screen[0] = uint8_t(pen.ch); *texpos_lower = 909; // basic black background