diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..8b077fbd74 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +changelog.txt merge=union diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..e682b17afc --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ +If this PR makes an externally-visible change in behavior, please add an appropriate line to `changelog.txt`. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ca5c64fcc..c5694a6f43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.1 + rev: 0.37.4 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: v1.5.6 hooks: - id: forbid-tabs exclude_types: @@ -34,6 +34,6 @@ repos: - json # specific to scripts: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: forbid-new-submodules diff --git a/CMakeLists.txt b/CMakeLists.txt index e9e5234b0d..a673d8298a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PATTERN "*.json" PATTERN "scripts/docs" EXCLUDE PATTERN "scripts/test" EXCLUDE + PATTERN ".github" EXCLUDE + PATTERN ".vscode" EXCLUDE ) if(BUILD_TESTS) diff --git a/add-recipe.lua b/add-recipe.lua index 222dc5511f..fe7fd63576 100644 --- a/add-recipe.lua +++ b/add-recipe.lua @@ -63,7 +63,7 @@ function addItems(category, exotic) --category: the category of items we're adding --exotic: whether to add exotic items --returns: list of item objects that were added - local known = category[1] + local known_category = category[1] local native = category[2] local all = category[3] local added = {} --as:df.itemdef[] @@ -72,16 +72,20 @@ function addItems(category, exotic) local item = item --as:df.itemdef_weaponst local subtype = item.subtype local itemOk = false + local known = known_category + -- digging implements are seperate from weapons in entity resources. + if (df.itemdef_weaponst:is_instance(item) and item.skill_melee == df.job_skill.MINING) then + known = diggers + end --check if it's a training weapon local t1, t2 = pcall(function () return item.flags.TRAINING == false end) local training = not(not t1 or t2) - --we don't want procedural items with adjectives such as "wavy spears" - --(because they don't seem to be craftable even if added) + --excludes procedural items, eg: "wavy spears" for divine origins --nor do we want known items or training items (because adding training --items seems to allow them to be made out of metals) - if (item.adjective == "" and not training and not checkKnown(known, subtype)) then + if (not item.base_flags.GENERATED and not training and not checkKnown(known, subtype)) then itemOk = true end @@ -89,10 +93,12 @@ function addItems(category, exotic) itemOk = false end - --check that the weapon we're adding is not already known to the civ as - --a digging implement so picks don't get duplicated - if (checkKnown(diggers, subtype)) then - itemOk = false + --if the weapon we're adding is a digging implement, add to diggers instead of weapons + --prevents picks from being duplicated, and puts great picks in correct category + if (df.itemdef_weaponst:is_instance(item) and item.skill_melee == df.job_skill.MINING) then + if (checkKnown(diggers, subtype)) then + itemOk = false + end end if (itemOk) then diff --git a/add-thought.lua b/add-thought.lua index 40228356c1..b68916d30b 100644 --- a/add-thought.lua +++ b/add-thought.lua @@ -14,7 +14,7 @@ function addEmotionToUnit(unit,thought,emotion,severity,strength,subthought) local properThought = tonumber(thought) or df.unit_thought_type[thought] local properSubthought = tonumber(subthought) if not properThought or not df.unit_thought_type[properThought] then - for _,syn in ipairs(df.global.world.raws.syndromes.all) do + for _,syn in ipairs(df.global.world.raws.mat_table.syndromes.all) do if syn.syn_name == thought then properThought = df.unit_thought_type.Syndrome properSubthought = syn.id diff --git a/advtools.lua b/advtools.lua index 6b2b0d09af..9f4d2ec3b3 100644 --- a/advtools.lua +++ b/advtools.lua @@ -1,10 +1,12 @@ --@ module=true local convo = reqscript('internal/advtools/convo') +local fastcombat = reqscript('internal/advtools/fastcombat') local party = reqscript('internal/advtools/party') OVERLAY_WIDGETS = { conversation=convo.AdvRumorsOverlay, + fastcombat=fastcombat.AdvCombatOverlay, } if dfhack_flags.module then diff --git a/agitation-rebalance.lua b/agitation-rebalance.lua index e5a9ad7fdc..ae22593a67 100644 --- a/agitation-rebalance.lua +++ b/agitation-rebalance.lua @@ -326,7 +326,7 @@ end local function do_preset(preset_name) local preset = presets[preset_name] if not preset then - qerror('preset not found: ' .. preset_name) + qerror(('preset not found: "%s"'):format(preset_name)) end utils.assign(custom_difficulty, preset) print('agitation-rebalance: preset applied: ' .. preset_name) @@ -729,8 +729,7 @@ local function print_status() for k,v in pairs(state.features) do print((' %15s: %s'):format(k, v)) end - print((' %15s: %s'):format('monitor', - overlay.get_state().config[WIDGET_NAME].enabled or 'false')) + print((' %15s: %s'):format('monitor', overlay.isOverlayEnabled(WIDGET_NAME) or 'false')) print() print('difficulty settings:') print((' Wilderness irritation minimum: %d (about %d tree(s) until initial attacks are possible)'):format( @@ -763,7 +762,7 @@ local function enable_feature(which, enabled) end local feature = state.features[which] if feature == nil then - qerror('feature not found: ' .. which) + qerror(('feature not found: "%s"'):format(which)) end state.features[which] = enabled print(('feature %sabled: %s'):format(enabled and 'en' or 'dis', which)) @@ -777,9 +776,9 @@ if dfhack_flags and dfhack_flags.enable then else do_disable() end elseif command == 'preset' then - do_preset(args[1]) + do_preset(args[1] or '') elseif command == 'enable' or command == 'disable' then - enable_feature(args[1], command == 'enable') + enable_feature(args[1] or '', command == 'enable') elseif not command or command == 'status' then print_status() return diff --git a/armoks-blessing.lua b/armoks-blessing.lua index 5f930f985f..6d57ab34eb 100644 --- a/armoks-blessing.lua +++ b/armoks-blessing.lua @@ -1,24 +1,9 @@ -- Adjust all attributes of all dwarves to an ideal -- by vjek +local rejuvenate = reqscript('rejuvenate') local utils = require('utils') -function rejuvenate(unit) - if unit==nil then - print ("No unit available! Aborting with extreme prejudice.") - return - end - - local current_year=df.global.cur_year - local newbirthyear=current_year - 20 - if unit.birth_year < newbirthyear then - unit.birth_year=newbirthyear - end - if unit.old_year < current_year+100 then - unit.old_year=current_year+100 - end - -end -- --------------------------------------------------------------------------- function brainwash_unit(unit) if unit==nil then @@ -38,7 +23,7 @@ function brainwash_unit(unit) unit.status.current_soul.personality.traits.GREED = 25 unit.status.current_soul.personality.traits.IMMODERATION = 25 unit.status.current_soul.personality.traits.VIOLENT = 50 - unit.status.current_soul.personality.traits.PERSEVERENCE = 75 + unit.status.current_soul.personality.traits.PERSEVERANCE = 75 unit.status.current_soul.personality.traits.WASTEFULNESS = 50 unit.status.current_soul.personality.traits.DISCORD = 25 unit.status.current_soul.personality.traits.FRIENDLINESS = 75 @@ -95,7 +80,7 @@ function brainwash_unit(unit) [df.value_type.HARD_WORK]=41, [df.value_type.SACRIFICE]=41, [df.value_type.COMPETITION]=-41, - [df.value_type.PERSEVERENCE]=41, + [df.value_type.PERSEVERANCE]=41, [df.value_type.LEISURE_TIME]=-11, [df.value_type.COMMERCE]=41, [df.value_type.ROMANCE]=41, @@ -248,10 +233,10 @@ end -- --------------------------------------------------------------------------- function adjust_all_dwarves(skillname) for _,v in ipairs(dfhack.units.getCitizens()) do - print("Adjusting "..dfhack.df2console(dfhack.TranslateName(dfhack.units.getVisibleName(v)))) + print("Adjusting "..dfhack.df2console(dfhack.units.getReadableName(v))) brainwash_unit(v) elevate_attributes(v) - rejuvenate(v) + rejuvenate.rejuvenate(v, true) if skillname then if df.job_skill_class[skillname] then LegendaryByClass(skillname,v) diff --git a/assign-beliefs.lua b/assign-beliefs.lua index 2a0d92cdb4..cc0de2c282 100644 --- a/assign-beliefs.lua +++ b/assign-beliefs.lua @@ -134,10 +134,6 @@ function assign(beliefs, unit, reset) for belief, level in pairs(beliefs) do assert(type(level) == "number") belief = belief:upper() - -- there's a typo in the game data - if belief == "PERSEVERANCE" then - belief = "PERSEVERENCE" - end if df.value_type[belief] then if level >= -3 and level <= 3 then local belief_value = calculate_random_belief_value(level) diff --git a/assign-facets.lua b/assign-facets.lua index 3e77b3e2e0..c91670f07c 100644 --- a/assign-facets.lua +++ b/assign-facets.lua @@ -134,10 +134,6 @@ function assign(facets, unit, reset) for facet, level in pairs(facets) do assert(type(level) == "number") facet = facet:upper() - -- there's a typo in the game data - if facet == "PERSEVERANCE" then - facet = "PERSEVERENCE" - end if df.personality_facet_type[facet] then if level >= -3 and level <= 3 then local facet_strength = calculate_random_facet_strength(level) diff --git a/assign-minecarts.lua b/assign-minecarts.lua index 3cff2cd726..53e498d2b7 100644 --- a/assign-minecarts.lua +++ b/assign-minecarts.lua @@ -2,6 +2,7 @@ --@ module = true local argparse = require('argparse') +local utils = require('utils') function get_free_vehicles() local free_vehicles = {} @@ -13,16 +14,12 @@ function get_free_vehicles() return free_vehicles end -local function has_minecart(route) - return #route.vehicle_ids > 0 -end - local function has_stops(route) return #route.stops > 0 end local function get_minecart(route) - if not has_minecart(route) then return end + if #route.vehicle_ids == 0 then return end local vehicle = utils.binsearch(df.global.world.vehicles.active, route.vehicle_ids[0], 'id') if not vehicle then return end return df.item.find(vehicle.item_id) @@ -37,8 +34,9 @@ local function get_id_and_name(route) end local function assign_minecart_to_route(route, quiet, minecart) - if has_minecart(route) then - return get_minecart(route) + local assigned_minecart = get_minecart(route) + if assigned_minecart then + return assigned_minecart end if not has_stops(route) then if not quiet then @@ -57,6 +55,12 @@ local function assign_minecart_to_route(route, quiet, minecart) return false end end + for _,vehicle_id in ipairs(route.vehicle_ids) do + local vehicle = utils.binsearch(df.global.world.vehicles.all, vehicle_id, 'id') + if vehicle then vehicle.route_id = -1 end + end + route.vehicle_ids:resize(0) + route.vehicle_stops:resize(0) route.vehicle_ids:insert('#', minecart.id) route.vehicle_stops:insert('#', 0) minecart.route_id = route.id @@ -99,7 +103,7 @@ local function list() for _,route in ipairs(routes) do print(('%-8d %-9s %-9s %s') :format(route.id, - has_minecart(route) and 'yes' or 'NO', + get_minecart(route) and 'yes' or 'NO', has_stops(route) and 'yes' or 'NO', get_name(route))) end @@ -113,7 +117,7 @@ local function all(quiet) local minecarts, idx = get_free_vehicles(), 1 local routes = df.global.plotinfo.hauling.routes for _,route in ipairs(routes) do - if has_minecart(route) then + if get_minecart(route) then goto continue end if not assign_minecart_to_route(route, quiet, minecarts[idx]) then @@ -148,7 +152,7 @@ local function main(args) local route = get_route_by_id(requested_route_id) if not route then dfhack.printerr('route id not found: '..requested_route_id) - elseif has_minecart(route) then + elseif get_minecart(route) then if not quiet then print(('Route %s already has a minecart assigned.') :format(get_id_and_name(route))) diff --git a/assign-preferences.lua b/assign-preferences.lua index ba791278f2..9a8c1bdc6a 100644 --- a/assign-preferences.lua +++ b/assign-preferences.lua @@ -1,193 +1,12 @@ -- Change the preferences of a unit. --@ module = true -local help = [====[ - -assign-preferences -================== -A script to change the preferences of a unit. - -Preferences are classified into 12 types. The first 9 are: - -* like material; -* like creature; -* like food; -* hate creature; -* like item; -* like plant; -* like tree; -* like colour; -* like shape. - -These can be changed using this script. - -The remaining three are not currently managed by this script, -and are: like poetic form, like musical form, like dance form. - -To produce the correct description in the "thoughts and preferences" -page, you must specify the particular type of preference. For -each type, a description is provided in the section below. - -You will need to know the token of the object you want your dwarf to like. -You can find them in the wiki, otherwise in the folder "/raw/objects/" under -the main DF directory you will find all the raws defined in the game. - -For more information: -https://dwarffortresswiki.org/index.php/DF2014:Preferences - -Usage: - -``-help``: - print the help page. - -``-unit ``: - set the target unit ID. If not present, the - currently selected unit will be the target. - -``-likematerial [ <...> ]``: - usually a type of stone, a type of metal and a type - of gem, plus it can also be a type of wood, a type of - glass, a type of leather, a type of horn, a type of - pearl, a type of ivory, a decoration material - coral - or amber, a type of bone, a type of shell, a type - of silk, a type of yarn, or a type of plant cloth. - Write the full tokens. - There must be a space before and after each square - bracket. - -``-likecreature [ <...> ]``: - one or more creatures liked by the unit. You can - just list the species: the creature token will be - something similar to ``CREATURE:SPARROW:SKIN``, - so the name of the species will be ``SPARROW``. Nothing - will stop you to write the full token, if you want: the - script will just ignore the first and the last parts. - There must be a space before and after each square - bracket. - -``-likefood [ <...> ]``: - usually a type of alcohol, plus it can be a type of - meat, a type of fish, a type of cheese, a type of edible - plant, a cookable plant/creature extract, a cookable - mill powder, a cookable plant seed or a cookable plant - leaf. Write the full tokens. - There must be a space before and after each square - bracket. - -``-hatecreature [ <...> ]``: - works the same way as ``-likecreature``, but this time - it's one or more creatures that the unit detests. They - should be a type of ``HATEABLE`` vermin which isn't already - explicitly liked, but no check is performed about this. - Like before, you can just list the creature species. - There must be a space before and after each square - bracket. - -``-likeitem [ <...> ]``: - a kind of weapon, a kind of ammo, a kind of piece of - armor, a piece of clothing (including backpacks or - quivers), a type of furniture (doors, floodgates, beds, - chairs, windows, cages, barrels, tables, coffins, - statues, boxes, armor stands, weapon racks, cabinets, - bins, hatch covers, grates, querns, millstones, traction - benches, or slabs), a kind of craft (figurines, amulets, - scepters, crowns, rings, earrings, bracelets, or large - gems), or a kind of miscellaneous item (catapult parts, - ballista parts, a type of siege ammo, a trap component, - coins, anvils, totems, chains, flasks, goblets, - buckets, animal traps, an instrument, a toy, splints, - crutches, or a tool). The item tokens can be found here: - https://dwarffortresswiki.org/index.php/DF2014:Item_token - If you want to specify an item subtype, look into the files - listed under the column "Subtype" of the wiki page (they are - in the "/raw/ojects/" folder), then specify the items using - the full tokens found in those files (see examples below). - There must be a space before and after each square - bracket. - -``-likeplant [ <...> ]``: - works in a similar way as ``-likecreature``, this time - with plants. You can just List the plant species (the - middle part of the token). - There must be a space before and after each square - bracket. - -``-liketree [ <...> ]``: - works exactly as ``-likeplant``. I think this - preference type is here for backward compatibility (?). - You can still use it, however. As before, - you can just list the tree (plant) species. - There must be a space before and after each square - bracket. - -``-likecolor [ <...> ]``: - you can find the color tokens here: - https://dwarffortresswiki.org/index.php/DF2014:Color#Color_tokens - or inside the "descriptor_color_standard.txt" file - (in the "/raw/ojects/" folder). You can use the full token or - just the color name. - There must be a space before and after each square - bracket. - -``-likeshape [ <...> ]``: - I couldn't find a list of shape tokens in the wiki, but you - can find them inside the "descriptor_shape_standard.txt" - file (in the "/raw/ojects/" folder). You can - use the full token or just the shape name. - There must be a space before and after each square - bracket. - -``-reset``: - clear all preferences. If the script is called - with both this option and one or more preferences, - first all the unit preferences will be cleared - and then the listed preferences will be added. - -Examples: - -* "likes alabaster and willow wood":: - - assign-preferences -reset -likematerial [ INORGANIC:ALABASTER PLANT:WILLOW:WOOD ] - -* "likes sparrows for their ...":: - - assign-preferences -reset -likecreature SPARROW - -* "prefers to consume dwarven wine and olives":: - - assign-preferences -reset -likefood [ PLANT:MUSHROOM_HELMET_PLUMP:DRINK PLANT:OLIVE:FRUIT ] - -* "absolutely detests jumping spiders:: - - assign-preferences -reset -hatecreature SPIDER_JUMPING - -* "likes logs and battle axes":: - - assign-preferences -reset -likeitem [ WOOD ITEM_WEAPON:ITEM_WEAPON_AXE_BATTLE ] - -* "likes straberry plants for their ...":: - - assign-preferences -reset -likeplant BERRIES_STRAW - -* "likes oaks for their ...":: - - assign-preferences -reset -liketree OAK - -* "likes the color aqua":: - - assign-preferences -reset -likecolor AQUA - -* "likes stars":: - - assign-preferences -reset -likeshape STAR - -]====] - local utils = require("utils") local valid_args = utils.invert({ 'help', 'unit', + 'show', 'likematerial', 'likecreature', 'likefood', @@ -207,6 +26,38 @@ local function print_yellow(text) dfhack.color(-1) end +local function format_preference(pref, index) + print(string.format("Preference #%d:", index)) + + local pref_type = df.unitpref_type[pref.type] + local description = "" + if pref_type == "LikeMaterial" then + description = "Likes material: " .. dfhack.matinfo.getToken(pref.mattype, pref.matindex) + elseif pref_type == "LikeFood" then + description = "Likes food: " .. dfhack.matinfo.getToken(pref.mattype, pref.matindex) + elseif pref_type == "LikeItem" then + description = "Likes item type: " .. tostring(pref.item_type) + elseif pref_type == "LikePlant" then + description = "Likes plant: " .. dfhack.matinfo.getToken(pref.mattype, pref.matindex) + elseif pref_type == "HateCreature" then + description = "Hates creature: " .. df.global.world.raws.creatures.all[pref.creature_id].creature_id + elseif pref_type == "LikeColor" then + description = "Likes color: " .. df.global.world.raws.descriptors.colors[pref.color_id].id + elseif pref_type == "LikeShape" then + description = "Likes shape: " .. df.global.world.raws.descriptors.shapes[pref.shape_id].id + elseif pref_type == "LikePoeticForm" then + description = "Likes poetic form: " .. dfhack.translation.translateName(df.global.world.poetic_forms.all[pref.poetic_form_id].name, true) + elseif pref_type == "LikeMusicalForm" then + description = "Likes musical form: " .. dfhack.translation.translateName(df.global.world.musical_forms.all[pref.musical_form_id].name, true) + elseif pref_type == "LikeDanceForm" then + description = "Likes dance form: " .. dfhack.translation.translateName(df.global.world.dance_forms.all[pref.dance_form_id].name, true) + else + description = "Unknown preference type: " .. tostring(pref.type) + end + + print(description) +end + -- initialise random number generator local rng = dfhack.random.new() @@ -225,7 +76,7 @@ local preference_functions = { local ret = {} if mat_info then ret = { --luacheck:retype - type = df.unit_preference.T_type.LikeMaterial, + type = df.unitpref_type.LikeMaterial, item_type = -1, creature_id = -1, color_id = -1, @@ -238,7 +89,7 @@ local preference_functions = { mattype = mat_info.type, matindex = mat_info.index, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -258,7 +109,7 @@ local preference_functions = { local index = utils.linear_index(df.global.world.raws.creatures.all, creature_id, "creature_id") if index then return { - type = df.unit_preference.T_type.LikeCreature, + type = df.unitpref_type.LikeCreature, item_type = index, creature_id = index, color_id = index, @@ -271,7 +122,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -335,7 +186,7 @@ local preference_functions = { if item_type then return { - type = df.unit_preference.T_type.LikeFood, + type = df.unitpref_type.LikeFood, item_type = item_type, creature_id = item_type, color_id = item_type, @@ -348,7 +199,7 @@ local preference_functions = { mattype = mat_info.type, matindex = mat_info.index, mat_state = 1, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } end @@ -370,7 +221,7 @@ local preference_functions = { local index = utils.linear_index(df.global.world.raws.creatures.all, creature_id, "creature_id") if index then return { - type = df.unit_preference.T_type.HateCreature, + type = df.unitpref_type.HateCreature, item_type = index, creature_id = index, color_id = index, @@ -383,7 +234,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -411,7 +262,7 @@ local preference_functions = { do if item_type then return { - type = df.unit_preference.T_type.LikeItem, + type = df.unitpref_type.LikeItem, item_type = item_type, creature_id = item_type, color_id = item_type, @@ -424,7 +275,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } end @@ -446,7 +297,7 @@ local preference_functions = { local index = utils.linear_index(df.global.world.raws.plants.all, plant_id, "id") if index then return { - type = df.unit_preference.T_type.LikePlant, + type = df.unitpref_type.LikePlant, item_type = index, creature_id = index, color_id = index, @@ -459,7 +310,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -479,7 +330,7 @@ local preference_functions = { local index = utils.linear_index(df.global.world.raws.plants.all, plant_id, "id") if index then return { - type = df.unit_preference.T_type.LikeTree, + type = df.unitpref_type.LikeTree, item_type = index, creature_id = index, color_id = index, @@ -492,7 +343,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -513,7 +364,7 @@ local preference_functions = { local _, found, index = utils.binsearch(df.global.world.raws.descriptors.colors, color_name, "id") if found then return { - type = df.unit_preference.T_type.LikeColor, + type = df.unitpref_type.LikeColor, item_type = index, creature_id = index, color_id = index, @@ -526,7 +377,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -546,7 +397,7 @@ local preference_functions = { local index, _ = utils.linear_index(df.global.world.raws.descriptors.shapes, shape_name, "id") if index then return { - type = df.unit_preference.T_type.LikeShape, + type = df.unitpref_type.LikeShape, item_type = index, creature_id = index, color_id = index, @@ -559,7 +410,7 @@ local preference_functions = { mattype = -1, matindex = -1, mat_state = 0, - active = true, + flags = {visible = true}, prefstring_seed = rng:random() } else @@ -624,12 +475,25 @@ function assign(preferences, unit, reset) end end +-- ----------------------------------------------- SHOW PREF UTILITY ------------------------------------------------ -- +local function showPreferences(unit) + assert(not unit or type(unit) == "number" or df.unit:is_instance(unit)) + unit = unit or dfhack.gui.getSelectedUnit(true) + if not unit then + qerror("No unit found.") + end + + for i, pref in ipairs(unit.status.current_soul.preferences) do + format_preference(pref, i) + end +end + -- ------------------------------------------------------ MAIN ------------------------------------------------------ -- local function main(...) local args = utils.processArgs({ ... }, valid_args) if args.help then - print(help) + print(dfhack.script_help()) return end @@ -646,9 +510,15 @@ local function main(...) reset = true end + if args.show then + showPreferences(unit) + return + end + -- parse preferences args.unit = nil -- remove from args table args.reset = nil -- remove from args table + args.show = nil -- remove from args table local preferences = {} utils.assign(preferences, args) diff --git a/assign-profile.lua b/assign-profile.lua index d1207a44bf..70ce2ac600 100644 --- a/assign-profile.lua +++ b/assign-profile.lua @@ -92,7 +92,7 @@ local scripts = { FACETS = reqscript("assign-facets"), } -local default_filename = "/hack/scripts/dwarf_profiles.json" +local default_filename = dfhack.getHackPath().."/scripts/dwarf_profiles.json" -- ------------------------------------------------- APPLY PROFILE -------------------------------------------------- -- --- Apply the given profile to a unit, erasing or resetting the unit characteristics as requested. diff --git a/autocheese.lua b/autocheese.lua new file mode 100644 index 0000000000..e9bdc146ae --- /dev/null +++ b/autocheese.lua @@ -0,0 +1,173 @@ +--@module = true + +---make cheese using a specific barrel and workshop +---@param barrel df.item +---@param workshop df.building_workshopst +---@return df.job +function makeCheese(barrel, workshop) + ---@type df.job + local job = dfhack.job.createLinked() + job.job_type = df.job_type.MakeCheese + + local jitem = df.job_item:new() + jitem.quantity = 0 + jitem.vector_id = df.job_item_vector_id.ANY_COOKABLE + jitem.flags1.unrotten = true + jitem.flags1.milk = true + job.job_items.elements:insert('#', jitem) + + if not dfhack.job.attachJobItem(job, barrel, df.job_role_type.Reagent, 0, -1) then + dfhack.error('could not attach item') + end + + dfhack.job.assignToWorkshop(job, workshop) + return job +end + +---checks that unit can path to workshop +---@param unit df.unit +---@param workshop df.building_workshopst +---@return boolean +function canAccessWorkshop(unit, workshop) + local workshop_position = xyz2pos(workshop.centerx, workshop.centery, workshop.z) + return dfhack.maps.canWalkBetween(unit.pos, workshop_position) +end + +---check if unit can perform labor at workshop +---@param unit df.unit +---@param unit_labor df.unit_labor +---@param workshop df.building +---@return boolean +function availableLaborer(unit, unit_labor, workshop) + return unit.status.labors[unit_labor] + and dfhack.units.isJobAvailable(unit) + and canAccessWorkshop(unit, workshop) +end + +---find unit with a particular labor enabled +---@param unit_labor df.unit_labor +---@param job_skill df.job_skill +---@param workshop df.building +---@return df.unit|nil +---@return integer|nil + function findAvailableLaborer(unit_labor, job_skill, workshop) + local max_unit = nil + local max_skill = -1 + for _, unit in ipairs(dfhack.units.getCitizens(true, false)) do + if + availableLaborer(unit, unit_labor, workshop) + then + local unit_skill = dfhack.units.getNominalSkill(unit, job_skill, true) + if unit_skill > max_skill then + max_unit = unit + max_skill = unit_skill + end + end + end + return max_unit, max_skill +end + +local function findMilkBarrel(min_liquids) + for _, container in ipairs(df.global.world.items.other.FOOD_STORAGE) do + if + not (container.flags.in_job or container.flags.forbid) and + container.flags.container and #container.general_refs >= min_liquids + then + local content_reference = dfhack.items.getGeneralRef(container, df.general_ref_type.CONTAINS_ITEM) + local contained_item = df.item.find(content_reference and content_reference.item_id or -1) + if contained_item then + local mat_info = dfhack.matinfo.decode(contained_item) + if mat_info:matches { milk = true } then + return container + end + end + end + end +end + +---find a workshop to which the barrel can be brought +---if the workshop has a master, only return workshop and master if the master is available +---@param pos df.coord +---@return df.building_workshopst? +---@return df.unit? +function findWorkshop(pos) + for _,workshop in ipairs(df.global.world.buildings.other.WORKSHOP_FARMER) do + if + dfhack.maps.canWalkBetween(pos, xyz2pos(workshop.centerx, workshop.centery, workshop.z)) and + not workshop.profile.blocked_labors[df.unit_labor.MAKE_CHEESE] and + #workshop.jobs == 0 + then + if #workshop.profile.permitted_workers == 0 then + -- immediately return workshop without master + return workshop, nil + else + unit = df.unit.find(workshop.profile.permitted_workers[0]) + if + unit and availableLaborer(unit, df.unit_labor.MAKE_CHEESE, workshop) + then + -- return workshop and master, if master is available + return workshop, unit + else + print("autocheese: Skipping farmer's workshop with unavailable master") + end + end + end + end +end + +if dfhack_flags.module then + return +end + +-- actual script action + +local argparse = require('argparse') + +local min_number = 50 + +local _ = argparse.processArgsGetopt({...}, +{ + { 'm', 'min-milk', hasArg = true, + handler = function(min) + min_number = argparse.nonnegativeInt(min, 'min-milk') + end } +}) + + +local reagent = findMilkBarrel(min_number) + +if not reagent then + -- print('autocheese: no sufficiently full barrel found') + return +end + +local workshop, worker = findWorkshop(xyz2pos(dfhack.items.getPosition(reagent))) + +if not workshop then + print("autocheese: no Farmer's Workshop available") + return +end + +-- try to find laborer for workshop without master +if not worker then + worker, _ = findAvailableLaborer(df.unit_labor.MAKE_CHEESE, df.job_skill.CHEESEMAKING, workshop) +end + +if not worker then + print('autocheese: no cheesemaker available') + return +end +local job = makeCheese(reagent, workshop) + +print(('autocheese: dispatching cheesemaking job for %s (%d milk) to %s'):format( + dfhack.df2console(dfhack.items.getReadableDescription(reagent)), + #reagent.general_refs, + dfhack.df2console(dfhack.units.getReadableName(worker)) +)) + + +-- assign a worker and send it to fetch the barrel +dfhack.job.addWorker(job, worker) +dfhack.units.setPathGoal(worker, reagent.pos, df.unit_path_goal.GrabJobResources) +job.items[0].flags.is_fetching = true +job.flags.fetching = true diff --git a/autotraining.lua b/autotraining.lua new file mode 100644 index 0000000000..386ab6bb0a --- /dev/null +++ b/autotraining.lua @@ -0,0 +1,296 @@ +-- Based on the original code by RNGStrategist (who also got some help from Uncle Danny) +--@ enable = true +--@ module = true + +local repeatUtil = require('repeat-util') +local utils=require('utils') + +local GLOBAL_KEY = "autotraining" +local MartialTraining = df.need_type['MartialTraining'] +local ignore_count = 0 + +local function get_default_state() + return { + enabled=false, + threshold=-5000, + ignored={}, + ignored_nobles={}, + training_squads = {}, + } +end + +state = state or get_default_state() + +function isEnabled() + return state.enabled +end + +-- persisting a table with numeric keys results in a json array with a huge number of null entries +-- therefore, we convert the keys to strings for persistence +local function to_persist(persistable) + local persistable_ignored = {} + for k, v in pairs(persistable) do + persistable_ignored[tostring(k)] = v + end + return persistable_ignored +end + +-- loads both from the older array format and the new string table format +local function from_persist(persistable) + if not persistable then + return + end + local ret = {} + for k, v in pairs(persistable) do + ret[tonumber(k)] = v + end + return ret +end + +function persist_state() + dfhack.persistent.saveSiteData(GLOBAL_KEY, { + enabled=state.enabled, + threshold=state.threshold, + ignored=to_persist(state.ignored), + ignored_nobles=state.ignored_nobles, + training_squads=to_persist(state.training_squads) + }) +end + +--- Load the saved state of the script +local function load_state() + -- load persistent data + local persisted_data = dfhack.persistent.getSiteData(GLOBAL_KEY, {}) + state.enabled = persisted_data.enabled or state.enabled + state.threshold = persisted_data.threshold or state.threshold + state.ignored = from_persist(persisted_data.ignored) or state.ignored + state.ignored_nobles = persisted_data.ignored_nobles or state.ignored_nobles + state.training_squads = from_persist(persisted_data.training_squads) or state.training_squads + return state +end + +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc == SC_MAP_UNLOADED then + state.enabled = false + return + end + -- the state changed, is a map loaded and is that map in fort mode? + if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then + -- no its isnt, so bail + return + end + -- yes it was, so: + + -- retrieve state saved in game. merge with default state so config + -- saved from previous versions can pick up newer defaults. + load_state() + if state.enabled then + start() + end + persist_state() +end + + +--###### +--Functions +--###### +local function isIgnoredNoble(unit) + local noblePos = dfhack.units.getNoblePositions(unit) + if noblePos ~= nil then + for _, position in ipairs(noblePos) do + if state.ignored_nobles[position.position.code] then + return true + end + end + end + return false +end + +---@return table +function getTrainingCandidates() + local ret = {} + ignore_count = 0 + for _, unit in ipairs(dfhack.units.getCitizens(true)) do + if not dfhack.units.isAdult(unit) then + goto next_unit + end + local need = getTrainingNeed(unit) + if not need or need.focus_level >= state.threshold then + goto next_unit + end + -- ignored units are those that would like to train but are forbidden from doing so + if state.ignored[unit.id] then + ignore_count = ignore_count + 1 + goto next_unit + end + if isIgnoredNoble(unit) then + ignore_count = ignore_count + 1 + goto next_unit + end + if unit.military.squad_id ~= -1 then + goto next_unit + end + table.insert(ret, { unit = unit, need = need.focus_level }) + ::next_unit:: + end + table.sort(ret, function (a, b) return a.need < b.need end) + return ret +end + +function getTrainingSquads() + local squads = {} + for squad_id, active in pairs(state.training_squads) do + local squad = df.squad.find(squad_id) + if active and squad then + table.insert(squads, squad) + else + -- setting to nil during iteration is permitted by lua + state.training_squads[squad_id] = nil + end + end + return squads +end + +function getTrainingNeed(unit) + if unit == nil then return nil end + local needs = unit.status.current_soul.personality.needs + for _, need in ipairs(needs) do + if need.id == MartialTraining then + return need + end + end + return nil +end + +--###### +--Main +--###### + +-- Find all training squads +-- Abort if no squads found +function checkSquads() + local squads = {} + for _, squad in ipairs(getTrainingSquads()) do + if squad.entity_id == df.global.plotinfo.group_id then + local leader = squad.positions[0].occupant + if leader ~= -1 then + table.insert(squads,squad) + end + end + end + + if #squads == 0 then + return nil + end + + return squads +end + +function addTraining(unit,good_squads) + if unit.military.squad_id ~= -1 then + for _, squad in ipairs(good_squads) do + if unit.military.squad_id == squad.id then + return true + end + end + return false + end + for _, squad in ipairs(good_squads) do + for i=1,9,1 do + if squad.positions[i].occupant == -1 then + return dfhack.military.addToSquad(unit.id,squad.id,i) + end + end + end + + return false +end + +function removeAll() + if state.training_squads == nil then return end + for _, squad in ipairs(getTrainingSquads()) do + for i=1,9,1 do + local hf = df.historical_figure.find(squad.positions[i].occupant) + if hf ~= nil then + dfhack.military.removeFromSquad(hf.unit_id) + end + end + end +end + + +function check() + local squads = checkSquads() + local intraining_count = 0 + local inque_count = 0 + if squads == nil then return end + for _,squad in ipairs(squads) do + for i=1,9,1 do + if squad.positions[i].occupant ~= -1 then + local hf = df.historical_figure.find(squad.positions[i].occupant) + if hf ~= nil then + local unit = df.unit.find(hf.unit_id) + local training_need = getTrainingNeed(unit) + if not training_need or training_need.focus_level >= state.threshold then + dfhack.military.removeFromSquad(unit.id) + end + end + end + end + end + for _, p in ipairs(getTrainingCandidates()) do + local added = addTraining(p.unit, squads) + if added then + intraining_count = intraining_count +1 + else + inque_count = inque_count +1 + end + end + print(("%s: %d training, %d waiting, and %d excluded units with training needs"): + format(GLOBAL_KEY, intraining_count, inque_count, ignore_count)) +end + +function start() + repeatUtil.scheduleEvery(GLOBAL_KEY, 1, 'days', check) +end + +function stop() + repeatUtil.cancel(GLOBAL_KEY) +end + +function enable() + state.enabled = true + persist_state() + start() +end + +function disable() + state.enabled = false + persist_state() + stop() + removeAll() +end + +if dfhack_flags.module then + return +end + +validArgs = utils.invert({ + 't' +}) + +local args = utils.processArgs({...}, validArgs) + +if dfhack_flags.enable then + if dfhack_flags.enable_state then + enable() + else + disable() + end +else + -- called on the command-line + if args.t then + state.threshold = 0-tonumber(args.t) + end + print(("autotraining is %s"):format(state.enabled and "enabled" or "disabled")) +end diff --git a/ban-cooking.lua b/ban-cooking.lua index fdb59fbe05..2254e116dd 100644 --- a/ban-cooking.lua +++ b/ban-cooking.lua @@ -80,8 +80,19 @@ funcs.booze = function() end funcs.honey = function() - local mat = dfhack.matinfo.find("CREATURE:HONEY_BEE:HONEY") - ban_cooking('honey bee honey', mat.type, mat.index, df.item_type.LIQUID_MISC, -1) + for _, c in ipairs(df.global.world.raws.creatures.all) do + for _, m in ipairs(c.material) do + 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(c.creature_id, m.id) + ban_cooking(c.name[2] .. ' ' .. m.id, matinfo.type, matinfo.index, df.item_type.LIQUID_MISC, -1) + break + end + end + end + end + end end funcs.tallow = function() diff --git a/brainwash.lua b/brainwash.lua index 12776de283..1cbdc29355 100644 --- a/brainwash.lua +++ b/brainwash.lua @@ -27,7 +27,7 @@ function brainwash_unit(profile) return end - unit_name=dfhack.TranslateName(dfhack.units.getVisibleName(unit)) + unit_name = dfhack.df2console(dfhack.units.getReadableName(unit)) print("Previous personality values for "..unit_name) printall(unit.status.current_soul.personality.traits) diff --git a/caravan.lua b/caravan.lua index 2d3a0cfb79..0dd2cf6d89 100644 --- a/caravan.lua +++ b/caravan.lua @@ -62,7 +62,7 @@ function commands.list() print(dfhack.df2console(('%d: %s caravan from %s'):format( id, df.creature_raw.find(df.historical_entity.find(car.entity).race).name[2], -- adjective - dfhack.TranslateName(df.historical_entity.find(car.entity).name) + dfhack.translation.translateName(df.historical_entity.find(car.entity).name) ))) print(' ' .. (df.caravan_state.T_trade_state[car.trade_state] or ('Unknown state: ' .. car.trade_state))) print((' %d day(s) remaining'):format(math.floor(car.time_remaining / 120))) @@ -126,15 +126,6 @@ local function isDisconnectedPackAnimal(unit) end end -local function getPrintableUnitName(unit) - local visible_name = dfhack.units.getVisibleName(unit) - local profession_name = dfhack.units.getProfessionName(unit) - if visible_name.has_name then - return ('%s (%s)'):format(dfhack.TranslateName(visible_name), profession_name) - end - return profession_name -- for unnamed animals -end - local function rejoin_pack_animals() print('Reconnecting disconnected pack animals...') local found = false @@ -142,8 +133,8 @@ local function rejoin_pack_animals() if unit.flags1.merchant and isDisconnectedPackAnimal(unit) then local dragger = unit.following print((' %s <-> %s'):format( - dfhack.df2console(getPrintableUnitName(unit)), - dfhack.df2console(getPrintableUnitName(dragger)) + dfhack.df2console(dfhack.units.getReadableName(unit)), + dfhack.df2console(dfhack.units.getReadableName(dragger)) )) unit.relationship_ids[ df.unit_relationship_type.Dragger ] = dragger.id dragger.relationship_ids[ df.unit_relationship_type.Draggee ] = unit.id diff --git a/changelog.txt b/changelog.txt index 2c84dd719c..8876a3b0ed 100644 --- a/changelog.txt +++ b/changelog.txt @@ -27,24 +27,515 @@ Template for new versions: # Future ## New Tools -- `embark-anyone`: allows you to embark as any civilisation, including dead, and non-dwarven ones -- `idle-crafting`: Allow dwarfs to automatically satisfy their need to craft objects. -- `gui/pregnancy`: view and generate pregnancies with specified parents + +## New Features + +## Fixes + +## Misc Improvements +- `caravan`: the ``Bring goods to depot``, ``Trade``, and ``Assign items for display`` overlays now allow searching for items with non-ASCII characters in their description +- `caravan`: the ``Trade`` overlay will use the trader's appraisal skill instead of the broker's to round/obfuscate the value of items + +## Removed + +# 53.15-r2 + +## New Tools + +## New Features + +## Fixes +- `combine`: stopgap fix for incorrectly combining dyes, no longer affects dyes +- `gui/rename`: fix script error that sometimes caused the script to malfunction when started from the Launcher. + +## Misc Improvements + +## Removed + +# 53.15-r1 + +## New Tools +- `machine-toggle`: interface for toggling gear assemblies, as well as pressure plates (previously in `trackstop`) (available only if ``armok`` tools are shown) + +## New Features + +## Fixes +- `add-recipe`: fix to include incorrectly excluded recipes +- `gui/control-panel`: fixed incorrect description of deteriorate commands +- `gui/petitions`: fix deity display + +## Misc Improvements +- `trackstop`: ``pressureplate`` overlay moved to `machine-toggle` as an ``armok`` tool + +## Removed + +# 53.14-r2 + +## New Tools + +## New Features + +## Fixes + +## Misc Improvements + +## Removed +- ``gui/logcleaner``: Removed + +# 53.14-r1 + +## New Tools + +## New Features + +## Fixes +- `caravan`: fix ethics warning for wooden weapons +- `gui/control-panel`: fixed inconsistent space in command help ("tweak" -> "tweak ", like with other commands) +- `gui/siegemanager`: consistently set the initial filter to "All" +- `immortal-cravings`: also take care of immortal units with alcohol dependence + +## Misc Improvements + +## Removed + +# 53.11-r2 + +## New Tools + +## New Features + +## Fixes +- `gui/rename`: skip ``NONE`` when iterating through language name options +- `quickfort`: work orders will no longer be created with a repetition frequency of ``NONE`` + +## Misc Improvements + +## Removed + +# 53.10-r2 + +## New Tools +- `gui/logcleaner`: graphical overlay for configuring the logcleaner plugin with enable and filter toggles. + +## New Features +- `trackstop`: can now modify pressure plates; permits minecart and creature triggers to be set beyond normal sensitivity + +## Fixes +- `gui/rename`: added check for entity_id input in get_target function +- `prioritize`: Fix the overlay appearing where it should not when following a unit + +## Misc Improvements +- `gui/notify`: reduced severity of the missing nemesis records warning if no units on the map are affected. clarified wording. + +## Removed + +# 53.10-r1 + +## New Tools + +## New Features +- `gui/quickcmd`: added custom command names and option to display command output +- `gui/notify`: new notification type: missing nemesis records; displays a warning message about game corruption. + +## Fixes + +## Misc Improvements + +## Removed + +# 53.07-r1 + +## New Tools +- `fix/codex-pages`: add pages to written content that have unspecified page counts. +- `gui/keybinds`: gui for managing and saving custom keybindings + +## New Features + +## Fixes +- `empty-bin`: renamed ``--liquids`` parameter to ``--force`` and made emptying of containers (bags) with powders contingent on that parameter. Previously powders would just always get disposed. + +## Misc Improvements +- `combine`: try harder to find the currently-selected stockpile + +## Removed + +# 53.06-r1 + +## New Tools + +## New Features + +## Fixes +- `gui/design`: designating a single-level stair construction now properly follows the selected stair type. +- `gui/design`: adjusted conflicting keybinds, diagonal line reverse becoming ``R`` and bottom stair type becoming ``g``. +- `modtools/set-personality`: use correct caste trait ranges; fixes `gui/gm-unit` being unable to correctly randomize traits or set traits to caste average + +## Misc Improvements + +## Removed + +# 53.04-r1 + +## New Tools +- `gui/siegemanager`: manage your siege engines at a glance. + +## New Features +- `item`: new ``--total-quality`` option for use in conjunction with ``--min-quality`` or ``--max-quality`` to filter items according to their total quality + +## Fixes + +## Misc Improvements +- `gui/design`: can now construct reinforced walls +- `quickfort`: support for reinforced walls and bolt throwers + +## Removed + +# 53.01-r1 + +## New Tools +- `fix/symbol-unstick`: unstick noble symbols that cannot be re-designated. +- `resize-armor`: resize armor or clothing item to any creature size. + +## New Features + +## Fixes +- `autotraining`: squads once used for training then disabled now properly are treated as disabled. + +## Misc Improvements + +## Removed +- `fix/archery-practice`: removed from the control panel's bug fixes tab. + +# 52.05-r2 + +## New Tools + +## New Features + +## Fixes +- `fix/archery-practice`: now splits instead of combining ammo items in quivers, and moves quivers to end of unit's inventory list + +## Misc Improvements + +## Removed + +# 52.05-r1 + +## New Tools +- `fix/archery-practice`: combine ammo items in units' quivers to fix 'Soldier (no item)' issue +- `gui/adv-finder`: UI for tracking historical figures and artifacts in adventure mode +- `store-owned`: task owned items to be stored in the owner's room furniture + +## New Features + +## Fixes +- `ban-cooking`: bans honey added by creatures other than vanilla honey bee +- `uniform-unstick`: added quivers, backpacks, and flasks/waterskins to uniform analysis +- `uniform-unstick`: the ``--drop`` option now only evaluates clothing as possible items to drop +- `uniform-unstick`: the ``--free`` option no longer redundantly reports an improperly assigned item when that item is removed from a uniform +- `uniform-unstick`: the ``--drop`` and ``--free`` options now only drop items which are actually in a unit's inventory +- `uniform-unstick`: the ``--all`` and ``--drop`` options, when used together, now print the separator line between each unit's report in the proper place + +## Misc Improvements +- adapt Lua tools to use new API functionality for creating and assigning jobs +- `idle-crafting`: properly interrupt interruptible (i.e. "green") social activities + +## Removed + +# 52.03-r2 + +## New Tools +- `autotraining`: new tool to assign citizens to a military squad when they need Martial Training +- `gui/autotraining`: configuration tool for autotraining +- `entomb`: allow any unit that has a corpse or body parts to be assigned a tomb zone +- `husbandry`: Automatically milk and shear animals at nearby farmer's workshops + +## New Features +- `deathcause`: added functionality to this script to fetch cause of death programatically + +## Fixes +- `ban-cooking`: will not fail trying to ban honey if the world has no honey +- `confirm`: only show pause option for pausable confirmations +- `confirm`: when editing a uniform, confirm discard of changes when exiting with Escape +- `confirm`: when removing a manager order, show correct order description when using non-100% interface setting +- `confirm`: when removing a manager order, show correct order description after prior order removal or window resize (when scrolled to bottom of order list) +- `confirm`: when removing a manager order, show specific item/job type for ammo, shield, helm, gloves, shoes, trap component, and meal orders +- `confirm`: the pause option now pauses individual confirmation types, allowing multiple different confirmations to be paused independently +- `immortal-cravings`: prioritize high-value meals, properly split of portions, and don't go eating or drinking on a full stomach +- `uniform-unstick`: no longer causes units to equip multiples of assigned items +- `caravan`: in the pedestal item assignment dialog, add new items at the end of the list of displayed items instead of at a random position +- `caravan`: in the pedestal item assignment dialog, consistently remove items from the list of displayed items + +## Misc Improvements +- `devel/hello-world`: updated to show off the new Slider widget + +## Removed + +# 52.03-r1 + +## New Tools + +## New Features + +## Fixes +- `make-legendary`: ``make-legendary all`` will no longer corrupt souls + +## Misc Improvements + +## Removed + +# 52.02-r2 + +## New Tools + +## New Features +- `gui/mod-manager`: now supports arena mode + +## Fixes +- `gui/mod-manager`: gracefully handle vanilla mods with different versions from the user's preset +- `gui/mod-manager`: hide other versions of loaded mods and unhides them when unloaded + +## Misc Improvements + +## Removed + +# 52.02-r1 + +## New Tools + +## New Features + +## Fixes +- ``embark-anyone``: validate viewscreen before using, avoids a crash + +## Misc Improvements + +## Removed + +# 52.01-r1 + +## New Tools + +## New Features + +## Fixes +- fixed references to removed ``unit.curse`` compound +- `gui/gm-unit`: remove reference to ``think_counter``, removed in v51.12 +- `gui/journal`: fix typo which caused the table of contents to always be regenerated even when not needed +- `gui/mod-manager`: gracefully handle mods with missing or broken ``info.txt`` files +- `uniform-unstick`: resolve overlap with new buttons in 51.13 + +## Misc Improvements + +## Removed + +# 51.12-r1 + +## New Tools +- `deteriorate`: (reinstated) allow corpses, body parts, food, and/or damaged clothes to rot away +- `modtools/moddable-gods`: (reinstated) create new deities from scratch + +## New Features +- `gui/spectate`: added "Prefer nicknamed" to the list of options +- `gui/mod-manager`: when run in a loaded world, shows a list of active mods -- click to export the list to the clipboard for easy sharing or posting +- `gui/blueprint`: now records zone designations +- `gui/design`: add option to draw N-point stars, hollow or filled or inverted, and change the main axis to orient in any direction + +## Fixes +- `starvingdead`: properly restore to correct enabled state when loading a new game that is different from the first game loaded in this session +- `starvingdead`: ensure undead decay does not happen faster than the declared decay rate when saving and loading the game +- `gui/design`: prevent line thickness from extending outside the map boundary + +## Misc Improvements +- `remove-stress`: also applied to long-term stress, immediately removing stressed and haggard statuses + +# 51.11-r1 + +## Fixes +- `list-agreements`: fix date math when determining petition age +- `gui/petitions`: fix date math when determining petition age +- `gui/rename`: fix commandline processing when manually specifying target ids +- `gui/sandbox`: restore metal equipment options when spawning units + +## Misc Improvements +- `fix/loyaltycascade`: now also breaks up brawls and other intra-fort conflicts that *look* like loyalty cascades +- `makeown`: remove selected unit from any current conflicts so they don't just start attacking other citizens when you make them a citizen of your fort + +# 51.09-r1 + +## New Features +- `gui/mass-remove`: add a button to the bottom toolbar when eraser mode is active for launching `gui/mass-remove` +- `idle-crafting`: default to only considering happy and ecstatic units for the highest need threshold +- `gui/sitemap`: add a button to the toolbar at the bottom left corner of the screen for launching `gui/sitemap` + +## Fixes +- `idle-crafting`: check that units still have crafting needs before creating a job for them +- `gui/journal`: prevent pause/unpause events from leaking through the UI when keys are mashed + +# 51.07-r1 + +## New Tools +- `devel/export-map`: export map tile data to a JSON file +- `autocheese`: automatically make cheese using barrels that have accumulated sufficient milk +- `gui/spectate`: interactive UI for configuring `spectate` +- `gui/notes`: UI for adding and managing notes attached to tiles on the map +- `launch`: (reinstated) new adventurer fighting move: thrash your enemies with a flying suplex +- `putontable`: (reinstated) make an item appear on a table + +## New Features +- `advtools`: ``advtools.fastcombat`` overlay (enabled by default) allows you to skip combat animations and the announcement "More" button by mashing the movement keys +- `gui/journal`: now working in adventure mode -- journal is per-adventurer, so if you unretire an adventurer, you get the same journal +- `emigration`: ``nobles`` command for sending freeloader barons back to the sites that they rule over +- `toggle-kbd-cursor`: support adventure mode (Alt-k keybinding now toggles Look mode) + +## Fixes +- `hfs-pit`: use correct wall types when making pits with walls +- `gui/liquids`: don't add liquids to wall tiles +- `gui/liquids`: using the remove tool with magma selected will no longer create unexpected unpathable tiles +- `idle-crafting`: do not assign crafting jobs to nobles holding meetings (avoids dangling jobs) +- `rejuvenate`: update unit portrait and sprite when aging up babies and children +- `rejuvenate`: recalculate labor assignments for unit when aging up babies and children (so they can start accepting jobs) + +## Misc Improvements +- `hide-tutorials`: handle tutorial popups for adventure mode +- `hide-tutorials`: new ``reset`` command that will re-enable popups in the current game (in case you hid them all and now want them back) +- `gui/notify`: moody dwarf notification turns red when they can't reach workshop or items +- `gui/notify`: save reminder now appears in adventure mode +- `gui/notify`: save reminder changes color to yellow at 30 minutes and to orange at 60 minutes +- `gui/confirm`: in the delete manager order confirmation dialog, show a description of which order you have selected to delete +- `gui/create-item`: now accepts a ``pos`` argument of where to spawn items +- `modtools/create-item`: exported ``hackWish`` function now supports ``opts.pos`` for determining spawn location +- `hfs-pit`: improve placement of stairs w/r/t eerie pits and ramp tops +- `position`: add adventurer tile position +- `position`: add global site position +- `position`: when a tile is selected, display relevant map block and intra-block offset +- `gui/sitemap`: shift click to start following the selected unit or artifact +- `prioritize`: when prioritizing jobs of a specified type, also output how many of those jobs were already prioritized before you ran the command +- `prioritize`: don't include already-prioritized jobs in the output of ``prioritize -j`` +- `gui/design`: only display vanilla dimensions tooltip if the DFHack dimensions tooltip is disabled +- `devel/query`: support adventure mode +- `devel/tree-info`: support adventure mode +- `hfs-pit`: support adventure mode +- `colonies`: support adventure mode +- `position`: report position of the adventure mode look cursor, if active + +# 51.04-r1.1 + +## Fixes +- `advtools`: fix dfhack-added conversation options not appearing in the ask whereabouts conversation tree +- `gui/rename`: fix error when changing the language of a unit's name + +## Misc Improvements +- `assign-preferences`: new ``--show`` option to display the preferences of the selected unit +- `pref-adjust`: new ``show`` command to display the preferences of the selected unit + +## Removed +- `gui/control-panel`: removed ``craft-age-wear`` tweak for Windows users; the tweak doesn't currently load on Windows + +# 51.02-r1 + +## Fixes +- `deathcause`: fix error when retrieving the name of a historical figure + +# 50.15-r2 + +## New Tools +- `fix/stuck-squad`: allow squads and messengers returning from missions to rescue squads that have gotten stuck on the world map +- `gui/rename`: (reinstated) give new in-game language-based names to anything that can be named (units, governments, fortresses, the world, etc.) + +## New Features +- `gui/settings-manager`: new overlay on the Labor -> Standing Orders tab for configuring the number of barrels to reserve for job use (so you can brew alcohol and not have all your barrels claimed by stockpiles for container storage) +- `gui/settings-manager`: standing orders save/load now includes the reserved barrels setting +- `gui/rename`: add overlay to worldgen screen allowing you to rename the world before the new world is saved +- `gui/rename`: add overlay to the "Prepare carefully" embark screen that transparently fixes a DF bug where you can't give units nicknames or custom professions +- `gui/notify`: new notification type: save reminder; appears if you have gone more than 15 minutes without saving; click to autosave + +## Fixes +- `fix/dry-buckets`: don't empty buckets for wells that are actively in use +- `gui/unit-info-viewer`: skill progress bars now show correct XP thresholds for skills past Legendary+5 +- `caravan`: no longer incorrectly identify wood-based plant items and plant-based soaps as being ethically unsuitable for trading with the elves +- `gui/design`: don't require an extra right click on the first cancel of building area designations +- `gui/gm-unit`: refresh unit sprite when profession is changed + +## Misc Improvements +- `immortal-cravings`: goblins and other naturally non-eating/non-drinking races will now also satisfy their needs for eating and drinking +- `caravan`: add filter for written works in display furniture assignment dialog +- `fix/wildlife`: don't vaporize stuck wildlife that is onscreen -- kill them instead (as if they died from old age) +- `gui/sitemap`: show primary group affiliation for visitors and invaders (e.g. civilization name or performance troupe) + +# 50.14-r2 + +## New Tools +- `fix/wildlife`: prevent wildlife from getting stuck when trying to exit the map. This fix needs to be enabled manually in `gui/control-panel` on the Bug Fixes tab since not all players want this bug to be fixed (you can intentionally stall wildlife incursions by trapping wildlife in an enclosed area so they are not caged but still cannot escape). +- `immortal-cravings`: allow immortals to satisfy their cravings for food and drink +- `justice`: pardon a criminal's prison sentence + +## New Features +- `force`: add support for a ``Wildlife`` event to allow additional wildlife to enter the map + +## Fixes +- `gui/quickfort`: only print a help blueprint's text once even if the repeat setting is enabled +- `makeown`: quell any active enemy or conflict relationships with converted creatures +- `makeown`: halt any hostile jobs the unit may be engaged in, like kidnapping +- `fix/loyaltycascade`: allow the fix to work on non-dwarven citizens +- `control-panel`: fix error when setting numeric preferences from the commandline +- `gui/quickfort`: fix build mode evaluation rules to allow placement of furniture and constructions on tiles with stair shapes or without orthagonal floors +- `emigration`: save-and-reload no longer resets the emigration cycle timeout +- `geld`, `ungeld`: save-and-reload no longer loses changes done by `geld` and `ungeld` for units who are historical figures +- `rejuvenate`: fix error when specifying ``--age`` parameter +- `gui/notify`: don't classify (peacefully) visiting night creatures as hostile +- `exportlegends`: ensure historical figure race filter is usable after re-entering legends mode with a different loaded world + +## Misc Improvements +- `idle-crafting`: also support making shell crafts for workshops with linked input stockpiles +- `gui/gm-editor`: automatically resolve and display names for ``language_name`` fields +- `fix/stuck-worship`: reduced console output by default. Added ``--verbose`` and ``--quiet`` options. +- `gui/design`: add dimensions tooltip to vanilla zone painting interface +- `necronomicon`: new ``--world`` option to list all secret-containing items in the entire world +- `gui/design`: new ``gui/design.rightclick`` overlay that allows you to cancel out of partially drawn box and minecart designations without canceling completely out of drawing mode + +## Removed +- `modtools/force`: merged into `force` + +# 50.13-r5 + +## New Tools +- `embark-anyone`: allows you to embark as any civilization, including dead and non-dwarven civs +- `idle-crafting`: allow dwarves to independently satisfy their need to craft objects +- `gui/family-affairs`: (reinstated) inspect or meddle with pregnancies, marriages, or lover relationships +- `notes`: attach notes to locations on a fort map ## New Features - `caravan`: DFHack dialogs for trade screens (both ``Bring goods to depot`` and the ``Trade`` barter screen) can now filter by item origins (foreign vs. fort-made) and can filter bins by whether they have a mix of ethically acceptable and unacceptable items in them - `caravan`: If you have managed to select an item that is ethically unacceptable to the merchant, an "Ethics warning" badge will now appear next to the "Trade" button. Clicking on the badge will show you which items that you have selected are problematic. The dialog has a button that you can click to deselect the problematic items in the trade list. - `confirm`: If you have ethically unacceptable items selected for trade, the "Are you sure you want to trade" confirmation will warn you about them +- `quickfort`: ``#zone`` blueprints now integrated with `preserve-rooms` so you can create a zone and automatically assign it to a noble or administrative role +- `exportlegends`: option to filter by race on historical figures page ## Fixes -- `timestream`: ensure child growth events (e.g. becoming an adult) are not skipped over -- `empty-bin`: ``--liquids`` option correctly emptying containers filled with LIQUID_MISC +- `timestream`: ensure child growth events (that is, a child's transition to adulthood) are not skipped; existing "overage" children will be automatically fixed within a year +- `empty-bin`: ``--liquids`` option now correctly empties containers filled with LIQUID_MISC (like lye) +- `gui/design`: don't overcount "affected tiles" for Line & Freeform drawing tools +- `deep-embark`: fix error when embarking where there is no land to stand on (e.g. when embarking in the ocean with `gui/embark-anywhere`) +- `deep-embark`: fix failure to transport units and items when embarking where there is no room to spawn the starting wagon +- `gui/create-item`, `modtools/create-item`: items of type "VERMIN", "PET", "REMANS", "FISH", "RAW FISH", and "EGG" no longer spawn creature item "nothing" and will now stack correctly +- `rejuvenate`: don't set a lifespan limit for creatures that are immortal (e.g. elves, goblins) +- `rejuvenate`: properly disconnect babies from mothers when aging babies up to adults ## Misc Improvements -- `gui/sitemap`: show whether a unit is friendly, hostile, or wildlife +- `gui/sitemap`: show whether a unit is friendly, hostile, or wild - `gui/sitemap`: show whether a unit is caged +- `gui/control-panel`: include option for turning off dumping of old clothes for `tailor`, for players who have magma pit dumps and want to save old clothes from being dumped into the magma +- `position`: report current historical era (e.g., "Age of Myth"), site/adventurer world coords, and mouse map tile coords +- `position`: option to copy keyboard cursor position to the clipboard +- `assign-minecarts`: reassign vehicles to routes where the vehicle has been destroyed (or has otherwise gone missing) +- `fix/dry-buckets`: prompt DF to recheck requests for aid (e.g. "bring water" jobs) when a bucket is unclogged and becomes available for use +- `exterminate`: show descriptive names for the listed races in addition to their IDs +- `exterminate`: show actual names for unique creatures such as forgotten beasts and titans +- `fix/ownership`: now also checks and fixes room ownership links -## Removed +## Documentation +- `gui/embark-anywhere`: add information about how the game determines world tile pathability and instructions for bridging two landmasses # 50.13-r4 @@ -135,7 +626,7 @@ Template for new versions: - `caravan`: remember filter settings for pedestal item assignment dialog - `quickfort`: new ``delete`` command for deleting player-owned blueprints (library and mod-added blueprints cannot be deleted) - `quickfort`: support enabling `logistics` features for autoforbid and autoclaim on stockpiles -- `gui/quickfort`: allow farm plots, dirt roads, and paved roads to be designated around partial obstructions without callling it an error, matching vanilla behavior +- `gui/quickfort`: allow farm plots, dirt roads, and paved roads to be designated around partial obstructions without calling it an error, matching vanilla behavior - `gui/launcher`: refresh default tag filter when mortal mode is toggled in `gui/control-panel` so changes to which tools autocomplete take effect immediately - `gui/civ-alert`: you can now register multiple burrows as civilian alert safe spaces - `exterminate`: add ``all`` target for convenient scorched earth tactics @@ -207,7 +698,7 @@ Template for new versions: ## Fixes - `open-legends`: don't interfere with the dragging of vanilla list scrollbars - `gui/create-item`: properly restrict bags to bag materials by default -- `gui/create-item`: allow gloves and shoees to be made out of textiles by default +- `gui/create-item`: allow gloves and shoes to be made out of textiles by default - `exterminate`: don't classify dangerous non-invader units as friendly (e.g. snatchers) ## Misc Improvements @@ -255,7 +746,7 @@ Template for new versions: # 50.12-r2 ## New Tools -- `agitation-rebalance`: alter mechanics of irriation-related attacks so they are less constant and are more responsive to recent player bahavior +- `agitation-rebalance`: alter mechanics of irritation-related attacks so they are less constant and are more responsive to recent player behavior - `fix/ownership`: fix instances of multiple citizens claiming the same items, resulting in "Store owned item" job loops - `fix/stuck-worship`: fix prayer so units don't get stuck in uninterruptible "Worship!" states - `instruments`: provides information on how to craft the instruments used by the player civilization @@ -304,7 +795,7 @@ Template for new versions: - `gui/notify`: display important notifications that vanilla doesn't support yet and provide quick zoom links to notification targets. - `list-waves`: (reinstated) show migration wave information - `make-legendary`: (reinstated) make a dwarf legendary in specified skills -- `combat-harden`: (reinstated) set a dwarf's resistence to being affected by visible corpses +- `combat-harden`: (reinstated) set a dwarf's resistance to being affected by visible corpses - `add-thought`: (reinstated) add custom thoughts to a dwarf - `devel/input-monitor`: interactive UI for debugging input issues @@ -563,13 +1054,11 @@ Template for new versions: - `workorder`: reduce existing orders for automatic shearing and milking jobs when animals are no longer available - `gui/quickfort`: adapt "cursor lock" to mouse controls so it's easier to see the full preview for multi-level blueprints before you apply them - `gui/quickfort`: only display post-blueprint messages once when repeating the blueprint up or down z-levels -- `combine`: reduce max different stacks in containers to 30 to prevent contaners from getting overfull +- `combine`: reduce max different stacks in containers to 30 to prevent containers from getting overfull ## Removed - `gui/automelt`: replaced by an overlay panel that appears when you click on a stockpile -# 50.08-r3 - # 50.08-r2 ## New Scripts @@ -595,7 +1084,7 @@ Template for new versions: ## Misc Improvements - `gui/quickfort`: blueprints that designate items for dumping/forbidding/etc. no longer show an error highlight for tiles that have no items on them - `gui/quickfort`: place (stockpile layout) mode is now supported. note that detailed stockpile configurations were part of query mode and are not yet supported -- `gui/quickfort`: you can now generate manager orders for items required to complete bluerpints +- `gui/quickfort`: you can now generate manager orders for items required to complete blueprints - `gui/create-item`: ask for number of items to spawn by default - `light-aquifers-only`: now available as a fort Autostart option in `gui/control-panel`. note that it will only appear if "armok" tools are configured to be shown on the Preferences tab. - `gui/gm-editor`: when passing the ``--freeze`` option, further ensure that the game is frozen by halting all rendering (other than for DFHack tool windows) @@ -618,8 +1107,6 @@ Template for new versions: # 50.07-r1 -## New Scripts - ## Fixes -@ `caravan`: fix trade good list sometimes disappearing when you collapse a bin -@ `gui/gm-editor`: no longer nudges last open window when opening a new one @@ -632,8 +1119,6 @@ Template for new versions: - `prioritize`: revise and simplify the default list of prioritized jobs -- be sure to tell us if your forts are running noticeably better (or worse!) -@ `gui/control-panel`: add `faststart` to the system services -## Removed - # 50.07-beta2 ## New Scripts @@ -711,7 +1196,7 @@ Template for new versions: - `troubleshoot-item`: reports on the contents of containers with counts for each contained item type - `devel/visualize-structure`: now automatically inspects the contents of most pointer fields, rather than inspecting the pointers themselves - `devel/query`: will now search for jobs at the map coordinate highlighted, if no explicit job is highlighted and there is a map tile highlighted -- `caravan`: add trade screen overlay that assists with seleting groups of items and collapsing groups in the UI +- `caravan`: add trade screen overlay that assists with selecting groups of items and collapsing groups in the UI - `gui/gm-editor`: will now inspect a selected building itself if the building has no current jobs ## Removed @@ -777,7 +1262,7 @@ Template for new versions: - `gui/quickfort`: don't close the window when applying a blueprint so players can apply the same blueprint multiple times more easily - `locate-ore`: now only searches revealed tiles by default - `modtools/spawn-liquid`: sets tile temperature to stable levels when spawning water or magma --@ `prioritize`: pushing minecarts is now included in the default priortization list +-@ `prioritize`: pushing minecarts is now included in the default prioritization list - `prioritize`: now automatically starts boosting the default list of job types when enabled - `unforbid`: avoids unforbidding unreachable and underwater items by default - `gui/create-item`: added whole corpse spawning alongside corpsepieces. (under "corpse") diff --git a/colonies.lua b/colonies.lua index 554795229e..2672b66ab9 100644 --- a/colonies.lua +++ b/colonies.lua @@ -1,24 +1,7 @@ -- List, create, or change wild colonies (eg honey bees) -- By PeridexisErrant and Warmist -local help = [====[ - -colonies -======== -List vermin colonies, place honey bees, or convert all vermin -to honey bees. Usage: - -:colonies: List all vermin colonies on the map. -:colonies place: Place a honey bee colony under the cursor. -:colonies convert: Convert all existing colonies to honey bees. - -The ``place`` and ``convert`` subcommands by default create or -convert to honey bees, as this is the most commonly useful. -However both accept an optional flag to use a different vermin -type, for example ``colonies place ANT`` creates an ant colony -and ``colonies convert TERMITE`` ends your beekeeping industry. - -]====] +local guidm = require('gui.dwarfmode') function findVermin(target_verm) for k,v in ipairs(df.global.world.raws.creatures.all) do @@ -50,8 +33,8 @@ function convert_vermin_to(target_verm) end function place_vermin(target_verm) - local pos = copyall(df.global.cursor) - if pos.x == -30000 then + local pos = guidm.getCursorPos() + if not pos then qerror("Cursor must be pointing somewhere") end local verm = df.vermin:new() @@ -69,7 +52,7 @@ local args = {...} local target_verm = args[2] or "HONEY_BEE" if args[1] == 'help' or args[1] == '?' then - print(help) + print(dfhack.script_help()) elseif args[1] == 'convert' then convert_vermin_to(target_verm) elseif args[1] == 'place' then diff --git a/combine.lua b/combine.lua index 3253d28db3..cd9e2522f0 100644 --- a/combine.lua +++ b/combine.lua @@ -182,6 +182,17 @@ local function stack_type_new(type_vals) return stack_type end +local function isDye(item) + -- Dyes should not be combined as this will cause bugs when mixing them together + if item:getType() ~= df.item_type.POWDER_MISC then return false end + -- pcall guards items/materials that can't be decoded or lack the flag + local ok, is_dye = pcall(function() + local mat = dfhack.matinfo.decode(item.mat_type, item.mat_index) + return mat and mat.material.flags.IS_DYE or false + end) + return ok and is_dye or false +end + local function stacks_add_item(stockpile, stacks, stack_type, item, container) -- add an item to the matching comp_items table; based on comp_key. local comp_key = '' @@ -436,7 +447,7 @@ local function stacks_add_items(stockpile, stacks, items, container, ind) local stack_type = stacks.stack_types[type_id] -- item type in list of included types? - if stack_type and not item:isSand() and not item:isPlaster() and isValidPart(item) then + if stack_type and not item:isSand() and not item:isPlaster() and not isDye(item) and isValidPart(item) then if not isRestrictedItem(item) and item.stack_size <= stack_type.max_stack_qty then stacks_add_item(stockpile, stacks, stack_type, item, container) @@ -736,6 +747,26 @@ local function get_stockpile_here() -- return the stockpile as a table local stockpiles = {} local building = dfhack.gui.getSelectedStockpile(true) + + -- try finding the stockpile by viewed item or first item in itemlist viewsheet. + if building == nil then + local item = nil + if dfhack.gui.getSelectedItem(true) ~= nil then + item = dfhack.gui.getSelectedItem(true) + elseif tonumber(dfhack.DF_VERSION:match("^0*%.*(%d+%.%d+)")) >= 50.07 -- matchFocusString() in Commit a770a4c + and dfhack.gui.matchFocusString("dwarfmode/ViewSheets/ITEM_LIST", dfhack.gui.getDFViewscreen()) + and df.global.game.main_interface.view_sheets.open == true + and df.global.game.main_interface.view_sheets.active_sheet == df.view_sheet_type.ITEM_LIST + and #df.global.game.main_interface.view_sheets.viewing_itid > 0 + then + local itemid = df.global.game.main_interface.view_sheets.viewing_itid[0] + item = df.item.find(itemid) + end + local pos = (item) and xyz2pos(dfhack.items.getPosition(item)) or nil + building = (pos) and dfhack.buildings.findAtTile(pos) or nil + building = (df.building_stockpilest:is_instance(building)) and building or nil + end + if not building then qerror('Please select a stockpile.') end table.insert(stockpiles, building) if opts.verbose > 0 then diff --git a/confirm.lua b/confirm.lua index fb0a108ed6..5142013d1f 100644 --- a/confirm.lua +++ b/confirm.lua @@ -63,6 +63,7 @@ function ConfirmOverlay:init() } end end + self.paused_confs = {} end function ConfirmOverlay:preUpdateLayout() @@ -77,11 +78,14 @@ function ConfirmOverlay:preUpdateLayout() end function ConfirmOverlay:overlay_onupdate() - if self.paused_conf and - not dfhack.gui.matchFocusString(self.paused_conf.context, + for conf in pairs(self.paused_confs) do + if not dfhack.gui.matchFocusString(conf.context, dfhack.gui.getDFViewscreen(true)) - then - self.paused_conf = nil + then + self.paused_confs[conf] = nil + end + end + if not next(self.paused_confs) then self.overlay_onupdate_max_freq_seconds = 300 end end @@ -108,19 +112,22 @@ function ConfirmOverlay:matches_conf(conf, keys, scr) end function ConfirmOverlay:onInput(keys) - if self.paused_conf or self.simulating then + if self.simulating then return false end local scr = dfhack.gui.getDFViewscreen(true) for id, conf in pairs(specs.REGISTRY) do if specs.config.data[id].enabled and self:matches_conf(conf, keys, scr) then + if self.paused_confs[conf] then + return false + end local mouse_pos = xy2pos(dfhack.screen.getMousePos()) local propagate_fn = function(pause) if conf.on_propagate then conf.on_propagate() end if pause then - self.paused_conf = conf + self.paused_confs[conf] = true self.overlay_onupdate_max_freq_seconds = 0 end if keys._MOUSE_L then @@ -131,8 +138,9 @@ function ConfirmOverlay:onInput(keys) gui.simulateInput(scr, keys) self.simulating = false end + local pause_fn = conf.pausable and curry(propagate_fn, true) or nil dialogs.showYesNoPrompt(conf.title, utils.getval(conf.message):wrap(45), COLOR_YELLOW, - propagate_fn, nil, curry(propagate_fn, true), curry(dfhack.run_script, 'gui/confirm', tostring(conf.id))) + propagate_fn, nil, pause_fn, curry(dfhack.run_script, 'gui/confirm', tostring(conf.id))) return true end end diff --git a/deathcause.lua b/deathcause.lua index 9b0d4fad61..3fd62fd115 100644 --- a/deathcause.lua +++ b/deathcause.lua @@ -1,10 +1,12 @@ -- show death cause of a creature +--@ module = true local DEATH_TYPES = reqscript('gui/unit-info-viewer').DEATH_TYPES -- Gets the first corpse item at the given location -function getItemAtPosition(pos) +local function getItemAtPosition(pos) for _, item in ipairs(df.global.world.items.other.ANY_CORPSE) do + -- could this maybe be `if same_xyz(pos, item.pos) then`? if item.pos.x == pos.x and item.pos.y == pos.y and item.pos.z == pos.z then print("Automatically chose first corpse at the selected location.") return item @@ -12,11 +14,11 @@ function getItemAtPosition(pos) end end -function getRaceNameSingular(race_id) +local function getRaceNameSingular(race_id) return df.creature_raw.find(race_id).name[0] end -function getDeathStringFromCause(cause) +local function getDeathStringFromCause(cause) if cause == -1 then return "died" else @@ -24,15 +26,13 @@ function getDeathStringFromCause(cause) end end -function displayDeathUnit(unit) - local str = ("The %s"):format(getRaceNameSingular(unit.race)) - if unit.name.has_name then - str = str .. (" %s"):format(dfhack.TranslateName(unit.name)) - end +-- Returns a cause of death given a unit +local function getDeathCauseFromUnit(unit) + local str = unit.name.has_name and '' or 'The ' + str = str .. dfhack.units.getReadableName(unit) if not dfhack.units.isDead(unit) then - print(str .. " is not dead yet!") - return + return str .. " is not dead yet!" end str = str .. (" %s"):format(getDeathStringFromCause(unit.counters.death_cause)) @@ -46,18 +46,18 @@ function displayDeathUnit(unit) if killer then str = str .. (", killed by the %s"):format(getRaceNameSingular(killer.race)) if killer.name.has_name then - str = str .. (" %s"):format(dfhack.TranslateName(killer.name)) + str = str .. (" %s"):format(dfhack.translation.translateName(dfhack.units.getVisibleName(killer))) end end end end - print(str .. '.') + return str .. '.' end -- returns the item description if the item still exists; otherwise -- returns the weapon name -function getWeaponName(item_id, subtype) +local function getWeaponName(item_id, subtype) local item = df.item.find(item_id) if not item then return df.global.world.raws.itemdefs.weapons[subtype].name @@ -65,10 +65,10 @@ function getWeaponName(item_id, subtype) return dfhack.items.getDescription(item, 0, false) end -function displayDeathEventHistFigUnit(histfig_unit, event) +local function getDeathEventHistFigUnit(histfig_unit, event) local str = ("The %s %s %s in year %d"):format( getRaceNameSingular(histfig_unit.race), - dfhack.TranslateName(histfig_unit.name), + dfhack.translation.translateName(dfhack.units.getVisibleName(histfig_unit)), getDeathStringFromCause(event.death_cause), event.year ) @@ -77,7 +77,7 @@ function displayDeathEventHistFigUnit(histfig_unit, event) if slayer_histfig then str = str .. (", killed by the %s %s"):format( getRaceNameSingular(slayer_histfig.race), - dfhack.TranslateName(slayer_histfig.name) + dfhack.translation.translateName(dfhack.units.getVisibleName(slayer_histfig)) ) end @@ -89,11 +89,11 @@ function displayDeathEventHistFigUnit(histfig_unit, event) end end - print(str .. '.') + return str .. '.' end -- Returns the death event for the given histfig or nil if not found -function getDeathEventForHistFig(histfig_id) +local function getDeathEventForHistFig(histfig_id) for i = #df.global.world.history.events - 1, 0, -1 do local event = df.global.world.history.events[i] if event:getType() == df.history_event_type.HIST_FIGURE_DIED then @@ -104,17 +104,18 @@ function getDeathEventForHistFig(histfig_id) end end -function displayDeathHistFig(histfig) +-- Returns the cause of death given a histfig +local function getDeathCauseFromHistFig(histfig) local histfig_unit = df.unit.find(histfig.unit_id) if not histfig_unit then qerror("Cause of death not available") end if not dfhack.units.isDead(histfig_unit) then - print(("%s is not dead yet!"):format(dfhack.TranslateName(histfig_unit.name))) + return ("%s is not dead yet!"):format(dfhack.units.getReadableName(histfig_unit)) else local death_event = getDeathEventForHistFig(histfig.id) - displayDeathEventHistFigUnit(histfig_unit, death_event) + return getDeathEventHistFigUnit(histfig_unit, death_event) end end @@ -149,6 +150,19 @@ local function get_target() return selected_item.hist_figure_id, df.unit.find(selected_item.unit_id) end +-- wrapper function to take either a unit or a histfig and get the death cause +function getDeathCause(target) + if df.unit:is_instance(target) then + return getDeathCauseFromUnit(target) + else + return getDeathCauseFromHistFig(target) + end +end + +if dfhack_flags.module then + return +end + local hist_figure_id, selected_unit = get_target() if not hist_figure_id then @@ -157,7 +171,7 @@ elseif hist_figure_id == -1 then if not selected_unit then qerror("Cause of death not available") end - displayDeathUnit(selected_unit) + print(dfhack.df2console(getDeathCause(selected_unit))) else - displayDeathHistFig(df.historical_figure.find(hist_figure_id)) + print(dfhack.df2console(getDeathCause(df.historical_figure.find(hist_figure_id)))) end diff --git a/deep-embark.lua b/deep-embark.lua index 2e5f141283..6738bd532f 100644 --- a/deep-embark.lua +++ b/deep-embark.lua @@ -1,348 +1,360 @@ --@ module = true -local utils = require 'utils' +local dlg = require('gui.dialogs') +local utils = require('utils') function getFeatureID(cavernType) - local features = df.global.world.features - local map_features = features.map_features - if cavernType == 'CAVERN_1' then - for i, feature in ipairs(map_features) do - if feature._type == df.feature_init_subterranean_from_layerst - and feature.start_depth == 0 then - return features.feature_global_idx[i] - end - end - elseif cavernType == 'CAVERN_2' then - for i, feature in ipairs(map_features) do - if feature._type == df.feature_init_subterranean_from_layerst - and feature.start_depth == 1 then - return features.feature_global_idx[i] - end - end - elseif cavernType == 'CAVERN_3' then - for i, feature in ipairs(map_features) do - if feature._type == df.feature_init_subterranean_from_layerst - and feature.start_depth == 2 then - return features.feature_global_idx[i] - end - end - elseif cavernType == 'UNDERWORLD' then - for i, feature in ipairs(map_features) do - if feature._type == df.feature_init_underworld_from_layerst - and feature.start_depth == 4 then - return features.feature_global_idx[i] - end + local features = df.global.world.features + local map_features = features.map_features + if cavernType == 'CAVERN_1' then + for i, feature in ipairs(map_features) do + if feature._type == df.feature_init_subterranean_from_layerst and feature.start_depth == 0 then + return features.feature_global_idx[i] + end + end + elseif cavernType == 'CAVERN_2' then + for i, feature in ipairs(map_features) do + if feature._type == df.feature_init_subterranean_from_layerst and feature.start_depth == 1 then + return features.feature_global_idx[i] + end + end + elseif cavernType == 'CAVERN_3' then + for i, feature in ipairs(map_features) do + if feature._type == df.feature_init_subterranean_from_layerst and feature.start_depth == 2 then + return features.feature_global_idx[i] + end + end + elseif cavernType == 'UNDERWORLD' then + for i, feature in ipairs(map_features) do + if feature._type == df.feature_init_underworld_from_layerst and feature.start_depth == 4 then + return features.feature_global_idx[i] + end + end end - end end function getFeatureBlocks(featureID) - local featureBlocks = {} --as:number[] - for i,block in ipairs(df.global.world.map.map_blocks) do - if block.global_feature == featureID and block.local_feature == -1 then - table.insert(featureBlocks, i) + local featureBlocks = {} --as:number[] + for i, block in ipairs(df.global.world.map.map_blocks) do + if block.global_feature == featureID and block.local_feature == -1 then + table.insert(featureBlocks, i) + end end - end - return featureBlocks + return featureBlocks end function isValidTiletype(tiletype) - local tt = df.tiletype[tiletype] - local tiletypeAttrs = df.tiletype.attrs[tt] - local material = tiletypeAttrs.material - local forbiddenMaterials = { - df.tiletype_material.TREE, -- so as not to embark stranded on top of a tree - df.tiletype_material.MUSHROOM, - df.tiletype_material.FIRE, - df.tiletype_material.CAMPFIRE - } - for _,forbidden in ipairs(forbiddenMaterials) do - if material == forbidden then - return false - end - end - local shapeAttrs = df.tiletype_shape.attrs[tiletypeAttrs.shape] - return shapeAttrs.walkable + local tt = df.tiletype[tiletype] + local tiletypeAttrs = df.tiletype.attrs[tt] + local material = tiletypeAttrs.material + local forbiddenMaterials = utils.invert{ + df.tiletype_material.TREE, -- so as not to embark stranded on top of a tree + df.tiletype_material.MUSHROOM, + df.tiletype_material.FIRE, + df.tiletype_material.CAMPFIRE + } + if forbiddenMaterials[material] then return false end + local shapeAttrs = df.tiletype_shape.attrs[tiletypeAttrs.shape] + return shapeAttrs.walkable end function getValidEmbarkTiles(block) - local validTiles = {} --as:{_type:table,x:number,y:number,z:number}[] - for xi = 0,15 do - for yi = 0,15 do - if block.designation[xi][yi].flow_size == 0 - and isValidTiletype(block.tiletype[xi][yi]) then - table.insert(validTiles, {x = block.map_pos.x + xi, y = block.map_pos.y + yi, z = block.map_pos.z}) - end + local validTiles = {} --as:{_type:table,x:number,y:number,z:number}[] + for xi = 0, 15 do + for yi = 0, 15 do + if block.designation[xi][yi].flow_size == 0 + and isValidTiletype(block.tiletype[xi][yi]) + then + table.insert(validTiles, { x = block.map_pos.x + xi, y = block.map_pos.y + yi, z = block.map_pos.z }) + end + end end - end - return validTiles + return validTiles end function blockGlowingBarrierAnnouncements(recenter) --- temporarily disables the "glowing barrier has disappeared" announcement --- announcement settings are restored after 1 tick --- setting recenter to true enables recentering of game view to the announcement position - local announcementFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1 -- glowing barrier disappearance announcement - local oldFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1:new() -- backup announcement settings - announcementFlags.DO_MEGA = false - announcementFlags.PAUSE = false - announcementFlags.RECENTER = recenter and true or false - announcementFlags.A_DISPLAY = false - announcementFlags.D_DISPLAY = recenter and true or false -- an actual announcement is required for recentering to occur - dfhack.timeout(1,'ticks', function() -- barrier disappears after 1 tick - announcementFlags:assign(oldFlags) -- restore announcement settings - if recenter then - -- Remove glowing barrier notifications: - local status = df.global.world.status - local announcements = status.announcements - for i = #announcements-1, 0, -1 do - if string.find(announcements[i].text,"glowing barrier has disappeared") then - announcements:erase(i) - break - end - end - local reports = status.reports - for i = #reports-1, 0, -1 do - if string.find(reports[i].text,"glowing barrier has disappeared") then - reports:erase(i) - break + -- temporarily disables the "glowing barrier has disappeared" announcement + -- announcement settings are restored after 1 tick + -- setting recenter to true enables recentering of game view to the announcement position + -- glowing barrier disappearance announcement + local announcementFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1 + local oldFlags = df.global.d_init.announcements.flags.ENDGAME_EVENT_1:new() -- backup announcement settings + announcementFlags.DO_MEGA = false + announcementFlags.PAUSE = false + announcementFlags.RECENTER = recenter and true or false + announcementFlags.A_DISPLAY = false + announcementFlags.D_DISPLAY = recenter and true or false -- an actual announcement is required for recentering to occur + dfhack.timeout(1, 'ticks', function() -- barrier disappears after 1 tick + announcementFlags:assign(oldFlags) -- restore announcement settings + if recenter then + -- Remove glowing barrier notifications: + local status = df.global.world.status + local announcements = status.announcements + for i = #announcements - 1, 0, -1 do + if string.find(announcements[i].text, "glowing barrier has disappeared") then + announcements:erase(i) + break + end + end + local reports = status.reports + for i = #reports - 1, 0, -1 do + if string.find(reports[i].text, "glowing barrier has disappeared") then + reports:erase(i) + break + end + end + status.display_timer = 0 -- to avoid displaying other announcements end - end - status.display_timer = 0 -- to avoid displaying other announcements - end - end) + end) end function reveal(pos) --- creates an unbound glowing barrier at the target location --- so as to trigger tile revelation when it disappears 1 tick later (fortress mode only) --- should be run in conjunction with blockGlowingBarrierAnnouncements() - local x,y,z = pos2xyz(pos) - local block = dfhack.maps.getTileBlock(x,y,z) - local tiletype = block.tiletype[x%16][y%16] - if tiletype ~= df.tiletype.GlowingBarrier then -- to avoid multiple instances - block.tiletype[x%16][y%16] = df.tiletype.GlowingBarrier + -- creates an unbound glowing barrier at the target location + -- so as to trigger tile revelation when it disappears 1 tick later (fortress mode only) + -- should be run in conjunction with blockGlowingBarrierAnnouncements() + local x, y, z = pos2xyz(pos) + local block = dfhack.maps.getTileBlock(x, y, z) + local tiletype = block.tiletype[x % 16][y % 16] + if tiletype == df.tiletype.GlowingBarrier then -- to avoid multiple instances + return + end + block.tiletype[x % 16][y % 16] = df.tiletype.GlowingBarrier local barriers = df.global.world.event.glowing_barriers local barrier = df.glowing_barrier:new() - barrier.buildings:insert('#',-1) -- being unbound to a building makes the barrier disappear immediately + barrier.buildings:insert('#', -1) -- being unbound to a building makes the barrier disappear immediately barrier.pos:assign(pos) - barriers:insert('#',barrier) + barriers:insert('#', barrier) local hfs = df.glowing_barrier:new() - hfs.triggered = 1 -- this prevents HFS events (which can otherwise be triggered by the barrier disappearing) - barriers:insert('#',hfs) - dfhack.timeout(1,'ticks', function() -- barrier tiletype disappears after 1 tick - block.tiletype[x%16][y%16] = tiletype -- restore old tiletype - barriers:erase(#barriers-1) -- remove hfs blocker - barriers:erase(#barriers-1) -- remove revelation barrier + hfs.triggered = 1 -- this prevents HFS events (which can otherwise be triggered by the barrier disappearing) + barriers:insert('#', hfs) + dfhack.timeout(1, 'ticks', function() -- barrier tiletype disappears after 1 tick + block.tiletype[x % 16][y % 16] = tiletype -- restore old tiletype + barriers:erase(#barriers - 1) -- remove hfs blocker + barriers:erase(#barriers - 1) -- remove revelation barrier end) - end end function moveEmbarkStuff(selectedBlock, embarkTiles) - local spawnPosCentre - for _, hotkey in ipairs(df.global.plotinfo.main.hotkeys) do - if hotkey.name == "Wagon arrival location" then -- the preset hotkey is centred around the spawn point - spawnPosCentre = xyz2pos(hotkey.x, hotkey.y, hotkey.z) - hotkey:assign(embarkTiles[math.random(1, #embarkTiles)]) -- set the hotkey to the new spawn point - break + local spawnPosCentre + for _, hotkey in ipairs(df.global.plotinfo.main.hotkeys) do + if hotkey.cmd == df.hotkey_type.Zoom then -- the preset hotkey is centred around the spawn point + spawnPosCentre = xyz2pos(hotkey.x, hotkey.y, hotkey.z) + hotkey:assign(embarkTiles[math.random(1, #embarkTiles)]) -- set the hotkey to the new spawn point + break + end + end + + if not spawnPosCentre then -- no place for the wagon; use the position of the first unit + spawnPosCentre = xyz2pos(dfhack.units.getPosition(dfhack.units.getCitizens()[1])) end - end --- only target things within this zone to help avoid teleporting non-embark stuff: --- the following values might need to be modified - local x1 = spawnPosCentre.x - 15 - local x2 = spawnPosCentre.x + 15 - local y1 = spawnPosCentre.y - 15 - local y2 = spawnPosCentre.y + 15 - local z1 = spawnPosCentre.z - 3 -- units can be spread across multiple z-levels when embarking on a mountain - local z2 = spawnPosCentre.z + 3 + -- only target things within this zone to help avoid teleporting non-embark stuff: + -- the following values might need to be modified + local x1 = spawnPosCentre.x - 15 + local x2 = spawnPosCentre.x + 15 + local y1 = spawnPosCentre.y - 15 + local y2 = spawnPosCentre.y + 15 + local z1 = spawnPosCentre.z - 3 -- units can be spread across multiple z-levels when embarking on a mountain + local z2 = spawnPosCentre.z + 3 --- Move citizens and pets: - local unitsAtSpawn = dfhack.units.getUnitsInBox(x1,y1,z1,x2,y2,z2) - local movedUnit = false - for i, unit in ipairs(unitsAtSpawn) do - if unit.civ_id == df.global.plotinfo.civ_id and not unit.flags1.inactive and not unit.flags2.killed then - local pos = embarkTiles[math.random(1, #embarkTiles)] - dfhack.units.teleport(unit, pos) - reveal(pos) - movedUnit = true + -- Move citizens and pets: + local unitsAtSpawn = dfhack.units.getUnitsInBox(x1, y1, z1, x2, y2, z2) + local movedUnit = false + for i, unit in ipairs(unitsAtSpawn) do + if unit.civ_id == df.global.plotinfo.civ_id and not unit.flags1.inactive and not unit.flags2.killed then + local pos = embarkTiles[math.random(1, #embarkTiles)] + dfhack.units.teleport(unit, pos) + reveal(pos) + movedUnit = true + end + end + if movedUnit then + blockGlowingBarrierAnnouncements(true) -- this is separate from the reveal() function as it only needs to be called once per tick, regardless of how many times reveal() has been run end - end - if movedUnit then - blockGlowingBarrierAnnouncements(true) -- this is separate from the reveal() function as it only needs to be called once per tick, regardless of how many times reveal() has been run - end --- Move wagon contents: - local wagonFound = false - for _, wagon in ipairs(df.global.world.buildings.other.WAGON) do --as:df.building_wagonst - if wagon.age == 0 then -- just in case there's an older wagon present for some reason - local contained = wagon.contained_items - for i = #contained-1, 0, -1 do - if contained[i].use_mode == df.building_item_role_type.TEMP then -- actual contents (as opposed to building components) - local item = contained[i].item --- dfhack.items.moveToGround() does not handle items within buildings, so do this manually: - contained:erase(i) - for k = #item.general_refs-1, 0, -1 do - if item.general_refs[k]._type == df.general_ref_building_holderst then - item.general_refs:erase(k) + -- Move wagon contents: + local wagonFound = false + for _, wagon in ipairs(df.global.world.buildings.other.WAGON) do --as:df.building_wagonst + if wagon.age == 0 then -- just in case there's an older wagon present for some reason + local contained = wagon.contained_items + for i = #contained - 1, 0, -1 do + if contained[i].use_mode == df.building_item_role_type.TEMP then -- actual contents (as opposed to building components) + local item = contained[i].item + -- dfhack.items.moveToGround() does not handle items within buildings, so do this manually: + contained:erase(i) + for k = #item.general_refs - 1, 0, -1 do + if item.general_refs[k]._type == df.general_ref_building_holderst then + item.general_refs:erase(k) + end + end + item.flags.in_building = false + item.flags.on_ground = true + local pos = embarkTiles[math.random(1, #embarkTiles)] + item.pos:assign(pos) + selectedBlock.items:insert('#', item.id) + selectedBlock.occupancy[pos.x % 16][pos.y % 16].item = true + end end - end - item.flags.in_building = false - item.flags.on_ground = true - local pos = embarkTiles[math.random(1, #embarkTiles)] - item.pos:assign(pos) - selectedBlock.items:insert('#', item.id) - selectedBlock.occupancy[pos.x%16][pos.y%16].item = true + dfhack.buildings.deconstruct(wagon) + wagon.flags.almost_deleted = true -- wagon vanishes a tick later + wagonFound = true + break end - end - dfhack.buildings.deconstruct(wagon) - wagon.flags.almost_deleted = true -- wagon vanishes a tick later - wagonFound = true - break end - end --- Move items scattered around the spawn point if there's no wagon: - if not wagonFound then - for _, item in ipairs(df.global.world.items.other.IN_PLAY) do - local flags = item.flags - if item.age == 0 -- embark equipment consists of newly created items - and item.pos.x >= x1 and item.pos.x <= x2 - and item.pos.y >= y1 and item.pos.y <= y2 - and item.pos.z >= z1 and item.pos.z <= z2 - and flags.on_ground - and not flags.in_inventory - and not flags.in_building - and not flags.construction - and not flags.spider_web - and not flags.encased then - dfhack.items.moveToGround(item, embarkTiles[math.random(1, #embarkTiles)]) - end + -- Move items scattered around the spawn point if there's no wagon: + if not wagonFound then + for _, item in ipairs(df.global.world.items.other.IN_PLAY) do + local flags = item.flags + local item_pos = xyz2pos(dfhack.items.getPosition(item)) + -- items spawned into mid-air incorrectly have the `in_job` flag set + if 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 + flags.in_job = false + end + if item.age == 0 -- embark equipment consists of newly created items + and item_pos.x >= x1 and item_pos.x <= x2 + and item_pos.y >= y1 and item_pos.y <= y2 + and item_pos.z >= z1 and item_pos.z <= z2 + and not flags.in_inventory + and not flags.in_building + and not flags.construction + and not flags.spider_web + and not flags.encased + then + dfhack.items.moveToGround(item, embarkTiles[math.random(1, #embarkTiles)]) + end + end end - end + + dlg.showMessage('deep-embark', 'Please unpause to zoom to your deep embark.', COLOR_WHITE) end function deepEmbark(cavernType, blockDemons) - if not cavernType then - qerror('Cavern type not specified!') - end + if not cavernType then + qerror('Cavern type not specified!') + end - local cavernBlocks = getFeatureBlocks(getFeatureID(cavernType)) - if #cavernBlocks == 0 then - qerror(cavernType .. " not found!") - end + local cavernBlocks = getFeatureBlocks(getFeatureID(cavernType)) + if #cavernBlocks == 0 then + qerror(cavernType .. " not found!") + end - local moved = false - for n = 1, #cavernBlocks do - local i = math.random(1, #cavernBlocks) - local selectedBlock = df.global.world.map.map_blocks[cavernBlocks[i]] - local embarkTiles = getValidEmbarkTiles(selectedBlock) - if #embarkTiles >= 20 then -- value chosen arbitrarily; might want to increase/decrease (determines how cramped the embark spot is allowed to be) - moveEmbarkStuff(selectedBlock, embarkTiles) - moved = true - break + local moved = false + for n = 1, #cavernBlocks do + local i = math.random(1, #cavernBlocks) + local selectedBlock = df.global.world.map.map_blocks[cavernBlocks[i]] + local embarkTiles = getValidEmbarkTiles(selectedBlock) + if #embarkTiles >= 20 then -- value chosen arbitrarily; might want to increase/decrease (determines how cramped the embark spot is allowed to be) + moveEmbarkStuff(selectedBlock, embarkTiles) + moved = true + break + end + table.remove(cavernBlocks, i) + end + if not moved then + qerror('Insufficient space at ' .. cavernType) end - table.remove(cavernBlocks, i) - end - if not moved then - qerror('Insufficient space at ' .. cavernType) - end - if blockDemons then - disableSpireDemons() - end + if blockDemons then + disableSpireDemons() + end end function disableSpireDemons() --- marks underworld spires on the map as having been breached already, preventing HFS events - for _, spire in ipairs(df.global.world.event.deep_vein_hollows) do - spire.triggered = true - end + -- marks underworld spires on the map as having been breached already, preventing HFS events + for _, spire in ipairs(df.global.world.event.deep_vein_hollows) do + spire.triggered = true + end end function inEmbarkMode() - if df.global.gametype ~= df.game_type.DWARF_MAIN then -- is always set at fortress mode setup - return false - end - local embarkViewScreens = { - df.viewscreen_adopt_regionst, -- onLoad.init kicks in early; this is the viewscreen present at this stage (the 'loading world' viewscreen is also present at adventure mode setup and legends mode, hence the game_type check above) - df.viewscreen_choose_start_sitest, - df.viewscreen_setupdwarfgamest - } - local view = dfhack.gui.getCurViewscreen() - for _, valid in ipairs(embarkViewScreens) do - if view._type == valid or view.parent._type == valid and view._type ~= df.viewscreen_textviewerst then -- df.viewscreen_textviewerst is present right after embarking (displays the embark message) and has .parent._type == df.viewscreen_setupdwarfgamest - return true + if df.global.gametype ~= df.game_type.DWARF_MAIN then -- is always set at fortress mode setup + return false + end + local embarkViewScreens = { + df.viewscreen_adopt_regionst, -- onLoad.init kicks in early; this is the viewscreen present at this stage (the 'loading world' viewscreen is also present at adventure mode setup and legends mode, hence the game_type check above) + df.viewscreen_choose_start_sitest, + df.viewscreen_setupdwarfgamest + } + local view = dfhack.gui.getCurViewscreen() + for _, valid in ipairs(embarkViewScreens) do + if view._type == valid then + return true + end end - end - return false + return false end local validArgs = utils.invert({ - 'depth', - 'atReclaim', - 'blockDemons', - 'clear', - 'help' + 'depth', + 'atReclaim', + 'blockDemons', + 'clear', + 'help' }) -local args = utils.processArgs({...}, validArgs) +local args = utils.processArgs({ ... }, validArgs) if moduleMode then - return + return end if args.help then - print(dfhack.script_help()) - return + print(dfhack.script_help()) + return end if args.clear then - dfhack.onStateChange.DeepEmbarkMonitor = nil - print("Cleared settings; now embarking normally.") - return + dfhack.onStateChange.DeepEmbarkMonitor = nil + print("Cleared settings; now embarking normally.") + return end if not args.depth then - qerror('Depth not specified! Enter "help deep-embark" for more information.') + qerror('Depth not specified! Enter "help deep-embark" for more information.') end local validDepths = { - ["CAVERN_1"] = true, - ["CAVERN_2"] = true, - ["CAVERN_3"] = true, - ["UNDERWORLD"] = true + ["CAVERN_1"] = true, + ["CAVERN_2"] = true, + ["CAVERN_3"] = true, + ["UNDERWORLD"] = true } if not validDepths[args.depth] then - qerror("Invalid depth: " .. args.depth) + qerror("Invalid depth: " .. args.depth) end local consoleMode = dfhack.is_interactive() -- true if the script has been called directly from the DFHack console, false if called from onLoad.init if consoleMode and not inEmbarkMode() then - -- if running from the console (not onLoad.init), abort if not currently in an embark viewscreen. - qerror('When run from the command line, this script should be run during the embark setup screens. Enter "help deep-embark" for more information.') + -- if running from the console (not onLoad.init), abort if not currently in an embark viewscreen. + qerror( + 'When run from the command line, this script should be run during the embark setup screens. Enter "help deep-embark" for more information.') end if consoleMode then - print("Embarking at: " .. tostring(args.depth)) + print("Embarking at: " .. tostring(args.depth)) end dfhack.onStateChange.DeepEmbarkMonitor = function(event) - if event == SC_VIEWSCREEN_CHANGED then -- I initially tried using SC_MAP_LOADED, but the map appears to be loaded too early when reclaiming sites - local view = dfhack.gui.getCurViewscreen() - if not consoleMode and not args.atReclaim and df.global.gametype == df.game_type.DWARF_RECLAIM then -- it's assumed that a player who chooses to run the script from console whilst reclaiming knows what they're doing, so there's no need to check for -atReclaim in this scenario - dfhack.onStateChange.DeepEmbarkMonitor = nil -- stop monitoring - return -- don't deepEmbark if running from onLoad.init and in reclaim mode without -atReclaim - elseif view._type == df.viewscreen_choose_start_sitest then -- on embark screen - if view.choosing_embark or view.choosing_reclaim then -- on a fresh embark, or on a reclaim - deepEmbark(args.depth, args.blockDemons) + if event == SC_VIEWSCREEN_CHANGED then -- I initially tried using SC_MAP_LOADED, but the map appears to be loaded too early when reclaiming sites + local view = dfhack.gui.getCurViewscreen() + if not consoleMode and not args.atReclaim and df.global.gametype == df.game_type.DWARF_RECLAIM then -- it's assumed that a player who chooses to run the script from console whilst reclaiming knows what they're doing, so there's no need to check for -atReclaim in this scenario + dfhack.onStateChange.DeepEmbarkMonitor = nil -- stop monitoring + return -- don't deepEmbark if running from onLoad.init and in reclaim mode without -atReclaim + elseif view._type == df.viewscreen_choose_start_sitest then -- on embark screen + if view.choosing_embark or view.choosing_reclaim then -- on a fresh embark, or on a reclaim + deepEmbark(args.depth, args.blockDemons) + dfhack.onStateChange.DeepEmbarkMonitor = nil + end + elseif view._type == df.viewscreen_dwarfmodest then -- we're in game. If we got here then we never got an embark screen, so this is loading a save and we abort. + dfhack.onStateChange.DeepEmbarkMonitor = nil + end + elseif event == SC_WORLD_UNLOADED then -- embark aborted dfhack.onStateChange.DeepEmbarkMonitor = nil - end - elseif view._type == df.viewscreen_dwarfmodest then -- we're in game. If we got here then we never got an embark screen, so this is loading a save and we abort. - dfhack.onStateChange.DeepEmbarkMonitor = nil end - elseif event == SC_WORLD_UNLOADED then -- embark aborted - dfhack.onStateChange.DeepEmbarkMonitor = nil - end end diff --git a/deteriorate.lua b/deteriorate.lua index ddd6c0f872..42c0498aad 100644 --- a/deteriorate.lua +++ b/deteriorate.lua @@ -1,88 +1,159 @@ -- Cause selected item types to quickly rot away --@module = true +--@enable = true local argparse = require('argparse') local utils = require('utils') -local function get_clothes_vectors() - return {df.global.world.items.other.GLOVES, - df.global.world.items.other.ARMOR, - df.global.world.items.other.SHOES, - df.global.world.items.other.PANTS, - df.global.world.items.other.HELM} +-------------------- +-- state + +local GLOBAL_KEY = 'deteriorate' + +local categories = { + 'clothes', + 'food', + 'corpses', + 'usable-parts', + 'unusable-parts', +} + +local aliases = { + parts={'usable-parts', 'unusable-parts'}, + all=categories, +} + +local function get_default_state() + local default_state = { + enabled=false, + categories={}, + } + for _,category in ipairs(categories) do + local default_enabled = category == 'corpses' or category == 'unusable-parts' + default_state.categories[category] = { + enabled=default_enabled, + frequency=1, + last_cycle_tick=0, + } + end + return default_state end -local function get_corpse_vectors() - return {df.global.world.items.other.ANY_CORPSE} +state = state or get_default_state() + +function isEnabled() + return state.enabled end -local function get_remains_vectors() - return {df.global.world.items.other.REMAINS} +local function persist_state() + dfhack.persistent.saveSiteData(GLOBAL_KEY, state) +end + +----------------------- +-- deterioration logic + +local function get_clothes_vectors() + return { + df.global.world.items.other.GLOVES, + df.global.world.items.other.ARMOR, + df.global.world.items.other.SHOES, + df.global.world.items.other.PANTS, + df.global.world.items.other.HELM, + } end local function get_food_vectors() - return {df.global.world.items.other.FISH, - df.global.world.items.other.FISH_RAW, - df.global.world.items.other.EGG, - df.global.world.items.other.CHEESE, - df.global.world.items.other.PLANT, - df.global.world.items.other.PLANT_GROWTH, - df.global.world.items.other.FOOD} + return { + df.global.world.items.other.FISH, + df.global.world.items.other.FISH_RAW, + df.global.world.items.other.EGG, + df.global.world.items.other.CHEESE, + df.global.world.items.other.PLANT, + df.global.world.items.other.PLANT_GROWTH, + df.global.world.items.other.FOOD, + df.global.world.items.other.MEAT, + df.global.world.items.other.LIQUID_MISC, + } +end + +local function get_corpse_vectors() + return { + df.global.world.items.other.CORPSE, + df.global.world.items.other.REMAINS, + } +end + +local function get_parts_vectors() + return { + df.global.world.items.other.CORPSEPIECE, + } end local function is_valid_clothing(item) + -- includes discarded owned clothes return item.subtype.armorlevel == 0 and item.flags.on_ground and item.wear > 0 end -local function keep_usable(opts, item) - return opts.keep_usable and ( - not item.corpse_flags.unbutchered and ( - item.corpse_flags.bone or - item.corpse_flags.horn or - item.corpse_flags.leather or - item.corpse_flags.skull or - item.corpse_flags.tooth) or ( - item.corpse_flags.hair_wool or - item.corpse_flags.pearl or - item.corpse_flags.plant or - item.corpse_flags.shell or - item.corpse_flags.silk or - item.corpse_flags.yarn) ) +local function is_valid_food(item) + if not df.item_liquid_miscst:is_instance(item) then + return true + end + local mi = dfhack.matinfo.decode(item) + return mi:getToken():endswith(':MILK') end -local function is_valid_corpse(opts, item) - -- check if the corpse is a resident of the fortress and is not keep_usable - local unit = df.unit.find(item.unit_id) - if not unit then - return not keep_usable(opts, item) - end - local hf = df.historical_figure.find(unit.hist_figure_id) - if not hf then - return not keep_usable(opts, item) +-- TODO: is just checking in_building sufficient, or do we need to validate +-- that the building it is in is a coffin? +local function is_entombed(item) + return item.flags.in_building +end + +local function is_valid_corpse(item) + return not is_entombed(item) +end + +local usable_types = { + 'plant', + 'silk', + 'leather', + 'bone', + 'shell', + 'wood', + 'soap', + 'tooth', + 'horn', + 'pearl', + 'skull', + 'hair_wool', + 'yarn', +} + +local function is_usable_corpse_piece(item) + if item.flags.dead_dwarf or item.corpse_flags.unbutchered then + return false end - for _,link in ipairs(hf.entity_links) do - if link.entity_id == df.global.plotinfo.group_id and df.histfig_entity_link_type[link:getType()] == 'MEMBER' then - return false - end + for _,flag in ipairs(usable_types) do + if item.corpse_flags[flag] then return true end end - return not keep_usable(opts, item) + return false end -local function is_valid_remains(opts, item) - return true +local function is_valid_usable_corpse_piece(item) + return not is_entombed(item) and is_usable_corpse_piece(item) end -local function is_valid_food(opts, item) - return true +local function is_valid_unusable_corpse_piece(item) + return not is_entombed(item) and not is_usable_corpse_piece(item) end +-- different algorithm for clothes so they rot away when they become tattered local function increment_clothes_wear(item) item.wear_timer = math.ceil(item.wear_timer * (item.wear + 0.5)) return item.wear > 2 end -local function increment_generic_wear(item, threshold) +local function increment_wear(threshold, item) item.wear_timer = item.wear_timer + 1 if item.wear_timer > threshold then item.wear_timer = 0 @@ -91,211 +162,231 @@ local function increment_generic_wear(item, threshold) return item.wear > 3 end -local function increment_corpse_wear(item) - return increment_generic_wear(item, 24) -end - -local function increment_remains_wear(item) - return increment_generic_wear(item, 6) -end - -local function increment_food_wear(item) - return increment_generic_wear(item, 24) -end - -local function deteriorate(opts, get_item_vectors_fn, is_valid_fn, increment_wear_fn) - local count = 0 +local function deteriorate_items(now, get_item_vectors_fn, is_valid_fn, increment_wear_fn) + local items_to_remove = {} for _,v in ipairs(get_item_vectors_fn()) do for _,item in ipairs(v) do - if is_valid_fn(opts, item) and increment_wear_fn(item) - and not item.flags.garbage_collect then - dfhack.items.remove(item) - count = count + 1 + if is_valid_fn(item) and (now or increment_wear_fn(item)) and not item.flags.garbage_collect then + table.insert(items_to_remove, item) end end end - return count + for _,item in ipairs(items_to_remove) do + print(('deteriorate: %s crumbles away to dust'):format(dfhack.items.getReadableDescription(item))) + dfhack.items.remove(item) + end + return #items_to_remove end -local function always_worn() - return true +local function mk_deteriorate_fn(get_item_vectors_fn, is_valid_fn, increment_wear_fn) + return function(now) + return deteriorate_items(now, get_item_vectors_fn, is_valid_fn, increment_wear_fn) + end end -local function deteriorate_clothes(opts, now) - return deteriorate(opts, get_clothes_vectors, is_valid_clothing, - now and always_worn or increment_clothes_wear) -end +local category_fns = { + clothes=mk_deteriorate_fn(get_clothes_vectors, is_valid_clothing, increment_clothes_wear), + food=mk_deteriorate_fn(get_food_vectors, is_valid_food, curry(increment_wear, 24)), + corpses=mk_deteriorate_fn(get_corpse_vectors, is_valid_corpse, curry(increment_wear, 24)), + ['usable-parts']=mk_deteriorate_fn(get_parts_vectors, is_valid_usable_corpse_piece, curry(increment_wear, 24)), + ['unusable-parts']=mk_deteriorate_fn(get_parts_vectors, is_valid_unusable_corpse_piece, curry(increment_wear, 24)), +} -local function deteriorate_corpses(opts, now) - return deteriorate(opts, get_corpse_vectors, is_valid_corpse, - now and always_worn or increment_corpse_wear) - + deteriorate(opts, get_remains_vectors, is_valid_remains, - now and always_worn or increment_remains_wear) -end +---------------------------- +-- cycle and timer logic -local function deteriorate_food(opts, now) - return deteriorate(opts, get_food_vectors, is_valid_food, - now and always_worn or increment_food_wear) +local TICKS_PER_DAY = 1200 +local TICKS_PER_MONTH = 28 * TICKS_PER_DAY +local TICKS_PER_YEAR = 12 * TICKS_PER_MONTH + +local function get_normalized_tick() + return dfhack.world.ReadCurrentTick() + TICKS_PER_YEAR * dfhack.world.ReadCurrentYear() end -local type_fns = { - clothes=deteriorate_clothes, - corpses=deteriorate_corpses, - food=deteriorate_food, -} +timeout_ids = timeout_ids or {} --- maps the type string to {id=int, time=int, timeunit=string} -timeout_ids = timeout_ids or { - clothes={}, - corpses={}, - food={}, -} +local function event_loop(category) + local category_data = state.categories[category] + if not state.enabled or not category_data.enabled then return end -local function _stop(item_type) - local timeout_id = timeout_ids[item_type].id + local current_tick = get_normalized_tick() + local ticks_per_cycle = math.max(1, math.floor(TICKS_PER_DAY * category_data.frequency)) + local timeout_ticks = ticks_per_cycle + + if current_tick - category_data.last_cycle_tick < ticks_per_cycle then + timeout_ticks = category_data.last_cycle_tick - current_tick + ticks_per_cycle + else + category_fns[category](false) + category_data.last_cycle_tick = current_tick + persist_state() + end + timeout_ids[category] = dfhack.timeout(timeout_ticks, 'ticks', curry(event_loop, category)) +end + +-- launches timer. first cycle will be after the configured frequency +local function start_category(category, category_data, current_tick) + category_data = category_data or state.categories[category] + category_data.last_cycle_tick = current_tick or get_normalized_tick() + event_loop(category) +end + +local function stop_category(category) + local timeout_id = timeout_ids[category] if timeout_id then dfhack.timeout_active(timeout_id, nil) -- cancel callback - timeout_ids[item_type].id = nil - return true + timeout_ids[category] = nil end end -local function make_timeout_cb(item_type, opts) - local fn - fn = function(first_time) - local timeout_data = timeout_ids[item_type] - timeout_data.time, timeout_data.mode = opts.time, opts.mode - timeout_data.id = dfhack.timeout(opts.time, opts.mode, fn) - if not timeout_ids[item_type].id then - print('Map has been unloaded; stopping deteriorate') - for k in pairs(type_fns) do - _stop(k) - end - return - end - if not first_time then - local count = type_fns[item_type](opts) - if count > 0 then - print(('Deteriorated %d %s'):format(count, item_type)) - end +local function do_enable() + if state.enabled then return end + + state.enabled = true + local current_tick = get_normalized_tick() + for _,category in ipairs(categories) do + local category_data = state.categories[category] + if category_data.enabled then + start_category(category, category_data, current_tick) end end - return fn end -local function start(opts) - for _,v in ipairs(opts.types) do - _stop(v) - if not opts.quiet then - print(('Deterioration of %s commencing...'):format(v)) - end - -- create a callback and call it to make it register itself - make_timeout_cb(v, opts)(true) +local function do_disable() + if not state.enabled then return end + + state.enabled = false + for _,category in ipairs(categories) do + stop_category(category) end end -local function stop(opts) - for _,v in ipairs(opts.types) do - if _stop(v) and not opts.quiet then - print('Stopped deteriorating ' .. v) - end +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc == SC_MAP_UNLOADED then + do_disable() + return end -end -local function status() - for k in pairs(type_fns) do - local timeout_data = timeout_ids[k] - local status_str = 'Stopped' - if timeout_data.id then - local time, mode = timeout_data.time, timeout_data.mode - if time == 1 then - mode = mode:sub(1, #mode - 1) -- make singular - end - status_str = ('Running (every %s %s)') :format(time, mode) - end - print(('%7s:\t%s'):format(k, status_str)) + if sc ~= SC_MAP_LOADED or not dfhack.world.isFortressMode() then + return end -end -local function now(opts) - for _,v in ipairs(opts.types) do - local count = type_fns[v](opts, true) - if not opts.quiet then - print(('Deteriorated %d %s'):format(count, v)) - end + state = get_default_state() + utils.assign(state, dfhack.persistent.getSiteData(GLOBAL_KEY, state)) + + for _,category in ipairs(categories) do + event_loop(category) end end -local function help() - print(dfhack.script_help()) -end +--------------------- +-- CLI if dfhack_flags.module then return end -if not dfhack.isMapLoaded() then - qerror('deteriorate needs a fortress map to be loaded.') +if dfhack_flags.enable then + if dfhack_flags.enable_state then + do_enable() + else + do_disable() + end end -local command_switch = { - start=start, - stop=stop, - status=status, - now=now, -} - -local valid_timeunits = utils.invert{'days', 'months', 'years'} - -local function parse_freq(arg) - local elems = argparse.stringList(arg) - local num = tonumber(elems[1]) - if not num or num <= 0 then - qerror('number parameter for --freq option must be greater than 0') +local function parse_categories(arg) + local list = {} + for _,v in ipairs(argparse.stringList(arg)) do + if aliases[v] then + for _,alias in ipairs(aliases[v]) do + table.insert(list, alias) + end + elseif category_fns[v] then + table.insert(list, v) + else + qerror(('unrecognized category: "%s"'):format(v)) + end end - if #elems == 1 then - return num, 'days' + if #list == 0 then + qerror('no categories specified') end - local timeunit = elems[2]:lower() - if valid_timeunits[timeunit] then return num, timeunit end - timeunit = timeunit .. 's' -- it's ok if the user specified a singular - if valid_timeunits[timeunit] then return num, timeunit end - qerror(('invalid time unit: "%s"'):format(elems[2])) + return list end -local function parse_types(arg) - local types = argparse.stringList(arg) - for _,v in ipairs(types) do - if not type_fns[v] then - qerror(('unrecognized type: "%s"'):format(v)) +local function status() + local running_str = state.enabled and 'Running' or 'Would run' + print(('deteriorate is %s'):format(state.enabled and 'enabled' or 'disabled')) + print() + for _,category in ipairs(categories) do + local status_str = 'Stopped' + local category_data = state.categories[category] + if category_data.enabled then + status_str = ('%s every %s day%s') :format(running_str, + category_data.frequency, category_data.frequency == 1 and '' or 's') end + print(('%18s: %s'):format(category, status_str)) end - return types end -local opts = { - time = 1, - mode = 'days', - quiet = false, - types = {}, - keep_usable = false, - help = false, -} +local help = false -local nonoptions = argparse.processArgsGetopt({...}, { - {'f', 'freq', 'frequency', hasArg=true, - handler=function(optarg) opts.time,opts.mode = parse_freq(optarg) end}, - {'h', 'help', handler=function() opts.help = true end}, - {'q', 'quiet', handler=function() opts.quiet = true end}, - {'k', 'keep-usable', handler=function() opts.keep_usable = true end}, - {'t', 'types', hasArg=true, - handler=function(optarg) opts.types = parse_types(optarg) end}}) +local positionals = argparse.processArgsGetopt({...}, { + {'h', 'help', handler=function() help = true end}, +}) -local command = nonoptions[1] -if not command or not command_switch[command] then opts.help = true end +local command = table.remove(positionals, 1) +if command == 'help' or help then + print(dfhack.script_help()) + return +end -if not opts.help and command ~= 'status' and #opts.types == 0 then - qerror('no item types specified! try adding a --types parameter.') +if not command or command == 'status' then + status() +elseif command == 'enable' then + local cats = parse_categories(positionals[1]) + for _,v in ipairs(cats) do + if state.categories[v].enabled then + goto continue + end + state.categories[v].enabled = true + if state.enabled then + start_category(v) + end + ::continue:: + end +elseif command == 'disable' then + local cats = parse_categories(positionals[1]) + for _,v in ipairs(cats) do + if not state.categories[v].enabled then + goto continue + end + state.categories[v].enabled = false + if state.enabled then + stop_category(v) + end + ::continue:: + end +elseif command == 'frequency' or command == 'freq' then + local freq = tonumber(positionals[1]) + if not freq or freq <= 0 then + qerror('frequency must be greater than 0') + end + local cats = parse_categories(positionals[2]) + for _,v in ipairs(cats) do + state.categories[v].frequency = freq + if state.enabled then + stop_category(v) + start_category(v) + end + end +elseif command == 'now' then + local cats = parse_categories(positionals[1]) + local count = 0 + for _,v in ipairs(cats) do + count = count + category_fns[v](true) + end + print(('Deteriorated %d item%s'):format(count, count == 1 and '' or 's')) +else + qerror('unrecognized command: "' .. command .. '"') end -(command_switch[command] or help)(opts) +persist_state() diff --git a/devel/export-dt-ini.lua b/devel/export-dt-ini.lua index 5d8169697b..5bf5eadd6c 100644 --- a/devel/export-dt-ini.lua +++ b/devel/export-dt-ini.lua @@ -92,7 +92,7 @@ address('musical_forms_vector',globals,'world','musical_forms','all') address('dance_forms_vector',globals,'world','dance_forms','all') address('occupations_vector',globals,'world','occupations','all') address('world_data',globals,'world','world_data') -address('material_templates_vector',globals,'world','raws','material_templates') +address('material_templates_vector',globals,'world','raws','material_templates','all') address('inorganics_vector',globals,'world','raws','inorganics') address('plants_vector',globals,'world','raws','plants','all') address('races_vector',globals,'world','raws','creatures','all') @@ -116,7 +116,7 @@ address('colors_vector',globals,'world','raws','descriptors','colors') address('shapes_vector',globals,'world','raws','descriptors','shapes') address('reactions_vector',globals,'world','raws','reactions') address('base_materials',globals,'world','raws','mat_table','builtin') -address('all_syndromes_vector',globals,'world','raws','syndromes','all') +address('all_syndromes_vector',globals,'world','raws','mat_table','syndromes','all') address('events_vector',globals,'world','history','events') address('historical_figures_vector',globals,'world','history','figures') address('world_site_type',df.world_site,'type') @@ -201,7 +201,7 @@ address('hist_name',df.historical_figure,'name') address('id',df.historical_figure,'id') address('hist_fig_info',df.historical_figure,'info') address('reputation',df.historical_figure_info,'reputation') -address('current_ident',df.historical_figure_info.T_reputation,'cur_identity') +address('current_ident',df.reputation_profilest,'cur_identity') address('fake_name',df.identity,'name') address('fake_birth_year',df.identity,'birth_year') address('fake_birth_time',df.identity,'birth_second') @@ -255,14 +255,14 @@ address('pants_armor_properties',df.itemdef_pantsst,'props') address('other_armor_properties',df.itemdef_helmst,'props') header('material_offsets') -address('solid_name',df.material_common,'state_name','Solid') -address('liquid_name',df.material_common,'state_name','Liquid') -address('gas_name',df.material_common,'state_name','Gas') -address('powder_name',df.material_common,'state_name','Powder') -address('paste_name',df.material_common,'state_name','Paste') -address('pressed_name',df.material_common,'state_name','Pressed') -address('flags',df.material_common,'flags') -address('reaction_class',df.material_common,'reaction_class') +address('solid_name',df.material,'state_name','Solid') +address('liquid_name',df.material,'state_name','Liquid') +address('gas_name',df.material,'state_name','Gas') +address('powder_name',df.material,'state_name','Powder') +address('paste_name',df.material,'state_name','Paste') +address('pressed_name',df.material,'state_name','Pressed') +address('flags',df.material,'flags') +address('reaction_class',df.material,'reaction_class') address('prefix',df.material,'prefix') address('inorganic_materials_vector',df.inorganic_raw,'material') address('inorganic_flags',df.inorganic_raw,'flags') @@ -318,9 +318,9 @@ address('physical_attrs',df.unit,'body','physical_attrs') address('body_size',df.unit,'appearance','body_modifiers') address('size_info',df.unit,'body','size_info','size_cur') address('size_base',df.unit,'body','size_info','size_base') -address('curse',df.unit,'curse','name') -address('curse_add_flags1',df.unit,'curse','add_tags1') -address('turn_count',df.unit,'curse','time_on_site') +address('curse',df.unit,'uwss_display_name_sing') +address('curse_add_flags1',df.unit,'uwss_add_caste_flag') +address('turn_count',df.unit,'usable_interaction','time_on_site') address('souls',df.unit,'status','souls') address('states',df.unit,'status','misc_traits') address('labors',df.unit,'status','labors') @@ -336,7 +336,7 @@ address('counters3',df.unit, 'counters2','paralysis') address('limb_counters',df.unit,'status2','limbs_stand_max') address('blood',df.unit,'body','blood_max') address('body_component_info',df.unit,'body','components') -address('layer_status_vector',df.body_component_info,'layer_status') +address('layer_status_vector',df.unit.T_body.T_components,'layer_status') address('wounds_vector',df.unit,'body','wounds') address('mood_skill',df.unit,'job','mood_skill') address('used_items_vector',df.unit,'used_items') @@ -356,16 +356,16 @@ address('trans_race_vec',df.creature_interaction_effect_body_transformationst,'r header('unit_wound_offsets') address('parts',df.unit_wound,'parts') -address('id',df.unit_wound.T_parts,'body_part_id') -address('layer',df.unit_wound.T_parts,'layer_idx') +address('id',df.unit_wound_layerst,'body_part_id') +address('layer',df.unit_wound_layerst,'layer_idx') address('general_flags',df.unit_wound,'flags') -address('flags1',df.unit_wound.T_parts,'flags1') -address('flags2',df.unit_wound.T_parts,'flags2') -address('effects_vector',df.unit_wound.T_parts,'effect_type') -address('bleeding',df.unit_wound.T_parts,'bleeding') -address('pain',df.unit_wound.T_parts,'pain') -address('cur_pen',df.unit_wound.T_parts,'cur_penetration_perc') -address('max_pen',df.unit_wound.T_parts,'max_penetration_perc') +address('flags1',df.unit_wound_layerst,'flags1') +address('flags2',df.unit_wound_layerst,'flags2') +address('effects_vector',df.unit_wound_layerst,'effect_type') +address('bleeding',df.unit_wound_layerst,'bleeding') +address('pain',df.unit_wound_layerst,'pain') +address('cur_pen',df.unit_wound_layerst,'cur_penetration_perc') +address('max_pen',df.unit_wound_layerst,'max_penetration_perc') header('soul_details') address('name',df.unit_soul,'name') @@ -377,7 +377,7 @@ address('personality',df.unit_soul,'personality') address('beliefs',df.unit_personality,'values') address('emotions',df.unit_personality,'emotions') address('goals',df.unit_personality,'dreams') -address('goal_realized',df.unit_personality.T_dreams,'flags') +address('goal_realized',df.personality_goalst,'flags') address('traits',df.unit_personality,'traits') address('stress_level',df.unit_personality,'stress') address('needs',df.unit_personality,'needs') @@ -387,10 +387,10 @@ address('combat_hardened',df.unit_personality,'combat_hardened') address('likes_outdoors',df.unit_personality,'likes_outdoors') header('need_offsets') -address('id',df.unit_personality.T_needs,'id') -address('deity_id',df.unit_personality.T_needs,'deity_id') -address('focus_level',df.unit_personality.T_needs,'focus_level') -address('need_level',df.unit_personality.T_needs,'need_level') +address('id',df.personality_needst,'id') +address('deity_id',df.personality_needst,'deity_id') +address('focus_level',df.personality_needst,'focus_level') +address('need_level',df.personality_needst,'need_level') header('emotion_offsets') address('emotion_type',df.personality_moodst,'type') @@ -456,7 +456,7 @@ address('knowledge_category',df.activity_event_ponder_topicst,'topic','research' address('knowledge_flag',df.activity_event_ponder_topicst,'topic','research','flag_data') address('perf_type',df.activity_event_performancest,'type') address('perf_participants',df.activity_event_performancest,'participant_actions') -address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id') +address('perf_histfig',df.performance_rolest,'histfig_id') header('art_offsets') address('name',df.poetic_form,'name') diff --git a/devel/export-map.lua b/devel/export-map.lua new file mode 100644 index 0000000000..d01844a54b --- /dev/null +++ b/devel/export-map.lua @@ -0,0 +1,334 @@ +-- Export fortress map tile data to a JSON file +-- based on export-map.lua by mikerenfro: +-- https://github.com/mikerenfro/df-map-export/blob/main/export-map.lua +-- redux version by timothymtorres + +local tm = require('tile-material') +local utils = require('utils') +local json = require('json') +local argparse = require('argparse') + +local underworld_z +local underworld +local evilness + +-- the layer of the underworld +for _, feature in ipairs(df.global.world.features.map_features) do + if feature:getType() == df.feature_type.underworld_from_layer then + underworld_z = feature.layer + end +end + +-- right now the only tile_liquids are Water and Magma +local liquid_list = {} +for id, liquid in ipairs(df.tile_liquid) do + liquid_list[id] = string.upper(liquid) +end + +-- copied from agitation-rebalance.lua +-- check only one tile at the center of the map at ground lvl +-- (this ignores different biomes on the edges of the map) +local function get_evilness() + -- check around ground level + + local lvls_above + lvls_above = df.global.world.worldgen.worldgen_parms.levels_above_ground + local ground_z = (df.global.world.map.z_count - 2) - lvls_above + local xmax, ymax = dfhack.maps.getTileSize() + local center_x, center_y = math.floor(xmax/2), math.floor(ymax/2) + local rgnX, rgnY = dfhack.maps.getTileBiomeRgn(center_x, center_y, ground_z) + local biome = dfhack.maps.getRegionBiome(rgnX, rgnY) + + return biome and biome.evilness or 0 +end + +local function classify_tile(options, x, y, z) + -- if your map happens to cross a region boundary and different regions are + -- different depths, the last z-levels of hell MIGHT shrink their x/y size + -- so if your map is 190x190, the last hell z-levels can end up being 90x90 + + if dfhack.maps.getTileType(x, y, z) == nil then + return nil -- Designating the non-tiles of hell to be nil + end + + local tileattrs = df.tiletype.attrs[dfhack.maps.getTileType(x, y, z)] + local tileflags, tile_occupancy = dfhack.maps.getTileFlags(x, y, z) + + local tile_data = {} + + for map_option, position in pairs(options) do + if(map_option == "tiletype") then + tile_data[position] = tileattrs.material + elseif(map_option == "shape") then + tile_data[position] = tileattrs.shape + elseif(map_option == "special") then + tile_data[position] = tileattrs.special + elseif(map_option == "variant") then + tile_data[position] = tileattrs.variant + elseif(map_option == "hidden") then + tile_data[position] = tileflags.hidden + elseif(map_option == "light") then + tile_data[position] = tileflags.light + elseif(map_option == "subterranean") then + tile_data[position] = tileflags.subterranean + elseif(map_option == "outside") then + tile_data[position] = tileflags.outside + elseif(map_option == "liquid") then + if(tileflags.flow_size > 0) then + -- liquid_type is a boolean (true=Magma, false=Water) + -- converting it to a number for easy reference in key table + tile_data[position] = tileflags.liquid_type and 1 or 0 + else + tile_data[position] = nil + end + elseif(map_option == "flow") then + tile_data[position] = tileflags.flow_size + elseif(map_option == "aquifer") then + -- hardcoding these values bc they are not directly in a list + if(tileflags.water_table and tile_occupancy.heavy_aquifer) then + tile_data[position] = 2 + elseif(tileflags.water_table) then + tile_data[position] = 1 + else + tile_data[position] = 0 + end + elseif(map_option == "material") then + if(tileattrs.material >= 8 and tileattrs.material <= 11) then + -- grass material IDs [8-11] will throw an error so we skip them + tile_data[position] = nil + else + local material = tm.GetTileMat(x, y, z) + tile_data[position] = material and material.index or nil + end + end + end + + return tile_data +end + +local function setup_keys(options) + local KEYS = {} + + if(options.tiletype) then + KEYS.TILETYPE = {} + for id, material in ipairs(df.tiletype_material) do + KEYS.TILETYPE[id] = material + end + end + + if(options.shape) then + KEYS.SHAPE = {} + for id, shape in ipairs(df.tiletype_shape) do + KEYS.SHAPE[id] = shape + end + end + + if(options.special) then + KEYS.SPECIAL = {} + for id, special in ipairs(df.tiletype_special) do + KEYS.SPECIAL[id] = special + end + end + + if(options.variant) then + KEYS.VARIANT = {} + for id, variant in ipairs(df.tiletype_variant) do + KEYS.VARIANT[id] = variant + end + end + + if(options.aquifer) then + -- We are hardcoding since this info is not easily listed anywhere + KEYS.AQUIFER = { + [0] = "NONE", + [1] = "LIGHT", + [2] = "HEAVY", + } + end + + if(options.material) then + KEYS.MATERIAL = {} + KEYS.MATERIAL.PLANT = {} + for id, plant in ipairs(df.global.world.raws.plants.all) do + KEYS.MATERIAL.PLANT[id] = plant.id + end + + KEYS.MATERIAL.SOLID = {} -- everything but plants (stones, gems, metals) + KEYS.MATERIAL.METAL = {} + KEYS.MATERIAL.STONE = {} + KEYS.MATERIAL.GEM = {} + + for id, rock in ipairs(df.global.world.raws.inorganics.all) do + local material = rock.material + local name = material.state_adj.Solid + KEYS.MATERIAL.SOLID[id] = name +-- cant sort by key see +-- https://stackoverflow.com/questions/26160327/sorting-a-lua-table-by-key + KEYS.MATERIAL.STONE[id] = material.flags.IS_STONE and name or false + KEYS.MATERIAL.GEM[id] = material.flags.IS_GEM and name or false + KEYS.MATERIAL.METAL[id] = material.flags.IS_METAL and name or false + end + end + + if(options.liquid) then + KEYS.LIQUID = liquid_list + end + + if(options.flow) then + KEYS.FLOW = {} + for i=0, 7 do + KEYS.FLOW[i] = i + end + end + + return KEYS +end + +local function export_all_z_levels(fortress_name, folder, options) + local xmax, ymax, zmax = dfhack.maps.getTileSize() + local filename = string.format("%s/%s.json", folder, fortress_name) + + if dfhack.filesystem.exists(filename) then + qerror('Destination file ' .. filename .. ' already exists!') + return false + end + + local data = {} + + data.ARGUMENT_OPTION_ORDER = options + data.MAP_SIZE = { + x = xmax, + y = ymax, + -- subtract underworld levels if excluded from options + z = underworld and zmax or (zmax - underworld_z), + underworld_z_level = underworld and underworld_z or nil, + evilness = evilness and get_evilness() or nil, + } + data.KEYS = setup_keys(options) + + data.map = {} + + local zmin = 0 + if not underworld then -- skips all z-levels in the underworld + zmin = underworld_z + end + + -- start from bottom z-level (underworld) to top z-level (sky) + for z = zmin, zmax do + local level_data = {} + for y = 0, ymax - 1 do + local row_data = {} + for x = 0, xmax - 1 do + local classification = classify_tile(options, x, y, z) + table.insert(row_data, classification) + end + table.insert(level_data, row_data) + end + table.insert(data.map, level_data) + end + + local f = assert(io.open(filename, 'w')) + f:write(json.encode(data)) + f:close() + print("File created in Dwarf Fortress folder under " .. filename) +end + + +local function export_fortress_map(options) + local fortress_name = dfhack.TranslateName( + df.global.world.world_data.active_site[0].name + ) + local export_path = "map-exports/" .. fortress_name + dfhack.filesystem.mkdir_recursive(export_path) + export_all_z_levels(fortress_name, export_path, options) +end + +if not dfhack.isMapLoaded() then + qerror('This script requires a map to be loaded') +end + +local options, args = { + help = false, + tiletype = false, + shape = false, + special = false, + variant = false, + hidden = false, + light = false, + subterranean = false, + outside = false, + aquifer = false, + material = false, + flow = false, + liquid = false, + underworld = false, + evilness = false, +}, {...} + +local positionals = argparse.processArgsGetopt(args, { + {'', 'help', handler=function() options.help = true end}, + {'t', 'tiletype', handler=function() options.tiletype = true end}, + {'s', 'shape', handler=function() options.shape = true end}, + {'p', 'special', handler=function() options.special = true end}, + {'v', 'variant', handler=function() options.variant = true end}, + {'h', 'hidden', handler=function() options.hidden = true end}, + {'l', 'light', handler=function() options.light = true end}, + {'b', 'subterranean', handler=function() options.subterranean = true end}, + {'o', 'outside', handler=function() options.outside = true end}, + {'a', 'aquifer', handler=function() options.aquifer = true end}, + {'m', 'material', handler=function() options.material = true end}, + {'f', 'flow', handler=function() options.flow = true end}, + {'q', 'liquid', handler=function() options.liquid = true end}, + {'u', 'underworld', handler=function() options.underworld = true end}, + {'e', 'evilness', handler=function() options.evilness = true end}, +}) + +if positionals[1] == "help" or options.help then + print(dfhack.script_help()) + return false +elseif positionals[1] == "include" then + -- no need to change anything +elseif positionals[1] == "exclude" then + for setting in pairs(options) do + options[setting] = not options[setting] + end +else -- include everything + for setting in pairs(options) do + options[setting] = true + end +end + +local ordered_options = { + "tiletype", + "shape", + "special", + "variant", + "hidden", + "light", + "subterranean", + "outside", + "aquifer", + "material", + "flow", + "liquid", +} + +-- these get omitted from ordered_options since this data goes directly into the +-- JSON object for MAP_SIZE and doesn't need to be parsed into every tile +underworld = options.underworld +evilness = options.evilness + +-- reorganize ordered options based on selected options via argparse +-- this is so ARGUMENT_OPTION_ORDER has the correct order with no gaps +for setting in pairs(options) do + if not options[setting] then + for pos, json_setting in ipairs(ordered_options) do + if setting == json_setting then + table.remove(ordered_options, pos) + end + end + end +end + +ordered_options = utils.invert(ordered_options) +export_fortress_map(ordered_options) diff --git a/devel/hello-world.lua b/devel/hello-world.lua index 679bf1d52e..2af576f815 100644 --- a/devel/hello-world.lua +++ b/devel/hello-world.lua @@ -12,17 +12,27 @@ local HIGHLIGHT_PEN = dfhack.pen.parse{ HelloWorldWindow = defclass(HelloWorldWindow, widgets.Window) HelloWorldWindow.ATTRS{ - frame={w=20, h=14}, + frame={w=25, h=25}, frame_title='Hello World', autoarrange_subviews=true, - autoarrange_gap=1, + autoarrange_gap=2, + resizable=true, + resize_min={w=25, h=25}, } function HelloWorldWindow:init() + local LEVEL_OPTIONS = { + {label='Low', value=1}, + {label='Medium', value=2}, + {label='High', value=3}, + {label='Pro', value=4}, + {label='Insane', value=5}, + } + self:addviews{ widgets.Label{text={{text='Hello, world!', pen=COLOR_LIGHTGREEN}}}, widgets.HotkeyLabel{ - frame={l=0, t=0}, + frame={l=0}, label='Click me', key='CUSTOM_CTRL_A', on_activate=self:callback('toggleHighlight'), @@ -32,6 +42,28 @@ function HelloWorldWindow:init() frame={w=10, h=5}, frame_style=gui.INTERIOR_FRAME, }, + widgets.Divider{ + frame={h=1}, + frame_style_l=false, + frame_style_r=false, + }, + widgets.CycleHotkeyLabel{ + view_id='level', + frame={l=0, w=20}, + label='Level:', + key_back='CUSTOM_SHIFT_C', + key='CUSTOM_SHIFT_V', + options=LEVEL_OPTIONS, + initial_option=LEVEL_OPTIONS[1].value, + }, + widgets.Slider{ + frame={l=1}, + num_stops=#LEVEL_OPTIONS, + get_idx_fn=function() + return self.subviews.level:getOptionValue() + end, + on_change=function(idx) self.subviews.level:setOption(idx) end, + }, } end diff --git a/devel/kill-hf.lua b/devel/kill-hf.lua index e2cf5f7215..63b8083988 100644 --- a/devel/kill-hf.lua +++ b/devel/kill-hf.lua @@ -1,31 +1,5 @@ -- Kills the specified historical figure ---[====[ - -devel/kill-hf -============= - -Kills the specified historical figure, even if off-site, or terminates a -pregnancy. Useful for working around :bug:`11549`. - -Usage:: - - devel/kill-hf [-p|--pregnancy] [-n|--dry-run] HISTFIG_ID - -Arguments: - -``histfig_id``: - the ID of the historical figure to target - -``-p``, ``--pregnancy``: - if specified, and if the historical figure is pregnant, terminate the - pregnancy instead of killing the historical figure - -``-n``, ``--dry-run``: - if specified, only print the name of the historical figure - -]====] - local target_hf = -1 local target_pregnancy = false local dry_run = false @@ -44,7 +18,7 @@ end local hf = df.historical_figure.find(target_hf) or qerror('histfig not found: ' .. target_hf) -local hf_name = dfhack.df2console(dfhack.TranslateName(hf.name)) +local hf_name = dfhack.df2console(dfhack.translation.translateName(hf.name)) local hf_desc = ('%i: %s (%s)'):format(target_hf, hf_name, dfhack.units.getRaceNameById(hf.race)) if dry_run then diff --git a/devel/light.lua b/devel/light.lua index aa741f4d07..ff56e34c30 100644 --- a/devel/light.lua +++ b/devel/light.lua @@ -1,16 +1,5 @@ -- an experimental lighting engine ---[====[ -devel/light -=========== -An experimental lighting engine for DF, using the `rendermax` plugin. - -Call ``devel/light static`` to not recalculate lighting when in game. -Press :kbd:`~` to recalculate lighting. Press :kbd:`\`` to exit. - -]====] - -local gui = require 'gui' local guidm = require 'gui.dwarfmode' local render = require 'plugins.rendermax' @@ -27,12 +16,6 @@ function setCell(x,y,cell) cell.bo=cell.bo or {r=0,g=0,b=0} render.setCell(x,y,cell) end -function getCursorPos() - local g_cursor=df.global.cursor - if g_cursor.x ~= -30000 then - return copyall(g_cursor) - end -end --luacheck: skip function falloff(color,sqDist,maxdist) local v1=1/(sqDist/maxdist+1) @@ -273,7 +256,7 @@ function LightOverlay:calculateLightSun() end end function LightOverlay:calculateLightCursor() - local c=getCursorPos() + local c=guidm.getCursorPos() if c then diff --git a/devel/make-dt.pl b/devel/make-dt.pl index 4bffeb0d55..9d8ba2c433 100755 --- a/devel/make-dt.pl +++ b/devel/make-dt.pl @@ -325,9 +325,9 @@ ($$$$) emit_addr 'physical_attrs',%all,'unit','body.physical_attrs'; emit_addr 'body_size',%all,'unit','appearance.body_modifiers'; emit_addr 'size_info',%all,'unit','body.size_info'; - emit_addr 'curse',%all,'unit','curse.name'; - emit_addr 'curse_add_flags1',%all,'unit','curse.add_tags1'; - emit_addr 'turn_count',%all,'unit','curse.time_on_site'; + emit_addr 'curse',%all,'unit','uwss_display_name_sing'; + emit_addr 'curse_add_flags1',%all,'unit','uwss_add_caste_flag'; + emit_addr 'turn_count',%all,'unit','usable_interaction.time_on_site'; emit_addr 'souls',%all,'unit','status.souls'; emit_addr 'states',%all,'unit','status.misc_traits'; emit_addr 'labors',%all,'unit','status.labors'; diff --git a/devel/print-event.lua b/devel/print-event.lua index f2d9a54aad..771ac55299 100644 --- a/devel/print-event.lua +++ b/devel/print-event.lua @@ -34,7 +34,7 @@ end local function print_event(event) local str = df.new("string") local ctx = df.history_event_context:new() - event:getSentence(str, ctx) + event:getSentence(str, ctx, true, false) ctx:delete() print(str.value) str:delete() diff --git a/devel/query.lua b/devel/query.lua index 18859eaaed..610003873c 100644 --- a/devel/query.lua +++ b/devel/query.lua @@ -2,7 +2,8 @@ -- Written by Josh Cooper(cppcooper) on 2017-12-21, last modified: 2021-06-13 -- Version: 3.2 --luacheck:skip-entirely -local utils=require('utils') +local guidm = require('gui.dwarfmode') +local utils = require('utils') local validArgs = utils.invert({ 'help', @@ -221,14 +222,11 @@ function getSelectionData() elseif args.job then debugf(0,"job selection") selection = dfhack.gui.getSelectedJob(true) - if selection == nil and df.global.cursor.x >= 0 then - local pos = { x=df.global.cursor.x, - y=df.global.cursor.y, - z=df.global.cursor.z } + local pos = guidm.getCursorPos() + if selection == nil and pos then print("searching for a job at the cursor") for _link, job in utils.listpairs(df.global.world.jobs.list) do - local jp = job.pos - if jp.x == pos.x and jp.y == pos.y and jp.z == pos.z then + if same_xyz(job.pos, pos) then if selection == nil then selection = {} end @@ -240,7 +238,7 @@ function getSelectionData() path_info_pattern = path_info elseif args.tile then debugf(0,"tile selection") - local pos = copyall(df.global.cursor) + local pos = guidm.getCursorPos() selection = dfhack.maps.ensureTileBlock(pos.x,pos.y,pos.z) bpos = selection.map_pos path_info = string.format("tile[%d][%d][%d]",pos.x,pos.y,pos.z) @@ -249,7 +247,7 @@ function getSelectionData() tiley = pos.y%16 elseif args.block then debugf(0,"block selection") - local pos = copyall(df.global.cursor) + local pos = guidm.getCursorPos() selection = dfhack.maps.ensureTileBlock(pos.x,pos.y,pos.z) bpos = selection.map_pos path_info = string.format("blocks[%d][%d][%d]",bpos.x,bpos.y,bpos.z) diff --git a/devel/sc.lua b/devel/sc.lua index 3c3eb7c6cc..085514fc9b 100644 --- a/devel/sc.lua +++ b/devel/sc.lua @@ -175,7 +175,6 @@ local function check_container(obj, path) if not (obj._type == df.unit_preference and k == 'item_type') and not (obj._type == df.unit.T_job and k == 'mood_skill') and not (obj._type == df.unit and k == 'idle_area_type') - and not (obj._type == df.history_event_body_abusedst.T_props) and not (field._type == df.skill_rating) and field.value >= -1 and field.value < 1024 then local key = tostring(obj._type) .. '.' .. k .. tostring(field.value) diff --git a/devel/scan-vtables.lua b/devel/scan-vtables.lua index 74ce5a1294..069e43c0b0 100644 --- a/devel/scan-vtables.lua +++ b/devel/scan-vtables.lua @@ -1,48 +1,74 @@ -- Scan and dump likely vtable addresses -memscan = require('memscan') +local memscan = require('memscan') local osType = dfhack.getOSType() if osType ~= 'linux' then qerror('unsupported OS: ' .. osType) end -local df_ranges = {} -for _, mem in ipairs(dfhack.internal.getMemRanges()) do - if mem.read and ( - string.match(mem.name,'/dwarfort%.exe$') - or string.match(mem.name,'/dwarfort$') - or string.match(mem.name,'/Dwarf_Fortress$') - or string.match(mem.name,'Dwarf Fortress%.exe') - or string.match(mem.name,'/libg_src_lib.so$') - ) - then - table.insert(df_ranges, mem) +local function get_ranges() + local df_ranges, lib_names = {}, {} + + local raw_ranges = dfhack.internal.getMemRanges() + + -- add main binary mem ranges first + for _, range in ipairs(raw_ranges) do + if range.read and ( + string.match(range.name, '/dwarfort$') or + string.match(range.name, 'Dwarf Fortress%.exe') + ) + then + table.insert(df_ranges, range) + end + end + + for _, range in ipairs(raw_ranges) do + if range.read and string.match(range.name, '/libg_src_lib.so$') then + table.insert(df_ranges, range) + lib_names[range.name] = true + end + end + + return df_ranges, lib_names +end + +local df_ranges, lib_names = get_ranges() + +-- vtables that cross a range boundary can appear twice, a truncated version in the +-- lower memory range and a full version in the higher memory range +-- therefore, sort the memory ranges by start address, descending +-- but keep libraries last +local function sort_ranges(a, b) + if lib_names[a.name] == lib_names[b.name] then + return a.start_addr > b.start_addr end + return lib_names[b.name] end +table.sort(df_ranges, sort_ranges) + function is_df_addr(a) - for _, mem in ipairs(df_ranges) do - if a >= mem.start_addr and a < mem.end_addr then + for _, range in ipairs(df_ranges) do + if a >= range.start_addr and a < range.end_addr then return true end end return false end -local names = {} +local function is_vtable_range(range) + return not range.write and not range.execute +end -function scan_ranges(g_src) +function scan_ranges() local vtables = {} - for _, range in ipairs(df_ranges) do - if (not range.read) or range.write or range.execute then - goto next_range - end - if not not range.name:match('g_src') ~= g_src then - goto next_range - end + local seen = {} -- only record the first encountered vtable for each name + for _, range in ipairs(df_ranges) do + if not is_vtable_range(range) then goto next_range end local base = range.name:match('.*/(.*)$') local area = memscan.MemoryArea.new(range.start_addr, range.end_addr) + local is_lib = lib_names[range.name] for i = 1, area.uintptr_t.count - 1 do -- take every pointer-aligned value in memory mapped to the DF executable, and see if it is a valid vtable -- start by following the logic in Process::doReadClassName() and ensure it doesn't crash @@ -71,26 +97,26 @@ function scan_ranges(g_src) if demangled_name and not demangled_name:match('[<>]') and not demangled_name:match('^std::') and - not names[demangled_name] + not seen[demangled_name] and + (is_lib or demangled_name ~= 'widgets::widget') -- the widget in g_src takes precedence then local base_str = '' - if g_src then + if is_lib then vtable = vtable - range.base_addr base_str = (" base='%s'"):format(base) end vtables[demangled_name] = {value=vtable, base_str=base_str} + seen[demangled_name] = true end ::next_ptr:: end ::next_range:: end - for name, data in pairs(vtables) do - if not names[name] then - print((""):format(name, data.value, data.base_str)) - names[name] = true - end - end + + return vtables end -scan_ranges(false) -scan_ranges(true) +local vtables = scan_ranges() +for name, data in pairs(vtables) do + print((""):format(name, data.value, data.base_str)) +end diff --git a/devel/tree-info.lua b/devel/tree-info.lua index 4bd9811ed5..ec59563de0 100644 --- a/devel/tree-info.lua +++ b/devel/tree-info.lua @@ -1,29 +1,58 @@ --Print a tree_info visualization of the tree at the cursor. --@module = true - -local branch_dir = -{ - [0] = ' ', - [1] = string.char(26), --W - [2] = string.char(25), --N - [3] = string.char(217), --WN - [4] = string.char(27), --E - [5] = string.char(196), --WE - [6] = string.char(192), --NE - [7] = string.char(193), --WNE - [8] = string.char(24), --S - [9] = string.char(191), --WS - [10] = string.char(179), --NS - [11] = string.char(180), --WNS - [12] = string.char(218), --ES - [13] = string.char(194), --WES - [14] = string.char(195), --NES - [15] = string.char(197), --WNES +local guidm = require('gui.dwarfmode') + +-- [w][n][e][s] +local branch_chars = { + [true]={ + [true]={ + [true]={ + [true]=string.char(197), --WNES + [false]=string.char(193), --WNE + }, + [false]={ + [true]=string.char(180), --WNS + [false]=string.char(217), --WN + }, + }, + [false]={ + [true]={ + [true]=string.char(194), --WES + [false]=string.char(196), --WE + }, + [false]={ + [true]=string.char(191), --WS + [false]=string.char(26), --W + }, + }, + }, + [false]={ + [true]={ + [true]={ + [true]=string.char(195), --NES + [false]=string.char(192), --NE + }, + [false]={ + [true]=string.char(179), --NS + [false]=string.char(25), --N + }, + }, + [false]={ + [true]={ + [true]=string.char(218), --ES + [false]=string.char(27), --E + }, + [false]={ + [true]=string.char(24), --S + [false]=' ', + }, + }, + }, } local function print_color(s, color) dfhack.color(color) - dfhack.print(s) + dfhack.print(dfhack.df2console(s)) dfhack.color(COLOR_RESET) end @@ -68,7 +97,7 @@ function printTreeTile(bits) end chars = chars-2 - print_color(' '..(branch_dir[bits.branches_dir] or '?'), COLOR_GREY) + print_color(' '..(branch_chars[bits.branch_w][bits.branch_n][bits.branch_e][bits.branch_s] or '?'), COLOR_GREY) local dir = bits.parent_dir if dir > 0 then @@ -144,7 +173,9 @@ function printTree(t) end if not dfhack_flags.module then - local p = dfhack.maps.getPlantAtTile(copyall(df.global.cursor)) + local pos = guidm.getCursorPos() + if not pos then qerror('No cursor!') end + local p = dfhack.maps.getPlantAtTile(pos) if p and p.tree_info then printTree(p.tree_info) else diff --git a/devel/unit-path.lua b/devel/unit-path.lua index 459ff92159..6f5e34d58e 100644 --- a/devel/unit-path.lua +++ b/devel/unit-path.lua @@ -127,7 +127,7 @@ function UnitPathUI:onRenderBody(dc) dc:seek(2,3):pen(COLOR_BLUE):string(prof) if name and name.has_name then - dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.TranslateName(name)) + dc:seek(2,4):pen(COLOR_BLUE):string(dfhack.translation.translateName(name)) end local cursor = guidm.getCursorPos() diff --git a/diplomacy.lua b/diplomacy.lua index 7baee75ab6..512d0df4d0 100644 --- a/diplomacy.lua +++ b/diplomacy.lua @@ -7,7 +7,7 @@ local p_civ = df.historical_entity.find(df.global.plotinfo.civ_id) -- get list of civs: function get_civ_list() local civ_list = {} - for _, entity in pairs(p_civ.relations.diplomacy) do + for _, entity in ipairs(p_civ.relations.diplomacy.state) do local cur_civ_id = entity.group_id local cur_civ = df.historical_entity.find(cur_civ_id) if cur_civ.type == 0 then @@ -19,16 +19,16 @@ function get_civ_list() rel_str = "War" end matched = "No" - for _, entity2 in pairs(cur_civ.relations.diplomacy) do + for _, entity2 in ipairs(cur_civ.relations.diplomacy.state) do if entity2.group_id == p_civ_id and entity2.relation == entity.relation then matched = "Yes" end end table.insert(civ_list, { - cur_civ_id, - rel_str, - matched, - dfhack.TranslateName(cur_civ.name, true) + cur_civ_id, + rel_str, + matched, + dfhack.translation.translateName(cur_civ.name, true) }) end end @@ -51,12 +51,12 @@ end -- change relation: function change_relation(civ_id, relation) print("Changing relation with " .. civ_id .. " to " .. (relation == 0 and "Peace" or "War")) - for _, entity in pairs(p_civ.relations.diplomacy) do + for _, entity in ipairs(p_civ.relations.diplomacy.state) do local cur_civ_id = entity.group_id local cur_civ = df.historical_entity.find(cur_civ_id) if cur_civ.type == 0 and cur_civ_id == civ_id then entity.relation = relation - for _, entity2 in pairs(cur_civ.relations.diplomacy) do + for _, entity2 in pairs(cur_civ.relations.diplomacy.state) do if entity2.group_id == p_civ_id then entity2.relation = relation end diff --git a/do-job-now.lua b/do-job-now.lua index 4e83970c8f..3ef9585a9b 100644 --- a/do-job-now.lua +++ b/do-job-now.lua @@ -6,16 +6,6 @@ local function print_help() print(dfhack.script_help()) end -local function getUnitName(unit) - local language_name = dfhack.units.getVisibleName(unit) - if language_name.has_name then - return dfhack.df2console(dfhack.TranslateName( language_name )) - end - - -- animals - return dfhack.units.getProfessionName(unit) -end - local function doJobNow(job) local job_str = dfhack.job.getName(job) if not job.flags.do_now then @@ -30,7 +20,7 @@ local function doJobNow(job) end local unit = dfhack.job.getWorker(job) if unit then - print("... by " .. getUnitName(unit) ) + print("... by " .. dfhack.df2console(dfhack.units.getReadableName(unit))) end end @@ -64,7 +54,6 @@ end local function doUnitJobNow(unit) if dfhack.units.isCitizen(unit) then - --print('This will attempt to make a job of ' .. getUnitName(unit) .. ' a top priority') local t_job = unit.job if t_job then local job = t_job.current_job @@ -73,10 +62,8 @@ local function doUnitJobNow(unit) return end end - print("Couldn't find any job for " .. getUnitName(unit) ) + print("Couldn't find any job for " .. dfhack.df2console(dfhack.units.getReadableName(unit))) else - --print('This will attempt to make a job with ' .. getUnitName(unit) .. ' a top priority') - local needle = unit.id for _link, job in utils.listpairs(df.global.world.jobs.list) do if #job.general_refs > 0 then @@ -92,7 +79,7 @@ local function doUnitJobNow(unit) end end - print("Couldn't find any job involving " .. getUnitName(unit) ) + print("Couldn't find any job involving " .. dfhack.df2console(dfhack.units.getReadableName(unit))) end end diff --git a/docs/advtools.rst b/docs/advtools.rst index c62cdb1f83..113d6c969a 100644 --- a/docs/advtools.rst +++ b/docs/advtools.rst @@ -35,3 +35,16 @@ enemies will gain the ``slay`` and ``kill`` keywords. It will also add additional conversation options for asking whereabouts of your relationships -- in vanilla, you can only ask whereabouts of historical figures involved in rumors you personally witnessed or heard about. + +``advtools.fastcombat`` +~~~~~~~~~~~~~~~~~~~~~~~ + +When enabled, this overlay will allow you to skip most combat animations, +including the whooshes and projectiles travelling through the screen. It will +also let you skip the announcements window when the "More" button is active, +scrolling you to the very bottom with the first press, and skipping the window +entirely with the second press. This drastically speeds up combat while still +giving you the option not to skip the announcements. Skip keys are left mouse click, +the SELECT button, the movement keys and combat-related keys that don't bring up a +menu (such as bump attack). If clicking to skip past combat, it will only skip the +announcements if you're clicking outside the announcements panel. diff --git a/docs/agitation-rebalance.rst b/docs/agitation-rebalance.rst index 790af212b4..435d06225e 100644 --- a/docs/agitation-rebalance.rst +++ b/docs/agitation-rebalance.rst @@ -130,7 +130,7 @@ Irritation counters are saved with the cavern layer in the world region, which extends beyond the boundaries of your current fort. If you retire a fort and start another one nearby, the caverns will retain any irritation added by the first fort. This means that new forts may start with already-irritated caverns -and meet with immediate resistence. +and meet with immediate resistance. The settings ~~~~~~~~~~~~ @@ -182,7 +182,7 @@ When enabled, this mod makes the following changes: When agitated wildlife enters the map on the surface, the surface irritation counter is set to the value of ``Wilderness irritation minimum``, ensuring -that the *next* group of widlife that enters the map will *not* be agitated. +that the *next* group of wildlife that enters the map will *not* be agitated. This means that the incursions act more like a warning shot than an open floodgate. You will not be attacked again unless you continue your activities on the surface that raise the chance of a subsequent attack. @@ -211,7 +211,7 @@ attacks can still be controlled independently of this mod. Finally, if you have walled yourself off from the danger in the caverns, yet you continue to irritate nature down there, this mod will ensure that the number of -active cavern invaders, cumulative across all cavern levels, never exeeds the +active cavern invaders, cumulative across all cavern levels, never exceeds the value set for ``Cavern dweller maximum attackers``. This prevents excessive FPS loss during gameplay and keeps the number of creatures milling around outside your gates (or hidden in the shadows) to a reasonable number. diff --git a/docs/assign-preferences.rst b/docs/assign-preferences.rst index 1345b1d262..ee77a22757 100644 --- a/docs/assign-preferences.rst +++ b/docs/assign-preferences.rst @@ -2,7 +2,7 @@ assign-preferences ================== .. dfhack-tool:: - :summary: Adjust a unit's preferences. + :summary: View or adjust a unit's preferences. :tags: fort armok units You will need to know the token of the object you want your dwarf to like. @@ -71,6 +71,8 @@ brackets can be omitted. ``--unit `` The target unit ID. If not present, the currently selected unit will be the target. +``--show`` + Print the list of current likes/dislikes for the selected unit ``--likematerial [ [ ...] ]`` This is usually set to three tokens: a type of stone, a type of metal, and a type of gem. It can also be a type of wood, glass, leather, horn, pearl, diff --git a/docs/autocheese.rst b/docs/autocheese.rst new file mode 100644 index 0000000000..fb92874df5 --- /dev/null +++ b/docs/autocheese.rst @@ -0,0 +1,39 @@ +autocheese +========== + +.. dfhack-tool:: + :summary: Schedule cheese making jobs based on milk reserves. + :tags: fort auto + +Cheese making is difficult to automate using work orders. A single job +can consume anything from a bucket with a single unit of milk to a barrel +with 100 units of milk. This makes it hard to predict how much cheese will +actually be produced by an automated order. + +The script will scan your fort for barrels with a certain minimum amount of milk +(default: 50), create a cheese making job specifically for that barrel, and +assign this job to one of your idle dwarves (giving preference to skilled cheese +makers). + +When enabled using `gui/control-panel`, the script will run automatically, with +default options, twice a month. + +Usage +----- + +:: + + autocheese [] + +Examples +-------- + +``autocheese -m 100`` + Only create a job if there is a barrel that is filled to the maximum. + +Options +------- + +``-m``, ``--min-milk`` + Set the minimum number of milk items in a barrel for the barrel to be + considered for cheese making. diff --git a/docs/autotraining.rst b/docs/autotraining.rst new file mode 100644 index 0000000000..360f4d08e5 --- /dev/null +++ b/docs/autotraining.rst @@ -0,0 +1,41 @@ +autotraining +============ + +.. dfhack-tool:: + :summary: Assigns citizens to a military squad until they have fulfilled their need for Martial Training + :tags: fort auto bugfix units + +This script automatically assigns citizens with the need for military training to designated training squads. + +You need to have at least one squad that is set up for training. The squad should be set to "Constant Training" in the military screen. The squad doesn't need months off. The members leave the squad once they have satisfied their need for military training. + +The configured uniform determines the skills that are acquired by the training dwarves. Providing "No Uniform" is a perfectly valid choice and will make your militarily inclined civilians become wrestlers over time. However, you can also provide weapons and armor to pre-train civilians for future drafts. + +Once you have made squads for training use `gui/autotraining` to select the squads and ignored units, as well as the needs threshhold. + +Usage +----- + + ``autotraining []`` + +Examples +-------- + +``autotraining`` + Current status of script + +``enable autotraining`` + Checks to see if you have fullfilled the creation of a training squad. + If there is no squad marked for training use, a clickable notification will appear letting you know to set one up/ + Searches your fort for dwarves with a need for military training, and begins assigning them to a training squad. + Once they have fulfilled their need they will be removed from their squad to be replaced by the next dwarf in the list. + +``disable autotraining`` + Stops adding new units to the squad. + +Options +------- + ``-t`` + Use integer values. (Default 5000) + The negative need threshhold to trigger for each citizen + The greater the number the longer before a dwarf is added to the waiting list. diff --git a/docs/brainwash.rst b/docs/brainwash.rst index cbd952d22f..9c1eef4ed1 100644 --- a/docs/brainwash.rst +++ b/docs/brainwash.rst @@ -12,6 +12,7 @@ Usage ----- :: + brainwash Examples diff --git a/docs/caravan.rst b/docs/caravan.rst index 1e365cd159..17cb2ed071 100644 --- a/docs/caravan.rst +++ b/docs/caravan.rst @@ -74,7 +74,7 @@ selected, then the range of items will be selected. If any current merchants have ethical concerns, the list of goods that you can bring to the depot is automatically filtered (by default) to only show -ethically acceptible items. Be aware that, again, by default, if you have items +ethically acceptable items. Be aware that, again, by default, if you have items in bins, and there are unethical items mixed into the bins, then the bins will still be brought to the depot so you can trade the ethical items within those bins. Please use the DFHack enhanced trade screen for the actual barter to @@ -96,7 +96,7 @@ Trade screen **caravan.trade** -This overlay enables some convenent gestures and keyboard shortcuts for working +This overlay enables some convenient gestures and keyboard shortcuts for working with bins: - ``Shift-Click checkbox``: Select all items inside a bin without selecting the @@ -156,5 +156,9 @@ assignment GUI. The dialog allows you to sort by name, value, or where the item is currently assigned for display. -You can search by name, and you can filter by item quality and by whether the -item is forbidden. +You can search by name, and you can filter by: + +- item quality +- whether the item is forbidden +- whether the item is reachable from the display furniture +- whether the item is a written work (book or scroll) diff --git a/docs/colonies.rst b/docs/colonies.rst index e778692162..cebdc69db6 100644 --- a/docs/colonies.rst +++ b/docs/colonies.rst @@ -3,7 +3,7 @@ colonies .. dfhack-tool:: :summary: Manipulate vermin colonies and hives. - :tags: fort armok map + :tags: adventure fort armok map Usage ----- diff --git a/docs/combine.rst b/docs/combine.rst index 9a0ec5f530..7dea937672 100644 --- a/docs/combine.rst +++ b/docs/combine.rst @@ -38,7 +38,9 @@ Commands ``all`` Search all stockpiles. ``here`` - Search the currently selected stockpile. + Search the currently selected stockpile, or the stockpile that the + currently-seelected item is in, or the stockpile that the currently- + displayed item-list is in. Options ------- diff --git a/docs/confirm.rst b/docs/confirm.rst index ab7dff04d9..90408f0eb4 100644 --- a/docs/confirm.rst +++ b/docs/confirm.rst @@ -5,7 +5,7 @@ confirm :summary: Adds confirmation dialogs for destructive actions. :tags: fort interface -In the base game, it is frightenly easy to destroy hours of work with a single +In the base game, it is frighteningly easy to destroy hours of work with a single misclick. Now you can avoid the consequences of accidentally disbanding a squad (for example), or deleting a hauling route. diff --git a/docs/deathcause.rst b/docs/deathcause.rst index c9a2ae0a06..dac8a39ab9 100644 --- a/docs/deathcause.rst +++ b/docs/deathcause.rst @@ -14,3 +14,26 @@ Usage :: deathcause + +API +--- + +The ``deathcause`` script can be called programmatically by other scripts, either via the +commandline interface with ``dfhack.run_script()`` or via the API functions +defined in :source-scripts:`deathcause.lua`, available from the return value of +``reqscript('deathcause')``: + +* ``getDeathCause(unit or historical_figure)`` + +Returns a string with the unit or historical figure's cause of death. Note that using a historical +figure will sometimes provide more information than using a unit. + + + API usage example:: + + local dc = reqscript('deathcause') + + -- Note: this is an arguably bad example because this is the same as running deathcause + -- from the launcher, but this would theoretically still work. + local deathReason = dc.getDeathCauseFromUnit(dfhack.gui.getSelectedUnit()) + print(deathReason) diff --git a/docs/deteriorate.rst b/docs/deteriorate.rst index 583150afdb..f97cc3b089 100644 --- a/docs/deteriorate.rst +++ b/docs/deteriorate.rst @@ -3,11 +3,12 @@ deteriorate .. dfhack-tool:: :summary: Cause corpses, clothes, and/or food to rot away over time. - :tags: unavailable + :tags: fort fps gameplay items When enabled, this script will cause the specified item types to slowly rot -away. By default, items disappear after a few months, but you can choose to slow -this down or even make things rot away instantly! +away. As they deteriorate, they will acquire the normal ``x``, ``X``, and +``XX`` markings. By default, items disappear after a few months, but you can +choose to slow this down or even make things rot away instantly! Now all those slightly worn wool shoes that dwarves scatter all over the place or the toes, teeth, fingers, and limbs from the last undead siege will @@ -15,61 +16,63 @@ deteriorate at a greatly increased rate, and eventually just crumble into nothing. As warm and fuzzy as a dining room full of used socks makes your dwarves feel, your FPS does not like it! +By default (if you run ``enable deteriorate`` without changing any settings), +only non-entombed corpses and non-usable body parts will be affected. + +You can set other common options for new forts on the Gameplay / Autostart tab +of the DFHack control panel. + Usage ----- -``deteriorate start --types [--freq ] [--quiet] [--keep-usable]`` - Starts deteriorating the specified item types while you play. -``deteriorate stop --types `` - Stops deteriorating the specified item types. -``deteriorate status`` - Shows the item types that are currently being monitored and their - deterioration frequencies. -``deteriorate now --types [--quiet] [--keep-usable]`` - Causes all items (of the specified item types) to rot away within a few - ticks. - -You can have different types of items rotting away at different rates by running -``deteriorate start`` multiple times with different options. +:: + + enable deteriorate + deteriorate [status] + deteriorate (enable|disable) + deteriorate frequency + deteriorate now + +Where ```` is a comma-separated list of item types to affect. The +following categories are available: + +:clothes: All non-armor clothing pieces that are lying on the ground + that already have some damage. +:food: All food and plants. Milk is included, but seeds are left + untouched. +:corpses: All vermin remains and non-entombed corpses. This includes + former members of your fort, so if this category is enabled, + dwarves that have fallen down your well will rot away with + time. +:usable-parts: Non-entombed body parts that can be used for manufacturing, + crafting, or suturing (e.g. hair, wool, skulls, horns, etc.). +:unusable-parts: Non-entombed body parts that can't be used for manufacturing + or crafting. +:parts: Shorthand for the combination of the above two categories. +:all: Shorthand for all of the above categories. + +When setting a frequency, the number indicates the number of days between +adjustments of the deterioration counter. The default frequency of 1 day will +result in items disappearing after several months. The number does not need to +be a whole number. E.g. ``deteriorate frequency 0.5 all`` is perfectly valid. Examples -------- -Start deteriorating corpses and body parts, keeping usable parts such as hair, wool:: - - deteriorate start --types corpses --keep-usable - -Start deteriorating corpses and food and do it at twice the default rate:: - - deteriorate start --types corpses,food --freq 0.5,days - -Deteriorate corpses quickly but clothes slowly:: - - deteriorate start -tcorpses -f0.1 - deteriorate start -tclothes -f3,months - -Options -------- - -``-f``, ``--freq``, ``--frequency [,]`` - How often to increment the wear counters. ```` can be one of - ``days``, ``months``, or ``years`` and defaults to ``days`` if not - specified. The default frequency of 1 day will result in items disappearing - after several months. The number does not need to be a whole number. E.g. - ``--freq=0.5,days`` is perfectly valid. -``-k``, ``--keep-usable`` - Keep usable body parts such as hair, wool, hooves, bones, and skulls. -``-q``, ``--quiet`` - Silence non-error output. -``-t``, ``--types `` - The comma-separated list of item types to affect. This option is required - for ``start``, ``stop``, and ``now`` commands. - -Types ------ - -:clothes: All clothing pieces that have an armor rating of 0 and are lying on - the ground. -:corpses: All resident corpses and body parts. -:food: All food and plants, regardless of whether they are in barrels or - stockpiles. Seeds are left untouched. +``enable deteriorate`` + Start deteriorating items with current settings. +``deteriorate status`` + Show the current settings. +``deteriorate enable corpses,parts`` + Deteriorate corpses and body parts. This includes potentially useful parts + such as hair or wool, so use them quickly or lose them! +``deteriorate frequency 0.5 all`` + Deteriorate items of the enabled categories at twice the default rate. +``deteriorate frequency 14 clothes`` + Deteriorate clothes very slowly. +``deteriorate now corpses,unusable-parts`` + Deteriorate corpses and unusable body parts immediately. This is useful for + cleaning up after a siege or ambush (maybe after you have buried your own + casualties). +``deteriorate now food`` + Deteriorate all food items immediately. Instant famine! diff --git a/docs/devel/dump-tooltip-ids.rst b/docs/devel/dump-tooltip-ids.rst index beb6785221..5f066b94b0 100644 --- a/docs/devel/dump-tooltip-ids.rst +++ b/docs/devel/dump-tooltip-ids.rst @@ -19,7 +19,7 @@ as item 500, but we detect that caption at position 501 in ``main_interface.hove produced by the script will include the above element at position 501 instead of 500. Before running this script, the size of ``main_interface.hover_instruction`` must be aligned properly with the -loaded verison of DF so the array of strings can be read. +loaded version of DF so the array of strings can be read. Usage ----- diff --git a/docs/devel/export-map.rst b/docs/devel/export-map.rst new file mode 100644 index 0000000000..6beea7378b --- /dev/null +++ b/docs/devel/export-map.rst @@ -0,0 +1,159 @@ +devel/export-map +================ + +.. dfhack-tool:: + :summary: Export map tile data to a JSON file. + :tags: dev + +WARNING - This command will cause the game to freeze for minutes depending on +map size and options enabled. + +Exports the map tile data to a JSON file. The export does not include items, +characters, buildings, etc. Depending on options enabled, there will be a +``KEY`` table in the JSON with relevant [number ID] values that match a number +to their object type. + +Usage +----- + +:: + + devel/export-map + devel/export-map (include|exclude) + +Examples +-------- + +``devel/export-map`` + Export the map to JSON with ALL data included. + +``devel/export-map include -m -s -v`` + Export the map to JSON with only materials, shape, and variant + data included. + +``devel/export-map exclude --variant --hidden --light`` + Export the map to JSON with variant, hidden, and light data + excluded. + +Options +------- + +``-t``, ``--tiletype`` + The tile material classification. [number ID] (AIR/SOIL/STONE/RIVER/etc.) + +``-s``, ``--shape`` + The tile shape classification. [number ID] (EMPTY/FLOOR/WALL/STAIR/etc.) + +``-p``, ``--special`` + The tile surface special properties for smoothness. [number ID] + (NORMAL/SMOOTH/ROUGH/etc.) (used for engraving). + +``-v``, ``--variant`` + The specific variant of a tile that have visual variations. [number] (like + grass tiles in ASCII mode) + +``-h``, ``--hidden`` + Whether tile is revealed or unrevealed. [boolean] + +``-l``, ``--light`` + Whether tile is exposed to light. [boolean] + +``-b``, ``--subterranean`` + Whether the tile is considered underground. [boolean] (used to determine + crops that can be planted underground) + +``-o``, ``--outside`` + Whether the tile is considered “outside”. [boolean] (used by weather effects + to trigger on outside tiles) + +``-a``, ``--aquifer`` + Whether the tile is considered an aquifer. [number ID] (NONE/LIGHT/HEAVY) + +``-m``, ``--material`` + The material inside the tile. [number ID] (IRON/GRANITE/CLAY/ + TOPAZOLITE/BLACK_OPAL/etc.) (will return nil if the tile is empty) + +``-q``, ``--liquid`` + The type of liquid inside the tile. [number ID] (WATER/MAGMA) (will return + nil if the tile flow level is zero) + +``-f``, ``--flow`` + The level of liquids inside the tile. [number] (0-7) + +``-u``, ``--underworld`` + Whether the underworld z-levels will be included. + +``-e``, ``--evilness`` + Whether the evilness value will be included in MAP_SIZE table. This only + checks the value of the center map tile at ground level and will ignore + biomes at the edges of the map. + +JSON DATA +--------- + +``ARGUMENT_OPTION_ORDER`` + The order of the selected options for how data is arranged at a map + position. + + Example 1: + ``{"material": 1, "shape": 2, "hidden": 3}`` + + ``map[z][y][x] = {material_data, shape_data, hidden_data}`` + + Example 2: + ``{"variant": 3, "light": 1, "outside": 2, "aquifer": 4}`` + + ``map[z][y][x] = {light_data, outside_data, variant_data, aquifer_data}`` + +``MAP_SIZE`` + A table containing basic information about the map size for width, height, + depth. (x, y, z) The underworld_z_level is included if the underworld option + is enabled and the map depth (z) will be automatically adjusted. + +``KEYS`` + The tables containing the [number ID] values for different options. + + ``"SHAPE": {"-1": "NONE", "0": "EMPTY", "1": "FLOOR", "2": "BOULDERS", + "3": "PEBBLES", "4": "WALL", ..., "18": "ENDLESS_PIT"}`` + + ``"PLANT": {"0": "SINGLE-GRAIN_WHEAT", "1": "TWO-GRAIN_WHEAT", + "2": "SOFT_WHEAT", "3": "HARD_WHEAT", "4": "SPELT", "5": "BARLEY", ..., + "224": "PALM"}`` + + ``"AQUIFER": {"0": "NONE", "1": "LIGHT", "2": "HEAVY"}`` + + Note - when using the ``materials`` option, you need to pair the [number ID] + with the correct ``KEYS`` material table. Generally you use ``tiletype`` + option as a helper to sort tiles into different material types. I would + recommend consulting ``tile-material.lua`` to see how materials are sorted. + +``map`` + JSON map data is arranged as: ``map[z][y][x] = {tile_data}`` + + DF maps start at index [0]. (starts at map[0][0][0]) + + For most JSON libraries the index starts at [0] but some languages has the + index start at [1]. So to translate an actual DF map position from the JSON + map, you may need to add +1 to all x/y/z coordinates to get the correct tile + position. + + The ``ARGUMENT_OPTION_ORDER`` determines order of tile data. (see above) + I would recommend referencing the tile data like so: + + ``shape = json_data.map[z][x][y][json_data.ARGUMENT_OPTIONS_ORDER.shape]`` + + ``light = json_data.map[z][x][y][json_data.ARGUMENT_OPTIONS_ORDER.light]`` + + Note - some of the bottom z-levels for hell do not have the same + width/height as the default map. So if your map is 190x190, the last hell + z-levels are gonna be like 90x90. + + Instead of returning normal tile data like: + + ``map[0][90][90] = {tile_data}`` + + It will return nil instead: + + ``map[0][91][91] = nil`` + + So you need to account for this! diff --git a/docs/devel/input-monitor.rst b/docs/devel/input-monitor.rst index 8d271084c2..e20947cc27 100644 --- a/docs/devel/input-monitor.rst +++ b/docs/devel/input-monitor.rst @@ -11,7 +11,7 @@ and mouse device. The labels for Shift, Ctrl, and Alt light up when those modifier keys are being held down. -Similar lables for left, middle, and right mouse buttons light up when any of +Similar labels for left, middle, and right mouse buttons light up when any of those buttons are being held down. The input stream panel shows the keybindings that are being triggered. You can diff --git a/docs/devel/spawn-unit-helper.rst b/docs/devel/spawn-unit-helper.rst index c45dcf8e1e..fdf545d741 100644 --- a/docs/devel/spawn-unit-helper.rst +++ b/docs/devel/spawn-unit-helper.rst @@ -13,8 +13,8 @@ Usage 1. Enter the :kbd:`k` menu and change mode using ``rb_eval df.gametype = :DWARF_ARENA`` -2. Spawn creatures with the normal arena mode UI (:kbd:`c` ingame) -3. Revert to forgress mode using +2. Spawn creatures with the normal arena mode UI (:kbd:`c` in-game) +3. Revert to fortress mode using ``rb_eval df.gametype = #{df.gametype.inspect}`` 4. To convert spawned creatures to livestock, select each one with the :kbd:`v` menu, and enter ``rb_eval df.unit_find.civ_id = df.ui.civ_id`` diff --git a/docs/emigration.rst b/docs/emigration.rst index 58a3d623e7..074c8ebc96 100644 --- a/docs/emigration.rst +++ b/docs/emigration.rst @@ -16,9 +16,44 @@ even in the company of a visiting elven bard! The check is made monthly. A happy dwarf (i.e. with negative stress) will never emigrate. +The tool also supports ``nobles``, a manually-invoked command that makes nobles +emigrate to their rightful land of rule. No more freeloaders making inane demands! +Nobles assigned to squads or to fort administrator positions will not be emigrated. +Remove their assignments before retrying. Nobles holding elected positions +(i.e. mayors) may be emigrated, but will be marked with a ``*`` icon when listed. + Usage ----- :: enable emigration + emigration nobles [--list] + emigration nobles [] + +Examples +-------- + +``emigration nobles`` + Emigrate the selected noble if it does not rule your fortress. + If no unit is selected, list all nobles that do not rule your fortress. +``emigration nobles --list`` + List all nobles that do not rule your fortress. Nobles that cannot be emigrated + (see above) will have a ``!`` indicator while nobles holding elected positions + will have a ``*`` indicator. +``emigration nobles --all`` + Emigrate all nobles that do not rule your fortress. +``emigration nobles --unit 34534`` + Emigrate a noble matching the specified unit ID that does not rule your fortress. + +Options +------- + +These options are exclusive to the ``emigration nobles`` command. + +``-l``, ``--list`` + List all nobles that do not rule your fortress +``-a``, ``--all`` + Emigrate all nobles do not rule your fortress +``-u``, ``--unit `` + Emigrate noble matching specified unit ID that does not rule your fortress diff --git a/docs/empty-bin.rst b/docs/empty-bin.rst index 1d45eb81aa..e6e8bf4088 100644 --- a/docs/empty-bin.rst +++ b/docs/empty-bin.rst @@ -25,18 +25,20 @@ 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 the contents of selected containers or all containers in the selected stockpile or building, except containers with liquids or powders, 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 --force`` + Empty the contents of selected containers or all containers in the selected stockpile or building, including containers with liquids or powders, onto the floor. + +``empty-bin --recursive --force`` + Empty the contents of selected containers or all containers in the selected stockpile or building, including containers with liquids/powders and containers contents that are containers, such as a bags of seeds or filled waterskins, 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 --------------- +------- ``-r``, ``--recursive`` - Recursively empty containers. -``-l``, ``--liquids`` - Move contained liquids (DRINK and LIQUID_MISC) to the floor, making them unusable. + Recursively empty containers. + +``-f``, ``--force`` + Move contained liquid and powders (DRINK, LIQUID_MISC and POWDER_MISC) to the floor, making them unusable. diff --git a/docs/entomb.rst b/docs/entomb.rst new file mode 100644 index 0000000000..1352b990e4 --- /dev/null +++ b/docs/entomb.rst @@ -0,0 +1,66 @@ +entomb +====== + +.. dfhack-tool:: + :summary: Entomb any corpse into tomb zones. + :tags: fort items buildings + +Assign any unit regardless of citizenship, residency, pet status, +or affiliation to an unassigned tomb zone for burial. + +Usage +----- + +``entomb []`` + +Select a unit's corpse or body part, or specify the unit's ID +when executing this script to assign an unassigned tomb zone to +the unit, and flag the unit's corpse as well as any severed body +parts to become valid items for interment. + +Optionally, specify the tomb zone's ID to assign a specific tomb +zone to the unit. + +A non-citizen, non-resident, or non-pet unit that is still alive +may even be assigned a tomb zone if they have lost any body part +that can be placed inside a tomb, e.g. teeth or severed limbs. +New corpse items after a tomb has already been assigned will not +be properly interred until the script is executed again with the +unit ID specified, or the unit's corpse or any body part selected. + +If executed on slaughtered animals, all its butchering returns will +become valid burial items and no longer usable for cooking or crafting. + +Examples +-------- + +``entomb --unit `` + Assign an unassigned tomb zone to the unit with the specified ID. + +``entomb --tomb `` + Assign a tomb zone with the specified ID to the selected corpse + item's unit. + +``entomb -u -t -h`` + Assign a tomb zone with the specified ID to the unit with the + specified ID and task all its burial items for simultaneous + hauling into the coffin in the tomb zone. + +Options +------- + +``-u``, ``--unit `` + Specify the ID of the unit to be assigned to a tomb zone. + +``-t``, ``--tomb `` + Specify the ID of the zone into which a unit will be interred. + +``-a``, ``--add-item`` + Add a selected item, or multiple items at the keyboard cursor's + position to be interred together with a unit. A unit or tomb + zone ID must be specified when calling this option. + +``-n``, ``--haul-now`` + Task all of the unit's burial items for simultaneous hauling + into the coffin of its assigned tomb zone. This option can be + called even after a tomb zone is already assigned to the unit. diff --git a/docs/exportlegends.rst b/docs/exportlegends.rst index 2d7c4702d6..5d262a853c 100644 --- a/docs/exportlegends.rst +++ b/docs/exportlegends.rst @@ -11,7 +11,10 @@ about your world so that it can be browsed with external programs like get with vanilla export functionality, and many external tools depend on this extra information. -By default, ``exportlegends`` hooks into the standard vanilla ``Export XML`` button and runs in the background when you click it, allowing both the vanilla export and the extended data export to execute simultaneously. You can continue to browse legends mode via the vanilla UI while the export is running. +By default, ``exportlegends`` hooks into the standard vanilla ``Export XML`` +button and runs in the background when you click it, allowing both the vanilla +export and the extended data export to execute simultaneously. You can continue +to browse legends mode via the vanilla UI while the export is running. To use: @@ -35,7 +38,11 @@ Usage Overlay ------- -This script also provides an overlay that is managed by the `overlay` framework. +This script also provides several overlays that are managed by the `overlay` +framework. + +**exportlegends.export** + When the overlay is enabled, a toggle for exporting extended legends data will appear below the vanilla "Export XML" button. If the toggle is enabled when the "Export XML" button is clicked, then ``exportlegends`` will run alongside the @@ -45,6 +52,13 @@ While the extended data is being exported, a status line will appear in place of the toggle, reporting the current export target and the overall percent complete. -There is an additional overlay that masks out the "Done" button while the -extended export is running. This prevents the player from exiting legends mode -before the export is complete. +**exportlegends.mask** + +This overlay masks out the "Done" button while the extended export is running. +This prevents the player from accidentally exiting legends mode before the +export is complete. + +**exportlegends.histfigfilter** + +This overlay adds a filter widget to the Historical Figures legends page. +Clicking the widget allows you to filter the list of historical figures by race. diff --git a/docs/exterminate.rst b/docs/exterminate.rst index 664019aec2..9ce66b9791 100644 --- a/docs/exterminate.rst +++ b/docs/exterminate.rst @@ -43,7 +43,7 @@ Options ``-m``, ``--method `` Specifies the "method" of killing units. See below for details. ``-o``, ``--only-visible`` - Specifies the tool should only kill units visible to the player. + Specifies the tool should only kill units visible to the player on the map. ``-f``, ``--include-friendly`` Specifies the tool should also kill units friendly to the player. diff --git a/docs/extinguish.rst b/docs/extinguish.rst index ceaae431d2..67ba8c7797 100644 --- a/docs/extinguish.rst +++ b/docs/extinguish.rst @@ -3,7 +3,7 @@ extinguish .. dfhack-tool:: :summary: Put out fires. - :tags: fort armok buildings items map units + :tags: fort adventure armok buildings items map units With this tool, you can put out fires affecting map tiles, plants, units, items, and buildings. diff --git a/docs/firestarter.rst b/docs/firestarter.rst index 5e610d2e92..08e16e0626 100644 --- a/docs/firestarter.rst +++ b/docs/firestarter.rst @@ -3,7 +3,7 @@ firestarter .. dfhack-tool:: :summary: Lights things on fire. - :tags: fort armok items map units + :tags: fort adventure armok items map units Feel the need to burn something? Set items, locations, or even entire inventories on fire! Use while viewing an item, with the cursor over a map tile, diff --git a/docs/fix/archery-practice.rst b/docs/fix/archery-practice.rst new file mode 100644 index 0000000000..89cf6e0d81 --- /dev/null +++ b/docs/fix/archery-practice.rst @@ -0,0 +1,75 @@ +fix/archery-practice +==================== + +.. dfhack-tool:: + :summary: Fix quivers and training ammo items to allow archery practice to take place. + :tags: fort bugfix items + +Make quivers the last item in the inventory of every ranged unit currently +training and split stacks of ammo items assigned for training inside the +quivers to ensure each training unit can have more than one stack to allow +archery practice to take place. + +Note +---- + +The bug preventing units from initiating archery practice was fixed in +DF version 53.01. See below for more information about the issue and how +this tool works to mitigate it. Running this tool for any other archery +related issues will not yield useful results. + +Usage +----- + +``fix/archery-practice`` + Move quivers to the end of units' inventory list and split stacks of + training ammo items inside the quivers. + +``fix/archery-practice -q``, ``fix/archery-practice --quiet`` + Move quivers to the end of units' inventory list and split stacks of + training ammo items inside the quivers. Do not print to console. + +This tool will set quivers as the last item in the inventory of units in +squads that are currently set to train as well as split ammo items inside +their quivers into multiple stacks if a quiver contains only ammo item +with a stack size of 25 or larger assigned for training. The original +training ammo item with a reduced stack size will remain in the quiver +while new ammo items split from it will be placed on the ground where +the unit is located to be picked up later. + +Why are archers not practicing archery? +--------------------------------------- + +Due to a bug in the game, a unit that is scheduled to train will not be +able to practice archery at the archery range when their quiver contains +only one stack of ammo item assigned for training. This is sometimes +indicated on the unit by the 'Soldier (no item)' status. + +During versions 52.03 and 52.04, the issue was the complete reverse; +units would not practice when their quivers contained more than one +stack of ammo items assigned for training. + +Another issue in 52.05 is that units will not practice archery if their +quiver is not the last item in their inventory. + +This tool provides an interim remedy by moving quivers to the end of +every training unit's inventory list and splitting stacks of ammo items +inside their quivers to prompt the game to give them multiple stacks +of training ammo items. + +Limitations +----------- + +The game has a tendency to reshuffle the squad's ammo/unit pairings if +the newly split ammo items are force paired to the units holding the +original ammo item. As a compromise, the new items are placed on the +ground instead and added to the squad's training ammo assignment pool, +so that the game can distribute the items normally without causing the +pairing for ammo items already in quivers to be reshuffled. + +Although this tool would allow units to practice archery, the activity +will still be aborted once they have only one stack of training ammo +item remaining in their quivers. Practicing units will gain skill from +practice, but not the positive thought they would have gained from +having completed the activity. Once the game assigns more training +ammo items to them, they can continue practicing archery. diff --git a/docs/fix/codex-pages.rst b/docs/fix/codex-pages.rst new file mode 100644 index 0000000000..b5378622bf --- /dev/null +++ b/docs/fix/codex-pages.rst @@ -0,0 +1,44 @@ +fix/codex-pages +=============== + +.. dfhack-tool:: + :summary: Add pages to written content that have no pages. + :tags: fort bugfix items + +Add pages to codices, quires, and scrolls that do not have specified page counts. + +Usage +----- + +``fix/codex-pages [this|site|all]`` + +Pages will be added to written works that do not have properly specified page +counts. The number of pages to be added will be determined mainly by the type +of the written content, modified by its writing style and the strength of the +style, with weighted randomization. + +Options +------- + +``this`` + Add pages to the selected codex, quire, or scroll item. + +``site`` + Add pages to all written works that are currently in the player's fortress. + +``all`` + Add pages to all written works to have ever existed in the world. + +Note +---- + +This tool mitigates :bug:`9268` by generating new, randomized information for +written content that do not have the start and end pages specified in their +data structure. It cannot retrieve page count from written content that was +already missing the page count information. + +Also, unbound quires and scrolls do not display the number of pages they contain +in their item description even if the data structure of their written content +holds the information. However, once a quire that has written content with +appropriately specified page count information is bound into a codex, its page +count will be properly displayed in the resulting codex's item description. diff --git a/docs/fix/dead-units.rst b/docs/fix/dead-units.rst index 49156be5f7..4f194262ee 100644 --- a/docs/fix/dead-units.rst +++ b/docs/fix/dead-units.rst @@ -32,4 +32,4 @@ Options ``--burrow`` Scrub dead units from burrow membership lists. ``-q``, ``--quiet`` - Surpress console output (final status update is still printed if at least one item was affected). + Suppress console output (final status update is still printed if at least one item was affected). diff --git a/docs/fix/dry-buckets.rst b/docs/fix/dry-buckets.rst index 311e3e6c2e..65740e309d 100644 --- a/docs/fix/dry-buckets.rst +++ b/docs/fix/dry-buckets.rst @@ -12,9 +12,14 @@ water from them. This tool also fixes over-full buckets that are blocking well operations. +If enabled in `gui/control-panel` (it is enabled by default), this fix is +periodically run automaticaly, so you should not normally need to run it +manually. + Usage ----- -:: - - fix/dry-buckets +``fix/dry-buckets`` + Empty water buckets not currently used in jobs. +``fix/dry-buckets -q``, ``fix/dry-buckets --quiet`` + Empty water buckets not currently used in jobs. Don't print to the console. diff --git a/docs/fix/empty-wheelbarrows.rst b/docs/fix/empty-wheelbarrows.rst index eb6d155104..9fcbe15c70 100644 --- a/docs/fix/empty-wheelbarrows.rst +++ b/docs/fix/empty-wheelbarrows.rst @@ -26,12 +26,12 @@ Examples ``fix/empty-wheelbarrows --dry-run`` Lists all wheelbarrows that would be emptied and their contents without performing the action. ``fix/empty-wheelbarrows --quiet`` - Does the action while surpressing output to console. + Does the action while suppressing output to console. Options ------- ``-q``, ``--quiet`` - Surpress console output (final status update is still printed if at least one item was affected). + Suppress console output (final status update is still printed if at least one item was affected). ``-d``, ``--dry-run`` Dry run, don't commit changes. diff --git a/docs/fix/loyaltycascade.rst b/docs/fix/loyaltycascade.rst index 7a1eaad620..d4448f8c56 100644 --- a/docs/fix/loyaltycascade.rst +++ b/docs/fix/loyaltycascade.rst @@ -5,8 +5,10 @@ fix/loyaltycascade :summary: Halts loyalty cascades where dwarves are fighting dwarves. :tags: fort bugfix units -This tool aborts loyalty cascades by fixing units who consider their own -civilization to be the enemy. +This tool neutralizes loyalty cascades by fixing units who consider their own +civilization to be the enemy. It will also halt all fighting on the map that +involves your citizens, though "real" enemies will re-engage in combat after a +short delay. Usage ----- diff --git a/docs/fix/ownership.rst b/docs/fix/ownership.rst index 18ee5518a9..44c412db84 100644 --- a/docs/fix/ownership.rst +++ b/docs/fix/ownership.rst @@ -2,15 +2,20 @@ fix/ownership ============= .. dfhack-tool:: - :summary: Fixes instances of units claiming the same item or an item they don't own. - :tags: fort bugfix units + :summary: Fixes ownership links. + :tags: fort bugfix items units -Due to a bug a unit can believe they own an item when they actually do not. +Due to a bug, a unit can believe they own an item when they actually do not. +Additionally, a room can remember that it is owned by a unit, but the unit can +forget that they own the room. -When enabled in `gui/control-panel`, `fix/ownership` will run once a day to check citizens and residents and make sure they don't -mistakenly own an item they shouldn't. +Invalid item ownership links result in units getting stuck in a "Store owned +item" job. Missing room ownership links result in rooms becoming unused by the +nominal owner and unclaimable by any other unit. In particular, nobles and +administrators will not recognize that their room requirements are met. -This should help issues of units getting stuck in a "Store owned item" job. +When enabled in `gui/control-panel`, `fix/ownership` will run once a day to +validate and fix ownership links for items and rooms. Usage ----- @@ -18,3 +23,8 @@ Usage :: fix/ownership + +Links +----- + +Among other issues, this tool fixes :bug:`6578`. diff --git a/docs/fix/population-cap.rst b/docs/fix/population-cap.rst index c1ebb9ae04..a2eed10bdd 100644 --- a/docs/fix/population-cap.rst +++ b/docs/fix/population-cap.rst @@ -5,7 +5,7 @@ fix/population-cap :summary: Ensure the population cap is respected. :tags: fort bugfix -Run this if you continue to get migrant wave even after you have exceeded your +Run this if you continue to get migrant waves even after you have exceeded your set population cap. The reason this tool is needed is that the game only updates the records of your diff --git a/docs/fix/sleepers.rst b/docs/fix/sleepers.rst index 1b3a0bc6ae..bb316ad535 100644 --- a/docs/fix/sleepers.rst +++ b/docs/fix/sleepers.rst @@ -7,7 +7,7 @@ fix/sleepers Fixes :bug:`6798`. This bug is characterized by sleeping units who refuse to awaken in adventure mode regardless of talking to them, hitting them, or waiting -so long you die of thirst. If you come accross one or more bugged sleepers in +so long you die of thirst. If you come across one or more bugged sleepers in adventure mode, simply run the script and all nearby sleepers will be cured. Usage diff --git a/docs/fix/stuck-squad.rst b/docs/fix/stuck-squad.rst new file mode 100644 index 0000000000..21a2d5048d --- /dev/null +++ b/docs/fix/stuck-squad.rst @@ -0,0 +1,34 @@ +fix/stuck-squad +=============== + +.. dfhack-tool:: + :summary: Allow squads and messengers to rescue lost squads. + :tags: fort bugfix military + +Occasionally, squads that you send out on a mission get stuck on the world map. +They lose their ability to navigate and are unable to return to your fortress. +This tool allows a messenger that is returning from a holding or any other of +your squads that is returning from a mission to rescue the lost squad along the +way and bring them home. + +This fix is enabled by default in the DFHack +`control panel `, or you can run it as needed. However, it +is still up to you to send out a messenger or squad that can be tasked with the +rescue. If you have a holding that is linked to your fort, you can send out a +messenger -- you don't have to actually request any workers. Otherwise, you can +send a squad out on a mission with minimal risk, like "Demand one-time tribute". + +This tool is integrated with `gui/notify`, so you will get a notification in +the DFHack notification panel when a squad is stuck and there are no squads or +messengers currently out traveling that can rescue them. + +Note that there might be other reasons why your squad appears missing -- if it +got wiped out in combat and nobody survived to report back, for example -- but +this tool should allow you to recover from the cases that are actual bugs. + +Usage +----- + +:: + + fix/stuck-squad diff --git a/docs/fix/stuck-worship.rst b/docs/fix/stuck-worship.rst index b0dbfe07fe..04c4129364 100644 --- a/docs/fix/stuck-worship.rst +++ b/docs/fix/stuck-worship.rst @@ -28,4 +28,23 @@ Usage :: - fix/stuck-worship + fix/stuck-worship [] + +Reshuffle prayer needs of units in the fort. + +Examples +-------- + +``fix/stuck-worship`` + Rebalance prayer needs and print the total number of affected units. +``fix/stuck-worship -v`` + Same as above, but also print the names of all affected units. + +Options +------- + +``-v``, ``--verbose`` + Print the names of all affected units. +``-q``, ``--quiet`` + Don't print the number of affected units if it's zero. Intended for + automatic use. diff --git a/docs/fix/symbol-unstick.rst b/docs/fix/symbol-unstick.rst new file mode 100644 index 0000000000..82b3f77db7 --- /dev/null +++ b/docs/fix/symbol-unstick.rst @@ -0,0 +1,18 @@ +fix/symbol-unstick +================== + +.. dfhack-tool:: + :summary: Unstick noble symbols that cannot be re-designated. + :tags: fort bugfix items + +Remove symbol designation from artifacts that cannot be re-designated +after the noble's promotion to a higher position. + +Usage +----- + +``fix/symbol-unstick`` + +Select an artifact that was designated as a noble's symbol and run the +command to remove its designation as a symbol. The operation will only +be performed if the symbol is claimed by a vacated noble position. diff --git a/docs/fix/wildlife.rst b/docs/fix/wildlife.rst new file mode 100644 index 0000000000..648f1f4dde --- /dev/null +++ b/docs/fix/wildlife.rst @@ -0,0 +1,70 @@ +fix/wildlife +============ + +.. dfhack-tool:: + :summary: Moves stuck wildlife off the map so new waves can enter. + :tags: fort bugfix animals + +This tool identifies wildlife that is trying to leave the map but has gotten +stuck. The stuck creatures will be moved off the map so that new waves of +wildlife can enter. When removing stuck wildlife, their regional population +counters are correctly incremented, just as if they had successfully left the +map on their own. + +Dwarf Fortress manages wildlife in "waves". A small group of creatures of a +species that has population associated with a local region enters the map, +wanders around for a while (or aggressively attacks you if it is an agitated +group), and then leaves the map. Any members of the group that successfully +leave the map will get added back to the regional population. + +The trouble, though, is that the group sometimes gets stuck when attempting to +leave. A new wave cannot enter until the previous group has been destroyed or +has left the map, so wildlife activity effectively completely halts. This is DF +:bug:`12921`. + +You can run this script without parameters to immediately remove stuck +wildlife, or you can enable it in the `gui/control-panel` on the Bug Fixes tab +to monitor and manage wildlife in the background. When enabled from the control +panel, it will monitor for stuck wildlife and remove wildlife after it has been +stuck for 7 days. + +Unlike most bugfixes, this one is not enabled by default since some players +like to keep wildlife around for creative purposes (e.g. for intentionally +stalling wildlife waves or for controlled startling of friendly necromancers). +These players can selectively ignore the wildlife they want to keep captive +before they enable `fix/wildlife`. + +Usage +----- +:: + + fix/wildlife [] + fix/wildlife ignore [unit ID] + +Examples +-------- + +``fix/wildlife`` + Remove any wildlife that is currently trying to leave the map but has not + yet succeeded. +``fix/wildlife --week`` + Remove wildlife that has been stuck for at least a week. The command must + be run periodically with this option so it can discover newly stuck + wildlife and remove wildlife when timeouts expire. +``fix/wildlife ignore`` + Disconnect the selected unit from its wildlife population so it doesn't + block new wildlife from entering the map, but keep the unit on the map. + This unit will not be touched by future invocations of this tool. + +Options +------- + +``-n``, ``--dry-run`` + Print out which creatures are stuck but take no action. +``-w``, ``--week`` + Discover newly stuck units and associate the current in-game time with + them. Units that were discovered on a previous invocation where this + parameter was specified will be removed if that time was at least a week + ago. +``-q``, ``--quiet`` + Don't print the number of affected units if no units were affected. diff --git a/docs/force.rst b/docs/force.rst index 915863a75f..fb52a15803 100644 --- a/docs/force.rst +++ b/docs/force.rst @@ -15,6 +15,7 @@ Usage :: force [] + force Wildlife [all] The civ id is only used for ``Diplomat`` and ``Caravan`` events, and defaults to the player civilization if not specified. @@ -27,18 +28,36 @@ The default civ IDs that you are likely to be interested in are: But to see IDs for all civilizations in your current game, run this command:: - devel/query --table df.global.world.entities.all --search code --maxdepth 2 + :lua ids={} for _,en in ipairs(world.entities.all) do ids[en.entity_raw.code] = true end for id in pairs(ids) do print(id) end + +Examples +-------- + +``force Caravan`` + Spawn a caravan from your parent civilization. +``force Diplomat FOREST`` + Spawn an elven diplomat. +``force Megabeast`` + Call in a megabeast to attack your fort. The megabeast will enter the map + on the surface. +``force Wildlife`` + Allow additional wildlife to enter the map. Only affects areas that you can + see, so if you haven't opened the caverns, cavern wildlife won't be + affected. +``force Wildlife all`` + Allow additional wildlife to enter the map, even in areas you haven't + explored yet. Event types ----------- -The recognized event types are: +The supported event types are: - ``Caravan`` - ``Migrants`` - ``Diplomat`` - ``Megabeast`` -- ``WildlifeCurious`` -- ``WildlifeMischievous`` -- ``WildlifeFlier`` -- ``NightCreature`` +- ``Wildlife`` + +Most events happen on the next tick. The ``Wildlife`` event may take up to 100 +ticks to take effect. diff --git a/docs/gui/adv-finder.rst b/docs/gui/adv-finder.rst new file mode 100644 index 0000000000..a1d12128e7 --- /dev/null +++ b/docs/gui/adv-finder.rst @@ -0,0 +1,112 @@ +gui/adv-finder +============== + +.. dfhack-tool:: + :summary: Find and track historical figures and artifacts + :tags: adventure armok inspection items units + +A real-time tracker for historical figures and artifacts. Select a target by +clicking the settings icon [☼] and selecting an entry from the list in the +relevant tab. The list can be filtered by search string, as well as by +excluding dead figures (displayed in red text). Artifacts can exclude books, +and the "dead" option excludes artifacts held by dead figures (which are +generally unrecoverable). Dismissing the screen (e.g., right-click) will +close the target search window first. A second dismissal will close the +finder window, but target settings will be preserved until the world is +unloaded. + +Your coordinates will be kept up to date alongside your target's. There are +two types of coordinates, and they will be displayed as long as they can be +determined. + +========== ========== +Coord Type Meaning +========== ========== +Global Distance in map blocks from the world origin (northwest corner). + The adventurer usually moves by 3 blocks during fast travel, but + slows to 1 when the zoomed site map is displayed. Equivalent to + 16 local tiles. Always available except for targets with an + indeterminate location. +Local Tile coordinates, available outside of fast travel and sleeping. + Your target's local coordinates are displayed when nearby and + loaded. Local coordinates will remain consistent within a site, but + may jump around in the wilderness as areas of the world are loaded. +========== ========== + +For global coordinates, the Z component will only be displayed if it can be +specifically determined by the location type. This represents an underground +layer depth, so the surface is indicated by ``Z0`` and the first cavern layer +is ``Z-1``. + +A compass and relative coordinates will be displayed. The relative coordinate +display uses the most precise coordinate type shared between you and your +target. + +There are six types of location types displayed for targets: + +============= ========== +Location Type Meaning +============= ========== +Nearby The target is loaded into the map area and the local + coordinates will be displayed. If you don't see this when you're + in the correct area and outside fast travel, then the target + isn't loading for some reason and you'll never be able to find + them. +Site The target is located within a site. The text displays + "At " and the global coords will represent the center + of the site if the target doesn't track its own precise + coordinates (e.g., worldgen being vague). +Traveling The target is traveling around the world map like an army. +Wilderness The target is somewhere on the surface not in a site. +Underground The target is somewhere in the caverns not in a site. +None The target's location isn't defined in the game world. + Maybe they're a deity. Maybe they got dropped off in limbo + after their army disbanded. If they're dead, the location + wasn't recorded properly in history. The text displays "Missing" + if they're dead or can die of old age, else "Transcendent" + because nothing can touch them. +============= ========== + +Dead figures generally can't be encountered at all, and they take their items +with them if they weren't separated properly by worldgen. The coord given is +usually a death or abstract burial location, but the corpse isn't guaranteed to +exist. Generally, wilderness and underground locations only have coords if you +left something there in adventure mode. Anything lost there during worldgen or a +fort mode mission likely can't be located. Anything in a site is usually a safe +bet, but sometimes items won't load. (Fort missions can be used to acquire these +for later retrieval, however.) Traveling targets are always valid. + +Usage +----- + +:: + + gui/adv-finder [] + +Examples +-------- + +``gui/adv-finder`` + Open the finder window (unless already open). Target will be blank on first + use, but maintained on future invocations. +``gui/adv-finder --histfig 1234`` + Track the historical figure with ID #1234. Finder will be opened if not + already. +``gui/adv-finder -h -1 -a -1`` + Clear any target so it's just the adventurer. Finder will be opened if not + already. +``gui/adv-finder --debug`` + Display selected target IDs in the finder window title bar. Finder will be + opened if not already. This setting isn't saved, so it can be disabled by + invoking ``gui/adv-finder`` again without the option. + +Options +------- + +``-h``, ``--histfig `` + Set the target to the historical figure with the given ID. +``-a``, ``--artifact `` + Set the target to the artifact record with the given ID. (Not an item ID!) +``-d``, ``--debug`` + Display selected target IDs in the finder window title bar. Doesn't persist + between invocations. diff --git a/docs/gui/aquifer.rst b/docs/gui/aquifer.rst index 52d47541b8..a595e197d1 100644 --- a/docs/gui/aquifer.rst +++ b/docs/gui/aquifer.rst @@ -12,7 +12,7 @@ tiles or warm tiles). Note that "just damp" tiles will still be highlighted if they are otherwise already visible. You can draw boxes around areas of tiles to alter their aquifer properties, or -you can use the :kbd:`Ctrl`:kbd:`A`` shortcut to affect entire layers at a time. +you can use the :kbd:`Ctrl`:kbd:`A` shortcut to affect entire layers at a time. If you want to see where the aquifer tiles are so you can designate digging, please run `gui/reveal`. If you only want to see the aquifer tiles and not diff --git a/docs/gui/autotraining.rst b/docs/gui/autotraining.rst new file mode 100644 index 0000000000..a86b28adf9 --- /dev/null +++ b/docs/gui/autotraining.rst @@ -0,0 +1,15 @@ +gui/autotraining +================ + +.. dfhack-tool:: + :summary: GUI interface for ``autotraining`` + :tags: fort auto interface + +This is an in-game configuration interface for `autotraining`. You can pick squads for training, select ignored units, and set the needs threshold. + +Usage +----- + +:: + + gui/autotraining diff --git a/docs/gui/create-item.rst b/docs/gui/create-item.rst index ef501afa65..580dec5f1d 100644 --- a/docs/gui/create-item.rst +++ b/docs/gui/create-item.rst @@ -42,6 +42,10 @@ Options ``-f``, ``--unrestricted`` Don't restrict the material options to only those that are normally appropriate for the selected item type. +``-p``, ``--pos ,,`` + If specified, items will be spawned at the given coordinates instead of at + the creator unit's feet. ``here`` can be used in place of ``,,`` + to use the active keyboard cursor. ``--startup`` Instead of showing the item creation interface, start monitoring for a modded reaction with a code of ``DFHACK_WISH``. When a reaction with that diff --git a/docs/gui/design.rst b/docs/gui/design.rst index 29d46dfebb..1c182ad262 100644 --- a/docs/gui/design.rst +++ b/docs/gui/design.rst @@ -4,10 +4,11 @@ gui/design .. dfhack-tool:: :summary: Design designation utility with shapes. - :tags: fort design productivity map + :tags: fort design productivity interface map This tool provides a point and click interface to make designating shapes -and patterns easier. Supports both digging designations and placing constructions. +and patterns easier. Supports both digging designations and placing +constructions. Usage ----- @@ -16,10 +17,64 @@ Usage gui/design +Shapes +------ + +- Rectangle + - They can be hollow or filled using 'h'. + - When hollow line thickness can be increased/decreased using 'T'/'t'. + - They can be inverted using 'i'. +- Ellipse + - They can be hollow or filled using 'h'. + - When hollow line thickness can be increased/decreased using 'T'/'t'. + - They can be inverted using 'i'. +- Star + - The default has 5 points, use 'B'/'b' to increase/decrease points. + - They can be hollow or filled using 'h'. + - When hollow line thickness can be increased/decreased using 'T'/'t'. + - They can be inverted using 'i'. + - The next-point offset can be increased/decreased using 'N'/'n' which + particularly affects 7 point stars and above to make them spikier or + smoother, but can also be used to decrease to 1 to make symmetrical + polygons or increase to N which only paints the vertexes. + - The orientation can be changed by adding a main axis point using 'v' and + moving this to point in the desired direction. +- Rows + - Vertical rows can be toggled using 'v'. + - Horizontal rows can be toggled using 'h'. + - Spacing can be increased/decreased using 'T'/'t'. + - They can be inverted using 'i'. +- Diagonal + - Direction can be reversed using 'R'. + - Spacing can be increased/decreased using 'T'/'t'. + - They can be inverted using 'i'. +- Line + - Line thickness can be increased/decreased using 'T'/'t'. + - Can be curved by adding one or more control points using 'v'. +- FreeForm + - Can be toggled open multi-line sequence or closed polygon using 'y' + - Line thickness can be increased/decreased using 'T'/'t'. + Overlay ------- -This script provides an overlay that shows the selected dimensions when -designating something with vanilla tools, for example when painting a burrow or +This tool also provides two overlays that are managed by the `overlay` +framework. + +dimensions +~~~~~~~~~~ + +The ``gui/design.dimensions`` overlay shows the selected dimensions when +designating with vanilla tools, for example when painting a burrow or designating digging. The dimensions show up in a tooltip that follows the mouse cursor. + +When this overlay is enabled, the vanilla dimensions display will be hidden. +When this overlay is disabled, the vanilla dimensions display will be unhidden. + +rightclick +~~~~~~~~~~ + +The ``gui/design.rightclick`` overlay prevents the right mouse button and other +keys bound to "Leave screen" from exiting out of designation mode when drawing +a box with vanilla tools, instead making it cancel the designation first. diff --git a/docs/gui/embark-anywhere.rst b/docs/gui/embark-anywhere.rst index 4583b04cd7..db5c06fc34 100644 --- a/docs/gui/embark-anywhere.rst +++ b/docs/gui/embark-anywhere.rst @@ -11,8 +11,13 @@ embark in an inaccessible location on top of a mountain range? Go for it! Want to try a brief existence in the middle of the ocean? Nobody can stop you! Want to tempt fate by embarking *inside of* a necromancer tower? !!FUN!! +If you are using this tool to create a fort that will bridge two disconnected +areas of land, see `So you want to bridge a gap?`_ below for tips and caveats. + Any and all consequences of embarking in strange locations are up to you to -handle (possibly with other `armok ` tools). +handle (possibly with other `armok ` tools). In particular, +embarking in inaccessible locations will prevent migrants, caravans, and +visitors from arriving. Usage ----- @@ -21,9 +26,37 @@ Usage gui/embark-anywhere -The command will only work when you are on the screen where you can choose your -embark site. The DFHack logo is not displayed on that screen since its default -position conflicts with the vanilla embark size selection panel. Remember that -you can bring up DFHack's `context menu ` with -:kbd:`Ctrl`:kbd:`Shift`:kbd:`C` or the -`in-game command launcher ` directly with the backtick key (\`). +The command will only work when you are on the screen where you can choose the +embark site for your fort. + +So you want to bridge a gap? +---------------------------- + +A popular use case for this tool is to create a fort (or a series of forts) that +bridges two disconnected landmasses so sites on the two landmasses can reach +each other (that is, they can send raiding parties and/or engage in trade). + +However, the way this works is not entirely intuitive. + +A single large embark is not necessarily going to functionally connect the two +shores so that armies can cross the gap. You could still choose to use this +approach to build a continuous constructed bridge in fort mode for later use as +an *adventurer* in adventure mode, but it will not be usable by the other +characters/armies in the world. + +The DF world map is divided into blocks of 16x16 tiles. When you are choosing +where to embark and you move the mouse so that your embark area "shadow" moves +over a little bit -- that's one "tile". An embark area can span block +boundaries, and there is no indication on the map where those boundaries are. + +The way DF determines world pathability is to check if the ground is continuous +**or** if the enclosing 16x16 block contains the upper left tile of a fort +embark area. + +In order for a connection to be formed for armies, one fort upper left corner +must exist in each 16x16 block that contains part of the gap. + +Therefore, the simplest solution for making a "bridge" that armies can use (but +walking adventurers cannot) is to make a 1x1 fort every 16 tiles across the +water gap, starting on land on one shore and finishing on land on the opposite +shore. That will ensure that every 16x16 block in the gap is covered by a fort. diff --git a/docs/gui/family-affairs.rst b/docs/gui/family-affairs.rst index 7a1e0a4bfc..a6d0a039fd 100644 --- a/docs/gui/family-affairs.rst +++ b/docs/gui/family-affairs.rst @@ -2,28 +2,46 @@ gui/family-affairs ================== .. dfhack-tool:: - :summary: Inspect or meddle with romantic relationships. - :tags: unavailable + :summary: Manage romantic relationships and generate pregnancies. + :tags: adventure fort armok animals units -This tool provides a user-friendly interface to view romantic relationships, -with the ability to add, remove, or otherwise change them at your whim - -fantastic for depressed dwarves with a dead spouse (or matchmaking players...). +This tool provides an interface for inspecting (or meddling with) romantic +relationships and for producing pregnancies with specific mothers and fathers. +Perfect for matchmaking players! -The target/s must be alive, sane, and in fortress mode. +If a unit is selected when you run `gui/family-affairs`, they will be +pre-loaded as a romantic partner (or prospective parent). While the window is +up, you can click on units on the map and assign them roles in the +`gui/family-affairs` UI. + +You can click on unit names in the `gui/family-affairs` UI to zoom the map to +their location. + +Whereas you can choose any historical figure (of the same race) to serve as a +spouse or lover, a unit must be on the map to participate in a pregnancy. For +example, you cannot generate a pregnancy with a father that is not on-site, +even if they are the selected mother's spouse. + +Children and units that are insane cannot be selected for participation in a +pregnancy, and, due to game limitations, cross-species pregnancies are not +supported. Usage ----- -``gui/family-affairs [unitID]`` - Show GUI for the selected unit, or the unit with the specified unit ID. -``gui/family-affairs divorce [unitID]`` - Remove all spouse and lover information from the unit and their partner. -``gui/family-affairs [unitID] [unitID]`` - Divorce the two specified units and their partners, then arrange for the two - units to marry. +:: + + gui/family-affairs + gui/family-affairs --pregnancy + +Passing the ``--pregnancy`` option will start the `gui/family-affairs` UI on +the "Pregnancies" tab, and any (adult, sane) unit you have selected at the time +will be pre-selected as a parent. -Screenshot ----------- +Technical notes +--------------- -.. image:: /docs/images/family-affairs.png - :align: center +The reason for the requirement that a father must be on the map to contribute +to a pregnancy is that the genes used for the pregnancy are associated with the +physical unit. They are not stored with the "historical figure" that represents +the father when he is off-map. diff --git a/docs/gui/gm-editor.rst b/docs/gui/gm-editor.rst index 084b367ecf..e2a34a488b 100644 --- a/docs/gui/gm-editor.rst +++ b/docs/gui/gm-editor.rst @@ -16,9 +16,10 @@ Hold down :kbd:`Shift` and right click to exit, even if you are inspecting a substructure, no matter how deep. If you just want to browse without fear of accidentally changing anything, hit -:kbd:`Ctrl`:kbd:`D` to toggle read-only mode. If you want `gui/gm-editor` to -automatically pick up changes to game data in realtime, hit :kbd:`Alt`:kbd:`A` -to switch to auto update mode. +:kbd:`Ctrl`:kbd:`D` to toggle read-only mode. + +If you want `gui/gm-editor` to automatically pick up changes to game data in +realtime, hit :kbd:`Alt`:kbd:`A` to switch to auto update mode. .. warning:: @@ -28,18 +29,26 @@ to switch to auto update mode. your game before poking around in `gui/gm-editor`, especially if you are examining data while the game is unpaused. +.. warning:: + + Union data structures contain fields that occupy the same memory space. + When you see the ``[union structure]`` badge at the top of the screen, be + aware that only one of the fields in the structure is likely to make sense. + The "correct" field is usually indicated by some context in the parent + structure. If there are any pointers to substructures in the union, + inspecting the pointer when it is not the "correct" field may crash the + game. + Usage ----- -``gui/gm-editor [-f]`` - Open the editor on whatever is selected or viewed (e.g. unit/item/building/ - engraving/etc.) -``gui/gm-editor [-f] `` - Evaluate a lua expression and opens the editor on its results. Field - prefixes of ``df.global`` can be omitted. -``gui/gm-editor [-f] dialog`` - Show an in-game dialog to input the lua expression to evaluate. Works the - same as the version above. +:: + + gui/gm-editor [] [] + gui/gm-editor [] dialog + +When specifying a lua expression, field prefixes of ``df.global`` can be +omitted. Examples -------- @@ -48,15 +57,22 @@ Examples Opens the editor on the selected unit/item/job/workorder/stockpile etc. ``gui/gm-editor world.items.all`` Opens the editor on the items list. +``gui/gm-editor df.unit.find(12345)`` + Opens the editor on the unit with id 12345. +``gui/gm-editor reqscript('gui/quickfort').view`` + Opens the editor on a running instance of `gui/quickfort`. Useful for + debugging GUI tool state during development. ``gui/gm-editor --freeze scr`` Opens the editor on the current DF viewscreen data (bypassing any DFHack - layers) and prevents the underlying viewscreen from getting updates while - you have the editor open. + tools that may be open) and prevents the underlying viewscreen from getting + updates while you are inspecting the data. +``gui/gm-editor dialog`` + Show an in-game dialog to input the lua expression to evaluate. Options ------- -``-f``, ``--freeze`` +``-f``, ``--freeze``, ``--safe-mode`` Freeze the underlying viewscreen so that it does not receive any updates. This allows you to be sure that whatever you are inspecting or modifying will not be read or changed by the game until you are done with it. Note @@ -65,6 +81,12 @@ Options `gui/gm-editor` as usual when the game is frozen. The black background will disappear when the last `gui/gm-editor` window that was opened with the ``--freeze`` option is dismissed. +``--no-stringification`` + Don't attempt to provide helpful string representations of potentially + unsafe fields like language_name when browsing the data structures. Specify + this option when you know you will be browsing garbage data that could lead + to crashes if accessed for stringification. Note that fields in union data + structures are never stringified. Screenshot ---------- diff --git a/docs/gui/journal.rst b/docs/gui/journal.rst index ba3619d2e1..5240002c5c 100644 --- a/docs/gui/journal.rst +++ b/docs/gui/journal.rst @@ -3,18 +3,18 @@ gui/journal .. dfhack-tool:: :summary: Fort journal with a multi-line text editor. - :tags: fort interface + :tags: adventure fort interface The `gui/journal` interface makes it easy to take notes and document -important details for the fortresses. +important details for your fortresses and adventurers. With this multi-line text editor, -you can keep track of your fortress's background story, goals, notable events, -and both short-term and long-term plans. +you can keep track of your fortress's/adventurer's background story, goals, +notable events, and both short-term and long-term plans. This is particularly useful when you need to take a longer break from the game. Having detailed notes makes it much easier to resume your game after -a few weekds or months, without losing track of your progress and objectives. +a few weeks or months, without losing track of your progress and objectives. Supported Features ------------------ @@ -26,8 +26,8 @@ Supported Features - New Lines: Easily insert new lines using the :kbd:`Enter` key, supporting multiline text input. - Text Wrapping: Text automatically wraps within the editor, ensuring lines fit within the display without manual adjustments. - Backspace Support: Use the backspace key to delete characters to the left of the cursor. -- Delete Character: :kbd:`Ctrl` + :kbd:`D` deletes the character under the cursor. -- Line Navigation: :kbd:`Ctrl` + :kbd:`H` (like "Home") moves the cursor to the beginning of the current line, and :kbd:`Ctrl` + :kbd:`E` (like "End") moves it to the end. +- Delete Character: :kbd:`Delete` deletes the character under the cursor. +- Line Navigation: :kbd:`Home` moves the cursor to the beginning of the current line, and :kbd:`End` moves it to the end. - Delete Current Line: :kbd:`Ctrl` + :kbd:`U` deletes the entire current line where the cursor is located. - Delete Rest of Line: :kbd:`Ctrl` + :kbd:`K` deletes text from the cursor to the end of the line. - Delete Last Word: :kbd:`Ctrl` + :kbd:`W` removes the word immediately before the cursor. diff --git a/docs/gui/keybinds.rst b/docs/gui/keybinds.rst new file mode 100644 index 0000000000..f6031a27ce --- /dev/null +++ b/docs/gui/keybinds.rst @@ -0,0 +1,30 @@ +gui/keybinds +============ + +.. dfhack-tool:: + :summary: Manage your dfhack keybinds visually. + :tags: dfhack + +This tool allows you to create, edit, save, and delete custom keybinds that +run dfhack commands. + +Usage +----- + +:: + + gui/keybinds + +Focus Strings +------------- + +Keybinds may have a focus filter applied, enabling or disabling the keybind +based on the current open menu or gamemode. More information on the percise +format can be found in `keybinding`. + +Saved Keybinds +-------------- + +If saved, all currently active keybinds are stored in a dfhack init script at +``dfhack-config/init/dfhack.auto.keybinds.init``. The save does not remove any +keybinds set in other init scripts, nor created in-game. diff --git a/docs/gui/launcher.rst b/docs/gui/launcher.rst index fdfe2cb729..f5e39579bc 100644 --- a/docs/gui/launcher.rst +++ b/docs/gui/launcher.rst @@ -118,7 +118,7 @@ Default tag filters By default, commands intended for developers and modders are filtered out of the autocomplete list. This includes any tools tagged with ``unavailable``. If you have "mortal mode" enabled in the `gui/control-panel` preferences, any tools -with the ``armok`` tag are filterd out as well. +with the ``armok`` tag are filtered out as well. You can toggle this default filtering by hitting :kbd:`Ctrl`:kbd:`D` to switch into "Dev mode" at any time. You can also adjust your command filters in the diff --git a/docs/gui/manipulator.rst b/docs/gui/manipulator.rst new file mode 100644 index 0000000000..353d894c77 --- /dev/null +++ b/docs/gui/manipulator.rst @@ -0,0 +1,18 @@ +gui/manipulator +=============== + +.. dfhack-tool:: + :summary: Multi-function unit management interface. + :tags: unavailable + +This spreadsheet-like UI allows you to see all your units, their skills, +properties, assignments, etc. at a glance. You can search, sort, and filter to +see exactly the information that you're looking for. Moreover, you can assign +units to burrows, squads, work details, and workshops. + +Usage +----- + +:: + + gui/manipulator diff --git a/docs/gui/mass-remove.rst b/docs/gui/mass-remove.rst index 6a3becb8f4..3829d3b2c2 100644 --- a/docs/gui/mass-remove.rst +++ b/docs/gui/mass-remove.rst @@ -19,3 +19,16 @@ Usage :: gui/mass-remove + +Overlay +------- + +This tool also provides one overlay that is managed by the `overlay` +framework. + +gui/mass-remove.toolbar +~~~~~~~~~~~~~~~~~~~~~~~ + +The ``gui/mass-remove.toolbar`` overlay adds a button to the toolbar at the +bottom of the screen when eraser mode is active. It allows you to conveniently +open the ``gui/mass-remove`` interface. diff --git a/docs/gui/mod-manager.rst b/docs/gui/mod-manager.rst index 8972fece72..56b1538f29 100644 --- a/docs/gui/mod-manager.rst +++ b/docs/gui/mod-manager.rst @@ -2,11 +2,11 @@ gui/mod-manager =============== .. dfhack-tool:: - :summary: Save and restore lists of active mods. + :summary: Manange your active mods. :tags: dfhack interface -Adds an optional overlay to the mod list screen that allows you to save and -load mod list presets, as well as set a default mod list preset for new worlds. +When run with a world loaded, shows a list of active mods. You can copy the +list to the system clipboard for easy sharing or posting. Usage ----- @@ -14,3 +14,21 @@ Usage :: gui/mod-manager + +Overlay +------- + +This tool also provides two overlays that are managed by the `overlay` +framework. + +gui/mod-manager.button +~~~~~~~~~~~~~~~~~~~~~~ + +Adds a widget to the mod list screen that allows you to save and load mod list +presets. You can also set a default mod list preset for new worlds so you don't +have to manualy re-select the same mods every time you generate a world. + +gui/mod-manager.notification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Displays a message when a mod preset has been auto-applied. diff --git a/docs/gui/notes.rst b/docs/gui/notes.rst new file mode 100644 index 0000000000..d10b5b6d57 --- /dev/null +++ b/docs/gui/notes.rst @@ -0,0 +1,33 @@ +gui/notes +========= + +.. dfhack-tool:: + :summary: Interactive panel for managing map-specific notes. + :tags: fort interface map + +The `gui/notes` tool provides a comprehensive interface for interacting +with map-specific notes. It is designed to streamline the process +of note management in Dwarf Fortress, making it simpler +and more intuitive to keep track of important map-specific information. + +This tool builds upon the functionality of the `notes` tool, +enhancing it by providing a user-friendly panel for easier management +and visibility of notes across the Dwarf Fortress game map. + +Usage +----- + +:: + + gui/notes + +Launch the notes management panel. + +Supported Features +------------------ + +- Interactive Panel: Manage all aspects of notes through a centralized graphical interface. +- Search (:kbd:`Alt` + :kbd:`S`): Quickly find notes +- Direct Map Interaction (:kbd:`Alt` + :kbd:`N`): Add new notes by clicking on the map. +- Edit (:kbd:`Alt` + :kbd:`U`): Easily modify existing notes. +- Delete (:kbd:`Alt` + :kbd:`D`): Easily remove existing notes. diff --git a/docs/gui/pregnancy.rst b/docs/gui/pregnancy.rst deleted file mode 100644 index 9ed48161d4..0000000000 --- a/docs/gui/pregnancy.rst +++ /dev/null @@ -1,32 +0,0 @@ -gui/pregnancy -============= - -.. dfhack-tool:: - :summary: Generate pregnancies with pairings of your choice. - :tags: adventure fort armok animals units - -This tool provides an interface for producing pregnancies with specific mothers -and fathers. - -If a unit is selected when you run `gui/pregnancy`, they will be pre-selected -as a parent. If the unit has a spouse of a different gender, they will be -automatically selected as the other parent. You can click on other units on the -map and choose them as alternate mothers or fathers as desired. - -If a unit is selected as a mother or father, or is listed as a spouse, you can -zoom the map to their location by clicking on their name in the `gui/pregnancy` -UI. - -A unit must be on the map to participate in a pregnancy. For example, you -cannot designate a father that is not on-site, even if they are the selected -mother's spouse. - -Children cannot be selected as a parent, and, due to game limitations, -cross-species pregnancies are not supported. - -Usage ------ - -:: - - gui/pregnancy diff --git a/docs/gui/rename.rst b/docs/gui/rename.rst index 5688354bb7..56705b57c3 100644 --- a/docs/gui/rename.rst +++ b/docs/gui/rename.rst @@ -2,28 +2,109 @@ gui/rename ========== .. dfhack-tool:: - :summary: Give buildings and units new names, optionally with special chars. - :tags: unavailable + :summary: Edit in-game language-based names. + :tags: adventure fort productivity animals items units -Once you select a target on the game map, this tool allows you to rename it. It -is more powerful than the in-game rename functionality since it allows you to -use special characters (like diamond symbols), and it also allows you to rename -enemies and overwrite animal species strings. +Once you select a target (by clicking on something on the game map, by passing +a commandline parameter, or by using the selection dialog) this tool allows you +change the language of the name, generate a new random name, or replace +components of the name with your preferred words. -This tool supports renaming units, zones, stockpiles, workshops, furnaces, -traps, and siege engines. +`gui/rename` provides an interface similar to the in-game naming panel that you +can use to customize your fortress name at embark. That is, it allows you to +choose words from an in-game language to assemble a name, just like the default +names that the game generates. You will be able to assign units new given and +last names, or even rename the world itself. + +You can run `gui/rename` while on the "prepare carefully" embark screen to +rename your starting dwarves. + +Start typing to search for a word. You can search in English, in the selected +native language, or by the part of speech. Click on a word to assign it to the +selected name component slot. You can also clear or randomize each individual +name component slot. + +When giving a name to a unit that didn't previously have a name, you must +assign a word to the First Name slot. Otherwise, the game will not display the +name for the unit. + +When you change the language of a unit's name, the unit's existing first name +will be translated into the new language. Usage ----- +:: + + gui/rename [] + +The selection dialog will appear if no options are provided. You can +interactively choose one of the following to rename: + +- An artifact on the current map +- A location (e.g. tavern, hospital, guildhall, temple) on the current map +- The current fortress (or adventurer site) +- A squad belonging to the current fortress +- The civilization of the current fortress +- The government of the current fortress +- A unit on the current map +- The world + +Some of the options above will not show up if there is nothing of the given +type to rename (e.g. you won't get the option to rename a squad if there are no +squads, and you won't get an option to rename the government of the fortress if +you aren't in fortress mode). + +Examples +-------- + ``gui/rename`` - Renames the selected building, zone, or unit. -``gui/rename unit-profession`` - Set the unit profession or the animal species string. + Load the selected artifact, location, or unit for renaming. If nothing is + selected, you can select a target from a list. +``gui/rename -u 123 --no-target-selector`` + Load the unit with id ``123`` for renaming and remove the widget that + allows selecting a different target. +``gui/rename --location 2 --site 456`` + Load the location with "abstract building" ID ``2`` attached to the site + with id ``456`` for renaming. + +Options +------- + +Targets specified via these options do not need to be on the local map. + +``-a``, ``--artifact `` + Rename the artifact with the given item ID. +``-e``, ``--entity `` + Rename the historical entity (e.g. site government, world religion, etc) + with the given ID. +``-f``, ``--histfig `` + Rename the historical figure with the given ID. +``-l``, ``--location `` + Rename the location (e.g. tavern, hospital, guildhall, temple) with the + given ID. If this option is used, ``--site`` can be specified to indicate + locations attached to a specific site. If ``--site`` is not specified, the + location will be loaded from the current site. +``-q``, ``--squad `` + Rename the squad with the given ID. +``-s``, ``--site `` + Rename the site with the given ID. +``-u``, ``--unit `` + Rename the unit with the given ID. Renaming a unit also renames the + associated historical figure. +``-w``, ``--world`` + Rename the current world. +``--no-target-selector`` + Do not allow the player to switch naming targets. An option that sets the + initial target is required when using this option. -Screenshots ------------ +Overlays +-------- -.. image:: /docs/images/rename-bld.png +This tool supports the following overlays: -.. image:: /docs/images/rename-prof.png +``gui/rename.world`` + Adds a widget to the world generation screen for renaming the world. +``gui/rename.unit_embark`` + Transparently fixes DF :bug:`12060` on the "Prepare carefully" embark screen + where the player is unable to give units nicknames or custom professions. diff --git a/docs/gui/seedwatch.rst b/docs/gui/seedwatch.rst index 95ae306c20..6ee1723dee 100644 --- a/docs/gui/seedwatch.rst +++ b/docs/gui/seedwatch.rst @@ -5,11 +5,11 @@ gui/seedwatch :summary: Manages seed and plant cooking based on seed stock levels. :tags: fort auto plants -This is the configuration interface for the `seedwatch` plugin. You can configure -a target stock amount for each seed type. If the number of seeds of that type falls -below the 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. +This is the configuration interface for the `seedwatch` plugin. You can +configure a target stock amount for each seed type. If the number of seeds of +that type falls below the 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 ----- diff --git a/docs/gui/settings-manager.rst b/docs/gui/settings-manager.rst index 683ccb8917..fd5bec8a51 100644 --- a/docs/gui/settings-manager.rst +++ b/docs/gui/settings-manager.rst @@ -39,13 +39,16 @@ back. You can also toggle an option to automatically load the saved settings for new embarks. When a fort is loaded, you can also go to the Labor -> Standing Orders page. -You will see a new panel that allows you to save and restore your settings for -standing orders. You can also toggle whether the saved standing orders are -automatically restored when you embark on a new fort. This will toggle the -relevant command in `gui/control-panel` on the Automation -> Autostart page. +You will see tow new panels. The first allows you to configure how many barrels +to reserve for use by workshop jobs (instead of being claimed by stockpiles for +container storage). The second panel allows you to save and restore your +settings for standing orders. You can also toggle whether the saved standing +orders are automatically restored when you embark on a new fort. This will +toggle the relevant command in `gui/control-panel` on the Automation -> +Autostart page. There is a similar panel on the Labor -> Work Details page that allows for -saving and restoring of work detail definitons. Be aware that work detail +saving and restoring of work detail definitions. Be aware that work detail assignments to units cannot be saved, so you have to assign the work details to individual units after you restore the definitions. Another caveat is that DF doesn't evaluate work detail definitions until a change (any change) is made on diff --git a/docs/gui/siegemanager.rst b/docs/gui/siegemanager.rst new file mode 100644 index 0000000000..45ae1f3d69 --- /dev/null +++ b/docs/gui/siegemanager.rst @@ -0,0 +1,16 @@ +gui/siegemanager +================ + +.. dfhack-tool:: + :summary: Manage siege engines at a glance + :tags: buildings interface productivity + +This interface provides a list of siege engines, their ammo count, and current active +jobs whilst providing shortcuts to configure their firing/standy mode and view them in-world. + +Usage +----- + +:: + + gui/siegemanager diff --git a/docs/gui/sitemap.rst b/docs/gui/sitemap.rst index 74e1fde942..493f3ba5f1 100644 --- a/docs/gui/sitemap.rst +++ b/docs/gui/sitemap.rst @@ -7,9 +7,11 @@ gui/sitemap This simple UI gives you searchable lists of people, locations (temples, guildhalls, hospitals, taverns, and libraries), and artifacts in the local area. -Clicking on a list item will zoom the map to the target. If you are zooming to -a location and the location has multiple zones attached to it, clicking again -will zoom to each component zone in turn. +Clicking on a list item will zoom the map to the target. In fort mode, +shift-clicking will zoom to the unit or artifact and lock the camera to the +target with follow mode. If you are zooming to a location and the location has +multiple zones attached to it, clicking again will zoom to each component zone +in turn. Locations are attached to a site, so if you're in adventure mode, you must enter a site before searching for locations. For worldgen sites, many locations @@ -22,3 +24,16 @@ Usage :: gui/sitemap + +Overlay +------- + +This tool also provides one overlay that is managed by the `overlay` +framework. + +gui/sitemap.toolbar +~~~~~~~~~~~~~~~~~~~ + +The ``gui/sitemap.toolbar`` overlay adds a button to the toolbar at the bottom left corner of the +screen with the other menu buttons. It allows you to conveniently open the ``gui/sitemap`` +interface. diff --git a/docs/gui/spectate.rst b/docs/gui/spectate.rst new file mode 100644 index 0000000000..802d4d3dac --- /dev/null +++ b/docs/gui/spectate.rst @@ -0,0 +1,19 @@ +gui/spectate +============ + +.. dfhack-tool:: + :summary: Automated spectator mode. + :tags: fort inspection interface + +This is an in-game configuration interface for `spectate`, which automatically +sets the camera to follow interesting units. + +You can configure the overlay tooltip settings as well as the follow mode +settings. + +Usage +----- + +:: + + gui/spectate diff --git a/docs/gui/teleport.rst b/docs/gui/teleport.rst index 6222af4f8d..513f26bbb0 100644 --- a/docs/gui/teleport.rst +++ b/docs/gui/teleport.rst @@ -14,7 +14,7 @@ pre-selected for teleport. Note that you *can* select enemies that are lying in ambush and are not visible on the map yet, so you if you select an area and see a marker that indicates that a unit is selected, but you don't see the unit itself, this is likely what -it is. You can stil teleport these units while they are hidden. +it is. You can still teleport these units while they are hidden. Usage ----- diff --git a/docs/gui/tiletypes.rst b/docs/gui/tiletypes.rst index 578238c8e2..62b2fdbe15 100644 --- a/docs/gui/tiletypes.rst +++ b/docs/gui/tiletypes.rst @@ -79,7 +79,7 @@ smooth. Note that when creating walls, they will inherit the smoothness property of whatever was there before unless you specifically set the Special selector to ``NORMAL`` (for rough walls) or ``SMOOTH`` (for smooth walls). -Extended special properties are avaialable via the gear button. +Extended special properties are available via the gear button. Variant ~~~~~~~ diff --git a/docs/gui/unit-info-viewer.rst b/docs/gui/unit-info-viewer.rst index 6139bb266c..ed9d29a2b2 100644 --- a/docs/gui/unit-info-viewer.rst +++ b/docs/gui/unit-info-viewer.rst @@ -5,8 +5,8 @@ gui/unit-info-viewer :summary: Display detailed information about a unit. :tags: adventure fort interface inspection units -When run, it displays information about age, birth, maxage, shearing, milking, grazing, egg -laying, body size, and death for the selected unit. +When run, it displays information about age, birth, maxage, shearing, milking, +grazing, egg laying, body size, and death for the selected unit. You can click on different units while the tool window is open and the displayed information will refresh for the selected unit. @@ -21,8 +21,8 @@ Usage Overlays -------- -This tool adds progress bars, experience points and levels in the unit skill panels, -color-coded to highlight rust and the highest skill levels: +This tool adds progress bars, experience points and levels in the unit skill +panels, color-coded to highlight rust and the highest skill levels: - If a skill is rusty, then the level marker is colored light red - If a skill is at Legendary level or higher, it is colored light cyan diff --git a/docs/hide-tutorials.rst b/docs/hide-tutorials.rst index 417d5e278c..94b2a27a28 100644 --- a/docs/hide-tutorials.rst +++ b/docs/hide-tutorials.rst @@ -3,12 +3,12 @@ hide-tutorials .. dfhack-tool:: :summary: Hide new fort tutorial popups. - :tags: fort interface + :tags: adventure fort interface If you've played the game before and don't need to see the tutorial popups that show up on every new fort, ``hide-tutorials`` can hide them for you. You can enable this tool as a system service in the "Services" tab of -`gui/control-panel` so it takes effect for all new or loaded forts. +`gui/control-panel` so it takes effect for all forts and adventures. Specifically, this tool hides: @@ -16,6 +16,8 @@ Specifically, this tool hides: - The "Do you want to start a tutorial embark" popup - Popups displayed the first time you open the labor, burrows, justice, and other similar screens in a new fort +- Popups displayed when you perform certain actions for the first time in an + adventure Note that only unsolicited tutorial popups are hidden. If you directly request a tutorial page from the help, then it will still function normally. @@ -27,6 +29,10 @@ Usage enable hide-tutorials hide-tutorials + hide-tutorials reset -If you haven't enabled the tool, but you run the command while a fort is -loaded, all future popups for the loaded fort will be hidden. +If you haven't enabled the tool, but you run the command while a fort or +adventure is loaded, all future popups for the loaded game will be hidden. + +If you run the command with the ``reset`` option, all popups will be re-enabled +as if they had never been seen or dismissed. diff --git a/docs/husbandry.rst b/docs/husbandry.rst new file mode 100644 index 0000000000..27fa459cfe --- /dev/null +++ b/docs/husbandry.rst @@ -0,0 +1,61 @@ +husbandry +========= + +.. dfhack-tool:: + :summary: Automatically milk and shear animals. + :tags: fort auto + +This tool will automatically create milking and shearing orders at farmer's +workshops. Unlike the ``automilk`` and ``autoshear`` options from the control +panel, which create general work orders for milking and shearing jobs, +``husbandry`` will directly create jobs for individual animals at specific +workshops. This allows milking and shearing jobs to reliably be created at +nearby workshops (e.g. inside the pasture that an animal is assigned to), +minimizing the labor required to re-pasture animals after milking or shearing, +in particular in the case of multiple pastures that are far apart. + + +Usage +----- + +:: + + enable husbandry + husbandry [status] + husbandry now + husbandry [set|unset] [shearing|milking|roaming|pasture]+ + +Flags can be set or unset using the command ``husbandry set`` or ``husbandry +unset``. The ``shearing`` and ``milking`` flags (both enabled by default) +control whether shearing or milking jobs are created at all. + +Further, ``husbandry`` distinguishes between animals that are assigned to +pastures and those that are "roaming". + +If an animal is pastured and the pasture contains at least one workshop with the +appropriate labour (i.e. milking or shearing) enabled, jobs will be created +exclusively at those workshops. If the pasture does not contain a workshop with +the appropriate labor enabled the behavior depends on the ``pasture`` flag +(disabled by default): if set, no jobs will be created at workshops outside of +pastures, otherwise jobs may be created at the closest workshop in your fort. + +For animals that are roaming, jobs will only be created if the ``roaming`` flag +is set, which is the default. In this case, jobs are created at the closest +workshop with the appropriate labours enabled. + +Examples +-------- + +``enable husbandry`` + Start generating milking and shearing orders for animals. + +``husbandry now`` + Run a single cycle, detecting animals that can be milked/sheared an creating + jobs. Does not require the tool to be enabled. + +``husbandry unset roaming`` + Disable the creation of jobs for roaming animals. + +``husbandry set milking shearing pasture`` + Create milking and shearing jobs for pastured animals, but only at workshops + inside their pastures. diff --git a/docs/idle-crafting.rst b/docs/idle-crafting.rst index 39086443a9..6eeb571e7f 100644 --- a/docs/idle-crafting.rst +++ b/docs/idle-crafting.rst @@ -26,6 +26,12 @@ Usage given unit. Units meeting higher thresholds will be prioritized. Defaults to ``500,1000,10000``. +``idle-crafting happy [yes|no]`` + If set to ``no``, "happy" and "ecstatic" dwarves not suffering from + long-term stress will only satisfy their crafting needs when meeting the + highest configured threshold (eg. ``10000`` at default settings). Defaults + to ``no``. + ``disable idle-crafting`` Disallow idle crafting at all workshops. You can re-enable idle crafting at individual Craftsdwarf's workshops. @@ -34,7 +40,10 @@ Examples -------- ``idle-crafting thresholds 500,1000,10000`` - Reset thresholds to defaults. + Reset thresholds to defaults. + +``idle-crafting happy yes`` + Treat happy and ecstatic dwarves the same as everyone else. Overlay ------- @@ -45,9 +54,16 @@ needs to craft objects. Workshops that have a master assigned cannot be used in this way. When a workshop is designated for idle crafting, this tool will create crafting -jobs and assign them to idle dwarves who have a need for crafting -objects. Currently, bone carving and stonecrafting are supported, with -stonecrafting being the default option. This script respects the setting for -permitted general work orders from the "Workers" tab. Thus, to designate a +jobs and assign them to idle dwarves who have a need for crafting objects. This +script respects the setting for permitted general work order labors from the "Workers" +tab. + +For workshops without input stockpile links, bone carving and stonecrafting are +supported, with stonecrafting being the default option. Thus, to designate a workshop for bone carving, disable the stonecrafting labor while keeping the bone carving labor enabled. + +For workshops with input stockpile links, the creation of totems, shell crafts, +and horn crafts are supported as well. In this case, the choice of job is made +randomly based on the resources available in the input stockpiles (respecting +the permitted labors from the workshop profile). diff --git a/docs/immortal-cravings.rst b/docs/immortal-cravings.rst new file mode 100644 index 0000000000..cd8b09e6b3 --- /dev/null +++ b/docs/immortal-cravings.rst @@ -0,0 +1,19 @@ +immortal-cravings +================= + +.. dfhack-tool:: + :summary: Allow immortals to satisfy their cravings for food and drink. + :tags: fort gameplay + +When enabled, this script watches your fort for units that have no physiological +need to eat or drink but are still alcohol dependent or have personality needs +that can only be satisfied by eating or drinking (e.g. necromancers or goblins). +This enables those units to help themselves to a drink or a meal when they crave +one and are not otherwise occupied. + +Usage +----- + +:: + + enable immortal-cravings diff --git a/docs/instruments.rst b/docs/instruments.rst index 815eb81b82..5165433b1f 100644 --- a/docs/instruments.rst +++ b/docs/instruments.rst @@ -5,7 +5,8 @@ instruments :summary: Show how to craft instruments or create work orders for them. :tags: fort inspection workorders -This tool is used to query information about instruments or to create work orders for them. +This tool is used to query information about instruments or to create work +orders to produce them. The ``list`` subcommand provides information on how to craft the instruments used by the player civilization. For single-piece instruments, it shows the @@ -15,7 +16,7 @@ necessary pieces. It also shows whether the instrument is handheld or placed as a building. The ``order`` subcommand is used to create work orders for an instrument and -all of it's parts. The final assemble instrument -order waits for the part +all of it's parts. The final assemble instrument order waits for the part orders to complete before starting. Usage @@ -38,10 +39,9 @@ Examples If the instrument named ``givel`` in your world has four components, this will create a total of 5 work orders: one for assembling 10 givels, and an order of 10 for each of the givel's parts. Instruments are randomly - generated, so your givel components may vary. - + generated, so your instrument names and components will vary. ``instruments order ilul`` - Creates work orders to assemble one ïlul. Spelling doesn't need to include + Creates work orders to assemble one ïlul. Spelling does not need to include the special ï character. Options diff --git a/docs/item.rst b/docs/item.rst index 465382eebe..403dadcafe 100644 --- a/docs/item.rst +++ b/docs/item.rst @@ -49,7 +49,8 @@ Examples flood-fill to create a burrow covering an entire cavern layer). ``item melt -t weapon -m steel --max-quality 3`` - Designate all steel weapons whose quality is at most superior for melting. + Designate all steel weapons whose core quality is at most superior for + melting. ``item hide -t boulder --scattered`` Hide all scattered boulders, i.e. those that are not in stockpiles. @@ -121,6 +122,11 @@ Options Only include items whose quality level is at most ``integer``. Useful values are 0 (ordinary) to 5 (masterwork). +``--total-quality`` + Only applies to ``--min-quality`` and ``--max-quality`` options. Filter items + according to their total quality (to include improvements) of instead of + their core quality. + ``--stockpiled`` Only include items that are in stockpiles. Does not include empty bins, barrels, and wheelbarrows assigned as storage and transport for stockpiles. @@ -201,8 +207,12 @@ the filter is described. see above). * ``condition_quality(tab, lower, upper, negate)`` - Selects items with quality between ``lower`` and ``upper`` (Range 0-5, see - above). + Selects items with core quality between ``lower`` and ``upper`` (Range 0-5, + see above). + +* ``condition_overall_quality(tab, lower, upper, negate)`` + Selects items with total quality between ``lower`` and ``upper`` (Range 0-5, + see above). * ``condition_stockpiled(tab, negate)`` Corresponds to ``--stockpiled``. diff --git a/docs/justice.rst b/docs/justice.rst new file mode 100644 index 0000000000..99ca893873 --- /dev/null +++ b/docs/justice.rst @@ -0,0 +1,35 @@ +justice +======= + +.. dfhack-tool:: + :summary: Mess with the justice system. + :tags: fort armok units + +This tool allows control over aspects of the justice system, such as the +ability to pardon criminals. + +Usage +----- + +:: + + justice [list] + justice pardon [--unit ] + +Pardon the selected unit or the one specified by unit id (if provided). +Currently only applies to prison time and doesn't cancel beatings or +hammerings. + +Examples +-------- + +``justice`` + List the convicts currently serving sentences. +``justice pardon`` + Commutes the sentence of the currently selected convict. + +Options +------- + +``-u``, ``--unit `` + Specifies a specific unit instead of using a selected unit. diff --git a/docs/launch.rst b/docs/launch.rst index 6b884fcfb7..9f3648b88f 100644 --- a/docs/launch.rst +++ b/docs/launch.rst @@ -3,7 +3,7 @@ launch .. dfhack-tool:: :summary: Thrash your enemies with a flying suplex. - :tags: unavailable + :tags: adventure armok units Attack another unit and then run this command to grab them and fly in a glorious parabolic arc to where you have placed the cursor. You'll land safely and your diff --git a/docs/list-waves.rst b/docs/list-waves.rst index 52eb022d8e..c86e63a391 100644 --- a/docs/list-waves.rst +++ b/docs/list-waves.rst @@ -20,7 +20,7 @@ Usage list-waves [ ...] [] -You can show only information about specific waves by specifing the wave +You can show only information about specific waves by specifying the wave numbers on the commandline. Otherwise, all waves are shown. The first migration wave that normally arrives in a fort's second season is wave number 1. The founding dwarves arrive in wave 0. diff --git a/docs/machine-toggle.rst b/docs/machine-toggle.rst new file mode 100644 index 0000000000..020c2bb4c7 --- /dev/null +++ b/docs/machine-toggle.rst @@ -0,0 +1,21 @@ +machine-toggle +============== + +.. dfhack-tool:: + :summary: Overlay to modify pressure plates and gear assemblies after construction. + :tags: fort armok buildings interface + +This script provides 2 overlays that are managed by the `overlay` framework. +The script does nothing when executed. +Track stops and rollers are handled by `trackstop`. + +The ``pressureplate`` overlay allows the player to change the trigger settings +of a selected pressure plate after it has been constructed. Manual value entry +of ranges for minecart and creature triggers is provided, allowing greater +precision than the game interface normally permits. Incrementing or decrementing +values always restricts them to the usual intervals. + +The ``gearassembly`` overlay allows the player to toggle the state of a selected +gear assembly without linking it to a lever first. This is useful for dwarfputing +and other applications where it may be desirable to default to the disengaged +state until triggered. diff --git a/docs/makeown.rst b/docs/makeown.rst index 7e2a53ba8a..136e8f5100 100644 --- a/docs/makeown.rst +++ b/docs/makeown.rst @@ -6,7 +6,8 @@ makeown :tags: fort armok units Select a unit in the UI and run this tool to converts that unit to be a fortress -citizen (if sentient). It also removes their foreign affiliation, if any. +citizen (if sentient). It also removes their foreign affiliation, if any, and +removes the unit from any current conflict they are engaged in. This tool also fixes :bug:`10921`, where you request workers from your holdings, but they come with the "Merchant" profession and are unable to diff --git a/docs/modtools/create-item.rst b/docs/modtools/create-item.rst index fe96abdd47..a7fbd6ef65 100644 --- a/docs/modtools/create-item.rst +++ b/docs/modtools/create-item.rst @@ -48,4 +48,5 @@ Options caste associated with it. ``-p``, ``--pos ,,`` If specified, items will be spawned at the given coordinates instead of at - the creator unit's feet. + the creator unit's feet. ``here`` can be used in place of ``,,`` + to use the active keyboard cursor. diff --git a/docs/modtools/force.rst b/docs/modtools/force.rst deleted file mode 100644 index 9f71183fa1..0000000000 --- a/docs/modtools/force.rst +++ /dev/null @@ -1,32 +0,0 @@ -modtools/force -============== - -.. dfhack-tool:: - :summary: Trigger game events. - :tags: dev - -This tool triggers events like megabeasts, caravans, and migrants. - -Usage ------ - -:: - - -eventType event - specify the type of the event to trigger - examples: - Megabeast - Migrants - Caravan - Diplomat - WildlifeCurious - WildlifeMischievous - WildlifeFlier - NightCreature - -civ entity - specify the civ of the event, if applicable - examples: - player - MOUNTAIN - EVIL - 28 diff --git a/docs/modtools/moddable-gods.rst b/docs/modtools/moddable-gods.rst index 1a575c8325..22b890e6c5 100644 --- a/docs/modtools/moddable-gods.rst +++ b/docs/modtools/moddable-gods.rst @@ -3,21 +3,47 @@ modtools/moddable-gods .. dfhack-tool:: :summary: Create deities. - :tags: unavailable - -This is a standardized version of Putnam's moddableGods script. It allows you -to create gods on the command-line. - -Arguments:: - - -name godName - sets the name of the god to godName - if there's already a god of that name, the script halts - -spheres [ sphereList ] - define a space-separated list of spheres of influence of the god - -gender male|female|neuter - sets the gender of the god - -depictedAs str - often depicted as a str - -verbose - if specified, prints details about the created god + :tags: dev + +This script allows you to create new gods in an existing world. + +Usage +----- + +:: + + moddable-gods --name --spheres [] + +Examples +-------- + +``modtools/moddable-gods --name "Slarty Bog" --spheres FATE,WEATHER`` + Create a new god named "Slarty Bog" with spheres of influence of FATE and + WEATHER. The god will have a random gender and will be depicted as a dwarf. + +``modtools/moddable-gods -n Og -s SPEECH,SALT,SACRIFICE -g neuter -d emu`` + Create a new god named "Og" with spheres of influence of SPEECH, SALT, and + SACRIFICE. The god will be genderless and will be depicted as an emu. + +Options +------- + +``-n``, ``--name `` + The name of the god to create. This is a required argument. The name must + be unique in the world. If the name is already taken, the script will exit + without action. +``-s``, ``--spheres `` + A comma-separated list of spheres of influence for the god. This is a + required argument. To see the available spheres, run this command:: + + lua @df.sphere_type + +``-g``, ``--gender (male|female|neuter)`` + The gender of the god. If not specified, a random gender will be chosen. +``-d``, ``--depicted-as `` + When the deity is referenced in-game, it will be described as "often + depicted as a ". The string must match the token ID or descriptive + name of a race that exists in the world. You can also specify a numeric + race ID. If not specified, it defaults to "dwarf". +``-q``, ``--quiet`` + If specified, suppresses all non-error output. diff --git a/docs/modtools/pref-edit.rst b/docs/modtools/pref-edit.rst index c305c97a0a..cc154e117c 100644 --- a/docs/modtools/pref-edit.rst +++ b/docs/modtools/pref-edit.rst @@ -34,7 +34,7 @@ Valid filters: Include one of these to describe what the id argument represents. - ``-type ``: This describes the type of the preference. Can be entered either using the numerical ID or text id. - Run ``lua @df.unit_preference.T_type`` for a full list of valid values. + Run ``lua @df.unitpref_type`` for a full list of valid values. - ``-subtype ``: The value for an item's subtype - ``-material ``: diff --git a/docs/modtools/set-need.rst b/docs/modtools/set-need.rst index e2199553c3..da794939bc 100644 --- a/docs/modtools/set-need.rst +++ b/docs/modtools/set-need.rst @@ -28,9 +28,9 @@ Valid need targets: :need : ID of the need to target. For example 0 or DrinkAlcohol. - If the need is PrayOrMedidate, a -deity argument is also required. + If the need is PrayOrMeditate, a -deity argument is also required. :deity : - Required when using PrayOrMedidate needs. This value should be the historical figure ID of the deity in question. + Required when using PrayOrMeditate needs. This value should be the historical figure ID of the deity in question. :all: All of the target's needs will be affected. diff --git a/docs/modtools/syndrome-trigger.rst b/docs/modtools/syndrome-trigger.rst index 7e915fda1a..dccf02e610 100644 --- a/docs/modtools/syndrome-trigger.rst +++ b/docs/modtools/syndrome-trigger.rst @@ -13,9 +13,9 @@ Usage :: - modutils/syndrome-trigger --clear - modutils/syndrome-trigger --syndrome --command [ ] - modutils/syndrome-trigger --synclass --command [ ] + modtools/syndrome-trigger --clear + modtools/syndrome-trigger --syndrome --command [ ] + modtools/syndrome-trigger --synclass --command [ ] Options ------- @@ -41,4 +41,4 @@ Examples :: - modutils/syndrome-trigger --synclass VAMPCURSE --command [ modtools/spawn-flow -flowType Dragonfire -location [ \\LOCATION ] ] + modtools/syndrome-trigger --synclass VAMPCURSE --command [ modtools/spawn-flow -flowType Dragonfire -location [ \\LOCATION ] ] diff --git a/docs/necronomicon.rst b/docs/necronomicon.rst index 5028304ee3..274501327d 100644 --- a/docs/necronomicon.rst +++ b/docs/necronomicon.rst @@ -5,10 +5,10 @@ necronomicon :summary: Find books that contain the secrets of life and death. :tags: fort inspection items -Lists all books in the fortress that contain the secrets to life and death. -To find the books in fortress mode, go to the Written content submenu in -Objects (O). Slabs are not shown by default since dwarves cannot read secrets -from a slab in fort mode. +Lists all books in the fortress (or world) that contain the secrets to life and +death. To zoom to the books in fortress mode, go to the ``Artifacts`` tab in +`gui/sitemap` and click on their names. Slabs are not listed by default since +dwarves cannot read secrets from a slab in fort mode. Usage ----- @@ -23,3 +23,7 @@ Options ``-s``, ``--include-slabs`` Also list slabs that contain the secrets of life and death. Note that dwarves cannot read the secrets from a slab in fort mode. + +``-w``, ``--world`` + Lists ALL secret-containing items across the entire world, not just your + fortress. diff --git a/docs/notes.rst b/docs/notes.rst new file mode 100644 index 0000000000..a67e05b37c --- /dev/null +++ b/docs/notes.rst @@ -0,0 +1,42 @@ +notes +===== + +.. dfhack-tool:: + :summary: Manage map-specific notes. + :tags: fort interface map + +The `notes` tool enables players to annotate specific tiles +on the Dwarf Fortress game map with customizable notes. + +Each note is displayed as a green pin on the map and includes a one-line title and a detailed comment. + +It can be used to e.g.: + - marking plans for future constructions + - explaining mechanisms or traps + - noting historical events + +Usage +----- + +:: + + notes add + +Add new note in the current position of the keyboard cursor. + +Creating a Note +--------------- +1. Use the keyboard cursor to select the desired map tile where you want to place a note. +2. Execute ``notes add`` via the DFHack console. +3. In the pop-up dialog, fill in the note's title and detailed comment. +4. Press :kbd:`Ctrl` + :kbd:`Enter` to create the note. + +Editing or Deleting a Note +-------------------------- +- Click on the green pin representing the note directly on the map. +- A dialog will appear, offering options to edit the title or comment, or to delete the note entirely. + +Managing Notes Visibility +------------------------- +- Access the `gui/control-panel` / ``UI Overlays`` tab. +- Toggle the ``notes.map-notes`` overlay to show or hide the notes on the map. diff --git a/docs/position.rst b/docs/position.rst index fada2ec54c..56be5993c7 100644 --- a/docs/position.rst +++ b/docs/position.rst @@ -3,15 +3,34 @@ position .. dfhack-tool:: :summary: Report cursor and mouse position, along with other info. - :tags: fort inspection map + :tags: adventure dfhack fort inspection map -This tool reports the current date, clock time, month, and season. It also -reports the cursor position (or just the z-level if no cursor), window size, and -mouse location on the screen. +This tool reports the current date, clock time, month, season, and historical +era. It also reports the keyboard cursor position (or just the z-level if no +active cursor), window size, and mouse location on the screen. If a site is +loaded, it prints the world coordinates of the site. It also prints the world +coordinates of the adventurer (if applicable). + +Can also be used to copy the current keyboard cursor position for later use. Usage ----- :: - position + position [--copy] + +Examples +-------- + +``position`` + Print various information. +``position -c`` + Copy cursor position to system clipboard. + +Options +------- + +``-c``, ``--copy`` + Copy current keyboard cursor position to the clipboard in format ``0,0,0`` + instead of reporting info. For convenience with other tools. diff --git a/docs/pref-adjust.rst b/docs/pref-adjust.rst index a6794319eb..4a151cbb96 100644 --- a/docs/pref-adjust.rst +++ b/docs/pref-adjust.rst @@ -2,7 +2,7 @@ pref-adjust =========== .. dfhack-tool:: - :summary: Set the preferences of a dwarf to an ideal. + :summary: See the preferences of a dwarf or set them to a designated profile. :tags: fort armok units This tool replaces a dwarf's preferences with an "ideal" set which is easy to @@ -16,19 +16,21 @@ satisfy:: Usage ----- +``pref-adjust list`` + List all types of preferences. No changes will be made to any units. +``pref-adjust show`` + Show the preferences of the selected unit. ``pref-adjust all|goth_all|clear_all`` - Changes/clears preferences for all dwarves. + Changes/clears preferences for all citizens. ``pref-adjust one|goth|clear`` Changes/clears preferences for the currently selected dwarf. -``pref-adjust list`` - List all types of preferences. No changes will be made to any dwarves. Examples -------- ``pref-adjust all`` - Change preferences for all dwarves to an ideal. + Change preferences for all citizens to an ideal. Goth mode --------- diff --git a/docs/prioritize.rst b/docs/prioritize.rst index acc1d55f65..bdfb5aca2a 100644 --- a/docs/prioritize.rst +++ b/docs/prioritize.rst @@ -7,7 +7,7 @@ prioritize This tool encourages specified types of jobs to get assigned and completed as soon as possible. Finally, you can be sure your food will be hauled before -rotting, your hides will be tanned before going bad, and the corpses of your +it rots, your hides will be tanned before they go bad, and the corpses of your enemies will be cleared expediently from your entranceway. You can prioritize a bunch of active jobs that you need done *right now*, or you @@ -40,7 +40,8 @@ Examples Watch for and prioritize the default set of job types that the community has suggested and playtested (see below for details). ``prioritize -j`` - Print out the list of active jobs that you can prioritize right now. + Print out the list of not-yet prioritized jobs that you can prioritize + right now. ``prioritize ConstructBuilding DestroyBuilding`` Prioritize all current building construction and destruction jobs. ``prioritize -a --haul-labor=Food,Body StoreItemInStockpile`` @@ -56,8 +57,7 @@ Options ``-d``, ``--delete`` Stop automatically prioritizing new jobs of the specified job types. ``-j``, ``--jobs`` - Print out how many current jobs of each type there are. This is useful for - discovering the types of the jobs that you can prioritize right now. If any + Print out how many current unprioritized jobs of each type there are. If any job types are specified, only jobs of those types are listed. ``-l``, ``--haul-labor [,...]`` For StoreItemInStockpile jobs, match only the specified hauling labor(s). @@ -97,7 +97,7 @@ staring at the screen in annoyance for too long. You may be tempted to automatically prioritize ``ConstructBuilding`` jobs, but beware that if you engage in megaprojects where many constructions must be built, these jobs can consume your entire fortress if prioritized. It is often -better to run ``prioritize ConstructBuilding`` by itself (i.e. without the +better to run ``prioritize ConstructBuilding`` by itself (that is, without the ``-a`` parameter) as needed to just prioritize the construction jobs that you have ready at the time if you need to "clear the queue". diff --git a/docs/putontable.rst b/docs/putontable.rst index e880ac896d..10d94705a6 100644 --- a/docs/putontable.rst +++ b/docs/putontable.rst @@ -3,7 +3,7 @@ putontable .. dfhack-tool:: :summary: Make an item appear on a table. - :tags: unavailable + :tags: adventure fort armok buildings items To use this tool, move an item to the ground on the same tile as a built table. Then, place the cursor over the table and item and run this command. The item diff --git a/docs/rejuvenate.rst b/docs/rejuvenate.rst index 754510921c..aaa2216ca3 100644 --- a/docs/rejuvenate.rst +++ b/docs/rejuvenate.rst @@ -6,8 +6,9 @@ rejuvenate :tags: fort armok units If your most valuable citizens are getting old, this tool can save them. It -decreases the age of the selected dwarf to 20 years, or to the age specified. -Age is only increased using the --force option. +decreases the age of the selected dwarf to the minimum adult age, or to the age +specified. Age can only be increased (e.g. when this tool is run on babies or +children) if the ``--force`` option is specified. Usage ----- @@ -20,11 +21,15 @@ Examples -------- ``rejuvenate`` - Set the age of the selected dwarf to 20 (if they're older). + Set the age of the selected dwarf to 18 (if they're older than 18). The + target age may be different if you have modded dwarves to become an adult + at a different age, or if you have selected a unit that is not a dwarf. ``rejuvenate --all`` - Set the age of all dwarves over 20 to 20. + Set the ages of all adult citizens and residents to their minimum adult + ages. ``rejuvenate --all --force`` - Set the age of all dwarves (including babies) to 20. + Set the ages of all citizens and residents (including children and babies) + to their minimum adult ages. ``rejuvenate --age 149 --force`` Set the age of the selected dwarf to 149, even if they are younger. @@ -32,11 +37,11 @@ Options ------- ``--all`` - Rejuvenate all citizens, not just the selected one. + Rejuvenate all citizens and residents instead of a selected unit. ``--age `` - Sets the target to the age specified. If this is not set, the target age is 20. + Sets the target to the age specified. If this is not set, the target age defaults to the minimum adult age for the unit. ``--force`` - Set age for units under the specified age to the specified age. Useful if there are too - many babies around... + Set age for units under the specified age to the specified age. Useful if + there are too many babies around... ``--dry-run`` Only list units that would be changed; don't actually change ages. diff --git a/docs/resize-armor.rst b/docs/resize-armor.rst new file mode 100644 index 0000000000..1ea1e0c094 --- /dev/null +++ b/docs/resize-armor.rst @@ -0,0 +1,26 @@ +resize-armor +============ + +.. dfhack-tool:: + :summary: Resize armor and clothing. + :tags: adventure fort armok gameplay items + +Resize any armor or clothing item to suit any creature size. + +Usage +----- + +``resize-armor [