From 6a18e3ed462b63a3befb16ba9e6e2d11ff24e7d9 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 9 Jun 2024 03:13:36 +0300 Subject: [PATCH 01/15] If bodyswap is ran with no arguments, bring up a list of units on the map to bodyswap to Bodyswapping adjusts the extra members list, not core party - consistent with vanilla behavior for retiring/unretiring with core party. Reveal tiles near the bodyswapped unit --- bodyswap.lua | 77 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index cbc014b580..56e250ed86 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -1,5 +1,5 @@ --@ module = true - +local dialogs = require 'gui.dialogs' local utils = require 'utils' local validArgs = utils.invert({ 'unit', @@ -12,16 +12,16 @@ if args.help then return end -function setNewAdvNemFlags(nem) +local function setNewAdvNemFlags(nem) nem.flags.ACTIVE_ADVENTURER = true nem.flags.ADVENTURER = true end -function setOldAdvNemFlags(nem) +local function setOldAdvNemFlags(nem) nem.flags.ACTIVE_ADVENTURER = false end -function clearNemesisFromLinkedSites(nem) +local function clearNemesisFromLinkedSites(nem) -- omitting this step results in duplication of the unit entry in df.global.world.units.active when the site to which the historical figure is linked is reloaded with said figure present as a member of the player party -- this can be observed as part of the normal recruitment process when the player adds a site-linked historical figure to their party if not nem.figure then @@ -33,15 +33,15 @@ function clearNemesisFromLinkedSites(nem) end end -function createNemesis(unit) +local function createNemesis(unit) local nemesis = unit:create_nemesis(1, 1) nemesis.figure.flags.never_cull = true return nemesis end -function isPet(nemesis) +local function isPet(nemesis) if nemesis.unit then - if nemesis.unit.relationship_ids.Pet ~= -1 then + if nemesis.unit.relationship_ids.PetOwner ~= -1 then return true end elseif nemesis.figure then -- in case the unit is offloaded @@ -54,7 +54,7 @@ function isPet(nemesis) return false end -function processNemesisParty(nemesis, targetUnitID, alreadyProcessed) +local function processNemesisParty(nemesis, targetUnitID, alreadyProcessed) -- configures the target and any leaders/companions to behave as cohesive adventure mode party members local alreadyProcessed = alreadyProcessed or {} alreadyProcessed[tostring(nemesis.id)] = true @@ -66,8 +66,7 @@ function processNemesisParty(nemesis, targetUnitID, alreadyProcessed) elseif isPet(nemesis) then -- pets belonging to the target or to their companions df.global.adventure.interactions.party_pets:insert('#', nemesis.figure.id) else - df.global.adventure.interactions.party_core_members:insert('#', nemesis.figure.id) -- placing all non-pet companions into the core party list to enable tactical mode swapping - nemesis.flags.ADVENTURER = true + df.global.adventure.interactions.party_extra_members:insert('#', nemesis.figure.id) -- placing all non-pet companions into the extra party list if nemUnit then -- check in case the companion is offloaded nemUnit.relationship_ids.GroupLeader = targetUnitID end @@ -92,7 +91,7 @@ function processNemesisParty(nemesis, targetUnitID, alreadyProcessed) end end -function configureAdvParty(targetNemesis) +local function configureAdvParty(targetNemesis) local party = df.global.adventure.interactions party.party_core_members:resize(0) party.party_pets:resize(0) @@ -100,7 +99,15 @@ function configureAdvParty(targetNemesis) processNemesisParty(targetNemesis, targetNemesis.unit_id) end -function swapAdvUnit(newUnit) +-- shamelessly copy pasted from flashstep.lua +local function reveal_tile(pos) + local block = dfhack.maps.getTileBlock(pos) + local des = block.designation[pos.x%16][pos.y%16] + des.hidden = false + des.pile = true -- reveal the tile on the map +end + +local function swapAdvUnit(newUnit) if not newUnit then qerror('Target unit not specified!') end @@ -122,10 +129,51 @@ function swapAdvUnit(newUnit) df.global.adventure.player_id = newNem.id df.global.world.units.adv_unit = newUnit oldUnit.idle_area:assign(oldUnit.pos) + local pos = xyz2pos(dfhack.units.getPosition(newUnit)) + + -- reveal the tiles around the bodyswapped unit + reveal_tile(xyz2pos(pos.x-1, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x+1, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x-1, pos.y, pos.z)) + reveal_tile(pos) + reveal_tile(xyz2pos(pos.x+1, pos.y, pos.z)) + reveal_tile(xyz2pos(pos.x-1, pos.y+1, pos.z)) + reveal_tile(xyz2pos(pos.x, pos.y+1, pos.z)) + reveal_tile(xyz2pos(pos.x+1, pos.y+1, pos.z)) + + dfhack.gui.revealInDwarfmodeMap(pos, true) +end - dfhack.gui.revealInDwarfmodeMap(xyz2pos(dfhack.units.getPosition(newUnit)), true) +-- shamelessly copy pasted from gui/sitemap.lua +local function get_unit_choices() + local choices = {} + for _, unit in ipairs(df.global.world.units.active) do + if not dfhack.units.isActive(unit) or + dfhack.units.isHidden(unit) + then + goto continue + end + local name = dfhack.units.getReadableName(unit) + table.insert(choices, { + text=name, + unit=unit, + search_key=dfhack.toSearchNormalized(name), + }) + ::continue:: + end + return choices +end + +local function swapAdvUnitPrompt() + local choices = get_unit_choices() + dialogs.showListPrompt('bodyswap', "Select a unit to bodyswap to:", COLOR_WHITE, + choices, function(id, choice) + swapAdvUnit(choice.unit) + end, nil, nil, true) end + if not dfhack_flags.module then if df.global.gamemode ~= df.game_mode.ADVENTURE then qerror("This script can only be used in adventure mode!") @@ -137,7 +185,8 @@ if not dfhack_flags.module then if args.unit then qerror("Invalid unit id: " .. args.unit) else - qerror("Target unit not specified!") + swapAdvUnitPrompt() + return end end swapAdvUnit(unit) From e59bc4345192744c1fccdf064471b3b7036defe4 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sun, 9 Jun 2024 04:21:02 +0300 Subject: [PATCH 02/15] When bodyswapping to someone with no name, forcibly give them a name --- bodyswap.lua | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/bodyswap.lua b/bodyswap.lua index 56e250ed86..cc46e457cb 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -33,6 +33,49 @@ local function clearNemesisFromLinkedSites(nem) end end +-- shamelessly copypasted from makeown.lua +local function get_translation(race_id) + local race_name = df.global.world.raws.creatures.all[race_id].creature_id + local backup = nil + for _,translation in ipairs(df.global.world.raws.language.translations) do + if translation.name == race_name then + return translation + end + if translation.name == 'GEN_DIVINE' then + backup = translation + end + end + -- Use a divine name if no normal name is found + if backup then + return backup + end + -- Use the first language in the list if no divine language is found, this is normally the DWARF language. + return df.global.world.raws.language.translations[0] +end + +local function pick_first_name(race_id) + local translation = get_translation(race_id) + return translation.words[math.random(0, #translation.words-1)].value +end + +local LANGUAGE_IDX = 0 +local word_table = df.global.world.raws.language.word_table[LANGUAGE_IDX][35] + +local function name_nemesis(nemesis) + local figure = nemesis.figure + if figure.name.has_name then return end + + figure.name.first_name = pick_first_name(figure.race) + figure.name.words.FrontCompound = word_table.words.FrontCompound[math.random(0, #word_table.words.FrontCompound-1)] + figure.name.words.RearCompound = word_table.words.RearCompound[math.random(0, #word_table.words.RearCompound-1)] + + figure.name.language = LANGUAGE_IDX + figure.name.parts_of_speech.FrontCompound = df.part_of_speech.Noun + figure.name.parts_of_speech.RearCompound = df.part_of_speech.Verb3rdPerson + figure.name.type = df.language_name_type.Figure + figure.name.has_name = true +end + local function createNemesis(unit) local nemesis = unit:create_nemesis(1, 1) nemesis.figure.flags.never_cull = true @@ -122,6 +165,8 @@ local function swapAdvUnit(newUnit) if not newNem then qerror("Failed to obtain target nemesis!") end + + name_nemesis(newNem) setOldAdvNemFlags(oldNem) setNewAdvNemFlags(newNem) From 10998115b1380e20faccc93e8b84fccc3ef7eedb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:13:40 +0000 Subject: [PATCH 03/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- bodyswap.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index cc46e457cb..cc9c790110 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -165,7 +165,7 @@ local function swapAdvUnit(newUnit) if not newNem then qerror("Failed to obtain target nemesis!") end - + name_nemesis(newNem) setOldAdvNemFlags(oldNem) @@ -186,7 +186,7 @@ local function swapAdvUnit(newUnit) reveal_tile(xyz2pos(pos.x-1, pos.y+1, pos.z)) reveal_tile(xyz2pos(pos.x, pos.y+1, pos.z)) reveal_tile(xyz2pos(pos.x+1, pos.y+1, pos.z)) - + dfhack.gui.revealInDwarfmodeMap(pos, true) end From afe30e6711a2f863aae69c1e72093a78191c6ddb Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Sat, 22 Jun 2024 01:47:20 +0300 Subject: [PATCH 04/15] add bodyswap extra behaviors to the documentation --- docs/bodyswap.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index 772c882de0..3a9f59c00d 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -17,12 +17,15 @@ Usage If no specific unit id is specified, the target unit is the one selected in the user interface, such as by opening the unit's status screen or viewing its -description. +description. Otherwise, a valid list of units to bodyswap into will be shown. +If bodyswapping into an entity that has no historical figure, a new historical figure is created for it. +If said unit has no name, a new name is randomly generated for it, based on the unit's race. +If no valid language is found for that race, it will use the DIVINE language. Examples -------- ``bodyswap`` - Takes control of the selected unit. + Takes control of the selected unit, or brings up a list of swappable units if no unit is selected. ``bodyswap --unit 42`` Takes control of unit with id 42. From 9c6d73f846d789d99bb8853493ac989557e3b0f0 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 25 Jun 2024 17:07:42 +0300 Subject: [PATCH 05/15] Just use makeown's name_unit --- bodyswap.lua | 51 ++++++--------------------------------------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index cc9c790110..995909b2d7 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -1,6 +1,9 @@ --@ module = true local dialogs = require 'gui.dialogs' local utils = require 'utils' + +local makeown = reqscript('makeown') + local validArgs = utils.invert({ 'unit', 'help' @@ -33,49 +36,6 @@ local function clearNemesisFromLinkedSites(nem) end end --- shamelessly copypasted from makeown.lua -local function get_translation(race_id) - local race_name = df.global.world.raws.creatures.all[race_id].creature_id - local backup = nil - for _,translation in ipairs(df.global.world.raws.language.translations) do - if translation.name == race_name then - return translation - end - if translation.name == 'GEN_DIVINE' then - backup = translation - end - end - -- Use a divine name if no normal name is found - if backup then - return backup - end - -- Use the first language in the list if no divine language is found, this is normally the DWARF language. - return df.global.world.raws.language.translations[0] -end - -local function pick_first_name(race_id) - local translation = get_translation(race_id) - return translation.words[math.random(0, #translation.words-1)].value -end - -local LANGUAGE_IDX = 0 -local word_table = df.global.world.raws.language.word_table[LANGUAGE_IDX][35] - -local function name_nemesis(nemesis) - local figure = nemesis.figure - if figure.name.has_name then return end - - figure.name.first_name = pick_first_name(figure.race) - figure.name.words.FrontCompound = word_table.words.FrontCompound[math.random(0, #word_table.words.FrontCompound-1)] - figure.name.words.RearCompound = word_table.words.RearCompound[math.random(0, #word_table.words.RearCompound-1)] - - figure.name.language = LANGUAGE_IDX - figure.name.parts_of_speech.FrontCompound = df.part_of_speech.Noun - figure.name.parts_of_speech.RearCompound = df.part_of_speech.Verb3rdPerson - figure.name.type = df.language_name_type.Figure - figure.name.has_name = true -end - local function createNemesis(unit) local nemesis = unit:create_nemesis(1, 1) nemesis.figure.flags.never_cull = true @@ -161,13 +121,14 @@ local function swapAdvUnit(newUnit) return end + -- Make sure the unit we're swapping into isn't nameless + makeown.name_unit(newUnit) + local newNem = dfhack.units.getNemesis(newUnit) or createNemesis(newUnit) if not newNem then qerror("Failed to obtain target nemesis!") end - name_nemesis(newNem) - setOldAdvNemFlags(oldNem) setNewAdvNemFlags(newNem) configureAdvParty(newNem) From e61ff46d1e2871b94a656f51d91be748bdcfc27a Mon Sep 17 00:00:00 2001 From: Crystalwarrior Date: Tue, 25 Jun 2024 17:08:53 +0300 Subject: [PATCH 06/15] Apply suggestions from code review Co-authored-by: Myk --- bodyswap.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index 995909b2d7..b2a6c89619 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -104,8 +104,7 @@ end -- shamelessly copy pasted from flashstep.lua local function reveal_tile(pos) - local block = dfhack.maps.getTileBlock(pos) - local des = block.designation[pos.x%16][pos.y%16] + local des = dfhack.maps.getTileFlags(pos) des.hidden = false des.pile = true -- reveal the tile on the map end From 4eec063b022ae703234701c60e27121d06fbc0e3 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 25 Jun 2024 18:32:45 +0300 Subject: [PATCH 07/15] fix swapAdvUnit being a local function (so linger can be used again) Merge linger into bodyswap --- bodyswap.lua | 42 +++++++++++++++++++++++++++++++++++++++++- changelog.txt | 1 + docs/bodyswap.rst | 9 +++++++++ linger.lua | 42 ------------------------------------------ 4 files changed, 51 insertions(+), 43 deletions(-) delete mode 100644 linger.lua diff --git a/bodyswap.lua b/bodyswap.lua index b2a6c89619..d883759bbb 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -6,6 +6,7 @@ local makeown = reqscript('makeown') local validArgs = utils.invert({ 'unit', + 'linger', 'help' }) local args = utils.processArgs({ ... }, validArgs) @@ -109,7 +110,7 @@ local function reveal_tile(pos) des.pile = true -- reveal the tile on the map end -local function swapAdvUnit(newUnit) +function swapAdvUnit(newUnit) if not newUnit then qerror('Target unit not specified!') end @@ -178,6 +179,45 @@ local function swapAdvUnitPrompt() end, nil, nil, true) end +function getHistoricalSlayer(unit) + local histFig = unit.hist_figure_id ~= -1 and df.historical_figure.find(unit.hist_figure_id) + if not histFig then + return + end + + local deathEvents = df.global.world.history.events_death + for i = #deathEvents - 1, 0, -1 do + local event = deathEvents[i] --as:df.history_event_hist_figure_diedst + if event.victim_hf == unit.hist_figure_id then + return df.historical_figure.find(event.slayer_hf) + end + end +end + +if args.linger then + local adventurer = dfhack.world.getAdventurer() + if not adventurer.flags2.killed then + qerror("Your adventurer hasn't died yet!") + end + + local slayerHistFig = getHistoricalSlayer(adventurer) + local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) + if not slayer then + slayer = df.unit.find(adventurer.relationship_ids.LastAttacker) + end + if not slayer then + qerror("Killer not found!") + elseif slayer.flags2.killed then + local slayerName = "" + if slayer.name.has_name then + slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," + end + qerror("Your slayer" .. slayerName .. " is dead!") + end + + swapAdvUnit(slayer) + return +end if not dfhack_flags.module then if df.global.gamemode ~= df.game_mode.ADVENTURE then diff --git a/changelog.txt b/changelog.txt index 693b867fba..723c8d39b7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -66,6 +66,7 @@ Template for new versions: - `max-wave`: merged into `pop-control` - `devel/find-offsets`, `devel/find-twbt`, `devel/prepare-save`: remove development scripts that are no longer useful - `fix/item-occupancy`, `fix/tile-occupancy`: merged into `fix/occupancy` +- `linger`: merged into `bodyswap` as bodyswap linger # 50.13-r2 diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index 3a9f59c00d..40f360620a 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -14,6 +14,7 @@ Usage :: bodyswap [--unit ] + bodyswap [--linger] If no specific unit id is specified, the target unit is the one selected in the user interface, such as by opening the unit's status screen or viewing its @@ -22,6 +23,12 @@ If bodyswapping into an entity that has no historical figure, a new historical f If said unit has no name, a new name is randomly generated for it, based on the unit's race. If no valid language is found for that race, it will use the DIVINE language. +If you run bodyswap linger, the killer is identified by examining the historical event generated +when the adventurer died. If this is unsuccessful, the killer is assumed to be the last unit to have +attacked the adventurer prior to their death. + +This will fail if the unit in question is no longer present on the local map or is also dead. + Examples -------- @@ -29,3 +36,5 @@ Examples Takes control of the selected unit, or brings up a list of swappable units if no unit is selected. ``bodyswap --unit 42`` Takes control of unit with id 42. +``bodyswap --linger`` + Takes control of your killer when you die diff --git a/linger.lua b/linger.lua deleted file mode 100644 index 2207c48d1c..0000000000 --- a/linger.lua +++ /dev/null @@ -1,42 +0,0 @@ -local bodyswap = reqscript('bodyswap') - -if df.global.gamemode ~= df.game_mode.ADVENTURE then - qerror("This script can only be used in adventure mode!") -end - -local adventurer = df.nemesis_record.find(df.global.adventure.player_id).unit -if not adventurer.flags2.killed then - qerror("Your adventurer hasn't died yet!") -end - -function getHistoricalSlayer(unit) - local histFig = unit.hist_figure_id ~= -1 and df.historical_figure.find(unit.hist_figure_id) - if not histFig then - return - end - - local deathEvents = df.global.world.history.events_death - for i = #deathEvents - 1, 0, -1 do - local event = deathEvents[i] --as:df.history_event_hist_figure_diedst - if event.victim_hf == unit.hist_figure_id then - return df.historical_figure.find(event.slayer_hf) - end - end -end - -local slayerHistFig = getHistoricalSlayer(adventurer) -local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) -if not slayer then - slayer = df.unit.find(adventurer.relationship_ids.LastAttacker) -end -if not slayer then - qerror("Killer not found!") -elseif slayer.flags2.killed then - local slayerName = "" - if slayer.name.has_name then - slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," - end - qerror("Your slayer" .. slayerName .. " is dead!") -end - -bodyswap.swapAdvUnit(slayer) From 928919f05b30c302ce8261904259b3e2ea6f33c9 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Wed, 3 Jul 2024 00:34:23 +0300 Subject: [PATCH 08/15] simplify arg parsing for bodyswap --- bodyswap.lua | 20 ++++++++------------ docs/bodyswap.rst | 8 ++++---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index d883759bbb..6efe2fe0d3 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -4,14 +4,10 @@ local utils = require 'utils' local makeown = reqscript('makeown') -local validArgs = utils.invert({ - 'unit', - 'linger', - 'help' -}) -local args = utils.processArgs({ ... }, validArgs) - -if args.help then +local args = {...} +local command = table.remove(args, 1) + +if command == 'help' then print(dfhack.script_help()) return end @@ -194,7 +190,7 @@ function getHistoricalSlayer(unit) end end -if args.linger then +if command == 'linger' then local adventurer = dfhack.world.getAdventurer() if not adventurer.flags2.killed then qerror("Your adventurer hasn't died yet!") @@ -224,11 +220,11 @@ if not dfhack_flags.module then qerror("This script can only be used in adventure mode!") end - local unit = args.unit and df.unit.find(tonumber(args.unit)) or dfhack.gui.getSelectedUnit() + local unit = command == 'unit' and df.unit.find(tonumber(args[1])) or dfhack.gui.getSelectedUnit() if not unit then print("Enter the following if you require assistance: help bodyswap") - if args.unit then - qerror("Invalid unit id: " .. args.unit) + if command == 'unit' then + qerror("Invalid unit id: " .. tonumber(args[1])) else swapAdvUnitPrompt() return diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index 40f360620a..bbcfe54308 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -13,8 +13,8 @@ Usage :: - bodyswap [--unit ] - bodyswap [--linger] + bodyswap unit + bodyswap linger If no specific unit id is specified, the target unit is the one selected in the user interface, such as by opening the unit's status screen or viewing its @@ -34,7 +34,7 @@ Examples ``bodyswap`` Takes control of the selected unit, or brings up a list of swappable units if no unit is selected. -``bodyswap --unit 42`` +``bodyswap unit 42`` Takes control of unit with id 42. -``bodyswap --linger`` +``bodyswap linger`` Takes control of your killer when you die From 7901e4a0bd8c6f76552f750a96632e0e13f8e00a Mon Sep 17 00:00:00 2001 From: Crystalwarrior Date: Wed, 3 Jul 2024 00:37:39 +0300 Subject: [PATCH 09/15] Update changelog.txt Co-authored-by: Myk --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index be753cc82d..ea0ee26f01 100644 --- a/changelog.txt +++ b/changelog.txt @@ -99,7 +99,7 @@ Template for new versions: - `max-wave`: merged into `pop-control` - `devel/find-offsets`, `devel/find-twbt`, `devel/prepare-save`: remove development scripts that are no longer useful - `fix/item-occupancy`, `fix/tile-occupancy`: merged into `fix/occupancy` -- `linger`: merged into `bodyswap` as bodyswap linger +- `linger`: merged into `bodyswap` as ``bodyswap linger`` - `adv-fix-sleepers`: renamed to `fix/sleepers` - `adv-rumors`: merged into `advtools` From 2ade5839ba9bd8a8dee1522e3657ddce726795ee Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Wed, 3 Jul 2024 05:01:27 +0300 Subject: [PATCH 10/15] brackets --- docs/bodyswap.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index bbcfe54308..8bf52879c0 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -13,7 +13,7 @@ Usage :: - bodyswap unit + bodyswap [unit ] bodyswap linger If no specific unit id is specified, the target unit is the one selected in the From f2dc8da77fdad80692c49018d0ac64ede413f3af Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Wed, 3 Jul 2024 23:43:46 +0300 Subject: [PATCH 11/15] Bodyswap positional arguments --- bodyswap.lua | 53 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index 6efe2fe0d3..84799ca90c 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -1,13 +1,21 @@ --@ module = true -local dialogs = require 'gui.dialogs' -local utils = require 'utils' - +local dialogs = require('gui.dialogs') +local utils = require('utils') +local argparse = require('argparse') local makeown = reqscript('makeown') -local args = {...} -local command = table.remove(args, 1) +local args = { ... } +local options = { + help = false, + unit = -1, +} + +local positionals = argparse.processArgsGetopt(args, { + {'h', 'help', handler = function() options.help = true end}, + {'u', 'unit', handler = function(arg) options.unit = tonumber(arg) end, hasArg = true}, +}) -if command == 'help' then +if positionals[1] == 'help' or options.help then print(dfhack.script_help()) return end @@ -106,6 +114,18 @@ local function reveal_tile(pos) des.pile = true -- reveal the tile on the map end +local function reveal_around(pos) + reveal_tile(xyz2pos(pos.x-1, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x+1, pos.y-1, pos.z)) + reveal_tile(xyz2pos(pos.x-1, pos.y, pos.z)) + reveal_tile(pos) + reveal_tile(xyz2pos(pos.x+1, pos.y, pos.z)) + reveal_tile(xyz2pos(pos.x-1, pos.y+1, pos.z)) + reveal_tile(xyz2pos(pos.x, pos.y+1, pos.z)) + reveal_tile(xyz2pos(pos.x+1, pos.y+1, pos.z)) +end + function swapAdvUnit(newUnit) if not newUnit then qerror('Target unit not specified!') @@ -132,18 +152,9 @@ function swapAdvUnit(newUnit) df.global.world.units.adv_unit = newUnit oldUnit.idle_area:assign(oldUnit.pos) local pos = xyz2pos(dfhack.units.getPosition(newUnit)) - -- reveal the tiles around the bodyswapped unit - reveal_tile(xyz2pos(pos.x-1, pos.y-1, pos.z)) - reveal_tile(xyz2pos(pos.x, pos.y-1, pos.z)) - reveal_tile(xyz2pos(pos.x+1, pos.y-1, pos.z)) - reveal_tile(xyz2pos(pos.x-1, pos.y, pos.z)) - reveal_tile(pos) - reveal_tile(xyz2pos(pos.x+1, pos.y, pos.z)) - reveal_tile(xyz2pos(pos.x-1, pos.y+1, pos.z)) - reveal_tile(xyz2pos(pos.x, pos.y+1, pos.z)) - reveal_tile(xyz2pos(pos.x+1, pos.y+1, pos.z)) - + reveal_around(pos) + -- Focus on the revealed pos dfhack.gui.revealInDwarfmodeMap(pos, true) end @@ -190,7 +201,7 @@ function getHistoricalSlayer(unit) end end -if command == 'linger' then +if positionals[1] == 'linger' then local adventurer = dfhack.world.getAdventurer() if not adventurer.flags2.killed then qerror("Your adventurer hasn't died yet!") @@ -220,11 +231,11 @@ if not dfhack_flags.module then qerror("This script can only be used in adventure mode!") end - local unit = command == 'unit' and df.unit.find(tonumber(args[1])) or dfhack.gui.getSelectedUnit() + local unit = options.unit ~= -1 and df.unit.find(options.unit) or dfhack.gui.getSelectedUnit() if not unit then print("Enter the following if you require assistance: help bodyswap") - if command == 'unit' then - qerror("Invalid unit id: " .. tonumber(args[1])) + if options.unit ~= -1 then + qerror("Invalid unit id: " .. options.unit) else swapAdvUnitPrompt() return From 58a9a3d05f76702697675ee6116f496f832440eb Mon Sep 17 00:00:00 2001 From: Crystalwarrior Date: Tue, 16 Jul 2024 13:52:01 +0300 Subject: [PATCH 12/15] Apply suggestions from code review Co-authored-by: Myk --- bodyswap.lua | 4 ++-- docs/bodyswap.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index 84799ca90c..cc3a220cea 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -12,7 +12,7 @@ local options = { local positionals = argparse.processArgsGetopt(args, { {'h', 'help', handler = function() options.help = true end}, - {'u', 'unit', handler = function(arg) options.unit = tonumber(arg) end, hasArg = true}, + {'u', 'unit', handler = function(arg) options.unit = argparse.nonnegativeInt(arg, 'unit') end, hasArg = true}, }) if positionals[1] == 'help' or options.help then @@ -231,7 +231,7 @@ if not dfhack_flags.module then qerror("This script can only be used in adventure mode!") end - local unit = options.unit ~= -1 and df.unit.find(options.unit) or dfhack.gui.getSelectedUnit() + local unit = options.unit == -1 and dfhack.gui.getSelectedUnit(true) or df.unit.find(options.unit) if not unit then print("Enter the following if you require assistance: help bodyswap") if options.unit ~= -1 then diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index 8bf52879c0..72ad36aba6 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -13,7 +13,7 @@ Usage :: - bodyswap [unit ] + bodyswap [--unit ] bodyswap linger If no specific unit id is specified, the target unit is the one selected in the From e31a14c2e70b5c247c46fc845b085d311d72aad9 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 16 Jul 2024 13:58:26 +0300 Subject: [PATCH 13/15] Fix arg processing when script is ran as module --- bodyswap.lua | 77 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index cc3a220cea..de98704914 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -4,22 +4,6 @@ local utils = require('utils') local argparse = require('argparse') local makeown = reqscript('makeown') -local args = { ... } -local options = { - help = false, - unit = -1, -} - -local positionals = argparse.processArgsGetopt(args, { - {'h', 'help', handler = function() options.help = true end}, - {'u', 'unit', handler = function(arg) options.unit = argparse.nonnegativeInt(arg, 'unit') end, hasArg = true}, -}) - -if positionals[1] == 'help' or options.help then - print(dfhack.script_help()) - return -end - local function setNewAdvNemFlags(nem) nem.flags.ACTIVE_ADVENTURER = true nem.flags.ADVENTURER = true @@ -201,34 +185,51 @@ function getHistoricalSlayer(unit) end end -if positionals[1] == 'linger' then - local adventurer = dfhack.world.getAdventurer() - if not adventurer.flags2.killed then - qerror("Your adventurer hasn't died yet!") + +if not dfhack_flags.module then + if df.global.gamemode ~= df.game_mode.ADVENTURE then + qerror("This script can only be used in adventure mode!") end - local slayerHistFig = getHistoricalSlayer(adventurer) - local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) - if not slayer then - slayer = df.unit.find(adventurer.relationship_ids.LastAttacker) + local options = { + help = false, + unit = -1, + } + + local args = { ... } + local positionals = argparse.processArgsGetopt(args, { + {'h', 'help', handler = function() options.help = true end}, + {'u', 'unit', handler = function(arg) options.unit = argparse.nonnegativeInt(arg, 'unit') end, hasArg = true}, + }) + + if positionals[1] == 'help' or options.help then + print(dfhack.script_help()) + return end - if not slayer then - qerror("Killer not found!") - elseif slayer.flags2.killed then - local slayerName = "" - if slayer.name.has_name then - slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," + + if positionals[1] == 'linger' then + local adventurer = dfhack.world.getAdventurer() + if not adventurer.flags2.killed then + qerror("Your adventurer hasn't died yet!") end - qerror("Your slayer" .. slayerName .. " is dead!") - end - swapAdvUnit(slayer) - return -end + local slayerHistFig = getHistoricalSlayer(adventurer) + local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) + if not slayer then + slayer = df.unit.find(adventurer.relationship_ids.LastAttacker) + end + if not slayer then + qerror("Killer not found!") + elseif slayer.flags2.killed then + local slayerName = "" + if slayer.name.has_name then + slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," + end + qerror("Your slayer" .. slayerName .. " is dead!") + end -if not dfhack_flags.module then - if df.global.gamemode ~= df.game_mode.ADVENTURE then - qerror("This script can only be used in adventure mode!") + swapAdvUnit(slayer) + return end local unit = options.unit == -1 and dfhack.gui.getSelectedUnit(true) or df.unit.find(options.unit) From e066acddef8c886d6d312d6052566a52cd987d3e Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Tue, 16 Jul 2024 14:02:44 +0300 Subject: [PATCH 14/15] Turn linger into its own function so it can be used as a module --- bodyswap.lua | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/bodyswap.lua b/bodyswap.lua index de98704914..f4b0e6676d 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -185,6 +185,28 @@ function getHistoricalSlayer(unit) end end +function lingerAdvUnit(unit) + if not unit.flags2.killed then + qerror("Target unit hasn't died yet!") + end + + local slayerHistFig = getHistoricalSlayer(unit) + local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) + if not slayer then + slayer = df.unit.find(unit.relationship_ids.LastAttacker) + end + if not slayer then + qerror("Slayer not found!") + elseif slayer.flags2.killed then + local slayerName = "" + if slayer.name.has_name then + slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," + end + qerror("The unit's slayer" .. slayerName .. " is dead!") + end + + swapAdvUnit(slayer) +end if not dfhack_flags.module then if df.global.gamemode ~= df.game_mode.ADVENTURE then @@ -208,27 +230,7 @@ if not dfhack_flags.module then end if positionals[1] == 'linger' then - local adventurer = dfhack.world.getAdventurer() - if not adventurer.flags2.killed then - qerror("Your adventurer hasn't died yet!") - end - - local slayerHistFig = getHistoricalSlayer(adventurer) - local slayer = slayerHistFig and df.unit.find(slayerHistFig.unit_id) - if not slayer then - slayer = df.unit.find(adventurer.relationship_ids.LastAttacker) - end - if not slayer then - qerror("Killer not found!") - elseif slayer.flags2.killed then - local slayerName = "" - if slayer.name.has_name then - slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," - end - qerror("Your slayer" .. slayerName .. " is dead!") - end - - swapAdvUnit(slayer) + lingerAdvUnit(dfhack.world.getAdventurer()) return end From aca2fc461354bca4d4c1ea06cfe997b32c5b0a22 Mon Sep 17 00:00:00 2001 From: Alex Noir Date: Mon, 27 Jan 2025 18:26:59 +0300 Subject: [PATCH 15/15] Implement suggestions --- bodyswap.lua | 6 +++--- changelog.txt | 2 +- docs/bodyswap.rst | 8 ++++---- docs/linger.rst | 22 ---------------------- 4 files changed, 8 insertions(+), 30 deletions(-) delete mode 100644 docs/linger.rst diff --git a/bodyswap.lua b/bodyswap.lua index f4b0e6676d..fce5a96513 100644 --- a/bodyswap.lua +++ b/bodyswap.lua @@ -186,7 +186,7 @@ function getHistoricalSlayer(unit) end function lingerAdvUnit(unit) - if not unit.flags2.killed then + if not dfhack.units.isKilled(unit) then qerror("Target unit hasn't died yet!") end @@ -197,10 +197,10 @@ function lingerAdvUnit(unit) end if not slayer then qerror("Slayer not found!") - elseif slayer.flags2.killed then + elseif dfhack.units.isKilled(slayer) then local slayerName = "" if slayer.name.has_name then - slayerName = ", " .. dfhack.TranslateName(slayer.name) .. "," + slayerName = ", " .. dfhack.units.getReadableName(slayer) .. "," end qerror("The unit's slayer" .. slayerName .. " is dead!") end diff --git a/changelog.txt b/changelog.txt index 4db0153813..2baab22be6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,6 +42,7 @@ Template for new versions: - `hide-tutorials`: new ``reset`` command that will re-enable popups in the current game ## Removed +- `linger`: merged into `bodyswap` as ``bodyswap linger`` # 51.02-r1 @@ -259,7 +260,6 @@ Template for new versions: - `max-wave`: merged into `pop-control` - `devel/find-offsets`, `devel/find-twbt`, `devel/prepare-save`: remove development scripts that are no longer useful - `fix/item-occupancy`, `fix/tile-occupancy`: merged into `fix/occupancy` -- `linger`: merged into `bodyswap` as ``bodyswap linger`` - `adv-fix-sleepers`: renamed to `fix/sleepers` - `adv-rumors`: merged into `advtools` diff --git a/docs/bodyswap.rst b/docs/bodyswap.rst index 72ad36aba6..18d9bcf20a 100644 --- a/docs/bodyswap.rst +++ b/docs/bodyswap.rst @@ -23,9 +23,9 @@ If bodyswapping into an entity that has no historical figure, a new historical f If said unit has no name, a new name is randomly generated for it, based on the unit's race. If no valid language is found for that race, it will use the DIVINE language. -If you run bodyswap linger, the killer is identified by examining the historical event generated -when the adventurer died. If this is unsuccessful, the killer is assumed to be the last unit to have -attacked the adventurer prior to their death. +If you run bodyswap linger immediately after you have died, it will put you in the body of your killer. +The killer is identified by examining the historical event generated when the adventurer died. +If this is unsuccessful, the killer is assumed to be the last unit to have attacked the adventurer prior to their death. This will fail if the unit in question is no longer present on the local map or is also dead. @@ -34,7 +34,7 @@ Examples ``bodyswap`` Takes control of the selected unit, or brings up a list of swappable units if no unit is selected. -``bodyswap unit 42`` +``bodyswap --unit 42`` Takes control of unit with id 42. ``bodyswap linger`` Takes control of your killer when you die diff --git a/docs/linger.rst b/docs/linger.rst deleted file mode 100644 index 19d1bb6d9b..0000000000 --- a/docs/linger.rst +++ /dev/null @@ -1,22 +0,0 @@ -linger -====== - -.. dfhack-tool:: - :summary: Take control of your adventurer's killer. - :tags: unavailable - -Run this script after being presented with the "You are deceased." message to -abandon your dead adventurer and take control of your adventurer's killer. - -The killer is identified by examining the historical event generated when the -adventurer died. If this is unsuccessful, the killer is assumed to be the last -unit to have attacked the adventurer prior to their death. - -This will fail if the unit in question is no longer present on the local map. - -Usage ------ - -:: - - linger