From 3d1b6412320cbb69e0ce54d0164b1c030687b9e0 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 16 Jul 2024 16:42:13 -0700 Subject: [PATCH 01/38] Make use of dfhack.units.getCasteRaw * Update unretire-anyone.lua * Update workorder.lua * Update set-personality.lua * Update uniform-unstick.lua * Update editor_colors.lua * Update notifications.lua * Update unit-syndromes.lua * Update set-orientation.lua * Update unit-info-viewer.lua * Update companion-order.lua --- gui/companion-order.lua | 9 ++------- gui/unit-info-viewer.lua | 16 ++++++---------- gui/unit-syndromes.lua | 2 +- internal/gm-unit/editor_colors.lua | 4 ++-- internal/notify/notifications.lua | 6 ++---- modtools/set-personality.lua | 2 +- set-orientation.lua | 2 +- uniform-unstick.lua | 2 +- unretire-anyone.lua | 2 +- workorder.lua | 5 +---- 10 files changed, 18 insertions(+), 32 deletions(-) diff --git a/gui/companion-order.lua b/gui/companion-order.lua index 9f5449d3c1..9659c79785 100644 --- a/gui/companion-order.lua +++ b/gui/companion-order.lua @@ -62,13 +62,8 @@ function getxyz() -- this will return pointers x,y and z coordinates. return x,y,z -- return the coords end -function GetCaste(race_id,caste_id) - local race=df.creature_raw.find(race_id) - return race.caste[caste_id] -end - function EnumBodyEquipable(race_id,caste_id) - local caste=GetCaste(race_id,caste_id) + local caste=dfhack.units.getCasteRaw(race_id,caste_id) local bps=caste.body_info.body_parts local ret={} for k,v in pairs(bps) do @@ -149,7 +144,7 @@ function AddIfFits(body_equip,unit,item) return false end function EnumGrasps(race_id,caste_id) - local caste=GetCaste(race_id,caste_id) + local caste=dfhack.units.getCasteRaw(race_id,caste_id) local bps=caste.body_info.body_parts local ret={} for k,v in pairs(bps) do diff --git a/gui/unit-info-viewer.lua b/gui/unit-info-viewer.lua index 56751e9fd1..ed677453a9 100644 --- a/gui/unit-info-viewer.lua +++ b/gui/unit-info-viewer.lua @@ -168,10 +168,6 @@ local function get_death_type(death_cause) return DEATH_TYPES[death_cause] or ' died of unknown causes' end -local function get_caste_data(unit) - return df.global.world.raws.creatures.all[unit.race].caste[unit.caste] -end - local function get_creature_data(unit) return df.global.world.raws.creatures.all[unit.race] end @@ -190,7 +186,7 @@ local function get_translated_name_chunk(unit) end local function get_description_chunk(unit) - local desc = get_caste_data(unit).description + local desc = dfhack.units.getCasteRaw(unit).description if #desc == 0 then return end return {text=desc, pen=COLOR_WHITE} end @@ -271,7 +267,7 @@ end local function get_max_age_chunk(unit) if not dfhack.units.isAlive(unit) then return end - local caste = get_caste_data(unit) + local caste = dfhack.units.getCasteRaw(unit) local blurb if caste.misc.maxage_min == -1 then blurb = ' only die of unnatural causes.' @@ -395,7 +391,7 @@ end local function get_grazer_chunk(unit) if not dfhack.units.isGrazer(unit) then return end - local caste = get_caste_data(unit) + local caste = dfhack.units.getCasteRaw(unit) local blurb = 'Grazing satisfies ' .. tostring(caste.misc.grazer) .. ' units of hunger.' return {text=blurb, pen=COLOR_LIGHTGREEN} end @@ -403,7 +399,7 @@ end local function get_milkable_chunk(unit) if not dfhack.units.isAlive(unit) or not dfhack.units.isMilkable(unit) then return end if not dfhack.units.isAnimal(unit) then return end - local caste = get_caste_data(unit) + local caste = dfhack.units.getCasteRaw(unit) local milk = dfhack.matinfo.decode(caste.extracts.milkable_mat, caste.extracts.milkable_matidx) if not milk then return end local days, seconds = math.modf(caste.misc.milkable / TU_PER_DAY) @@ -419,7 +415,7 @@ end local function get_shearable_chunk(unit) if not dfhack.units.isAlive(unit) then return end if not dfhack.units.isAnimal(unit) then return end - local caste = get_caste_data(unit) + local caste = dfhack.units.getCasteRaw(unit) local mat_types = caste.body_info.materials.mat_type local mat_idxs = caste.body_info.materials.mat_index for idx, mat_type in ipairs(mat_types) do @@ -438,7 +434,7 @@ end local function get_egg_layer_chunk(unit) if not dfhack.units.isAlive(unit) or not dfhack.units.isEggLayer(unit) then return end - local caste = get_caste_data(unit) + local caste = dfhack.units.getCasteRaw(unit) local clutch = (caste.misc.clutch_size_max + caste.misc.clutch_size_min) // 2 local blurb = ('She lays clutches of about %d egg%s.'):format(clutch, clutch == 1 and '' or 's') return {text=blurb, pen=COLOR_GREEN} diff --git a/gui/unit-syndromes.lua b/gui/unit-syndromes.lua index b8ea3a37ee..b789bfd97d 100644 --- a/gui/unit-syndromes.lua +++ b/gui/unit-syndromes.lua @@ -306,7 +306,7 @@ local function getLivestock() local units = {} for _, unit in pairs(df.global.world.units.active) do - local caste_flags = unit.caste and df.global.world.raws.creatures.all[unit.race].caste[unit.caste].flags + local caste_flags = dfhack.units.getCasteRaw(unit).flags if dfhack.units.isFortControlled(unit) and caste_flags and (caste_flags.PET or caste_flags.PET_EXOTIC) then table.insert(units, unit) diff --git a/internal/gm-unit/editor_colors.lua b/internal/gm-unit/editor_colors.lua index 57a7c9fde5..decebda005 100644 --- a/internal/gm-unit/editor_colors.lua +++ b/internal/gm-unit/editor_colors.lua @@ -64,7 +64,7 @@ end function Editor_Colors:random() local featureChoiceIndex, featureChoice = self.subviews.features:getSelected() -- This is the part / feature that's selected - local caste = df.creature_raw.find(self.target_unit.race).caste[self.target_unit.caste] + local caste = dfhack.units.getCasteRaw(self.target_unit) -- Nil check in case there are no features if featureChoiceIndex == nil then @@ -136,7 +136,7 @@ function Editor_Colors:featureSelected(index, choice) end function Editor_Colors:updateChoices() - local caste = df.creature_raw.find(self.target_unit.race).caste[self.target_unit.caste] + local caste = dfhack.units.getCasteRaw(self.target_unit) local choices = {} for index, colorMod in ipairs(caste.color_modifiers) do table.insert(choices, {text = colorMod.part:gsub("^%l", string.upper), mod = colorMod, index = index}) diff --git a/internal/notify/notifications.lua b/internal/notify/notifications.lua index 5273d83b8e..b06f139d90 100644 --- a/internal/notify/notifications.lua +++ b/internal/notify/notifications.lua @@ -105,10 +105,8 @@ local function for_moody(fn, reverse) end, fn, reverse) end -local races = df.global.world.raws.creatures.all - local function is_stealer(unit) - local casteFlags = races[unit.race].caste[unit.caste].flags + local casteFlags = dfhack.units.getCasteRaw(unit).flags if casteFlags.CURIOUS_BEAST_EATER or casteFlags.CURIOUS_BEAST_GUZZLER or casteFlags.CURIOUS_BEAST_ITEM @@ -224,7 +222,7 @@ end local function summarize_units(for_fn) local counts = {} for_fn(function(unit) - local names = races[unit.race].caste[unit.caste].caste_name + local names = dfhack.units.getCasteRaw(unit).caste_name local record = ensure_key(counts, names[0], {count=0, plural=names[1]}) record.count = record.count + 1 end) diff --git a/modtools/set-personality.lua b/modtools/set-personality.lua index c0136c56a5..29c966e25c 100644 --- a/modtools/set-personality.lua +++ b/modtools/set-personality.lua @@ -165,7 +165,7 @@ end -- Gets the range of the unit caste's min, average, and max value for a trait, as defined in the PERSONALITY creature tokens. function getUnitCasteTraitRange(unit, trait) - local caste = df.creature_raw.find(unit.race).caste[unit.caste] + local caste = dfhack.units.getCasteRaw(unit) local range = {} range.min = caste.personality.a[df.personality_facet_type[trait]] diff --git a/set-orientation.lua b/set-orientation.lua index 19583f7913..6b821b8fcf 100644 --- a/set-orientation.lua +++ b/set-orientation.lua @@ -155,7 +155,7 @@ function randomiseOrientation(unit, sex) return end - local caste = df.creature_raw.find(unit.race).caste[unit.caste] + local caste = dfhack.units.getCasteRaw(unit) -- Build a weighted table for use in the weighted roll function local sexname = getSexString(sex) diff --git a/uniform-unstick.lua b/uniform-unstick.lua index 9c578bc18c..af86aa8006 100644 --- a/uniform-unstick.lua +++ b/uniform-unstick.lua @@ -50,7 +50,7 @@ end local function bodyparts_that_can_wear(unit, item) local bodyparts = {} - local unitparts = df.creature_raw.find(unit.race).caste[unit.caste].body_info.body_parts + local unitparts = dfhack.units.getCasteRaw(unit).body_info.body_parts if item._type == df.item_helmst then for index, part in ipairs(unitparts) do diff --git a/unretire-anyone.lua b/unretire-anyone.lua index c977ebdfd5..0e79fccaba 100644 --- a/unretire-anyone.lua +++ b/unretire-anyone.lua @@ -50,7 +50,7 @@ function showNemesisPrompt(advSetUpScreen) not histFlags.deity and not histFlags.force then - local creature = df.creature_raw.find(histFig.race).caste[histFig.caste] + local creature = dfhack.units.getCasteRaw(histFig.race, histFig.caste) local name = creature.caste_name[0] if histFig.info and histFig.info.curse then local curse = histFig.info.curse diff --git a/workorder.lua b/workorder.lua index 7c58e785b5..c72ec99981 100644 --- a/workorder.lua +++ b/workorder.lua @@ -594,10 +594,7 @@ end -- true/false or nil if no shearable_tissue_layer with length > 0. local function canShearCreature(u) - local stls = world.raws.creatures - .all[u.race] - .caste[u.caste] - .shearable_tissue_layer + local stls = dfhack.units.getCasteRaw(u).shearable_tissue_layer local any for _, stl in ipairs(stls) do From c74266ae5360704ff0476c82e205a57def0dbaab Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:11:25 +0200 Subject: [PATCH 02/38] [allneeds] display counts of fulfillment degrees --- allneeds.lua | 29 +++++++++++++++++++++++++---- changelog.txt | 1 + 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/allneeds.lua b/allneeds.lua index f23ab95351..a3fa117cf1 100644 --- a/allneeds.lua +++ b/allneeds.lua @@ -19,6 +19,18 @@ if not sorts[sort] then qerror(('unknown sort: "%s"'):format(sort)) end +local fulfillment_threshold = + { 300, 200, 100, -999, -9999, -99999 } + +local function getFulfillment(focus_level) + for i = 1, 6 do + if focus_level >= fulfillment_threshold[i] then + return i + end + end + return 7 +end + local fort_needs = {} local units = dfhack.gui.getSelectedUnit(true) @@ -39,6 +51,10 @@ for _, unit in ipairs(units) do needs.strength = (needs.strength or 0) + need.need_level needs.focus = (needs.focus or 0) + need.focus_level needs.freq = (needs.freq or 0) + 1 + + local level = getFulfillment(need.focus_level) + ensure_key(needs, 'fulfillment', {0, 0, 0, 0, 0, 0, 0}) + needs.fulfillment[level] = needs.fulfillment[level] + 1 end end @@ -49,15 +65,20 @@ for id, need in pairs(fort_needs) do strength=need.strength, focus=need.focus, freq=need.freq, + fulfillment=need.fulfillment }) end table.sort(sorted_fort_needs, sorts[sort]) -- Print sorted output -local fmt = '%20s %8s %12s %9s' -print(fmt:format("Need", "Strength", "Focus Impact", "Frequency")) -print(fmt:format("----", "--------", "------------", "---------")) +local fmt = '%20s %8s %12s %9s %35s' +print(fmt:format("Need", "Strength", "Focus Impact", "Frequency", "Num. Unfettered -> Badly distracted")) +print(fmt:format("----", "--------", "------------", "---------", "-----------------------------------")) for _, need in ipairs(sorted_fort_needs) do - print(fmt:format(need.id, need.strength, need.focus, need.freq)) + local res = "" + for i = 1, 7 do + res = res..(('%5d'):format(need.fulfillment[i])) + end + print(fmt:format(need.id, need.strength, need.focus, need.freq, res)) end diff --git a/changelog.txt b/changelog.txt index 288bd442d0..69d0c28894 100644 --- a/changelog.txt +++ b/changelog.txt @@ -43,6 +43,7 @@ Template for new versions: - `build-now`: if `suspendmanager` is running, run an unsuspend cycle immediately before scanning for buildings to build - `list-waves`: now outputs the names of the dwarves in each migration wave - `list-waves`: can now display information about specific migration waves (like identifying your starting 7 dwarves) +- `allneeds`: display counts of fulfillment degrees as a separate column ## Removed From 1472a4a00401ad8d6e96d35a41cf14288d6ae5b6 Mon Sep 17 00:00:00 2001 From: Droseran <97368320+Droseran@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:37:48 -0400 Subject: [PATCH 03/38] Increase efficiency of ban-cooking Stop iterating through materials when the material being searched for (STRUCTURAL) is found since material IDs should be unique. --- ban-cooking.lua | 63 +++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/ban-cooking.lua b/ban-cooking.lua index 2c309a8293..8f976c02d3 100644 --- a/ban-cooking.lua +++ b/ban-cooking.lua @@ -137,17 +137,20 @@ funcs.seeds = function() if p.material_defs.type.seed == -1 or p.material_defs.idx.seed == -1 or p.flags.TREE then goto continue end ban_cooking(p.name .. ' seeds', p.material_defs.type.seed, p.material_defs.idx.seed, df.item_type.SEEDS, -1) for _, m in ipairs(p.material) do - if m.id == "STRUCTURAL" and m.flags.EDIBLE_COOKED then - local has_drink = false - local has_seed = false - for _, s in ipairs(m.reaction_product.id) do - has_seed = has_seed or s.value == "SEED_MAT" - has_drink = has_drink or s.value == "DRINK_MAT" - end - if has_seed and has_drink then - local matinfo = dfhack.matinfo.find(p.id, m.id) - ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + if m.id == "STRUCTURAL" then + if m.flags.EDIBLE_COOKED then + local has_drink = false + local has_seed = false + for _, s in ipairs(m.reaction_product.id) do + has_seed = has_seed or s.value == "SEED_MAT" + has_drink = has_drink or s.value == "DRINK_MAT" + end + if has_seed and has_drink then + local matinfo = dfhack.matinfo.find(p.id, m.id) + ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + end end + break end end for k, g in ipairs(p.growths) do @@ -174,14 +177,18 @@ funcs.brew = function() for _, p in ipairs(df.global.world.raws.plants.all) do if p.material_defs.type.drink == -1 or p.material_defs.idx.drink == -1 then goto continue end for _, m in ipairs(p.material) do - if m.id == "STRUCTURAL" and m.flags.EDIBLE_COOKED then - for _, s in ipairs(m.reaction_product.id) do - if s.value == "DRINK_MAT" then - local matinfo = dfhack.matinfo.find(p.id, m.id) - ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) - break + if m.id == "STRUCTURAL" then + if m.flags.EDIBLE_COOKED then + for _, s in ipairs(m.reaction_product.id) do + if s.value == "DRINK_MAT" then + local matinfo = dfhack.matinfo.find(p.id, m.id) + ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + break + end end end + -- Stop iterating materials since there is only one STRUCTURAL + break end end for k, g in ipairs(p.growths) do @@ -205,9 +212,12 @@ funcs.mill = function() for _, p in ipairs(df.global.world.raws.plants.all) do if p.material_defs.idx.mill ~= -1 then for _, m in ipairs(p.material) do - if m.id == "STRUCTURAL" and m.flags.EDIBLE_COOKED then - local matinfo = dfhack.matinfo.find(p.id, m.id) - ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + if m.id == "STRUCTURAL" then + if m.flags.EDIBLE_COOKED then + local matinfo = dfhack.matinfo.find(p.id, m.id) + ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + end + break end end end @@ -218,14 +228,17 @@ funcs.thread = function() for _, p in ipairs(df.global.world.raws.plants.all) do if p.material_defs.idx.thread == -1 then goto continue end for _, m in ipairs(p.material) do - if m.id == "STRUCTURAL" and m.flags.EDIBLE_COOKED then - for _, s in ipairs(m.reaction_product.id) do - if s.value == "THREAD" then - local matinfo = dfhack.matinfo.find(p.id, m.id) - ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) - break + if m.id == "STRUCTURAL" then + if m.flags.EDIBLE_COOKED then + for _, s in ipairs(m.reaction_product.id) do + if s.value == "THREAD" then + local matinfo = dfhack.matinfo.find(p.id, m.id) + ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) + break + end end end + break end end for k, g in ipairs(p.growths) do From c31861a95052566251add41c858d4ff26568dc4e Mon Sep 17 00:00:00 2001 From: Droseran <97368320+Droseran@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:44:13 -0400 Subject: [PATCH 04/38] Change 'seeds' function to ban all seed producing items Removed the check for has_drink from funcs.seeds. Currently the 'seeds' function bans seed producing items only if the item is also brewable. This allows the possibility that seeds and replanting can be lost for cookable seed producing items that are also using for milling, spinning, etc. reactions. This maybe should be split into two functions, one for banning seeds themselves and one for banning seed producing items. Currently the documentation describes this function as only banning seeds, which may lead to some confusion. --- ban-cooking.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ban-cooking.lua b/ban-cooking.lua index 8f976c02d3..fdb59fbe05 100644 --- a/ban-cooking.lua +++ b/ban-cooking.lua @@ -139,13 +139,11 @@ funcs.seeds = function() for _, m in ipairs(p.material) do if m.id == "STRUCTURAL" then if m.flags.EDIBLE_COOKED then - local has_drink = false local has_seed = false for _, s in ipairs(m.reaction_product.id) do has_seed = has_seed or s.value == "SEED_MAT" - has_drink = has_drink or s.value == "DRINK_MAT" end - if has_seed and has_drink then + if has_seed then local matinfo = dfhack.matinfo.find(p.id, m.id) ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT, -1) end @@ -157,13 +155,11 @@ funcs.seeds = function() local matinfo = dfhack.matinfo.decode(g) local m = matinfo.material if m.flags.EDIBLE_COOKED then - local has_drink = false local has_seed = false for _, s in ipairs(m.reaction_product.id) do has_seed = has_seed or s.value == "SEED_MAT" - has_drink = has_drink or s.value == "DRINK_MAT" end - if has_seed and has_drink then + if has_seed then ban_cooking(p.name .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.PLANT_GROWTH, k) end end From 0dc367472ed38127046306f54f6a227ec930d26c Mon Sep 17 00:00:00 2001 From: Droseran <97368320+Droseran@users.noreply.github.com> Date: Thu, 25 Jul 2024 17:48:28 -0400 Subject: [PATCH 05/38] Update changelog.txt Added ban-cooking changes to changelog --- changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index 288bd442d0..43eb133134 100644 --- a/changelog.txt +++ b/changelog.txt @@ -93,6 +93,8 @@ Template for new versions: - `clear-smoke`: properly tag smoke flows for garbage collection to avoid memory leak - `warn-stranded`: don't warn for babies carried by mothers who happen to be gathering fruit from trees - `prioritize`: also boost priority of already-claimed jobs when boosting priority of a job type so those jobs are not interrupted +- `ban-cooking`: stop iterating through plant materials when the searched-for material is found +- `ban-cooking`: ban all seed producing items from being cooked when 'seeds' is chosen instead of just brewable seed producing items ## Misc Improvements - `item`: option for ignoring uncollected spider webs when you search for "silk" From 971cc31d1d286c1cbfbdd6835611eca7617a5d32 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 27 Jul 2024 19:15:41 -0700 Subject: [PATCH 06/38] Update autodump.lua --- gui/autodump.lua | 50 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index 21a08c0e3c..61265dc5f5 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -306,14 +306,50 @@ function Autodump:onRenderFrame(dc, rect) end end +local function tile_props(pos, tt) --Returns is_ground, is_open_air + local shape_attrs = df.tiletype_shape.attrs[df.tiletype.attrs[tt].shape] + if shape_attrs.walkable then --TODO: don't dump on statues, etc.? + return true, false --Floor, stair, or ramp + elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then + return false, false --Wall or fortification + end + + local _, occ = dfhack.maps.getTileFlags(pos) + if occ.building == df.tile_building_occ.None or + occ.building == df.tile_building_occ.Planned or + occ.building == df.tile_building_occ.Passable or + occ.building == df.tile_building_occ.Well then + return false, true --Item can fall safely through; any other may delete item projectile + elseif occ.building == df.tile_building_occ.Floored then + return true, false --Lowered bridge, forbidden hatch, etc. + elseif occ.building == df.tile_building_occ.Dynamic then + local bld = dfhack.buildings.findAtTile(pos) --Unforbidden closed hatch, etc. + return (bld and (bld._type == df.building_hatchst or + bld._type == df.building_grate_floorst or + bld._type == df.building_bars_floorst)), false + end + return false, false +end + function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() - if not pos then return end - local tileattrs = df.tiletype.attrs[dfhack.maps.getTileType(pos)] - local basic_shape = df.tiletype_shape.attrs[tileattrs.shape].basic_shape - local on_ground = basic_shape == df.tiletype_shape_basic.Floor or - basic_shape == df.tiletype_shape_basic.Stair or - basic_shape == df.tiletype_shape_basic.Ramp + if not pos then + print('No cursor') + return + end + + local tt = dfhack.maps.getTileType(pos) + if not tt then + print('No map block') + return + end + + local on_ground, in_air = tile_props(pos, tt) + if not (on_ground or in_air) then + print('Dump tile blocked') + return + end + local items = #self.selected_items.list > 0 and self.selected_items.list or self.dump_items local mark_as_forbidden = self.subviews.mark_as_forbidden:getOptionValue() print(('teleporting %d items'):format(#items)) @@ -330,7 +366,7 @@ function Autodump:do_dump(pos) if mark_as_forbidden then item.flags.forbid = true end - if not on_ground then + if in_air then dfhack.items.makeProjectile(item) end else From 90760c8f0a3a6cda99c829100223ffa16d088b74 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 27 Jul 2024 19:46:04 -0700 Subject: [PATCH 07/38] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 288bd442d0..81e6955cc7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,6 +38,7 @@ Template for new versions: - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) - `fix/dead-units`: fix error when removing dead units from burrows and the unit with the greatest ID was dead +- `gui/autodump`: fix dumping into invalid tiles, creating item projectiles that get destroyed ## Misc Improvements - `build-now`: if `suspendmanager` is running, run an unsuspend cycle immediately before scanning for buildings to build From c45a91be4c8583717e35b5451a89045d87d1ab9d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 28 Jul 2024 09:33:36 -0700 Subject: [PATCH 08/38] Update autodump.lua - Just comments --- gui/autodump.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index 61265dc5f5..3a3556aaaa 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -323,12 +323,12 @@ local function tile_props(pos, tt) --Returns is_ground, is_open_air elseif occ.building == df.tile_building_occ.Floored then return true, false --Lowered bridge, forbidden hatch, etc. elseif occ.building == df.tile_building_occ.Dynamic then - local bld = dfhack.buildings.findAtTile(pos) --Unforbidden closed hatch, etc. + local bld = dfhack.buildings.findAtTile(pos) --Unforbidden hatch, etc. return (bld and (bld._type == df.building_hatchst or bld._type == df.building_grate_floorst or bld._type == df.building_bars_floorst)), false end - return false, false + return false, false --Don't trust it end function Autodump:do_dump(pos) From b773d05177755a6d4ff55fad3437422714a05a0d Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 29 Jul 2024 16:51:12 +0300 Subject: [PATCH 09/38] empty-bin recursive usage and liquid include flag --- docs/empty-bin.rst | 10 ++++++- empty-bin.lua | 72 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 1cd6b9bb29..1e0a8d124d 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -22,4 +22,12 @@ Usage :: - empty-bin + empty-bin [] + +Options +-------------- + +``-r|--recursive`` + Whether to apply the command recursively. +``-l|--liquids`` + Include liquids (DRINK and LIQUID_MISC) in the items list to be moved to the ground. \ No newline at end of file diff --git a/empty-bin.lua b/empty-bin.lua index 3f10225e65..62280ad26c 100644 --- a/empty-bin.lua +++ b/empty-bin.lua @@ -3,28 +3,78 @@ -- https://gist.github.com/stonetoad/11129025 -- http://dwarffortresswiki.org/index.php/DF2014_Talk:Bin -local function moveItem(item, to_pos) - print(' ' .. dfhack.items.getReadableDescription(item)) - dfhack.items.moveToGround(item, to_pos) -end +local argparse = require("argparse") + +local options, args = { + help = false, + recursive = false, + liquids = false + }, + {...} local function emptyContainer(container) local items = dfhack.items.getContainedItems(container) - if #items > 0 then - print('Emptying ' .. dfhack.items.getReadableDescription(container)) + print("Emptying " .. dfhack.items.getReadableDescription(container)) local pos = xyz2pos(dfhack.items.getPosition(container)) for _, item in ipairs(items) do - moveItem(item, pos) + local skip_liquid = + item:getType() == df.item_type.LIQUID_MISC or + item:getType() == df.item_type.DRINK and not options.liquids + if skip_liquid then + print( + " " .. + dfhack.items.getReadableDescription(item) .. + " was skipped because the liquid flag was not provided" + ) + else + print(" " .. dfhack.items.getReadableDescription(item)) + dfhack.items.moveToGround(item, pos) + if options.recursive then + emptyContainer(item) + end + end end end end -local viewsheets = df.global.game.main_interface.view_sheets +argparse.processArgsGetopt( + args, + { + { + "h", + "help", + handler = function() + options.help = true + end + }, + { + "r", + "recursive", + handler = function() + options.recursive = true + end + }, + { + "l", + "liquids", + handler = function() + options.liquids = true + end + } + } +) + +if options.help then + print(dfhack.script_help()) + return +end +local viewsheets = df.global.game.main_interface.view_sheets local stockpile = dfhack.gui.getSelectedStockpile(true) local selectedItem = dfhack.gui.getSelectedItem(true) local selectedBuilding = dfhack.gui.getSelectedBuilding(true) + if stockpile then local contents = dfhack.buildings.getStockpileContents(stockpile) for _, container in ipairs(contents) do @@ -33,7 +83,9 @@ if stockpile then elseif selectedItem then emptyContainer(selectedItem) elseif selectedBuilding then - if not df.building_actual:is_instance(selectedBuilding) then return end + if not selectedBuilding:isActual() then + return + end for _, contained in ipairs(selectedBuilding.contained_items) do if contained.use_mode == df.building_item_role_type.TEMP then emptyContainer(contained.item) @@ -46,4 +98,4 @@ elseif viewsheets.open then end else qerror("Please select a container, building, stockpile, or tile with a list of items.") -end +end \ No newline at end of file From c0bade1a3015e8c802616a6fa79e2961dc6cef3d Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 29 Jul 2024 19:14:40 +0300 Subject: [PATCH 10/38] append missing additional empty line in end of files --- docs/empty-bin.rst | 2 +- empty-bin.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 1e0a8d124d..3f33bff683 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -30,4 +30,4 @@ Options ``-r|--recursive`` Whether to apply the command recursively. ``-l|--liquids`` - Include liquids (DRINK and LIQUID_MISC) in the items list to be moved to the ground. \ No newline at end of file + Include liquids (DRINK and LIQUID_MISC) in the items list to be moved to the ground. diff --git a/empty-bin.lua b/empty-bin.lua index 62280ad26c..c2636e31be 100644 --- a/empty-bin.lua +++ b/empty-bin.lua @@ -98,4 +98,4 @@ elseif viewsheets.open then end else qerror("Please select a container, building, stockpile, or tile with a list of items.") -end \ No newline at end of file +end From cbc384296a46829bb83f144efb53b4a76ecfbd36 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 30 Jul 2024 00:38:23 +0300 Subject: [PATCH 11/38] single quotes for strings --- empty-bin.lua | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/empty-bin.lua b/empty-bin.lua index c2636e31be..853563b8c3 100644 --- a/empty-bin.lua +++ b/empty-bin.lua @@ -1,9 +1,9 @@ -- Empty a bin onto the floor --- Based on "emptybin" by StoneToad +-- Based on 'emptybin' by StoneToad -- https://gist.github.com/stonetoad/11129025 -- http://dwarffortresswiki.org/index.php/DF2014_Talk:Bin -local argparse = require("argparse") +local argparse = require('argparse') local options, args = { help = false, @@ -15,7 +15,7 @@ local options, args = { local function emptyContainer(container) local items = dfhack.items.getContainedItems(container) if #items > 0 then - print("Emptying " .. dfhack.items.getReadableDescription(container)) + print('Emptying ' .. dfhack.items.getReadableDescription(container)) local pos = xyz2pos(dfhack.items.getPosition(container)) for _, item in ipairs(items) do local skip_liquid = @@ -23,12 +23,12 @@ local function emptyContainer(container) item:getType() == df.item_type.DRINK and not options.liquids if skip_liquid then print( - " " .. + ' ' .. dfhack.items.getReadableDescription(item) .. - " was skipped because the liquid flag was not provided" + ' was skipped because the liquid flag was not provided' ) else - print(" " .. dfhack.items.getReadableDescription(item)) + print(' ' .. dfhack.items.getReadableDescription(item)) dfhack.items.moveToGround(item, pos) if options.recursive then emptyContainer(item) @@ -42,22 +42,22 @@ argparse.processArgsGetopt( args, { { - "h", - "help", + 'h', + 'help', handler = function() options.help = true end }, { - "r", - "recursive", + 'r', + 'recursive', handler = function() options.recursive = true end }, { - "l", - "liquids", + 'l', + 'liquids', handler = function() options.liquids = true end @@ -97,5 +97,5 @@ elseif viewsheets.open then emptyContainer(item) end else - qerror("Please select a container, building, stockpile, or tile with a list of items.") + qerror('Please select a container, building, stockpile, or tile with a list of items.') end From a5a62b7b55823664b5e9376351c0e343f2587d9e Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 30 Jul 2024 00:42:38 +0300 Subject: [PATCH 12/38] Removed the warning about emptying liquid onto the floor --- docs/empty-bin.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 3f33bff683..3be4a2b674 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -9,9 +9,6 @@ This tool can quickly empty the contents of the selected container (bin, barrel, pot, wineskin, quiver, etc.) onto the floor, allowing you to access individual items that might otherwise be hard to get to. -Note that if there are liquids in the container, they will empty onto the floor -and become unusable. - If you instead select a stockpile or building, running `empty-bin` will empty *all* containers in the stockpile or building. Likewise, if you select a tile that has many items and the UI is showing the list of items, all containers on From a4990367a816f60d2c55414c4af9758378c9a8a9 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 30 Jul 2024 00:46:36 +0300 Subject: [PATCH 13/38] fixed command options formatting and descriptions --- docs/empty-bin.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 3be4a2b674..1f21c5249b 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -24,7 +24,7 @@ Usage Options -------------- -``-r|--recursive`` - Whether to apply the command recursively. -``-l|--liquids`` - Include liquids (DRINK and LIQUID_MISC) in the items list to be moved to the ground. +``-r``, ``--recursive`` + Recursively empty containers. +``-l``, ``--liquids`` + Move contained liquids (DRINK and LIQUID_MISC) to the floor, making them unusable. From 88024936773feb52d70eafc547cf25bc81d13375 Mon Sep 17 00:00:00 2001 From: Elias Date: Tue, 30 Jul 2024 00:56:29 +0300 Subject: [PATCH 14/38] short option handlers and badly autoformatted code rows now is oneliners --- empty-bin.lua | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/empty-bin.lua b/empty-bin.lua index 853563b8c3..9647333a69 100644 --- a/empty-bin.lua +++ b/empty-bin.lua @@ -18,15 +18,9 @@ local function emptyContainer(container) print('Emptying ' .. dfhack.items.getReadableDescription(container)) local pos = xyz2pos(dfhack.items.getPosition(container)) for _, item in ipairs(items) do - local skip_liquid = - item:getType() == df.item_type.LIQUID_MISC or - item:getType() == df.item_type.DRINK and not options.liquids + local skip_liquid = item:getType() == df.item_type.LIQUID_MISC or item:getType() == df.item_type.DRINK and not options.liquids if skip_liquid then - print( - ' ' .. - dfhack.items.getReadableDescription(item) .. - ' was skipped because the liquid flag was not provided' - ) + print(' ' .. dfhack.items.getReadableDescription(item) .. ' was skipped because the liquid flag was not provided') else print(' ' .. dfhack.items.getReadableDescription(item)) dfhack.items.moveToGround(item, pos) @@ -38,32 +32,11 @@ local function emptyContainer(container) end end -argparse.processArgsGetopt( - args, - { - { - 'h', - 'help', - handler = function() - options.help = true - end - }, - { - 'r', - 'recursive', - handler = function() - options.recursive = true - end - }, - { - 'l', - 'liquids', - handler = function() - options.liquids = true - end - } - } -) +argparse.processArgsGetopt(args,{ + { 'h', 'help', handler = function() options.help = true end }, + { 'r', 'recursive', handler = function() options.recursive = true end }, + { 'l', 'liquids', handler = function() options.liquids = true end } + }) if options.help then print(dfhack.script_help()) From 03a403fe706bd15656cee216dd3bdda287b06518 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 15:07:51 -0700 Subject: [PATCH 15/38] Update autodump.lua --- gui/autodump.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index 3a3556aaaa..fe925e342e 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -308,7 +308,7 @@ end local function tile_props(pos, tt) --Returns is_ground, is_open_air local shape_attrs = df.tiletype_shape.attrs[df.tiletype.attrs[tt].shape] - if shape_attrs.walkable then --TODO: don't dump on statues, etc.? + if shape_attrs.walkable then return true, false --Floor, stair, or ramp elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then return false, false --Wall or fortification @@ -334,19 +334,19 @@ end function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() if not pos then - print('No cursor') + dfhack.printerr('Please hover mouse cursor over a target tile to dump to!') return end local tt = dfhack.maps.getTileType(pos) - if not tt then - print('No map block') + if not tt or !dfhack.maps.isTileVisible(pos) then + dfhack.printerr('Dump tile not visible! Must be in a revealed area of map.') return end local on_ground, in_air = tile_props(pos, tt) if not (on_ground or in_air) then - print('Dump tile blocked') + dfhack.printerr('Dump tile blocked! Can\'t dump on walls, fortifications, or certain midair buildings.') return end From 2ba17c9c2ff5eaff531f11443a257d2c4ca3bddd Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 15:17:47 -0700 Subject: [PATCH 16/38] Docs * Update autodump.rst * Update autodump.lua --- docs/gui/autodump.rst | 7 +++++-- gui/autodump.lua | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/gui/autodump.rst b/docs/gui/autodump.rst index d20ca0021f..ec715af5a7 100644 --- a/docs/gui/autodump.rst +++ b/docs/gui/autodump.rst @@ -9,8 +9,11 @@ This is a general point and click interface for teleporting or destroying items. By default, it will teleport items you have marked for dumping, but if you draw boxes around items on the map, it will act on the selected items instead. Double-click anywhere on the map to teleport the items there. Be wary -(or excited) that if you teleport the items into an unsupported position (e.g. -mid-air), then they will become projectiles and fall. +(or excited) that if you teleport the items into an unsupported position +(e.g., mid-air), then they will become projectiles and fall. Items may not be +teleported into walls or fortifications. They may also not be teleported into +projectile-blocking mid-air buildings (such as water wheels), as this can +result in unintended item destruction. There are options to include or exclude forbidden items, items that are currently tagged as being used by an active job, and items dropped by traders. diff --git a/gui/autodump.lua b/gui/autodump.lua index fe925e342e..caee30c3b8 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -339,14 +339,14 @@ function Autodump:do_dump(pos) end local tt = dfhack.maps.getTileType(pos) - if not tt or !dfhack.maps.isTileVisible(pos) then + if not (tt and dfhack.maps.isTileVisible(pos)) then dfhack.printerr('Dump tile not visible! Must be in a revealed area of map.') return end local on_ground, in_air = tile_props(pos, tt) if not (on_ground or in_air) then - dfhack.printerr('Dump tile blocked! Can\'t dump on walls, fortifications, or certain midair buildings.') + dfhack.printerr('Dump tile blocked! Can\'t dump on walls, fortifications, or certain mid-air buildings.') return end From ec3e49e8580830fb27508880be30609315a07dcd Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 18:38:02 -0700 Subject: [PATCH 17/38] Just set proper projectile flags --- changelog.txt | 2 +- docs/gui/autodump.rst | 4 +--- gui/autodump.lua | 44 ++++++++++++++----------------------------- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/changelog.txt b/changelog.txt index 68e10f0dd6..9c3b96618a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,7 +38,7 @@ Template for new versions: - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) - `fix/dead-units`: fix error when removing dead units from burrows and the unit with the greatest ID was dead -- `gui/autodump`: fix dumping into invalid tiles, creating item projectiles that get destroyed +- `gui/autodump`: prevent dumping into walls or invalid map area, set proper projectile flags to prevent items from being destroyed ## Misc Improvements - `build-now`: if `suspendmanager` is running, run an unsuspend cycle immediately before scanning for buildings to build diff --git a/docs/gui/autodump.rst b/docs/gui/autodump.rst index ec715af5a7..2d92bfa41d 100644 --- a/docs/gui/autodump.rst +++ b/docs/gui/autodump.rst @@ -11,9 +11,7 @@ you draw boxes around items on the map, it will act on the selected items instead. Double-click anywhere on the map to teleport the items there. Be wary (or excited) that if you teleport the items into an unsupported position (e.g., mid-air), then they will become projectiles and fall. Items may not be -teleported into walls or fortifications. They may also not be teleported into -projectile-blocking mid-air buildings (such as water wheels), as this can -result in unintended item destruction. +teleported into walls or fortifications. There are options to include or exclude forbidden items, items that are currently tagged as being used by an active job, and items dropped by traders. diff --git a/gui/autodump.lua b/gui/autodump.lua index caee30c3b8..b26ea8b5c2 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -306,31 +306,6 @@ function Autodump:onRenderFrame(dc, rect) end end -local function tile_props(pos, tt) --Returns is_ground, is_open_air - local shape_attrs = df.tiletype_shape.attrs[df.tiletype.attrs[tt].shape] - if shape_attrs.walkable then - return true, false --Floor, stair, or ramp - elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then - return false, false --Wall or fortification - end - - local _, occ = dfhack.maps.getTileFlags(pos) - if occ.building == df.tile_building_occ.None or - occ.building == df.tile_building_occ.Planned or - occ.building == df.tile_building_occ.Passable or - occ.building == df.tile_building_occ.Well then - return false, true --Item can fall safely through; any other may delete item projectile - elseif occ.building == df.tile_building_occ.Floored then - return true, false --Lowered bridge, forbidden hatch, etc. - elseif occ.building == df.tile_building_occ.Dynamic then - local bld = dfhack.buildings.findAtTile(pos) --Unforbidden hatch, etc. - return (bld and (bld._type == df.building_hatchst or - bld._type == df.building_grate_floorst or - bld._type == df.building_bars_floorst)), false - end - return false, false --Don't trust it -end - function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() if not pos then @@ -344,9 +319,12 @@ function Autodump:do_dump(pos) return end - local on_ground, in_air = tile_props(pos, tt) - if not (on_ground or in_air) then - dfhack.printerr('Dump tile blocked! Can\'t dump on walls, fortifications, or certain mid-air buildings.') + local on_ground + local shape_attrs = df.tiletype_shape.attrs[df.tiletype.attrs[tt].shape] + if shape_attrs.walkable then + on_ground = true --Floor, stair, or ramp + elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then + dfhack.printerr('Dump tile blocked! Can\'t dump on walls or fortifications.') return end @@ -366,8 +344,14 @@ function Autodump:do_dump(pos) if mark_as_forbidden then item.flags.forbid = true end - if in_air then - dfhack.items.makeProjectile(item) + if not on_ground then + local proj = dfhack.items.makeProjectile(item) + proj.flags.no_impact_destroy = true + proj.flags.bouncing = true + proj.flags.piercing = true + proj.flags.parabolic = true + proj.flags.no_adv_pause = true + proj.flags.no_collide = true end else print(('Could not move item: %s from (%d, %d, %d)'):format( From b8cb6ab156240f1c19704bdbbcc4c103e8c7f4d2 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 19:05:54 -0700 Subject: [PATCH 18/38] Update autodump.lua --- gui/autodump.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index b26ea8b5c2..01191349b8 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -324,7 +324,7 @@ function Autodump:do_dump(pos) if shape_attrs.walkable then on_ground = true --Floor, stair, or ramp elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then - dfhack.printerr('Dump tile blocked! Can\'t dump on walls or fortifications.') + dfhack.printerr('Dump tile blocked! Can\'t dump inside walls or fortifications.') return end @@ -346,12 +346,14 @@ function Autodump:do_dump(pos) end if not on_ground then local proj = dfhack.items.makeProjectile(item) - proj.flags.no_impact_destroy = true - proj.flags.bouncing = true - proj.flags.piercing = true - proj.flags.parabolic = true - proj.flags.no_adv_pause = true - proj.flags.no_collide = true + if proj then + proj.flags.no_impact_destroy = true + proj.flags.bouncing = true + proj.flags.piercing = true + proj.flags.parabolic = true + proj.flags.no_adv_pause = true + proj.flags.no_collide = true + end end else print(('Could not move item: %s from (%d, %d, %d)'):format( From 2fa3a432d9d5cbfdc1eafa304a6303b5ca2fde6e Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 19:14:33 -0700 Subject: [PATCH 19/38] Update autodump.lua --- gui/autodump.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index 01191349b8..606c76ccdc 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -308,10 +308,7 @@ end function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() - if not pos then - dfhack.printerr('Please hover mouse cursor over a target tile to dump to!') - return - end + if not pos then return end --Shouldn't happen since button would be disabled local tt = dfhack.maps.getTileType(pos) if not (tt and dfhack.maps.isTileVisible(pos)) then From 9f4b85f4dab424ef5e8e57d1c50d3a93dc98ebc5 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 29 Jul 2024 19:18:17 -0700 Subject: [PATCH 20/38] Update autodump.lua --- gui/autodump.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/autodump.lua b/gui/autodump.lua index 606c76ccdc..f1975c0ce7 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -308,7 +308,9 @@ end function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() - if not pos then return end --Shouldn't happen since button would be disabled + if not pos then --We check this before calling + dfhack.printerr('Autodump:do_dump called with bad pos!') + end local tt = dfhack.maps.getTileType(pos) if not (tt and dfhack.maps.isTileVisible(pos)) then From 28724780425dad41566c4026967b0d8839b860ee Mon Sep 17 00:00:00 2001 From: Droseran <97368320+Droseran@users.noreply.github.com> Date: Tue, 30 Jul 2024 04:26:28 -0400 Subject: [PATCH 21/38] Update changelog.txt Remove line from changelog referencing a non-user facing aspect of this update. --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 43eb133134..7761ae2b63 100644 --- a/changelog.txt +++ b/changelog.txt @@ -93,7 +93,6 @@ Template for new versions: - `clear-smoke`: properly tag smoke flows for garbage collection to avoid memory leak - `warn-stranded`: don't warn for babies carried by mothers who happen to be gathering fruit from trees - `prioritize`: also boost priority of already-claimed jobs when boosting priority of a job type so those jobs are not interrupted -- `ban-cooking`: stop iterating through plant materials when the searched-for material is found - `ban-cooking`: ban all seed producing items from being cooked when 'seeds' is chosen instead of just brewable seed producing items ## Misc Improvements From daf59a37c0720aa699b1ad4fb6fcc213e22bf6dd Mon Sep 17 00:00:00 2001 From: Droseran <97368320+Droseran@users.noreply.github.com> Date: Tue, 30 Jul 2024 04:29:30 -0400 Subject: [PATCH 22/38] Update ban-cooking.rst Clarify that the 'seeds' option also bans plants and growths which produce seeds in reactions. --- docs/ban-cooking.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ban-cooking.rst b/docs/ban-cooking.rst index 0c53799b95..38d2ba2b1f 100644 --- a/docs/ban-cooking.rst +++ b/docs/ban-cooking.rst @@ -45,7 +45,7 @@ Valid types are: - ``milk`` - ``mill`` (millable plants) - ``oil`` -- ``seeds`` (plantable seeds) +- ``seeds`` (plantable seeds and items producing seeds) - ``tallow`` - ``thread`` From 73aea9af724cdefad758e982f95a8e0a346e607e Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 30 Jul 2024 21:57:19 -0700 Subject: [PATCH 23/38] Changes; prevent selecting in nil map block * Update autodump.lua * Update autodump.rst * Update changelog.txt --- changelog.txt | 3 ++- docs/gui/autodump.rst | 2 +- gui/autodump.lua | 13 +++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/changelog.txt b/changelog.txt index 9c3b96618a..5332812833 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,7 +38,8 @@ Template for new versions: - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) - `fix/dead-units`: fix error when removing dead units from burrows and the unit with the greatest ID was dead -- `gui/autodump`: prevent dumping into walls or invalid map area, set proper projectile flags to prevent items from being destroyed +- `gui/autodump`: prevent dumping into walls or invalid map area, as well as selecting in unallocated blocks +- `gui/autodump`: set proper projectile flags to prevent items from being destroyed ## Misc Improvements - `build-now`: if `suspendmanager` is running, run an unsuspend cycle immediately before scanning for buildings to build diff --git a/docs/gui/autodump.rst b/docs/gui/autodump.rst index 2d92bfa41d..1b8b28c823 100644 --- a/docs/gui/autodump.rst +++ b/docs/gui/autodump.rst @@ -11,7 +11,7 @@ you draw boxes around items on the map, it will act on the selected items instead. Double-click anywhere on the map to teleport the items there. Be wary (or excited) that if you teleport the items into an unsupported position (e.g., mid-air), then they will become projectiles and fall. Items may not be -teleported into walls or fortifications. +teleported into walls. There are options to include or exclude forbidden items, items that are currently tagged as being used by an active job, and items dropped by traders. diff --git a/gui/autodump.lua b/gui/autodump.lua index f1975c0ce7..d3ec16c840 100644 --- a/gui/autodump.lua +++ b/gui/autodump.lua @@ -229,7 +229,7 @@ function Autodump:select_box(bounds) for x=bounds.x1,bounds.x2 do local block = dfhack.maps.getTileBlock(xyz2pos(x, y, z)) local block_str = tostring(block) - if not seen_blocks[block_str] then + if block and not seen_blocks[block_str] then seen_blocks[block_str] = true self:select_items_in_block(block, bounds) end @@ -309,7 +309,7 @@ end function Autodump:do_dump(pos) pos = pos or dfhack.gui.getMousePos() if not pos then --We check this before calling - dfhack.printerr('Autodump:do_dump called with bad pos!') + qerror('Autodump:do_dump called with bad pos!') end local tt = dfhack.maps.getTileType(pos) @@ -319,11 +319,12 @@ function Autodump:do_dump(pos) end local on_ground - local shape_attrs = df.tiletype_shape.attrs[df.tiletype.attrs[tt].shape] - if shape_attrs.walkable then - on_ground = true --Floor, stair, or ramp + local shape = df.tiletype.attrs[tt].shape + local shape_attrs = df.tiletype_shape.attrs[shape] + if shape_attrs.walkable or shape == df.tiletype_shape.FORTIFICATION then + on_ground = true --Floor, stair, ramp, or fortification elseif shape_attrs.basic_shape == df.tiletype_shape_basic.Wall then - dfhack.printerr('Dump tile blocked! Can\'t dump inside walls or fortifications.') + dfhack.printerr('Dump tile blocked! Can\'t dump inside walls.') --Wall or brook bed return end From ad0df042da253c0c31e1310851f6a865199fd1ec Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 30 Jul 2024 22:01:59 -0700 Subject: [PATCH 24/38] Update autodump.rst --- docs/gui/autodump.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gui/autodump.rst b/docs/gui/autodump.rst index 1b8b28c823..cad8c4f4eb 100644 --- a/docs/gui/autodump.rst +++ b/docs/gui/autodump.rst @@ -11,7 +11,7 @@ you draw boxes around items on the map, it will act on the selected items instead. Double-click anywhere on the map to teleport the items there. Be wary (or excited) that if you teleport the items into an unsupported position (e.g., mid-air), then they will become projectiles and fall. Items may not be -teleported into walls. +teleported into walls or hidden tiles. There are options to include or exclude forbidden items, items that are currently tagged as being used by an active job, and items dropped by traders. From 039fe84e6316c9d0ed55f4e1e23642bbf33464a6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 30 Jul 2024 22:53:01 -0700 Subject: [PATCH 25/38] set histfig name as well as unit name --- changelog.txt | 1 + makeown.lua | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/changelog.txt b/changelog.txt index 720297b06c..9fbb1263cb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -38,6 +38,7 @@ Template for new versions: - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) - `fix/dead-units`: fix error when removing dead units from burrows and the unit with the greatest ID was dead +- `makeown`: ensure names given to adopted units (or units created with `gui/sandbox`) are respected later in legends mode ## Misc Improvements - `build-now`: if `suspendmanager` is running, run an unsuspend cycle immediately before scanning for buildings to build diff --git a/makeown.lua b/makeown.lua index cbfe3433aa..565b7c6856 100644 --- a/makeown.lua +++ b/makeown.lua @@ -32,6 +32,10 @@ function name_unit(unit) unit.name.parts_of_speech.RearCompound = df.part_of_speech.Verb3rdPerson unit.name.type = df.language_name_type.Figure unit.name.has_name = true + + local hf = df.historical_figure.find(unit.hist_figure_id) + if not hf then return end + hf.name:assign(unit.name) end local function fix_clothing_ownership(unit) From 65b18958ebbad6800d010c3d77179ab72f775a52 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 31 Jul 2024 07:14:35 -0700 Subject: [PATCH 26/38] appropriately speed up eating and drinking --- changelog.txt | 1 + timestream.lua | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/changelog.txt b/changelog.txt index 332137c2e1..70dd90b4d7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -34,6 +34,7 @@ Template for new versions: - `full-heal`: fix ``-r --all_citizens`` option combination not resurrecting citizens - `open-legends`: don't intercept text bound for vanilla search widgets - `gui/unit-info-viewer`: correctly display skill levels when rust is involved +- `timestream`: fix dwarves spending too long eating and drinking - `build-now`: fix error when building buildings that (in previous DF versions) required the architecture labor - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) diff --git a/timestream.lua b/timestream.lua index c520f696be..f02e766fc9 100644 --- a/timestream.lua +++ b/timestream.lua @@ -138,6 +138,19 @@ local function adjust_unit_counters(unit, timeskip) -- stored_fat wanders about based on other state; we can probably leave it alone end +-- TODO: the rquired algorithm is not yet fully known. for many job types, the decrement per +-- tick is <= 1 and depends on unit skills +local function adjust_job_counter(unit, timeskip) + local job = unit.job.current_job + if not job then return end + local job_type = job.job_type + if job_type == df.job_type.Eat or + job_type == df.job_type.Drink + then + decrement_counter(job, 'completion_timer', timeskip) + end +end + -- unit needs appear to be incremented on season ticks, so we don't need to worry about those local function adjust_units(timeskip) for _, unit in ipairs(df.global.world.units.active) do @@ -146,6 +159,7 @@ local function adjust_units(timeskip) dfhack.units.subtractGroupActionTimers(unit, timeskip, df.unit_action_type_group.All) if not dfhack.units.isOwnGroup(unit) then goto continue end adjust_unit_counters(unit, timeskip) + adjust_job_counter(unit, timeskip) ::continue:: end end From 9bb2b6e4bd7324a671dcf01547273f310fa740c0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 31 Jul 2024 09:36:52 -0700 Subject: [PATCH 27/38] ensure we choose an appropriate mineral tile --- changelog.txt | 1 + docs/locate-ore.rst | 22 +++--- locate-ore.lua | 188 +++++++++++++++++--------------------------- 3 files changed, 85 insertions(+), 126 deletions(-) diff --git a/changelog.txt b/changelog.txt index 70dd90b4d7..befc3fa316 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ Template for new versions: - `open-legends`: don't intercept text bound for vanilla search widgets - `gui/unit-info-viewer`: correctly display skill levels when rust is involved - `timestream`: fix dwarves spending too long eating and drinking +- `locate-ore`: fix sometimes selecting an incorrect tile when there are multiple mineral veins in a single map block - `build-now`: fix error when building buildings that (in previous DF versions) required the architecture labor - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) diff --git a/docs/locate-ore.rst b/docs/locate-ore.rst index c25a1c880f..3b8f2e955f 100644 --- a/docs/locate-ore.rst +++ b/docs/locate-ore.rst @@ -6,17 +6,21 @@ locate-ore :tags: fort armok productivity map This tool finds and designates for digging one tile of a specific metal ore. If -you want to dig **all** tiles of that kind of ore, select that tile with the -cursor and run `digtype `. +you want to dig **all** tiles of that kind of ore, highlight that tile with the +keyboard cursor and run `digtype `. -By default, the tool only searches for visible ore veins. +By default, the tool only searches ore veins that your dwarves have discovered. + +Note that looking for a particular metal might find an ore that contains that +metal along with other metals. For example, locating silver may find +tetrahedrite, which contains silver and copper. Usage ----- -``locate-ore list`` +``locate-ore [list] []`` List metal ores available on the map. -``locate-ore `` +``locate-ore []`` Finds a tile of the specified ore type, zooms the screen so that tile is visible, and designates that tile for digging. @@ -24,17 +28,17 @@ Options ------- ``-a``, ``--all`` - Allow undiscovered ore veins to be marked. + Also search undiscovered ore veins. Examples -------- +``locate-ore`` + List discovered + :: locate-ore hematite locate-ore iron locate-ore silver --all -Note that looking for a particular metal might find an ore that contains that -metal along with other metals. For example, locating silver may find -tetrahedrite, which contains silver and copper. diff --git a/locate-ore.lua b/locate-ore.lua index 6fbf9c67d5..61d89533cd 100644 --- a/locate-ore.lua +++ b/locate-ore.lua @@ -2,15 +2,11 @@ local argparse = require('argparse') -local tile_attrs = df.tiletype.attrs - local function extractKeys(target_table) local keyset = {} - for k, _ in pairs(target_table) do table.insert(keyset, k) end - return keyset end @@ -40,24 +36,6 @@ local function getRandomFromTable(target_table) return target_table[key] end -local function randomSort(target_table) - local rnd = {} - table.sort( target_table, - function ( a, b) - rnd[a] = rnd[a] or math.random() - rnd[b] = rnd[b] or math.random() - return rnd[a] > rnd[b] - end ) -end - -local function sequence(min, max) - local tbl = {} - for i=min,max do - table.insert(tbl, i) - end - return tbl -end - local function sortTableBy(tbl, sort_func) local sorted = {} for _, value in pairs(tbl) do @@ -80,14 +58,21 @@ local function matchesMetalOreById(mat_indices, target_ore) return false end -local function findOreVeins(target_ore, show_undiscovered) - if target_ore then - target_ore = string.lower(target_ore) - end +local tile_attrs = df.tiletype.attrs - local ore_veins = {} - for _, block in pairs(df.global.world.map.map_blocks) do - for _, bevent in pairs(block.block_events) do +local function isValidMineralTile(opts, pos, check_designation) + if not opts.all and not dfhack.maps.isTileVisible(pos) then return false end + local tt = dfhack.maps.getTileType(pos) + if not tt then return false end + return tile_attrs[tt].material == df.tiletype_material.MINERAL and + (not check_designation or dfhack.maps.getTileFlags(pos).dig == df.tile_dig_designation.No) and + tile_attrs[tt].shape == df.tiletype_shape.WALL +end + +local function findOres(opts, check_designation, target_ore) + local ore_types = {} + for _, block in ipairs(df.global.world.map.map_blocks) do + for _, bevent in ipairs(block.block_events) do if bevent:getType() ~= df.block_square_event_type.mineral then goto skipevent end @@ -97,130 +82,99 @@ local function findOreVeins(target_ore, show_undiscovered) goto skipevent end - if not show_undiscovered and not bevent.flags.discovered then + if not opts.all and not bevent.flags.discovered then goto skipevent end local lower_raw = string.lower(ino_raw.id) if not target_ore or lower_raw == target_ore or matchesMetalOreById(ino_raw.metal_ore.mat_index, target_ore) then - if not ore_veins[bevent.inorganic_mat] then - local vein_info = { + local positions = ensure_key(ore_types, bevent.inorganic_mat, { inorganic_id = ino_raw.id, inorganic_mat = bevent.inorganic_mat, metal_ore = ino_raw.metal_ore, positions = {} - } - ore_veins[bevent.inorganic_mat] = vein_info + }).positions + local block_pos = block.map_pos + for y=0,15 do + local row = bevent.tile_bitmask.bits[y] + for x=0,15 do + if row & (1 << x) == 1 then + local pos = xyz2pos(block_pos.x + x, block_pos.y + y, block_pos.z) + if isValidMineralTile(opts, pos, check_designation) then + table.insert(positions, pos) + end + end + end end - - table.insert(ore_veins[bevent.inorganic_mat].positions, block.map_pos) end - :: skipevent :: end end - return ore_veins + -- trim veins with zero valid tiles + for key,vein in pairs(ore_types) do + if #vein.positions == 0 then + ore_types[key] = nil + end + end + + return ore_types end local function designateDig(pos) local designation = dfhack.maps.getTileFlags(pos) designation.dig = df.tile_dig_designation.Default + dfhack.maps.getTileBlock(pos).flags.designated = true end -local function getOreDescription(ore) - local str = ("%s ("):format(string.lower(tostring(ore.inorganic_id))) - for _, mat_index in ipairs(ore.metal_ore.mat_index) do +local function getOreDescription(opts, vein) + local visible = opts.all and '' or 'visible ' + local str = ('%5d %stiles of %s ('):format(#vein.positions, visible, tostring(vein.inorganic_id):lower()) + for _, mat_index in ipairs(vein.metal_ore.mat_index) do local metal_raw = df.global.world.raws.inorganics[mat_index] - str = ("%s%s, "):format(str, string.lower(metal_raw.id)) + str = ('%s%s, '):format(str, string.lower(metal_raw.id)) end - str = str:gsub(", %s*$", "") .. ')' + str = str:gsub(', %s*$', '') .. ')' return str end -local options, args = { - help = false, - show_undiscovered = false -}, {...} +local function selectOreTile(opts, target_ore) + local ore_types = findOres(opts, true, target_ore) + local target_vein = getRandomFromTable(ore_types) + if target_vein == nil then + local visible = opts.all and '' or 'visible ' + qerror('Cannot find any undesignated ' .. visible .. target_ore) + end + local target_pos = target_vein.positions[math.random(#target_vein.positions)] + dfhack.gui.revealInDwarfmodeMap(target_pos, true, true) + designateDig(target_pos) + print(('Here is some %s'):format(target_vein.inorganic_id)) +end + +local opts = { + all=false, + help=false, +} -local positionals = argparse.processArgsGetopt(args, { - {'h', 'help', handler=function() options.help = true end}, - {'a', 'all', handler=function() options.show_undiscovered = true end}, +local positionals = argparse.processArgsGetopt({...}, { + {'a', 'all', handler=function() opts.all = true end}, + {'h', 'help', handler=function() opts.help = true end}, }) -if positionals[1] == "help" or options.help then +local target_ore = positionals[1] +if target_ore == 'help' or opts.help then print(dfhack.script_help()) return end -if positionals[1] == nil or positionals[1] == "list" then - print(dfhack.script_help()) - local veins = findOreVeins(nil, options.show_undiscovered) - local sorted = sortTableBy(veins, function(a, b) return #a.positions < #b.positions end) +if not target_ore or target_ore == 'list' then + local ore_types = findOres(opts, false) + local sorted = sortTableBy(ore_types, function(a, b) return #a.positions < #b.positions end) - for _, vein in ipairs(sorted) do - print(" " .. getOreDescription(vein)) + for _,ore_type in ipairs(sorted) do + print(' ' .. getOreDescription(opts, ore_type)) end - return else - local veins = findOreVeins(positionals[1], options.show_undiscovered) - local vein_keys = extractKeys(veins) - - if #vein_keys == 0 then - qerror("Cannot find unmined " .. positionals[1]) - end - - local target_vein = getRandomFromTable(veins) - if target_vein == nil then - -- really shouldn't happen at this point - qerror("Failed to choose vein from available choices") - end - - local pos_keyset = extractKeys(target_vein.positions) - local dxs = sequence(0, 15) - local dys = sequence(0, 15) - - randomSort(pos_keyset) - randomSort(dxs) - randomSort(dys) - - local target_pos = nil - for _, k in pairs(pos_keyset) do - local block_pos = target_vein.positions[k] - for _, dx in pairs(dxs) do - for _, dy in pairs(dys) do - local pos = { x = block_pos.x + dx, y = block_pos.y + dy, z = block_pos.z } - -- Enforce world boundaries - if pos.x <= 0 or pos.x >= df.global.world.map.x_count or pos.y <= 0 or pos.y >= df.global.world.map.y_count then - goto skip_pos - end - - if not options.show_undiscovered and not dfhack.maps.isTileVisible(pos) then - goto skip_pos - end - - local tile_type = dfhack.maps.getTileType(pos) - local tile_mat = tile_attrs[tile_type].material - local shape = tile_attrs[tile_type].shape - local designation = dfhack.maps.getTileFlags(pos) - if tile_mat == df.tiletype_material.MINERAL and designation.dig == df.tile_dig_designation.No and shape == df.tiletype_shape.WALL then - target_pos = pos - goto complete - end - - :: skip_pos :: - end - end - end - - :: complete :: - - if target_pos ~= nil then - dfhack.gui.pauseRecenter(target_pos) - designateDig(target_pos) - print(("Here is some %s at (%d, %d, %d)"):format(target_vein.inorganic_id, target_pos.x, target_pos.y, target_pos.z)) - else - qerror("Cannot find unmined " .. positionals[1]) - end + selectOreTile(opts, positionals[1]:lower()) end From e0053cff63c81b99193e6e817114c1c586341e71 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 31 Jul 2024 09:42:22 -0700 Subject: [PATCH 28/38] plurallll --- locate-ore.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locate-ore.lua b/locate-ore.lua index 61d89533cd..a9e027fbbf 100644 --- a/locate-ore.lua +++ b/locate-ore.lua @@ -129,7 +129,7 @@ end local function getOreDescription(opts, vein) local visible = opts.all and '' or 'visible ' - local str = ('%5d %stiles of %s ('):format(#vein.positions, visible, tostring(vein.inorganic_id):lower()) + local str = ('%5d %stile(s) of %s ('):format(#vein.positions, visible, tostring(vein.inorganic_id):lower()) for _, mat_index in ipairs(vein.metal_ore.mat_index) do local metal_raw = df.global.world.raws.inorganics[mat_index] str = ('%s%s, '):format(str, string.lower(metal_raw.id)) From 88d4867af4934401c5d339b292dab95e150721e9 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 31 Jul 2024 09:42:54 -0700 Subject: [PATCH 29/38] remove extra newline --- docs/locate-ore.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/locate-ore.rst b/docs/locate-ore.rst index 3b8f2e955f..d2fbf814b2 100644 --- a/docs/locate-ore.rst +++ b/docs/locate-ore.rst @@ -41,4 +41,3 @@ Examples locate-ore hematite locate-ore iron locate-ore silver --all - From a01304388145ec1ad2d219e113f9398d6991e108 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 31 Jul 2024 20:59:21 +0300 Subject: [PATCH 30/38] changed script message about missing --liquids option --- empty-bin.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empty-bin.lua b/empty-bin.lua index 9647333a69..f94a140816 100644 --- a/empty-bin.lua +++ b/empty-bin.lua @@ -20,7 +20,7 @@ local function emptyContainer(container) for _, item in ipairs(items) do local skip_liquid = item:getType() == df.item_type.LIQUID_MISC or item:getType() == df.item_type.DRINK and not options.liquids if skip_liquid then - print(' ' .. dfhack.items.getReadableDescription(item) .. ' was skipped because the liquid flag was not provided') + print(' ' .. dfhack.items.getReadableDescription(item) .. ' was skipped because the --liquids flag was not provided') else print(' ' .. dfhack.items.getReadableDescription(item)) dfhack.items.moveToGround(item, pos) From 4617ab166619b8ce3608221d5943b9a191af0885 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 31 Jul 2024 21:00:19 +0300 Subject: [PATCH 31/38] added example section --- docs/empty-bin.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 1f21c5249b..1d45eb81aa 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -21,6 +21,18 @@ Usage empty-bin [] +Examples +-------- + +``empty-bin`` + Empty the contents of selected containers or all containers in the selected stockpile or building, except containers with liquids, onto the floor. + +``empty-bin --liquids`` + Empty the contents of selected containers or all containers in the selected stockpile or building, including containers with liquids, onto the floor. + +``empty-bin --recursive --liquids`` + Empty the contents of selected containers or all containers in the selected stockpile or building, including containers with liquids and containers contents that are containers, such as a bags of seeds or filled waterskins, onto the floor. + Options -------------- From a56253e42da7b0b175d5fe5c65c9aa53bbe3293e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 11:27:11 -0700 Subject: [PATCH 32/38] handle job timers that don't depend on skill --- timestream.lua | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/timestream.lua b/timestream.lua index f02e766fc9..ce0a3cdd4f 100644 --- a/timestream.lua +++ b/timestream.lua @@ -135,23 +135,27 @@ local function adjust_unit_counters(unit, timeskip) decrement_counter(c2, 'stomach_content', timeskip * 5) decrement_counter(c2, 'stomach_food', timeskip * 5) decrement_counter(c2, 'vomit_timeout', timeskip) - -- stored_fat wanders about based on other state; we can probably leave it alone + -- stored_fat wanders about based on other state; we can likely leave it alone and + -- not materially affect gameplay end --- TODO: the rquired algorithm is not yet fully known. for many job types, the decrement per --- tick is <= 1 and depends on unit skills +-- TODO: for job types that depend on skill, the decrement per tick is <= 1 and depends on unit's skill level local function adjust_job_counter(unit, timeskip) local job = unit.job.current_job if not job then return end local job_type = job.job_type if job_type == df.job_type.Eat or - job_type == df.job_type.Drink + job_type == df.job_type.DrinkItem or + job_type == df.job_type.CollectSand then decrement_counter(job, 'completion_timer', timeskip) + elseif DEBUG and job.completion_timer > 1 then + print(('unhandled job type %s for unit %d'):format(df.job_type[job_type], unit.id)) end end -- unit needs appear to be incremented on season ticks, so we don't need to worry about those +-- since the TICK_TRIGGERS check makes sure that we never skip season ticks local function adjust_units(timeskip) for _, unit in ipairs(df.global.world.units.active) do if not dfhack.units.isActive(unit) then goto continue end @@ -190,14 +194,17 @@ local function adjust_activities(timeskip) -- countdown appears to never move from 0 decrement_counter(ev, 'countdown', timeskip) elseif df.activity_event_harassmentst:is_instance(ev) then - -- TODO: counter behavior not yet analyzed - -- print(i) + if DEBUG then + print('activity_event_harassmentst ready for analysis at index', i) + end elseif df.activity_event_encounterst:is_instance(ev) then - -- TODO: counter behavior not yet analyzed - -- print(i) + if DEBUG then + print('activity_event_encounterst ready for analysis at index', i) + end elseif df.activity_event_reunionst:is_instance(ev) then - -- TODO: counter behavior not yet analyzed - -- print(i) + if DEBUG then + print('activity_event_reunionst ready for analysis at index', i) + end elseif df.activity_event_conversationst:is_instance(ev) then increment_counter(ev, 'pause', timeskip) elseif df.activity_event_guardst:is_instance(ev) then @@ -235,8 +242,9 @@ local function adjust_activities(timeskip) elseif df.activity_event_performancest:is_instance(ev) then increment_counter(ev, 'current_position', timeskip) elseif df.activity_event_store_objectst:is_instance(ev) then - -- TODO: counter behavior not yet analyzed - -- print(i) + if DEBUG then + print('activity_event_store_objectst ready for analysis at index', i) + end end end end From 250123421e0142e4f863f0810a99190ca019367b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 13:18:29 -0700 Subject: [PATCH 33/38] decrement job completion timers we don't know what state the game uses to control the decrement, but we know that it happens once every 11 ticks, independent of the value of cur_year_tick. --- timestream.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/timestream.lua b/timestream.lua index ce0a3cdd4f..f97f9b02f0 100644 --- a/timestream.lua +++ b/timestream.lua @@ -139,7 +139,6 @@ local function adjust_unit_counters(unit, timeskip) -- not materially affect gameplay end --- TODO: for job types that depend on skill, the decrement per tick is <= 1 and depends on unit's skill level local function adjust_job_counter(unit, timeskip) local job = unit.job.current_job if not job then return end @@ -149,8 +148,13 @@ local function adjust_job_counter(unit, timeskip) job_type == df.job_type.CollectSand then decrement_counter(job, 'completion_timer', timeskip) - elseif DEBUG and job.completion_timer > 1 then - print(('unhandled job type %s for unit %d'):format(df.job_type[job_type], unit.id)) + elseif job.completion_timer > 1 then + -- for these job types, the decrement per tick is 1/11, but the counter on which + -- the decrement happens is unknown. it's not cur_year_tick + -- therefore, we decrement probabilistically + if math.random(11) <= timeskip then + decrement_counter(job, 'completion_timer', 1) + end end end @@ -283,6 +287,7 @@ local function on_tick() calendar_timeskip_deficit = math.max(0, desired_calendar_timeskip - calendar_timeskip) df.global.cur_year_tick = df.global.cur_year_tick + calendar_timeskip + df.global.cur_year_tick_advmode = df.global.cur_year_tick_advmode + calendar_timeskip*144 adjust_units(timeskip) adjust_activities(timeskip) From 069e217523e2dd6a1d6630e1b1877f28a4b01855 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 13:48:36 -0700 Subject: [PATCH 34/38] cancel jobs involving items in wheelbarrows before moving them --- fix/empty-wheelbarrows.lua | 45 +++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/fix/empty-wheelbarrows.lua b/fix/empty-wheelbarrows.lua index b3c0234832..00c00a9bfe 100644 --- a/fix/empty-wheelbarrows.lua +++ b/fix/empty-wheelbarrows.lua @@ -1,15 +1,12 @@ ---checks all wheelbarrows on map for rocks stuck in them. If a wheelbarrow isn't in use for a job (hauling) then there should be no rocks in them ---rocks will occasionally get stuck in wheelbarrows, and accumulate if the wheelbarrow gets used. ---this script empties all wheelbarrows which have rocks stuck in them. +-- checks all wheelbarrows on map for rocks stuck in them and empties such rocks onto the ground. If a wheelbarrow +-- isn't in use for a job (hauling) then there should be no rocks in them. local argparse = require("argparse") -local args = {...} - local quiet = false local dryrun = false -local cmds = argparse.processArgsGetopt(args, { +argparse.processArgsGetopt({...}, { {'q', 'quiet', handler=function() quiet = true end}, {'d', 'dry-run', handler=function() dryrun = true end}, }) @@ -17,24 +14,31 @@ local cmds = argparse.processArgsGetopt(args, { local i_count = 0 local e_count = 0 -local function emptyContainedItems(e, outputCallback) - local items = dfhack.items.getContainedItems(e) - if #items > 0 then - outputCallback('Emptying wheelbarrow: ' .. dfhack.items.getDescription(e, 0)) - e_count = e_count + 1 - for _,i in ipairs(items) do - outputCallback(' ' .. dfhack.items.getDescription(i, 0)) - if (not dryrun) then dfhack.items.moveToGround(i, e.pos) end - i_count = i_count + 1 +local function emptyContainedItems(wheelbarrow, outputCallback) + local items = dfhack.items.getContainedItems(wheelbarrow) + if #items == 0 then return end + outputCallback('Emptying wheelbarrow: ' .. dfhack.items.getReadableDescription(wheelbarrow)) + e_count = e_count + 1 + for _,item in ipairs(items) do + outputCallback(' ' .. dfhack.items.getReadableDescription(item)) + if not dryrun then + if item.flags.in_job then + local job_ref = dfhack.items.getSpecificRef(item, df.specific_ref_type.JOB) + if job_ref then + dfhack.job.removeJob(job_ref.data.job) + end + end + dfhack.items.moveToGround(item, wheelbarrow.pos) end + i_count = i_count + 1 end end local function emptyWheelbarrows(outputCallback) - for _,e in ipairs(df.global.world.items.other.TOOL) do + for _,item in ipairs(df.global.world.items.other.TOOL) do -- wheelbarrow must be on ground and not in a job - if ((not e.flags.in_job) and e.flags.on_ground and e:isWheelbarrow()) then - emptyContainedItems(e, outputCallback) + if ((not item.flags.in_job) and item.flags.on_ground and item:isWheelbarrow()) then + emptyContainedItems(item, outputCallback) end end end @@ -44,6 +48,7 @@ if (quiet) then output = (function(...) end) else output = print end emptyWheelbarrows(output) -if (i_count > 0 or (not quiet)) then - print(("fix/empty-wheelbarrows - removed %d items from %d wheelbarrows."):format(i_count, e_count)) +if i_count > 0 or not quiet then + local action = dryrun and 'would remove' or 'removed' + print(("fix/empty-wheelbarrows - %s %d item(s) from %d wheelbarrow(s)."):format(action, i_count, e_count)) end From 924f8d31ec1ab3ad4c79b2fe2bb9127449b48658 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 14:24:07 -0700 Subject: [PATCH 35/38] move timestream timeskip tracing to higher debug level --- timestream.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/timestream.lua b/timestream.lua index f97f9b02f0..9550c620d1 100644 --- a/timestream.lua +++ b/timestream.lua @@ -279,7 +279,9 @@ local function on_tick() -- don't let our deficit grow unbounded if we can never catch up timeskip_deficit = math.min(desired_timeskip - timeskip, 100.0) - if DEBUG then print(('timeskip (%d, +%.2f)'):format(timeskip, timeskip_deficit)) end + if DEBUG and (tonumber(DEBUG) or 0) >= 2 then + print(('timeskip (%d, +%.2f)'):format(timeskip, timeskip_deficit)) + end if timeskip <= 0 then return end local desired_calendar_timeskip = (timeskip * state.settings.calendar_rate) + calendar_timeskip_deficit From 684034b10f4619bb092efa721dc7dd1aac9d9c0b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 16:32:17 -0700 Subject: [PATCH 36/38] better account for unit actions with small timers --- timestream.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/timestream.lua b/timestream.lua index 9550c620d1..e41143e05d 100644 --- a/timestream.lua +++ b/timestream.lua @@ -139,23 +139,21 @@ local function adjust_unit_counters(unit, timeskip) -- not materially affect gameplay end +-- need to manually adjust job completion_timer values for jobs that are controlled by unit actions +-- with a timer of 1, which are destroyed immediately after they are created. longer-lived unit +-- actions are already sufficiently handled by dfhack.units.subtractGroupActionTimers(). +-- this will also decrement timers for jobs with actions that have just expired, but on average, this +-- should balance out to be correct, since we're losing time when we subtract from the action timers +-- and cap the value so it never drops below 1. local function adjust_job_counter(unit, timeskip) local job = unit.job.current_job if not job then return end - local job_type = job.job_type - if job_type == df.job_type.Eat or - job_type == df.job_type.DrinkItem or - job_type == df.job_type.CollectSand - then - decrement_counter(job, 'completion_timer', timeskip) - elseif job.completion_timer > 1 then - -- for these job types, the decrement per tick is 1/11, but the counter on which - -- the decrement happens is unknown. it's not cur_year_tick - -- therefore, we decrement probabilistically - if math.random(11) <= timeskip then - decrement_counter(job, 'completion_timer', 1) + for _,action in ipairs(unit.actions) do + if action.type == df.unit_action_type.Job or action.type == df.unit_action_type.JobRecover then + return end end + decrement_counter(job, 'completion_timer', timeskip) end -- unit needs appear to be incremented on season ticks, so we don't need to worry about those From a956c023e854d163310664600e5a0f3f6d3c4826 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 17:28:38 -0700 Subject: [PATCH 37/38] fix position of settings restored message when the player has no saved embark profiles --- changelog.txt | 1 + gui/settings-manager.lua | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index befc3fa316..d0f1ac59a3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -36,6 +36,7 @@ Template for new versions: - `gui/unit-info-viewer`: correctly display skill levels when rust is involved - `timestream`: fix dwarves spending too long eating and drinking - `locate-ore`: fix sometimes selecting an incorrect tile when there are multiple mineral veins in a single map block +- `gui/settings-manager`: fix position of "settings restored" message on embark when the player has no saved embark profiles - `build-now`: fix error when building buildings that (in previous DF versions) required the architecture labor - `prioritize`: fix incorrect loading of persisted data on some OS types - `list-waves`: no longer gets confused by units that leave the map and then return (e.g. squads who go out on raids) diff --git a/gui/settings-manager.lua b/gui/settings-manager.lua index 12a8a2e35e..1a7e468bf5 100644 --- a/gui/settings-manager.lua +++ b/gui/settings-manager.lua @@ -173,13 +173,13 @@ DifficultyEmbarkNotificationOverlay.ATTRS { default_pos={x=75, y=18}, viewscreens='setupdwarfgame/Default', default_enabled=true, - frame={w=23, h=3}, + frame={w=25, h=3}, } function DifficultyEmbarkNotificationOverlay:init() self:addviews{ widgets.Panel{ - frame={t=0, w=25}, + frame={h=3, b=0, w=25}, frame_style=gui.FRAME_MEDIUM, frame_background=gui.CLEAR_PEN, subviews={ @@ -197,6 +197,12 @@ function DifficultyEmbarkNotificationOverlay:preUpdateLayout(parent_rect) self.frame.w = parent_rect.width - (self.frame.l or (self.default_pos.x - 1)) end +function DifficultyEmbarkNotificationOverlay:render(dc) + local scr = dfhack.gui.getDFViewscreen(true) + self.frame.h = #scr.embark_profile == 0 and 11 or 3 + DifficultyEmbarkNotificationOverlay.super.render(self, dc) +end + local last_scr_type dfhack.onStateChange[GLOBAL_KEY] = function(sc) if sc ~= SC_VIEWSCREEN_CHANGED then return end From a6a452ade5957cfe1892a4bb92144e6fd755083d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 1 Aug 2024 19:32:09 -0700 Subject: [PATCH 38/38] remove unused function --- gui/mass-remove.lua | 7 ------- 1 file changed, 7 deletions(-) diff --git a/gui/mass-remove.lua b/gui/mass-remove.lua index c5c478f9ea..f138c50d87 100644 --- a/gui/mass-remove.lua +++ b/gui/mass-remove.lua @@ -58,13 +58,6 @@ end -- DimsPanel -- -local function get_dims(pos1, pos2) - local width, height, depth = math.abs(pos1.x - pos2.x) + 1, - math.abs(pos1.y - pos2.y) + 1, - math.abs(pos1.z - pos2.z) + 1 - return width, height, depth -end - DimsPanel = defclass(DimsPanel, widgets.ResizingPanel) DimsPanel.ATTRS{ get_mark_fn=DEFAULT_NIL,