From 973b5e8d1554bbf400e86d8f56b4d648077f7f8f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sun, 26 Jan 2025 16:03:09 +0800 Subject: [PATCH 01/45] Add emigrate-nobles.lua and rst --- docs/emigrate-nobles.rst | 16 ++++++++++++++ emigrate-nobles.lua | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 docs/emigrate-nobles.rst create mode 100644 emigrate-nobles.lua diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst new file mode 100644 index 0000000000..23975a2fa4 --- /dev/null +++ b/docs/emigrate-nobles.rst @@ -0,0 +1,16 @@ +emigrate-nobles +========== + +.. dfhack-tool:: + :summary: Allow inherited nobles to emigrate from fort for their rightful land. + :tags: fort units + +Tired of inherited nobility freeloading off your fortress making inane demands? Use this tool +to find them and have them (willingly) emigrate to their rightful land. + +Usage +----- + +:: + + emigrate-nobles [-h] diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua new file mode 100644 index 0000000000..d72c5f12b8 --- /dev/null +++ b/emigrate-nobles.lua @@ -0,0 +1,46 @@ +--Deport resident nobles of other lands and summon your rightful lord if he/she is elsewhere + +local argparse = require("argparse") + +--[[ +Planned modes/options: + - d/deport = remove inherited freeloaders + - list = list possible nobles to evict + - index = specific noble to kick + - all = kick all listed nobles + - i/import = find and invite heir to fortress (need a better name) +]]-- +local options = { + help = false +} + +function isNoble(unit) + for _, pos in ipairs(dfhack.units.getNoblePositions(unit) or {}) do + entity_pos = pos.position + if entity_pos.flags.IS_LAW_MAKER then + name = dfhack.df2console(dfhack.units.getReadableName(unit)) + print(name.." is a noble") + end + end +end + +function main() + for _, unit in ipairs(dfhack.units.getCitizens()) do + if not options.deport or dfhack.units.isDead(unit) then goto continue end + isNoble(unit) + ::continue:: + end +end + + +argparse.processArgsGetopt({...}, { + {"h", "help", handler=function() options.help = true end}, + {"a", "all", handler=function() options.deport = true end}, +}) + +if options.help then + print(dfhack.script_help()) + return +end + +main() From 7a8f07ea2f8b81119e2789107d1fff33561767f5 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 28 Jan 2025 03:48:36 +0800 Subject: [PATCH 02/45] Add logic for finding noble's sites --- emigrate-nobles.lua | 76 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index d72c5f12b8..7af10c5a95 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -14,33 +14,89 @@ local options = { help = false } +fortId=nil + function isNoble(unit) - for _, pos in ipairs(dfhack.units.getNoblePositions(unit) or {}) do - entity_pos = pos.position - if entity_pos.flags.IS_LAW_MAKER then - name = dfhack.df2console(dfhack.units.getReadableName(unit)) - print(name.." is a noble") + local nps = dfhack.units.getNoblePositions(unit) or {} + local isLawMaker = false + + local noblePos, nobleName + + for _, np in ipairs(nps) do + pos = np.position + if pos.flags.IS_LAW_MAKER then + isLawMaker = true + noblePos = np + nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) + break end end + + if not isLawMaker then return false end + + civ = noblePos.entity -- lawmakers seem to be all civ-level positions + assignments = civ.positions.assignments + for _, link in ipairs(civ.site_links) do + siteId = link.target + posProfId = link.position_profile_id + if posProfId < 0 then goto continue end + + assignment = assignments[posProfId] + if assignment.id ~= noblePos.assignment.id then goto continue end + + site = df.world_site.find(siteId) + siteName = dfhack.translation.translateName(site.name, true, true) + + if siteId == fortId then + print(nobleName.." holds a position in this fortress - "..siteName) + goto continue + end + + print(nobleName.." is lord of "..siteName) + ::continue:: + end + + return isLawMaker end function main() + fort = df.historical_entity.find(fortId) + fortName = dfhack.translation.translateName(fort.name, true) + print("Current fort is "..fortName) + for _, unit in ipairs(dfhack.units.getCitizens()) do - if not options.deport or dfhack.units.isDead(unit) then goto continue end + if dfhack.units.isDead(unit) then goto continue end isNoble(unit) ::continue:: end end +function initChecks() + if options.help then + print(dfhack.script_help()) + return false + end + + if not dfhack.world.isFortressMode() or not dfhack.isMapLoaded() then + qerror('needs a loaded fortress map') + return false + end + + fortId = dfhack.world.GetCurrentSiteId() + if fortId == -1 then + qerror('could not find current site') + return false + end + + return true +end argparse.processArgsGetopt({...}, { {"h", "help", handler=function() options.help = true end}, {"a", "all", handler=function() options.deport = true end}, }) -if options.help then - print(dfhack.script_help()) - return -end +pass = initChecks() +if not pass then return end main() From 414111e31a0d301076b79a279350fc3200812808 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 28 Jan 2025 22:40:39 +0800 Subject: [PATCH 03/45] Add monarch handling logic --- emigrate-nobles.lua | 109 +++++++++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index 7af10c5a95..e3da35ce1f 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -14,61 +14,85 @@ local options = { help = false } -fortId=nil +fort = nil +civ = nil +capital = nil + +-- adapted from Units::get_land_title(np) +function findSiteOfRule(np) + site = nil + civ = np.entity -- lawmakers seem to be all civ-level positions + assignments = civ.positions.assignments + for _, link in ipairs(civ.site_links) do + if not link.flags.land_for_holding then goto continue end + if link.position_profile_id ~= np.assignment.id then goto continue end -function isNoble(unit) - local nps = dfhack.units.getNoblePositions(unit) or {} - local isLawMaker = false + site = df.world_site.find(link.target) + break + ::continue:: + end - local noblePos, nobleName + return site +end - for _, np in ipairs(nps) do - pos = np.position - if pos.flags.IS_LAW_MAKER then - isLawMaker = true - noblePos = np - nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) +---comment +---@return df.world_site|nil +function findCapital(civ) + local capital = nil + for _, link in ipairs(civ.site_links) do + siteId = link.target + if link.flags.capital then + capital = df.world_site.find(siteId) break end end - if not isLawMaker then return false end - - civ = noblePos.entity -- lawmakers seem to be all civ-level positions - assignments = civ.positions.assignments - for _, link in ipairs(civ.site_links) do - siteId = link.target - posProfId = link.position_profile_id - if posProfId < 0 then goto continue end + return capital +end - assignment = assignments[posProfId] - if assignment.id ~= noblePos.assignment.id then goto continue end +function addIfRulesOtherSite(unit, freeloaders) + local nps = dfhack.units.getNoblePositions(unit) or {} + local noblePos = nil + for _, np in ipairs(nps) do + if np.position.flags.IS_LAW_MAKER then + noblePos = np + break + end + end - site = df.world_site.find(siteId) - siteName = dfhack.translation.translateName(site.name, true, true) + if noblePos == nil then return end -- unit is not nobility - if siteId == fortId then - print(nobleName.." holds a position in this fortress - "..siteName) - goto continue + -- Monarchs do not seem to have an world_site associated to them (?) + if noblePos.position.code == "MONARCH" then + if capital.id ~= fort.id then + freeloaders[unit.id] = capital end - - print(nobleName.." is lord of "..siteName) - ::continue:: + return end - return isLawMaker + name = dfhack.units.getReadableName(unit) + -- Logic for non-monarch nobility (dukes, counts, barons) + site = findSiteOfRule(noblePos) + if site == nil then qerror("could not find land of "..name) end + + if site.id == fort.id then return end -- noble rules current fort + freeloaders[unit.id] = site end function main() - fort = df.historical_entity.find(fortId) - fortName = dfhack.translation.translateName(fort.name, true) - print("Current fort is "..fortName) - + freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do - if dfhack.units.isDead(unit) then goto continue end - isNoble(unit) + if dfhack.units.isDead(unit) or not dfhack.units.isSane(unit) then goto continue end + addIfRulesOtherSite(unit, freeloaders) ::continue:: end + + for unitId, site in pairs(freeloaders) do + unit = df.unit.find(unitId) + unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) + siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) + print(unitName.." is lord of "..siteName) + end end function initChecks() @@ -82,11 +106,14 @@ function initChecks() return false end - fortId = dfhack.world.GetCurrentSiteId() - if fortId == -1 then - qerror('could not find current site') - return false - end + fort = dfhack.world.getCurrentSite() + if fort == nil then qerror("could not find current site") end + + civ = df.historical_entity.find(df.global.plotinfo.civ_id) + if civ == nil then qerror("could not find current civ") end + + capital = findCapital(civ) + if capital == nil then qerror("could not find capital") end return true end From 16f5939f96a081fd8538ce623f06281349f5fc90 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Wed, 29 Jan 2025 00:19:56 +0800 Subject: [PATCH 04/45] Add emigration logic --- emigrate-nobles.lua | 146 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 125 insertions(+), 21 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index e3da35ce1f..ae8c1a9b11 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -11,18 +11,17 @@ Planned modes/options: - i/import = find and invite heir to fortress (need a better name) ]]-- local options = { - help = false + help = false, } fort = nil civ = nil capital = nil --- adapted from Units::get_land_title(np) +-- adapted from Units::get_land_title() function findSiteOfRule(np) - site = nil - civ = np.entity -- lawmakers seem to be all civ-level positions - assignments = civ.positions.assignments + local site = nil + local civ = np.entity -- lawmakers seem to be all civ-level positions for _, link in ipairs(civ.site_links) do if not link.flags.land_for_holding then goto continue end if link.position_profile_id ~= np.assignment.id then goto continue end @@ -35,14 +34,12 @@ function findSiteOfRule(np) return site end ----comment ---@return df.world_site|nil function findCapital(civ) local capital = nil for _, link in ipairs(civ.site_links) do - siteId = link.target if link.flags.capital then - capital = df.world_site.find(siteId) + capital = df.world_site.find(link.target) break end end @@ -50,7 +47,7 @@ function findCapital(civ) return capital end -function addIfRulesOtherSite(unit, freeloaders) +function addNobleOfOtherSite(unit, nobleList) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil for _, np in ipairs(nps) do @@ -65,33 +62,141 @@ function addIfRulesOtherSite(unit, freeloaders) -- Monarchs do not seem to have an world_site associated to them (?) if noblePos.position.code == "MONARCH" then if capital.id ~= fort.id then - freeloaders[unit.id] = capital + table.insert(nobleList, {id = unit.id, site = capital}) end return end - name = dfhack.units.getReadableName(unit) + local name = dfhack.units.getReadableName(unit) -- Logic for non-monarch nobility (dukes, counts, barons) - site = findSiteOfRule(noblePos) + local site = findSiteOfRule(noblePos) if site == nil then qerror("could not find land of "..name) end if site.id == fort.id then return end -- noble rules current fort - freeloaders[unit.id] = site + table.insert(nobleList, {id = unit.id, site = site}) +end + +-- adapted from emigration::desert() +function emigrate(nobleId, site) + local unit = df.unit.find(nobleId) + local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) + local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) + print(unitName.." <"..siteName..">") + + unit.following = nil + local histFigId = unit.hist_figure_id + local histFig = df.historical_figure.find(unit.hist_figure_id) + local fortEnt = df.global.plotinfo.main.fortress_entity + + unit.civ_id = civ.id + unit.flags1.forest = true + unit.flags2.visitor = true + unit.animal.leave_countdown = 2 + + -- free owned rooms + for i = #unit.owned_buildings-1, 0, -1 do + local tmp = df.building.find(unit.owned_buildings[i].id) + dfhack.buildings.setOwner(tmp, nil) + end + + -- remove from workshop profiles + for _, bld in ipairs(df.global.world.buildings.other.WORKSHOP_ANY) do + for k, v in ipairs(bld.profile.permitted_workers) do + if v == unit.id then + bld.profile.permitted_workers:erase(k) + break + end + end + end + for _, bld in ipairs(df.global.world.buildings.other.FURNACE_ANY) do + for k, v in ipairs(bld.profile.permitted_workers) do + if v == unit.id then + bld.profile.permitted_workers:erase(k) + break + end + end + end + + -- disassociate from work details + for _, detail in ipairs(df.global.plotinfo.labor_info.work_details) do + for k, v in ipairs(detail.assigned_units) do + if v == unit.id then + detail.assigned_units:erase(k) + break + end + end + end + + -- unburrow + for _, burrow in ipairs(df.global.plotinfo.burrows.list) do + dfhack.burrows.setAssignedUnit(burrow, unit, false) + end + + -- erase the unit from the fortress entity + for k,v in ipairs(fortEnt.histfig_ids) do + if v == histFigId then + df.global.plotinfo.main.fortress_entity.histfig_ids:erase(k) + break + end + end + for k,v in ipairs(fortEnt.hist_figures) do + if v.id == histFigId then + df.global.plotinfo.main.fortress_entity.hist_figures:erase(k) + break + end + end + for k,v in ipairs(fortEnt.nemesis) do + if v.figure.id == histFigId then + df.global.plotinfo.main.fortress_entity.nemesis:erase(k) + df.global.plotinfo.main.fortress_entity.nemesis_ids:erase(k) + break + end + end + + -- remove the old entity link and create new one to indicate former membership + histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = fortEnt.id, link_strength = 100}) + for k,v in ipairs(histFig.entity_links) do + if v._type == df.histfig_entity_link_memberst and v.entity_id == fortEnt.id then + histFig.entity_links:erase(k) + break + end + end + + -- have unit join site government + local newEntId = site.cur_owner_id + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = newEntId, link_strength = 100}) + + -- have unit join new site + local newSiteId = site.id + local newEntity = df.historical_entity.find(newEntId) + newEntity.histfig_ids:insert('#', histFigId) + newEntity.hist_figures:insert('#', histFig) + local hf_event_id = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, civ = newEntId, histfig = histFigId, link_type = 0}) + + local hf_event_id = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, hfid = histFigId, state = 1, reason = -1, site = newSiteId}) end function main() freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do if dfhack.units.isDead(unit) or not dfhack.units.isSane(unit) then goto continue end - addIfRulesOtherSite(unit, freeloaders) + addNobleOfOtherSite(unit, freeloaders) ::continue:: end - for unitId, site in pairs(freeloaders) do - unit = df.unit.find(unitId) - unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) - siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) - print(unitName.." is lord of "..siteName) + -- for index, record in pairs(freeloaders) do + -- unit = df.unit.find(record.id) + -- unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) + -- siteName = dfhack.df2console(dfhack.translation.translateName(record.site.name, true)) + -- print(index..": "..unitName.." <"..siteName..">") + -- end + + for i, record in pairs(freeloaders) do + if i == 2 then emigrate(record.id, record.site) end end end @@ -119,8 +224,7 @@ function initChecks() end argparse.processArgsGetopt({...}, { - {"h", "help", handler=function() options.help = true end}, - {"a", "all", handler=function() options.deport = true end}, + {"h", "help", handler=function() options.help = true end} }) pass = initChecks() From c110f250f76c9d37609dcb390edccc9ae36c9224 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Wed, 29 Jan 2025 00:33:56 +0800 Subject: [PATCH 05/45] Add announcement --- emigrate-nobles.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index ae8c1a9b11..ded848fe6f 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -178,6 +178,11 @@ function emigrate(nobleId, site) local hf_event_id = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, hfid = histFigId, state = 1, reason = -1, site = newSiteId}) + + -- announce the changes + local line = unitName .. " has left the settlement to govern " .. siteName + print(dfhack.df2console(line)) + dfhack.gui.showAnnouncement(line, COLOR_WHITE) end function main() @@ -195,8 +200,8 @@ function main() -- print(index..": "..unitName.." <"..siteName..">") -- end - for i, record in pairs(freeloaders) do - if i == 2 then emigrate(record.id, record.site) end + for _, record in pairs(freeloaders) do + emigrate(record.id, record.site) end end From 2d6e5883abf7beb88120dee2ef5bfd9061386b92 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Wed, 29 Jan 2025 22:11:35 +0800 Subject: [PATCH 06/45] Add options handling --- emigrate-nobles.lua | 166 +++++++++++++++++++++++++++++++------------- 1 file changed, 118 insertions(+), 48 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index ded848fe6f..b577adfa41 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -12,13 +12,17 @@ Planned modes/options: ]]-- local options = { help = false, + all = false, + unitId = -1, + list = false } -fort = nil -civ = nil -capital = nil +fort = nil ---@type df.world_site +civ = nil ---@type df.historical_entity +capital = nil ---@type df.world_site -- adapted from Units::get_land_title() +---@return df.world_site|nil function findSiteOfRule(np) local site = nil local civ = np.entity -- lawmakers seem to be all civ-level positions @@ -68,7 +72,7 @@ function addNobleOfOtherSite(unit, nobleList) end local name = dfhack.units.getReadableName(unit) - -- Logic for non-monarch nobility (dukes, counts, barons) + -- Logic for dukes, counts, barons local site = findSiteOfRule(noblePos) if site == nil then qerror("could not find land of "..name) end @@ -76,22 +80,35 @@ function addNobleOfOtherSite(unit, nobleList) table.insert(nobleList, {id = unit.id, site = site}) end --- adapted from emigration::desert() -function emigrate(nobleId, site) - local unit = df.unit.find(nobleId) - local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) - local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) - print(unitName.." <"..siteName..">") +---@param histFig df.historical_figure +---@param newSite df.world_site +local function addHistFigToSite(histFig, newSite) + -- have unit join site government + local siteGovId = newSite.cur_owner_id + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) + local histFigId = histFig.id - unit.following = nil - local histFigId = unit.hist_figure_id - local histFig = df.historical_figure.find(unit.hist_figure_id) - local fortEnt = df.global.plotinfo.main.fortress_entity + -- have unit join new site + local siteId = newSite.id + local siteGov = df.historical_entity.find(siteGovId) + if siteGov == nil then qerror("could not find site!") end - unit.civ_id = civ.id - unit.flags1.forest = true - unit.flags2.visitor = true - unit.animal.leave_countdown = 2 + siteGov.histfig_ids:insert('#', histFigId) + siteGov.hist_figures:insert('#', histFig) + local hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGovId, histfig = histFigId, link_type = 0}) + + local hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) +end + +---@param unit df.unit +---@param histFig df.historical_figure +---@param oldSite df.historical_entity +local function removeUnitFromSiteEntity(unit, histFig, oldSite) + local histFigId = histFig.id -- free owned rooms for i = #unit.owned_buildings-1, 0, -1 do @@ -133,19 +150,19 @@ function emigrate(nobleId, site) end -- erase the unit from the fortress entity - for k,v in ipairs(fortEnt.histfig_ids) do + for k,v in ipairs(oldSite.histfig_ids) do if v == histFigId then df.global.plotinfo.main.fortress_entity.histfig_ids:erase(k) break end end - for k,v in ipairs(fortEnt.hist_figures) do + for k,v in ipairs(oldSite.hist_figures) do if v.id == histFigId then df.global.plotinfo.main.fortress_entity.hist_figures:erase(k) break end end - for k,v in ipairs(fortEnt.nemesis) do + for k,v in ipairs(oldSite.nemesis) do if v.figure.id == histFigId then df.global.plotinfo.main.fortress_entity.nemesis:erase(k) df.global.plotinfo.main.fortress_entity.nemesis_ids:erase(k) @@ -154,54 +171,88 @@ function emigrate(nobleId, site) end -- remove the old entity link and create new one to indicate former membership - histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = fortEnt.id, link_strength = 100}) + histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = oldSite.id, link_strength = 100}) for k,v in ipairs(histFig.entity_links) do - if v._type == df.histfig_entity_link_memberst and v.entity_id == fortEnt.id then + if v._type == df.histfig_entity_link_memberst and v.entity_id == oldSite.id then histFig.entity_links:erase(k) break end end +end - -- have unit join site government - local newEntId = site.cur_owner_id - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = newEntId, link_strength = 100}) +-- adapted from emigration::desert() +---@param unit df.unit +---@param toSite df.world_site +function emigrate(unit, toSite) + local histFig = df.historical_figure.find(unit.hist_figure_id) + if histFig == nil then qerror("could not find histfig!") end - -- have unit join new site - local newSiteId = site.id - local newEntity = df.historical_entity.find(newEntId) - newEntity.histfig_ids:insert('#', histFigId) - newEntity.hist_figures:insert('#', histFig) - local hf_event_id = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, civ = newEntId, histfig = histFigId, link_type = 0}) + local fortEnt = df.global.plotinfo.main.fortress_entity - local hf_event_id = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, hfid = histFigId, state = 1, reason = -1, site = newSiteId}) + unit.following = nil + unit.civ_id = civ.id + unit.flags1.forest = true + unit.flags2.visitor = true + unit.animal.leave_countdown = 2 + + removeUnitFromSiteEntity(unit, histFig, fortEnt) + addHistFigToSite(histFig, toSite) -- announce the changes - local line = unitName .. " has left the settlement to govern " .. siteName - print(dfhack.df2console(line)) + local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) + local siteName = dfhack.df2console(dfhack.translation.translateName(toSite.name, true)) + local line = unitName .. " has left to govern " .. siteName .. "." + print("[+] "..dfhack.df2console(line)) dfhack.gui.showAnnouncement(line, COLOR_WHITE) end +function listNoblesFound(nobleList) + for _, record in pairs(nobleList) do + local unit = df.unit.find(record.id) + local site = record.site + if unit == nil then qerror("could not find unit!") end + + local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) + local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) + print(unit.id..": "..nobleName.." - to be sent to "..siteName) + end +end + function main() - freeloaders = {} + local freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do + if options.unitId ~= -1 and unit.id ~= options.unitId then goto continue end if dfhack.units.isDead(unit) or not dfhack.units.isSane(unit) then goto continue end + addNobleOfOtherSite(unit, freeloaders) ::continue:: end - -- for index, record in pairs(freeloaders) do - -- unit = df.unit.find(record.id) - -- unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) - -- siteName = dfhack.df2console(dfhack.translation.translateName(record.site.name, true)) - -- print(index..": "..unitName.." <"..siteName..">") - -- end + if #freeloaders == 0 then + if options.unitId ~= -1 then + print("No eligible nobles to be emigrated.") + else + print("No eligible nobles found with ID = "..options.unitId) + end + end + + if options.list then + listNoblesFound(freeloaders) + return + end for _, record in pairs(freeloaders) do - emigrate(record.id, record.site) + local noble = df.unit.find(record.id) + local site = record.site + if noble == nil then qerror("could not find unit!") end + + if noble.military.squad_id ~= -1 then + local squadName = dfhack.military.getSquadName(noble.military.squad_id) + local nobleName = dfhack.units.getReadableName(noble) + print("[x] "..nobleName.." is a soldier of "..squadName..". Unassign him from the squad and try again.") + else + emigrate(noble, site) + end end end @@ -225,11 +276,30 @@ function initChecks() capital = findCapital(civ) if capital == nil then qerror("could not find capital") end + if options.list then return true end -- list option does not require unit options + + local noOptions = options.unitId == -1 and not options.all + if noOptions then + print("No options selected, defaulting to list mode.") + options.list = true + return true + end + + local invalidUnit = options.unitId ~= -1 and options.all + if invalidUnit then qerror("Either specify one unit or all.") end + return true end +------------------------------ +-- [[ SCRIPT STARTS HERE ]] -- +------------------------------ + argparse.processArgsGetopt({...}, { - {"h", "help", handler=function() options.help = true end} + {"h", "help", handler=function() options.help = true end}, + {"a", "all", handler=function() options.all = true end}, + {"u", "unit", hasArg=true, handler=function(id) options.unitId = tonumber(id) end}, + {"l", "list", handler=function() options.list = true end} }) pass = initChecks() From fe549d99d170ef31bc66a4ba9c7c6faafd8dff1e Mon Sep 17 00:00:00 2001 From: yg-ong Date: Wed, 29 Jan 2025 22:36:22 +0800 Subject: [PATCH 07/45] Add documentation --- docs/emigrate-nobles.rst | 28 ++++++++++++++++++++++++---- emigrate-nobles.lua | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 23975a2fa4..c44ac912ca 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -2,15 +2,35 @@ emigrate-nobles ========== .. dfhack-tool:: - :summary: Allow inherited nobles to emigrate from fort for their rightful land. + :summary: Allow inherited nobles to emigrate from fort to their site of governance. :tags: fort units Tired of inherited nobility freeloading off your fortress making inane demands? Use this tool -to find them and have them (willingly) emigrate to their rightful land. +to have them (willingly) emigrate to their rightful lands. + +The unit must not be assigned to a squad. + +Warning! Nobles will not surrender any assigned items, be sure to unassign your artefacts before +using this tool. Usage ----- -:: +``emigrate-nobles --list`` + List all nobles that do not rule your fortress +``emigrate-nobles --all`` + Emigrate all nobles that do not rule your fortress +``emigrate-nobles --unit `` + Emigrate a noble matching the specified unit ID that does not rule your fortress + +Options +------- - emigrate-nobles [-h] +``-h``, ``--help`` + View help +``-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/emigrate-nobles.lua b/emigrate-nobles.lua index b577adfa41..5ffea6f35c 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -249,7 +249,7 @@ function main() if noble.military.squad_id ~= -1 then local squadName = dfhack.military.getSquadName(noble.military.squad_id) local nobleName = dfhack.units.getReadableName(noble) - print("[x] "..nobleName.." is a soldier of "..squadName..". Unassign him from the squad and try again.") + print("[-] "..nobleName.." is a soldier of "..squadName..". Unassign him from the squad and try again.") else emigrate(noble, site) end From 9777b1a39c744db7bdf8338694e5d9dc291b7706 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Wed, 29 Jan 2025 23:40:59 +0800 Subject: [PATCH 08/45] Attempt to fix units stuck in social activities --- emigrate-nobles.lua | 61 +++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index 5ffea6f35c..1fed596ee1 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -1,15 +1,13 @@ --Deport resident nobles of other lands and summon your rightful lord if he/she is elsewhere -local argparse = require("argparse") - --[[ -Planned modes/options: - - d/deport = remove inherited freeloaders - - list = list possible nobles to evict - - index = specific noble to kick - - all = kick all listed nobles - - i/import = find and invite heir to fortress (need a better name) +TODO: + * Feature: have rightful ruler immigrate to fort if off-site + * QoL: make sure items are unassigned ]]-- + +local argparse = require("argparse") + local options = { help = false, all = false, @@ -23,7 +21,7 @@ capital = nil ---@type df.world_site -- adapted from Units::get_land_title() ---@return df.world_site|nil -function findSiteOfRule(np) +local function findSiteOfRule(np) local site = nil local civ = np.entity -- lawmakers seem to be all civ-level positions for _, link in ipairs(civ.site_links) do @@ -39,7 +37,7 @@ function findSiteOfRule(np) end ---@return df.world_site|nil -function findCapital(civ) +local function findCapital(civ) local capital = nil for _, link in ipairs(civ.site_links) do if link.flags.capital then @@ -61,7 +59,7 @@ function addNobleOfOtherSite(unit, nobleList) end end - if noblePos == nil then return end -- unit is not nobility + if not noblePos then return end -- unit is not nobility -- Monarchs do not seem to have an world_site associated to them (?) if noblePos.position.code == "MONARCH" then @@ -74,7 +72,7 @@ function addNobleOfOtherSite(unit, nobleList) local name = dfhack.units.getReadableName(unit) -- Logic for dukes, counts, barons local site = findSiteOfRule(noblePos) - if site == nil then qerror("could not find land of "..name) end + if not site then qerror("could not find land of "..name) end if site.id == fort.id then return end -- noble rules current fort table.insert(nobleList, {id = unit.id, site = site}) @@ -91,7 +89,7 @@ local function addHistFigToSite(histFig, newSite) -- have unit join new site local siteId = newSite.id local siteGov = df.historical_entity.find(siteGovId) - if siteGov == nil then qerror("could not find site!") end + if not siteGov then qerror("could not find site!") end siteGov.histfig_ids:insert('#', histFigId) siteGov.hist_figures:insert('#', histFig) @@ -189,12 +187,22 @@ function emigrate(unit, toSite) local fortEnt = df.global.plotinfo.main.fortress_entity + -- mark for leaving unit.following = nil - unit.civ_id = civ.id + unit.civ_id = civ.id -- should be redundant but oh well unit.flags1.forest = true unit.flags2.visitor = true unit.animal.leave_countdown = 2 + -- remove current job + if unit.job.current_job then dfhack.job.removeJob(unit.job.current_job) end + + -- break up any social activities + for _, actId in ipairs(unit.social_activities) do + local act = df.activity_entry.find(actId) + if act then act.events[0].flags.dismissed = true end + end + removeUnitFromSiteEntity(unit, histFig, fortEnt) addHistFigToSite(histFig, toSite) @@ -206,11 +214,20 @@ function emigrate(unit, toSite) dfhack.gui.showAnnouncement(line, COLOR_WHITE) end +---@param unit df.unit +local function inStrangeMood(unit) + local job = unit.job.current_job + if not job then return false end + + local jobType = job.job_type -- taken from notifications::for_moody() + return df.job_type_class[df.job_type.attrs[jobType].type] == 'StrangeMood' +end + function listNoblesFound(nobleList) for _, record in pairs(nobleList) do local unit = df.unit.find(record.id) local site = record.site - if unit == nil then qerror("could not find unit!") end + if not unit then qerror("could not find unit!") end local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) @@ -244,12 +261,14 @@ function main() for _, record in pairs(freeloaders) do local noble = df.unit.find(record.id) local site = record.site - if noble == nil then qerror("could not find unit!") end + if not noble then qerror("could not find unit!") end + local nobleName = dfhack.units.getReadableName(noble) if noble.military.squad_id ~= -1 then local squadName = dfhack.military.getSquadName(noble.military.squad_id) - local nobleName = dfhack.units.getReadableName(noble) - print("[-] "..nobleName.." is a soldier of "..squadName..". Unassign him from the squad and try again.") + print("[-] "..nobleName.." is a soldier of "..squadName..". Unassign from squad and try again.") + elseif inStrangeMood(noble) then + print("[-] "..nobleName.." is in a strange mood! Leave alone for now.") else emigrate(noble, site) end @@ -268,13 +287,13 @@ function initChecks() end fort = dfhack.world.getCurrentSite() - if fort == nil then qerror("could not find current site") end + if not fort then qerror("could not find current site") end civ = df.historical_entity.find(df.global.plotinfo.civ_id) - if civ == nil then qerror("could not find current civ") end + if not civ then qerror("could not find current civ") end capital = findCapital(civ) - if capital == nil then qerror("could not find capital") end + if not capital then qerror("could not find capital") end if options.list then return true end -- list option does not require unit options From 16544b2741f75dd07421bebbed10ad0442cdbb8f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Thu, 30 Jan 2025 19:36:39 +0800 Subject: [PATCH 09/45] Fix title underline --- docs/emigrate-nobles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index c44ac912ca..154d4cac61 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -1,5 +1,5 @@ emigrate-nobles -========== +=============== .. dfhack-tool:: :summary: Allow inherited nobles to emigrate from fort to their site of governance. From 537af8efd00ba2b11ddcf7a01f36d116db0d314f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Thu, 30 Jan 2025 19:50:37 +0800 Subject: [PATCH 10/45] Simplify logic --- emigrate-nobles.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index 1fed596ee1..55519a040e 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -64,7 +64,7 @@ function addNobleOfOtherSite(unit, nobleList) -- Monarchs do not seem to have an world_site associated to them (?) if noblePos.position.code == "MONARCH" then if capital.id ~= fort.id then - table.insert(nobleList, {id = unit.id, site = capital}) + table.insert(nobleList, {unit = unit, site = capital}) end return end @@ -75,7 +75,7 @@ function addNobleOfOtherSite(unit, nobleList) if not site then qerror("could not find land of "..name) end if site.id == fort.id then return end -- noble rules current fort - table.insert(nobleList, {id = unit.id, site = site}) + table.insert(nobleList, {unit = unit, site = site}) end ---@param histFig df.historical_figure @@ -225,9 +225,8 @@ end function listNoblesFound(nobleList) for _, record in pairs(nobleList) do - local unit = df.unit.find(record.id) + local unit = record.unit local site = record.site - if not unit then qerror("could not find unit!") end local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) @@ -259,9 +258,8 @@ function main() end for _, record in pairs(freeloaders) do - local noble = df.unit.find(record.id) + local noble = record.unit local site = record.site - if not noble then qerror("could not find unit!") end local nobleName = dfhack.units.getReadableName(noble) if noble.military.squad_id ~= -1 then From ebc3976d6736946b4479cf02ce1a548989b6306f Mon Sep 17 00:00:00 2001 From: Ong Ying Gao <52755148+ong-yinggao98@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:52:49 +0800 Subject: [PATCH 11/45] Update docs/emigrate-nobles.rst Co-authored-by: Myk --- docs/emigrate-nobles.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 154d4cac61..8f8c0563cb 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -16,11 +16,19 @@ using this tool. Usage ----- +:: + + emigrate-nobles [--list] + emigrate-nobles + +Examples +-------- + ``emigrate-nobles --list`` List all nobles that do not rule your fortress ``emigrate-nobles --all`` Emigrate all nobles that do not rule your fortress -``emigrate-nobles --unit `` +``emigrate-nobles --unit 34534`` Emigrate a noble matching the specified unit ID that does not rule your fortress Options From 4272ee7ec754489132198d7cbf24e7cd07b45cc9 Mon Sep 17 00:00:00 2001 From: Ong Ying Gao <52755148+ong-yinggao98@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:53:03 +0800 Subject: [PATCH 12/45] Update docs/emigrate-nobles.rst Co-authored-by: Myk --- docs/emigrate-nobles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 8f8c0563cb..90d3e21bca 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -40,5 +40,5 @@ Options List all nobles that do not rule your fortress ``-a``, ``--all`` Emigrate all nobles do not rule your fortress -``-u ``, ``--unit `` +``-u``, ``--unit `` Emigrate noble matching specified unit ID that does not rule your fortress From 8de561ee6fd4d84b76e336ade594d1b9dc069c35 Mon Sep 17 00:00:00 2001 From: Ong Ying Gao <52755148+ong-yinggao98@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:53:24 +0800 Subject: [PATCH 13/45] Update docs/emigrate-nobles.rst Co-authored-by: Myk --- docs/emigrate-nobles.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 90d3e21bca..2259f349a1 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -34,8 +34,6 @@ Examples Options ------- -``-h``, ``--help`` - View help ``-l``, ``--list`` List all nobles that do not rule your fortress ``-a``, ``--all`` From 18a28d53a5f3d33790ae8abaa4f7dc4cc4d5caa0 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Thu, 30 Jan 2025 19:55:01 +0800 Subject: [PATCH 14/45] Update docs/emigrate-nobles.rst --- docs/emigrate-nobles.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 2259f349a1..53ecf9124f 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -10,8 +10,8 @@ to have them (willingly) emigrate to their rightful lands. The unit must not be assigned to a squad. -Warning! Nobles will not surrender any assigned items, be sure to unassign your artefacts before -using this tool. +Warning! Nobles will not surrender any symbols of office before leaving, be sure to unassign +your artefacts before using this tool. Usage ----- From 3b40700f69f52226601f17a5f9047d1f61a90196 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Thu, 30 Jan 2025 22:58:34 +0800 Subject: [PATCH 15/45] Add functionality to remove from squad --- emigrate-nobles.lua | 71 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index 55519a040e..58a4fd16ca 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -83,6 +83,7 @@ end local function addHistFigToSite(histFig, newSite) -- have unit join site government local siteGovId = newSite.cur_owner_id + print("new site gov = "..siteGovId) histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) local histFigId = histFig.id @@ -100,6 +101,8 @@ local function addHistFigToSite(histFig, newSite) local hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) + + return siteGov end ---@param unit df.unit @@ -178,6 +181,63 @@ local function removeUnitFromSiteEntity(unit, histFig, oldSite) end end +---@param unit df.unit +local function removeUnitFromAnySquad(unit) + if unit.military.squad_id == -1 then return end + local fortEnt = df.global.plotinfo.main.fortress_entity + + -- remove from squad records + local squadId = unit.military.squad_id + local squadPos = unit.military.squad_position + local squad = df.squad.find(squadId) + if not squad then qerror("could not find squad") end + + squad.positions[squadPos].occupant = -1 + + -- remove from unit information + unit.military.squad_id = -1 + unit.military.squad_position = -1 + + -- remove assignment if captain or commander + local assignmentId = -1 + if squadPos == 0 then + for _, np in ipairs(dfhack.units.getNoblePositions(unit) or {}) do + if np.entity.id ~= fortEnt then goto continue end + if np.assignment.squad_id ~= squadId then goto continue end + + np.assignment.histfig = -1 + np.assignment.histfig2 = -1 + assignmentId = np.assignment.id + ::continue:: + end + end + + -- remove the old entity link and create new one to indicate former membership + local histFig = df.historical_figure.find(unit.hist_figure_id) + local yearOfPos = -1 + if not histFig then qerror("could not find histfig") end + for k,v in ipairs(histFig.entity_links) do + if df.histfig_entity_link_positionst:is_instance(v) + and v.assignment_id == assignmentId + and v.entity_id == fortEnt.id + then + histFig.entity_links:erase(k) -- unit was captain/commander + yearOfPos = v.start_year + elseif df.histfig_entity_link_squadst:is_instance(v) and v.squad_id == squadId then + histFig.entity_links:erase(k) -- unit was regular soldier + yearOfPos = v.start_year + else goto continue end + break + ::continue:: + end + + if assignmentId ~= -1 then -- unit was a captain/commander + histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_positionst, entity_id = fortEnt.id, assignment_id = assignmentId, start_year = yearOfPos, end_year = df.global.cur_year, link_strength = 100}) + else + histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_squadst, entity_id = fortEnt.id, squad_id = squadId, start_year = yearOfPos, end_year = df.global.cur_year, link_strength = 100}) + end +end + -- adapted from emigration::desert() ---@param unit df.unit ---@param toSite df.world_site @@ -186,6 +246,7 @@ function emigrate(unit, toSite) if histFig == nil then qerror("could not find histfig!") end local fortEnt = df.global.plotinfo.main.fortress_entity + removeUnitFromAnySquad(unit) -- self-explanatory -- mark for leaving unit.following = nil @@ -204,12 +265,13 @@ function emigrate(unit, toSite) end removeUnitFromSiteEntity(unit, histFig, fortEnt) - addHistFigToSite(histFig, toSite) + local siteGov = addHistFigToSite(histFig, toSite) -- announce the changes local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) local siteName = dfhack.df2console(dfhack.translation.translateName(toSite.name, true)) - local line = unitName .. " has left to govern " .. siteName .. "." + local govName = dfhack.df2console(dfhack.translation.translateName(siteGov.name, true)) + local line = unitName .. " has left to join " ..govName.. " as lord of " .. siteName .. "." print("[+] "..dfhack.df2console(line)) dfhack.gui.showAnnouncement(line, COLOR_WHITE) end @@ -262,10 +324,7 @@ function main() local site = record.site local nobleName = dfhack.units.getReadableName(noble) - if noble.military.squad_id ~= -1 then - local squadName = dfhack.military.getSquadName(noble.military.squad_id) - print("[-] "..nobleName.." is a soldier of "..squadName..". Unassign from squad and try again.") - elseif inStrangeMood(noble) then + if inStrangeMood(noble) then print("[-] "..nobleName.." is in a strange mood! Leave alone for now.") else emigrate(noble, site) From c521f5fe51372117a522ecb712c861544706955b Mon Sep 17 00:00:00 2001 From: yg-ong Date: Fri, 7 Feb 2025 20:53:56 +0800 Subject: [PATCH 16/45] Remove squad removal logic --- docs/emigrate-nobles.rst | 8 +++++--- emigrate-nobles.lua | 41 +++++++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst index 53ecf9124f..eba1071b70 100644 --- a/docs/emigrate-nobles.rst +++ b/docs/emigrate-nobles.rst @@ -8,10 +8,12 @@ emigrate-nobles Tired of inherited nobility freeloading off your fortress making inane demands? Use this tool to have them (willingly) emigrate to their rightful lands. -The unit must not be assigned to a squad. +Nobles assigned to squads will not be emigrated. Remove them from the squad before retrying. -Warning! Nobles will not surrender any symbols of office before leaving, be sure to unassign -your artefacts before using this tool. +.. warning:: + + Emigrated nobles will not surrender any symbols of office before leaving. + Unassign your artefacts before using this tool. Usage ----- diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index 58a4fd16ca..e12d3299e7 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -182,7 +182,7 @@ local function removeUnitFromSiteEntity(unit, histFig, oldSite) end ---@param unit df.unit -local function removeUnitFromAnySquad(unit) +local function removeUnitFromSquad(unit) if unit.military.squad_id == -1 then return end local fortEnt = df.global.plotinfo.main.fortress_entity @@ -200,17 +200,18 @@ local function removeUnitFromAnySquad(unit) -- remove assignment if captain or commander local assignmentId = -1 - if squadPos == 0 then - for _, np in ipairs(dfhack.units.getNoblePositions(unit) or {}) do - if np.entity.id ~= fortEnt then goto continue end - if np.assignment.squad_id ~= squadId then goto continue end - - np.assignment.histfig = -1 - np.assignment.histfig2 = -1 - assignmentId = np.assignment.id - ::continue:: - end + if squadPos ~= 0 then goto next end + for _, np in ipairs(dfhack.units.getNoblePositions(unit) or {}) do + if np.entity.id ~= fortEnt then goto continue end + if np.assignment.squad_id ~= squadId then goto continue end + + np.assignment.histfig = -1 + np.assignment.histfig2 = -1 + assignmentId = np.assignment.id + break + ::continue:: end + ::next:: -- remove the old entity link and create new one to indicate former membership local histFig = df.historical_figure.find(unit.hist_figure_id) @@ -246,7 +247,6 @@ function emigrate(unit, toSite) if histFig == nil then qerror("could not find histfig!") end local fortEnt = df.global.plotinfo.main.fortress_entity - removeUnitFromAnySquad(unit) -- self-explanatory -- mark for leaving unit.following = nil @@ -285,6 +285,11 @@ local function inStrangeMood(unit) return df.job_type_class[df.job_type.attrs[jobType].type] == 'StrangeMood' end +---@param unit df.unit +local function isSoldier(unit) + return unit.military.squad_id ~= -1 +end + function listNoblesFound(nobleList) for _, record in pairs(nobleList) do local unit = record.unit @@ -292,7 +297,15 @@ function listNoblesFound(nobleList) local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) - print(unit.id..": "..nobleName.." - to be sent to "..siteName) + local unitMsg = unit.id..": "..nobleName.." to be sent to "..siteName + if isSoldier(unit) then + local squad = df.squad.find(unit.military.squad_id) + if not squad then qerror("could not find unit's squad") end + local squadName = dfhack.df2console(dfhack.translation.translateName(squad.name, true)) + unitMsg = unitMsg.." [!] Unit is soldier in "..squadName + end + + print(unitMsg) end end @@ -326,6 +339,8 @@ function main() local nobleName = dfhack.units.getReadableName(noble) if inStrangeMood(noble) then print("[-] "..nobleName.." is in a strange mood! Leave alone for now.") + elseif isSoldier(noble) then + print("[-] "..nobleName.." is in a squad! Unassign the unit before proceeding.") else emigrate(noble, site) end From fdb16986b0c8dac173f3b35d5ec44537f465fd7f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 00:04:57 +0800 Subject: [PATCH 17/45] Remove unused function --- emigrate-nobles.lua | 58 --------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/emigrate-nobles.lua b/emigrate-nobles.lua index e12d3299e7..f6626afa19 100644 --- a/emigrate-nobles.lua +++ b/emigrate-nobles.lua @@ -181,64 +181,6 @@ local function removeUnitFromSiteEntity(unit, histFig, oldSite) end end ----@param unit df.unit -local function removeUnitFromSquad(unit) - if unit.military.squad_id == -1 then return end - local fortEnt = df.global.plotinfo.main.fortress_entity - - -- remove from squad records - local squadId = unit.military.squad_id - local squadPos = unit.military.squad_position - local squad = df.squad.find(squadId) - if not squad then qerror("could not find squad") end - - squad.positions[squadPos].occupant = -1 - - -- remove from unit information - unit.military.squad_id = -1 - unit.military.squad_position = -1 - - -- remove assignment if captain or commander - local assignmentId = -1 - if squadPos ~= 0 then goto next end - for _, np in ipairs(dfhack.units.getNoblePositions(unit) or {}) do - if np.entity.id ~= fortEnt then goto continue end - if np.assignment.squad_id ~= squadId then goto continue end - - np.assignment.histfig = -1 - np.assignment.histfig2 = -1 - assignmentId = np.assignment.id - break - ::continue:: - end - ::next:: - - -- remove the old entity link and create new one to indicate former membership - local histFig = df.historical_figure.find(unit.hist_figure_id) - local yearOfPos = -1 - if not histFig then qerror("could not find histfig") end - for k,v in ipairs(histFig.entity_links) do - if df.histfig_entity_link_positionst:is_instance(v) - and v.assignment_id == assignmentId - and v.entity_id == fortEnt.id - then - histFig.entity_links:erase(k) -- unit was captain/commander - yearOfPos = v.start_year - elseif df.histfig_entity_link_squadst:is_instance(v) and v.squad_id == squadId then - histFig.entity_links:erase(k) -- unit was regular soldier - yearOfPos = v.start_year - else goto continue end - break - ::continue:: - end - - if assignmentId ~= -1 then -- unit was a captain/commander - histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_positionst, entity_id = fortEnt.id, assignment_id = assignmentId, start_year = yearOfPos, end_year = df.global.cur_year, link_strength = 100}) - else - histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_squadst, entity_id = fortEnt.id, squad_id = squadId, start_year = yearOfPos, end_year = df.global.cur_year, link_strength = 100}) - end -end - -- adapted from emigration::desert() ---@param unit df.unit ---@param toSite df.world_site From c4ac1d6c5a5a61337280dca13c46b2e2f87a8a1f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 02:14:27 +0800 Subject: [PATCH 18/45] Move emigrate-nobles into emigration --- emigration.lua | 4 + .../emigration/emigrate-nobles.lua | 81 +++++++++++-------- 2 files changed, 50 insertions(+), 35 deletions(-) rename emigrate-nobles.lua => internal/emigration/emigrate-nobles.lua (86%) diff --git a/emigration.lua b/emigration.lua index ea494a7e36..e4f179f65b 100644 --- a/emigration.lua +++ b/emigration.lua @@ -2,6 +2,7 @@ --@enable = true local utils = require('utils') +local nobles = reqscript('internal/emigration/emigrate-nobles') local GLOBAL_KEY = 'emigration' -- used for state change hooks and persistence @@ -251,6 +252,9 @@ if args[1] == "enable" then state.enabled = true elseif args[1] == "disable" then state.enabled = false +elseif args[1] == "nobles" then + table.remove(args, 1) + nobles.run(args) else print('emigration is ' .. (state.enabled and 'enabled' or 'not enabled')) return diff --git a/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua similarity index 86% rename from emigrate-nobles.lua rename to internal/emigration/emigrate-nobles.lua index f6626afa19..1d2eeff656 100644 --- a/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -1,3 +1,5 @@ +--@module = true + --Deport resident nobles of other lands and summon your rightful lord if he/she is elsewhere --[[ @@ -9,15 +11,14 @@ TODO: local argparse = require("argparse") local options = { - help = false, all = false, unitId = -1, list = false } -fort = nil ---@type df.world_site -civ = nil ---@type df.historical_entity -capital = nil ---@type df.world_site +local fort = nil ---@type df.world_site +local playerCiv = nil ---@type df.historical_entity +local capital = nil ---@type df.world_site -- adapted from Units::get_land_title() ---@return df.world_site|nil @@ -38,18 +39,18 @@ end ---@return df.world_site|nil local function findCapital(civ) - local capital = nil + local civCapital = nil for _, link in ipairs(civ.site_links) do if link.flags.capital then - capital = df.world_site.find(link.target) + civCapital = df.world_site.find(link.target) break end end - return capital + return civCapital end -function addNobleOfOtherSite(unit, nobleList) +local function addNobleOfOtherSite(unit, nobleList) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil for _, np in ipairs(nps) do @@ -83,7 +84,6 @@ end local function addHistFigToSite(histFig, newSite) -- have unit join site government local siteGovId = newSite.cur_owner_id - print("new site gov = "..siteGovId) histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) local histFigId = histFig.id @@ -184,7 +184,7 @@ end -- adapted from emigration::desert() ---@param unit df.unit ---@param toSite df.world_site -function emigrate(unit, toSite) +local function emigrate(unit, toSite) local histFig = df.historical_figure.find(unit.hist_figure_id) if histFig == nil then qerror("could not find histfig!") end @@ -192,7 +192,7 @@ function emigrate(unit, toSite) -- mark for leaving unit.following = nil - unit.civ_id = civ.id -- should be redundant but oh well + unit.civ_id = playerCiv.id -- should be redundant but oh well unit.flags1.forest = true unit.flags2.visitor = true unit.animal.leave_countdown = 2 @@ -232,26 +232,28 @@ local function isSoldier(unit) return unit.military.squad_id ~= -1 end -function listNoblesFound(nobleList) +local function listNoblesFound(nobleList) for _, record in pairs(nobleList) do local unit = record.unit local site = record.site local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) - local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) - local unitMsg = unit.id..": "..nobleName.." to be sent to "..siteName + local unitMsg = unit.id..": "..nobleName if isSoldier(unit) then local squad = df.squad.find(unit.military.squad_id) if not squad then qerror("could not find unit's squad") end local squadName = dfhack.df2console(dfhack.translation.translateName(squad.name, true)) - unitMsg = unitMsg.." [!] Unit is soldier in "..squadName + unitMsg = "[!] "..unitMsg.." - soldier in "..squadName + else + local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) + unitMsg = unitMsg.." to be sent to "..siteName end print(unitMsg) end end -function main() +local function main() local freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do if options.unitId ~= -1 and unit.id ~= options.unitId then goto continue end @@ -262,7 +264,7 @@ function main() end if #freeloaders == 0 then - if options.unitId ~= -1 then + if options.unitId == -1 then print("No eligible nobles to be emigrated.") else print("No eligible nobles found with ID = "..options.unitId) @@ -289,12 +291,7 @@ function main() end end -function initChecks() - if options.help then - print(dfhack.script_help()) - return false - end - +local function initChecks() if not dfhack.world.isFortressMode() or not dfhack.isMapLoaded() then qerror('needs a loaded fortress map') return false @@ -303,10 +300,10 @@ function initChecks() fort = dfhack.world.getCurrentSite() if not fort then qerror("could not find current site") end - civ = df.historical_entity.find(df.global.plotinfo.civ_id) - if not civ then qerror("could not find current civ") end + playerCiv = df.historical_entity.find(df.global.plotinfo.civ_id) + if not playerCiv then qerror("could not find current civ") end - capital = findCapital(civ) + capital = findCapital(playerCiv) if not capital then qerror("could not find capital") end if options.list then return true end -- list option does not require unit options @@ -324,18 +321,32 @@ function initChecks() return true end +local function resetState() + options.all = false + options.unitId = -1 + options.list = false + + fort = nil + capital = nil + playerCiv = nil +end + ------------------------------ -- [[ SCRIPT STARTS HERE ]] -- ------------------------------ -argparse.processArgsGetopt({...}, { - {"h", "help", handler=function() options.help = true end}, - {"a", "all", handler=function() options.all = true end}, - {"u", "unit", hasArg=true, handler=function(id) options.unitId = tonumber(id) end}, - {"l", "list", handler=function() options.list = true end} -}) +function run(args) + argparse.processArgsGetopt(args, { + {"a", "all", handler=function() options.all = true end}, + {"u", "unit", hasArg=true, handler=function(id) options.unitId = tonumber(id) end}, + {"l", "list", handler=function() options.list = true end} + }) -pass = initChecks() -if not pass then return end + pass = initChecks() + if not pass then goto reset end -main() + main() + + ::reset:: + resetState() +end From b2ad9d1820248e3f48bdf61c9b9dac8d647a0b5d Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 02:22:59 +0800 Subject: [PATCH 19/45] Move emigrate-nobles.rst into emigration.rst --- docs/emigrate-nobles.rst | 44 ---------------------------------------- docs/emigration.rst | 34 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 44 deletions(-) delete mode 100644 docs/emigrate-nobles.rst diff --git a/docs/emigrate-nobles.rst b/docs/emigrate-nobles.rst deleted file mode 100644 index eba1071b70..0000000000 --- a/docs/emigrate-nobles.rst +++ /dev/null @@ -1,44 +0,0 @@ -emigrate-nobles -=============== - -.. dfhack-tool:: - :summary: Allow inherited nobles to emigrate from fort to their site of governance. - :tags: fort units - -Tired of inherited nobility freeloading off your fortress making inane demands? Use this tool -to have them (willingly) emigrate to their rightful lands. - -Nobles assigned to squads will not be emigrated. Remove them from the squad before retrying. - -.. warning:: - - Emigrated nobles will not surrender any symbols of office before leaving. - Unassign your artefacts before using this tool. - -Usage ------ - -:: - - emigrate-nobles [--list] - emigrate-nobles - -Examples --------- - -``emigrate-nobles --list`` - List all nobles that do not rule your fortress -``emigrate-nobles --all`` - Emigrate all nobles that do not rule your fortress -``emigrate-nobles --unit 34534`` - Emigrate a noble matching the specified unit ID that does not rule your fortress - -Options -------- - -``-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/emigration.rst b/docs/emigration.rst index 58a3d623e7..23a34ad97f 100644 --- a/docs/emigration.rst +++ b/docs/emigration.rst @@ -16,9 +16,43 @@ 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 will not be emigrated. +Remove them from the squad before retrying. + +.. warning:: + + Emigrated nobles will not surrender any symbols of office before leaving. + Unassign your artefacts before calling ``emigration nobles``. + Usage ----- :: enable emigration + emigration nobles [--list] + emigration nobles + +Examples +-------- + +``emigration nobles --list`` + List all nobles that do not rule your fortress +``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 From 07e633027480a08b574d33589c7408d41faa8baa Mon Sep 17 00:00:00 2001 From: Ong Ying Gao <52755148+ong-yinggao98@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:27:46 +0800 Subject: [PATCH 20/45] Update docs/emigration.rst Co-authored-by: Myk --- docs/emigration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/emigration.rst b/docs/emigration.rst index 23a34ad97f..d04e07e178 100644 --- a/docs/emigration.rst +++ b/docs/emigration.rst @@ -33,7 +33,7 @@ Usage enable emigration emigration nobles [--list] - emigration nobles + emigration nobles [] Examples -------- From 39dac77ac019384ea53709f42c1354d58e42e6c6 Mon Sep 17 00:00:00 2001 From: Ong Ying Gao <52755148+ong-yinggao98@users.noreply.github.com> Date: Sat, 8 Feb 2025 14:29:12 +0800 Subject: [PATCH 21/45] Update internal/emigration/emigrate-nobles.lua Co-authored-by: Myk --- internal/emigration/emigrate-nobles.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 1d2eeff656..b7f1664fef 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -342,11 +342,9 @@ function run(args) {"l", "list", handler=function() options.list = true end} }) - pass = initChecks() - if not pass then goto reset end - - main() + if initChecks() then + main() + end - ::reset:: resetState() end From a6d65e45e8c4e13cf91d0eaf64f1547b5d280d10 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 15:04:38 +0800 Subject: [PATCH 22/45] Remove module globals --- internal/emigration/emigrate-nobles.lua | 69 ++++++++++++------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index b7f1664fef..599f68823b 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -16,10 +16,6 @@ local options = { list = false } -local fort = nil ---@type df.world_site -local playerCiv = nil ---@type df.historical_entity -local capital = nil ---@type df.world_site - -- adapted from Units::get_land_title() ---@return df.world_site|nil local function findSiteOfRule(np) @@ -50,7 +46,11 @@ local function findCapital(civ) return civCapital end -local function addNobleOfOtherSite(unit, nobleList) +---@param unit df.unit +---@param nobleList { unit: df.unit, site: df.world_site }[] +---@param playerFort df.world_site +---@param civ df.historical_entity +local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil for _, np in ipairs(nps) do @@ -64,7 +64,8 @@ local function addNobleOfOtherSite(unit, nobleList) -- Monarchs do not seem to have an world_site associated to them (?) if noblePos.position.code == "MONARCH" then - if capital.id ~= fort.id then + local capital = findCapital(civ) + if capital and capital.id ~= playerFort.id then table.insert(nobleList, {unit = unit, site = capital}) end return @@ -75,7 +76,7 @@ local function addNobleOfOtherSite(unit, nobleList) local site = findSiteOfRule(noblePos) if not site then qerror("could not find land of "..name) end - if site.id == fort.id then return end -- noble rules current fort + if site.id == playerFort.id then return end -- noble rules current fort table.insert(nobleList, {unit = unit, site = site}) end @@ -182,9 +183,10 @@ local function removeUnitFromSiteEntity(unit, histFig, oldSite) end -- adapted from emigration::desert() ----@param unit df.unit ----@param toSite df.world_site -local function emigrate(unit, toSite) +---@param unit df.unit +---@param toSite df.world_site +---@param civ df.historical_entity +local function emigrate(unit, toSite, civ) local histFig = df.historical_figure.find(unit.hist_figure_id) if histFig == nil then qerror("could not find histfig!") end @@ -192,7 +194,7 @@ local function emigrate(unit, toSite) -- mark for leaving unit.following = nil - unit.civ_id = playerCiv.id -- should be redundant but oh well + unit.civ_id = civ.id -- should be redundant but oh well unit.flags1.forest = true unit.flags2.visitor = true unit.animal.leave_countdown = 2 @@ -253,22 +255,33 @@ local function listNoblesFound(nobleList) end end +local function printNoNobles() + if options.unitId == -1 then + print("No eligible nobles to be emigrated.") + else + print("No eligible nobles found with ID = "..options.unitId) + end +end + local function main() + local fort = dfhack.world.getCurrentSite() + if not fort then qerror("could not find current site") end + + local civ = df.historical_entity.find(df.global.plotinfo.civ_id) + if not civ then qerror("could not find current civ") end + + ---@type { unit: df.unit, site: df.world_site }[] local freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do if options.unitId ~= -1 and unit.id ~= options.unitId then goto continue end - if dfhack.units.isDead(unit) or not dfhack.units.isSane(unit) then goto continue end - addNobleOfOtherSite(unit, freeloaders) + addNobleOfOtherSite(unit, freeloaders, fort, civ) ::continue:: end if #freeloaders == 0 then - if options.unitId == -1 then - print("No eligible nobles to be emigrated.") - else - print("No eligible nobles found with ID = "..options.unitId) - end + printNoNobles() + return end if options.list then @@ -286,7 +299,7 @@ local function main() elseif isSoldier(noble) then print("[-] "..nobleName.." is in a squad! Unassign the unit before proceeding.") else - emigrate(noble, site) + emigrate(noble, site, civ) end end end @@ -294,18 +307,8 @@ end local function initChecks() if not dfhack.world.isFortressMode() or not dfhack.isMapLoaded() then qerror('needs a loaded fortress map') - return false end - fort = dfhack.world.getCurrentSite() - if not fort then qerror("could not find current site") end - - playerCiv = df.historical_entity.find(df.global.plotinfo.civ_id) - if not playerCiv then qerror("could not find current civ") end - - capital = findCapital(playerCiv) - if not capital then qerror("could not find capital") end - if options.list then return true end -- list option does not require unit options local noOptions = options.unitId == -1 and not options.all @@ -321,14 +324,10 @@ local function initChecks() return true end -local function resetState() +local function resetOptions() options.all = false options.unitId = -1 options.list = false - - fort = nil - capital = nil - playerCiv = nil end ------------------------------ @@ -346,5 +345,5 @@ function run(args) main() end - resetState() + resetOptions() end From b1cefb210677223f5b67c666818af0f0dcb9155f Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 15:07:18 +0800 Subject: [PATCH 23/45] Fix pair iteration --- internal/emigration/emigrate-nobles.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 599f68823b..d8a0bd7518 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -234,8 +234,9 @@ local function isSoldier(unit) return unit.military.squad_id ~= -1 end +---@param nobleList { unit: df.unit, site: df.world_site }[] local function listNoblesFound(nobleList) - for _, record in pairs(nobleList) do + for _, record in ipairs(nobleList) do local unit = record.unit local site = record.site @@ -289,7 +290,7 @@ local function main() return end - for _, record in pairs(freeloaders) do + for _, record in ipairs(freeloaders) do local noble = record.unit local site = record.site From ecd42a550b958f2b7c7a871c98e9f59a7d9c293a Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 16:13:20 +0800 Subject: [PATCH 24/45] Refactor logic --- emigration.lua | 81 +----------- internal/emigration/emigrate-nobles.lua | 129 +++--------------- internal/emigration/unit-link-utils.lua | 167 ++++++++++++++++++++++++ 3 files changed, 189 insertions(+), 188 deletions(-) create mode 100644 internal/emigration/unit-link-utils.lua diff --git a/emigration.lua b/emigration.lua index e4f179f65b..c9b5af28d0 100644 --- a/emigration.lua +++ b/emigration.lua @@ -2,7 +2,9 @@ --@enable = true local utils = require('utils') + local nobles = reqscript('internal/emigration/emigrate-nobles') +local unit_link_utils = reqscript('internal/emigration/unit-link-utils') local GLOBAL_KEY = 'emigration' -- used for state change hooks and persistence @@ -38,15 +40,12 @@ function desert(u,method,civ) local line = dfhack.units.getReadableName(u) .. " has " if method == 'merchant' then line = line.."joined the merchants" - u.flags1.merchant = true - u.civ_id = civ + unit_link_utils.markUnitForEmigration(u, civ, false) else line = line.."abandoned the settlement in search of a better life." - u.civ_id = civ - u.flags1.forest = true - u.flags2.visitor = true - u.animal.leave_countdown = 2 + unit_link_utils.markUnitForEmigration(u, civ, true) end + local hf_id = u.hist_figure_id local hf = df.historical_figure.find(u.hist_figure_id) local fort_ent = df.global.plotinfo.main.fortress_entity @@ -54,74 +53,8 @@ function desert(u,method,civ) local newent_id = -1 local newsite_id = -1 - -- free owned rooms - for i = #u.owned_buildings-1, 0, -1 do - local temp_bld = df.building.find(u.owned_buildings[i].id) - dfhack.buildings.setOwner(temp_bld, nil) - end - - -- remove from workshop profiles - for _, bld in ipairs(df.global.world.buildings.other.WORKSHOP_ANY) do - for k, v in ipairs(bld.profile.permitted_workers) do - if v == u.id then - bld.profile.permitted_workers:erase(k) - break - end - end - end - for _, bld in ipairs(df.global.world.buildings.other.FURNACE_ANY) do - for k, v in ipairs(bld.profile.permitted_workers) do - if v == u.id then - bld.profile.permitted_workers:erase(k) - break - end - end - end - - -- disassociate from work details - for _, detail in ipairs(df.global.plotinfo.labor_info.work_details) do - for k, v in ipairs(detail.assigned_units) do - if v == u.id then - detail.assigned_units:erase(k) - break - end - end - end - - -- unburrow - for _, burrow in ipairs(df.global.plotinfo.burrows.list) do - dfhack.burrows.setAssignedUnit(burrow, u, false) - end - - -- erase the unit from the fortress entity - for k,v in ipairs(fort_ent.histfig_ids) do - if v == hf_id then - df.global.plotinfo.main.fortress_entity.histfig_ids:erase(k) - break - end - end - for k,v in ipairs(fort_ent.hist_figures) do - if v.id == hf_id then - df.global.plotinfo.main.fortress_entity.hist_figures:erase(k) - break - end - end - for k,v in ipairs(fort_ent.nemesis) do - if v.figure.id == hf_id then - df.global.plotinfo.main.fortress_entity.nemesis:erase(k) - df.global.plotinfo.main.fortress_entity.nemesis_ids:erase(k) - break - end - end - - -- remove the old entity link and create new one to indicate former membership - hf.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = fort_ent.id, link_strength = 100}) - for k,v in ipairs(hf.entity_links) do - if v._type == df.histfig_entity_link_memberst and v.entity_id == fort_ent.id then - hf.entity_links:erase(k) - break - end - end + unit_link_utils.removeUnitAssociations(u) + unit_link_utils.removeHistFigFromEntity(hf, fort_ent) -- try to find a new entity for the unit to join for k,v in ipairs(civ_ent.entity_links) do diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index d8a0bd7518..48f976ffaa 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -9,6 +9,7 @@ TODO: ]]-- local argparse = require("argparse") +local unit_link_utils = reqscript("unit-site-links") local options = { all = false, @@ -80,124 +81,19 @@ local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) table.insert(nobleList, {unit = unit, site = site}) end ----@param histFig df.historical_figure ----@param newSite df.world_site -local function addHistFigToSite(histFig, newSite) - -- have unit join site government - local siteGovId = newSite.cur_owner_id - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) - local histFigId = histFig.id - - -- have unit join new site - local siteId = newSite.id - local siteGov = df.historical_entity.find(siteGovId) - if not siteGov then qerror("could not find site!") end - - siteGov.histfig_ids:insert('#', histFigId) - siteGov.hist_figures:insert('#', histFig) - local hfEventId = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGovId, histfig = histFigId, link_type = 0}) - - local hfEventId = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) - - return siteGov -end - ----@param unit df.unit ----@param histFig df.historical_figure ----@param oldSite df.historical_entity -local function removeUnitFromSiteEntity(unit, histFig, oldSite) - local histFigId = histFig.id - - -- free owned rooms - for i = #unit.owned_buildings-1, 0, -1 do - local tmp = df.building.find(unit.owned_buildings[i].id) - dfhack.buildings.setOwner(tmp, nil) - end - - -- remove from workshop profiles - for _, bld in ipairs(df.global.world.buildings.other.WORKSHOP_ANY) do - for k, v in ipairs(bld.profile.permitted_workers) do - if v == unit.id then - bld.profile.permitted_workers:erase(k) - break - end - end - end - for _, bld in ipairs(df.global.world.buildings.other.FURNACE_ANY) do - for k, v in ipairs(bld.profile.permitted_workers) do - if v == unit.id then - bld.profile.permitted_workers:erase(k) - break - end - end - end - - -- disassociate from work details - for _, detail in ipairs(df.global.plotinfo.labor_info.work_details) do - for k, v in ipairs(detail.assigned_units) do - if v == unit.id then - detail.assigned_units:erase(k) - break - end - end - end - - -- unburrow - for _, burrow in ipairs(df.global.plotinfo.burrows.list) do - dfhack.burrows.setAssignedUnit(burrow, unit, false) - end - - -- erase the unit from the fortress entity - for k,v in ipairs(oldSite.histfig_ids) do - if v == histFigId then - df.global.plotinfo.main.fortress_entity.histfig_ids:erase(k) - break - end - end - for k,v in ipairs(oldSite.hist_figures) do - if v.id == histFigId then - df.global.plotinfo.main.fortress_entity.hist_figures:erase(k) - break - end - end - for k,v in ipairs(oldSite.nemesis) do - if v.figure.id == histFigId then - df.global.plotinfo.main.fortress_entity.nemesis:erase(k) - df.global.plotinfo.main.fortress_entity.nemesis_ids:erase(k) - break - end - end - - -- remove the old entity link and create new one to indicate former membership - histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = oldSite.id, link_strength = 100}) - for k,v in ipairs(histFig.entity_links) do - if v._type == df.histfig_entity_link_memberst and v.entity_id == oldSite.id then - histFig.entity_links:erase(k) - break - end - end -end - -- adapted from emigration::desert() ---@param unit df.unit ---@param toSite df.world_site ---@param civ df.historical_entity local function emigrate(unit, toSite, civ) local histFig = df.historical_figure.find(unit.hist_figure_id) - if histFig == nil then qerror("could not find histfig!") end + if not histFig then + print("Could not find associated historical figure!") + return + end local fortEnt = df.global.plotinfo.main.fortress_entity - - -- mark for leaving - unit.following = nil - unit.civ_id = civ.id -- should be redundant but oh well - unit.flags1.forest = true - unit.flags2.visitor = true - unit.animal.leave_countdown = 2 + unit_link_utils.markUnitForEmigration(unit, civ.id, true) -- remove current job if unit.job.current_job then dfhack.job.removeJob(unit.job.current_job) end @@ -208,8 +104,11 @@ local function emigrate(unit, toSite, civ) if act then act.events[0].flags.dismissed = true end end - removeUnitFromSiteEntity(unit, histFig, fortEnt) - local siteGov = addHistFigToSite(histFig, toSite) + unit_link_utils.removeUnitAssociations(unit) + unit_link_utils.removeHistFigFromEntity(histFig, fortEnt) + + local siteGov = unit_link_utils.addHistFigToSite(histFig, toSite) + if not siteGov then qerror("could not add unit to new site") end -- announce the changes local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) @@ -244,8 +143,10 @@ local function listNoblesFound(nobleList) local unitMsg = unit.id..": "..nobleName if isSoldier(unit) then local squad = df.squad.find(unit.military.squad_id) - if not squad then qerror("could not find unit's squad") end - local squadName = dfhack.df2console(dfhack.translation.translateName(squad.name, true)) + local squadName = squad + and dfhack.df2console(dfhack.translation.translateName(squad.name, true)) + or "unknown squad" + unitMsg = "[!] "..unitMsg.." - soldier in "..squadName else local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua new file mode 100644 index 0000000000..9ed6d22292 --- /dev/null +++ b/internal/emigration/unit-link-utils.lua @@ -0,0 +1,167 @@ +--@module true + +---@param histFig df.historical_figure +---@param oldEntity df.historical_entity +function removeHistFigFromEntity(histFig, oldEntity) + if not histFig or not oldEntity then return end + + local histFigId = histFig.id + + -- erase the unit from the fortress entity + for k,v in ipairs(oldEntity.histfig_ids) do + if v == histFigId then + df.global.plotinfo.main.fortress_entity.histfig_ids:erase(k) + break + end + end + for k,v in ipairs(oldEntity.hist_figures) do + if v.id == histFigId then + df.global.plotinfo.main.fortress_entity.hist_figures:erase(k) + break + end + end + for k,v in ipairs(oldEntity.nemesis) do + if v.figure.id == histFigId then + df.global.plotinfo.main.fortress_entity.nemesis:erase(k) + df.global.plotinfo.main.fortress_entity.nemesis_ids:erase(k) + break + end + end + + -- remove the old entity link and create new one to indicate former membership + histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = oldEntity.id, link_strength = 100}) + for k,v in ipairs(histFig.entity_links) do + if v._type == df.histfig_entity_link_memberst and v.entity_id == oldEntity.id then + histFig.entity_links:erase(k) + break + end + end +end + +---@param histFig df.historical_figure +---@param newEntity df.historical_entity +function addHistFigToEntity(histFig, newEntity) + if not histFig or not newEntity then return end + + local histFigId = histFig.id + local newEntId = newEntity.id + + -- have unit join site government + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = newEntId, link_strength = 100}) + + -- create event indicating new membership + newEntity.histfig_ids:insert('#', histFigId) + newEntity.hist_figures:insert('#', histFig) + local hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = newEntId, histfig = histFigId, link_type = 0}) +end + +---@param histFig df.historical_figure +---@param newSite df.historical_entity +function createHistFigJoinSiteEvent(histFig, newSite) + if not histFig or not newSite then return end + + -- create event indicating histfig moved to site + local histFigId = histFig.id + local siteId = newSite.id + hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) +end + +---@param histFig df.historical_figure +---@param entity df.historical_entity +function insertNewHistFigEntityLink(histFig, entity) + if not histFig or not entity then return end + + local entityId = entity.id + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = entityId, link_strength = 100}) +end + +---@param histFig df.historical_figure +---@param newSite df.world_site +---@return df.historical_entity|nil siteGov New site entity histfig is associated with +function addHistFigToSite(histFig, newSite) + if not histFig or not newSite then return nil end + + -- have unit join site government + local siteGovId = newSite.cur_owner_id + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) + local histFigId = histFig.id + + -- have unit join new site + local siteId = newSite.id + local siteGov = df.historical_entity.find(siteGovId) + if not siteGov then qerror("could not find site!") end + + siteGov.histfig_ids:insert('#', histFigId) + siteGov.hist_figures:insert('#', histFig) + local hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGovId, histfig = histFigId, link_type = 0}) + + hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) + + return siteGov +end + +---@param unit df.unit +function removeUnitAssociations(unit) + -- free owned rooms + for i = #unit.owned_buildings-1, 0, -1 do + local tmp = df.building.find(unit.owned_buildings[i].id) + dfhack.buildings.setOwner(tmp, nil) + end + + -- remove from workshop profiles + for _, bld in ipairs(df.global.world.buildings.other.WORKSHOP_ANY) do + for k, v in ipairs(bld.profile.permitted_workers) do + if v == unit.id then + bld.profile.permitted_workers:erase(k) + break + end + end + end + for _, bld in ipairs(df.global.world.buildings.other.FURNACE_ANY) do + for k, v in ipairs(bld.profile.permitted_workers) do + if v == unit.id then + bld.profile.permitted_workers:erase(k) + break + end + end + end + + -- disassociate from work details + for _, detail in ipairs(df.global.plotinfo.labor_info.work_details) do + for k, v in ipairs(detail.assigned_units) do + if v == unit.id then + detail.assigned_units:erase(k) + break + end + end + end + + -- unburrow + for _, burrow in ipairs(df.global.plotinfo.burrows.list) do + dfhack.burrows.setAssignedUnit(burrow, unit, false) + end +end + +---@param unit df.unit +---@param civId number +---@param leaveNow boolean Decides if unit leaves immediately or with merchants +function markUnitForEmigration(unit, civId, leaveNow) + unit.following = nil + unit.civ_id = civId + + if leaveNow then + unit.flags1.forest = true + unit.flags2.visitor = true + unit.animal.leave_countdown = 2 + else + unit.flags1.merchant = true + end +end From ec335cb01e58d218a17a0e3584111f5c2b0eef8b Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 17:03:58 +0800 Subject: [PATCH 25/45] Refactor emigration logic --- emigration.lua | 26 +++------- internal/emigration/emigrate-nobles.lua | 6 ++- internal/emigration/unit-link-utils.lua | 66 ++++++------------------- 3 files changed, 25 insertions(+), 73 deletions(-) diff --git a/emigration.lua b/emigration.lua index c9b5af28d0..1bba9810e1 100644 --- a/emigration.lua +++ b/emigration.lua @@ -46,7 +46,6 @@ function desert(u,method,civ) unit_link_utils.markUnitForEmigration(u, civ, true) end - local hf_id = u.hist_figure_id local hf = df.historical_figure.find(u.hist_figure_id) local fort_ent = df.global.plotinfo.main.fortress_entity local civ_ent = df.historical_entity.find(hf.civ_id) @@ -57,35 +56,24 @@ function desert(u,method,civ) unit_link_utils.removeHistFigFromEntity(hf, fort_ent) -- try to find a new entity for the unit to join - for k,v in ipairs(civ_ent.entity_links) do - if v.type == df.entity_entity_link_type.CHILD and v.target ~= fort_ent.id then - newent_id = v.target + for _,entity_link in ipairs(civ_ent.entity_links) do + if entity_link.type == df.entity_entity_link_type.CHILD and entity_link.target ~= fort_ent.id then + newent_id = entity_link.target break end end if newent_id > -1 then - hf.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = newent_id, link_strength = 100}) - -- try to find a new site for the unit to join - for k,v in ipairs(df.global.world.entities.all[hf.civ_id].site_links) do + for _,site_link in ipairs(df.global.world.entities.all[hf.civ_id].site_links) do local site_id = df.global.plotinfo.site_id - if v.type == df.entity_site_link_type.Claim and v.target ~= site_id then - newsite_id = v.target + if site_link.type == df.entity_site_link_type.Claim and site_link.target ~= site_id then + newsite_id = site_link.target break end end local newent = df.historical_entity.find(newent_id) - newent.histfig_ids:insert('#', hf_id) - newent.hist_figures:insert('#', hf) - local hf_event_id = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, civ = newent_id, histfig = hf_id, link_type = 0}) - if newsite_id > -1 then - local hf_event_id = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hf_event_id, hfid = hf_id, state = 1, reason = -1, site = newsite_id}) - end + unit_link_utils.addHistFigToSite(hf, newsite_id, newent) end print(dfhack.df2console(line)) dfhack.gui.showAnnouncement(line, COLOR_WHITE) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 48f976ffaa..89acce67c3 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -107,8 +107,10 @@ local function emigrate(unit, toSite, civ) unit_link_utils.removeUnitAssociations(unit) unit_link_utils.removeHistFigFromEntity(histFig, fortEnt) - local siteGov = unit_link_utils.addHistFigToSite(histFig, toSite) - if not siteGov then qerror("could not add unit to new site") end + -- have unit join new site government + local siteGov = df.historical_entity.find(toSite.cur_owner_id) + if not siteGov then qerror("could not find entity associated with new site") end + unit_link_utils.addHistFigToSite(histFig, toSite.id, siteGov) -- announce the changes local unitName = dfhack.df2console(dfhack.units.getReadableName(unit)) diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 9ed6d22292..104b67ce68 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -38,74 +38,36 @@ function removeHistFigFromEntity(histFig, oldEntity) end end ----@param histFig df.historical_figure ----@param newEntity df.historical_entity -function addHistFigToEntity(histFig, newEntity) - if not histFig or not newEntity then return end - - local histFigId = histFig.id - local newEntId = newEntity.id - - -- have unit join site government - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = newEntId, link_strength = 100}) - - -- create event indicating new membership - newEntity.histfig_ids:insert('#', histFigId) - newEntity.hist_figures:insert('#', histFig) - local hfEventId = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = newEntId, histfig = histFigId, link_type = 0}) -end - ----@param histFig df.historical_figure ----@param newSite df.historical_entity -function createHistFigJoinSiteEvent(histFig, newSite) - if not histFig or not newSite then return end - - -- create event indicating histfig moved to site - local histFigId = histFig.id - local siteId = newSite.id - hfEventId = df.global.hist_event_next_id - df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) -end - ---@param histFig df.historical_figure ---@param entity df.historical_entity -function insertNewHistFigEntityLink(histFig, entity) - if not histFig or not entity then return end - - local entityId = entity.id - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = entityId, link_strength = 100}) +function addNewHistFigEntityLink(histFig, entity) end +---Creates events indicating a histfig's move to a new site and joining its entity. ---@param histFig df.historical_figure ----@param newSite df.world_site ----@return df.historical_entity|nil siteGov New site entity histfig is associated with -function addHistFigToSite(histFig, newSite) - if not histFig or not newSite then return nil end - - -- have unit join site government - local siteGovId = newSite.cur_owner_id - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGovId, link_strength = 100}) +---@param siteId number Set to -1 if unneeded +---@param siteGov df.historical_entity +function addHistFigToSite(histFig, siteId, siteGov) + if not histFig or not siteGov then return nil end + local histFigId = histFig.id - -- have unit join new site - local siteId = newSite.id - local siteGov = df.historical_entity.find(siteGovId) - if not siteGov then qerror("could not find site!") end + -- add new site gov to histfig links + histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGov.id, link_strength = 100}) + -- add histfig to new site gov siteGov.histfig_ids:insert('#', histFigId) siteGov.hist_figures:insert('#', histFig) local hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGovId, histfig = histFigId, link_type = 0}) + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGov.id, histfig = histFigId, link_type = 0}) + + if siteId <= -1 then return end -- skip site join event + -- create event indicating histfig moved to site hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) - - return siteGov end ---@param unit df.unit From 32afe4e51091f6d7fd1036cdd2e65e73ac1da7a9 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 17:23:24 +0800 Subject: [PATCH 26/45] Fix stupid typo --- internal/emigration/emigrate-nobles.lua | 3 ++- internal/emigration/unit-link-utils.lua | 7 +------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 89acce67c3..8a29fe5802 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -1,6 +1,6 @@ --@module = true ---Deport resident nobles of other lands and summon your rightful lord if he/she is elsewhere +--Deport resident nobles of other lands --[[ TODO: @@ -9,6 +9,7 @@ TODO: ]]-- local argparse = require("argparse") + local unit_link_utils = reqscript("unit-site-links") local options = { diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 104b67ce68..2188bb7117 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -1,4 +1,4 @@ ---@module true +--@module = true ---@param histFig df.historical_figure ---@param oldEntity df.historical_entity @@ -38,11 +38,6 @@ function removeHistFigFromEntity(histFig, oldEntity) end end ----@param histFig df.historical_figure ----@param entity df.historical_entity -function addNewHistFigEntityLink(histFig, entity) -end - ---Creates events indicating a histfig's move to a new site and joining its entity. ---@param histFig df.historical_figure ---@param siteId number Set to -1 if unneeded From 4fefcfb3c31d9f7297afded2f2fd86ee6163a03e Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 17:24:11 +0800 Subject: [PATCH 27/45] Fix stupid typo --- internal/emigration/emigrate-nobles.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 8a29fe5802..0c28e0a251 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -10,7 +10,7 @@ TODO: local argparse = require("argparse") -local unit_link_utils = reqscript("unit-site-links") +local unit_link_utils = reqscript("unit-link-utils") local options = { all = false, From 4419093ea97452fa306d8e4d44c757c7c83c3a51 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 17:30:49 +0800 Subject: [PATCH 28/45] Fix import error --- internal/emigration/emigrate-nobles.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 0c28e0a251..912b44fbf9 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -10,7 +10,7 @@ TODO: local argparse = require("argparse") -local unit_link_utils = reqscript("unit-link-utils") +local unit_link_utils = reqscript("internal/emigration/unit-link-utils") local options = { all = false, @@ -200,9 +200,9 @@ local function main() local nobleName = dfhack.units.getReadableName(noble) if inStrangeMood(noble) then - print("[-] "..nobleName.." is in a strange mood! Leave alone for now.") + print("[!] "..nobleName.." is in a strange mood! Leave alone for now.") elseif isSoldier(noble) then - print("[-] "..nobleName.." is in a squad! Unassign the unit before proceeding.") + print("[!] "..nobleName.." is in a squad! Unassign the unit before proceeding.") else emigrate(noble, site, civ) end From 2b42b13f750e45cd59100a5108ca03406ed80eb1 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 18:33:11 +0800 Subject: [PATCH 29/45] Add support for current unit --- internal/emigration/emigrate-nobles.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 912b44fbf9..24a8e87ee4 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -150,10 +150,10 @@ local function listNoblesFound(nobleList) and dfhack.df2console(dfhack.translation.translateName(squad.name, true)) or "unknown squad" - unitMsg = "[!] "..unitMsg.." - soldier in "..squadName + unitMsg = "! "..unitMsg.." - soldier in "..squadName else local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) - unitMsg = unitMsg.." to be sent to "..siteName + unitMsg = " "..unitMsg.." - to "..siteName end print(unitMsg) @@ -164,7 +164,7 @@ local function printNoNobles() if options.unitId == -1 then print("No eligible nobles to be emigrated.") else - print("No eligible nobles found with ID = "..options.unitId) + print("Unit ID "..options.unitId.." is not an eligible noble.") end end @@ -218,8 +218,16 @@ local function initChecks() local noOptions = options.unitId == -1 and not options.all if noOptions then - print("No options selected, defaulting to list mode.") - options.list = true + unit = dfhack.gui.getSelectedUnit(true) + if unit then + options.unitId = unit.id + local name = dfhack.units.getReadableName(unit) + print("Selecting "..name.." (ID "..unit.id..")") + else + options.list = true + print("Defaulting to list mode:") + end + return true end From 64c5b64759e6b6c79e330ca4cd65d68f1d54b8a9 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sat, 8 Feb 2025 18:42:38 +0800 Subject: [PATCH 30/45] Disable cancelling special jobs --- internal/emigration/emigrate-nobles.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 24a8e87ee4..aafe471405 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -123,10 +123,12 @@ local function emigrate(unit, toSite, civ) end ---@param unit df.unit -local function inStrangeMood(unit) +local function inSpecialJob(unit) local job = unit.job.current_job if not job then return false end + if job.flags.special then return true end -- cannot cancel + local jobType = job.job_type -- taken from notifications::for_moody() return df.job_type_class[df.job_type.attrs[jobType].type] == 'StrangeMood' end @@ -199,8 +201,8 @@ local function main() local site = record.site local nobleName = dfhack.units.getReadableName(noble) - if inStrangeMood(noble) then - print("[!] "..nobleName.." is in a strange mood! Leave alone for now.") + if inSpecialJob(noble) then + print("[!] "..nobleName.." is busy! Leave alone for now.") elseif isSoldier(noble) then print("[!] "..nobleName.." is in a squad! Unassign the unit before proceeding.") else From 16ff909d8dc4301277e7bc5a3c899ceb7873b93d Mon Sep 17 00:00:00 2001 From: yg-ong Date: Sun, 9 Feb 2025 13:34:56 +0800 Subject: [PATCH 31/45] Add mandate removal logic --- internal/emigration/emigrate-nobles.lua | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index aafe471405..0fbe5bdcfc 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -1,7 +1,5 @@ --@module = true ---Deport resident nobles of other lands - --[[ TODO: * Feature: have rightful ruler immigrate to fort if off-site @@ -48,10 +46,10 @@ local function findCapital(civ) return civCapital end ----@param unit df.unit ----@param nobleList { unit: df.unit, site: df.world_site }[] ----@param playerFort df.world_site ----@param civ df.historical_entity +---@param unit df.unit +---@param nobleList { unit: df.unit, site: df.world_site }[] +---@param playerFort df.world_site +---@param civ df.historical_entity local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil @@ -82,6 +80,18 @@ local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) table.insert(nobleList, {unit = unit, site = site}) end +---@param unit df.unit +local function removeMandates(unit) + local mandates = df.global.world.mandates + for i=#mandates-1,0,-1 do + local mandate = mandates[i] + if mandate.unit and mandate.unit.id == unit.id then + mandates:erase(i) + mandate:delete() + end + end +end + -- adapted from emigration::desert() ---@param unit df.unit ---@param toSite df.world_site @@ -105,6 +115,9 @@ local function emigrate(unit, toSite, civ) if act then act.events[0].flags.dismissed = true end end + -- cancel any associated mandates + removeMandates(unit) + unit_link_utils.removeUnitAssociations(unit) unit_link_utils.removeHistFigFromEntity(histFig, fortEnt) From 1fa7b042ace086d3506bd5a40ad0749b8c74483e Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 00:09:31 +0800 Subject: [PATCH 32/45] Add check for fort admins --- internal/emigration/emigrate-nobles.lua | 64 ++++++++++++++++++------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 0fbe5bdcfc..2d769d9791 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -48,12 +48,13 @@ end ---@param unit df.unit ---@param nobleList { unit: df.unit, site: df.world_site }[] ----@param playerFort df.world_site +---@param thisSite df.world_site ---@param civ df.historical_entity -local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) +local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil for _, np in ipairs(nps) do + -- TODO: also check if civ is not your fort? Some site govs have IS_LAW_MAKER positions if np.position.flags.IS_LAW_MAKER then noblePos = np break @@ -62,10 +63,14 @@ local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) if not noblePos then return end -- unit is not nobility + -- TODO: support other races that may not use MONARCH as position code + -- entity.type == df.historical_entity_type.Civilization + -- position.flags.RULES_FROM_LOCATION + -- Monarchs do not seem to have an world_site associated to them (?) if noblePos.position.code == "MONARCH" then local capital = findCapital(civ) - if capital and capital.id ~= playerFort.id then + if capital and capital.id ~= thisSite.id then table.insert(nobleList, {unit = unit, site = capital}) end return @@ -76,7 +81,7 @@ local function addNobleOfOtherSite(unit, nobleList, playerFort, civ) local site = findSiteOfRule(noblePos) if not site then qerror("could not find land of "..name) end - if site.id == playerFort.id then return end -- noble rules current fort + if site.id == thisSite.id then return end -- noble rules current fort table.insert(nobleList, {unit = unit, site = site}) end @@ -93,17 +98,17 @@ local function removeMandates(unit) end -- adapted from emigration::desert() ----@param unit df.unit ----@param toSite df.world_site ----@param civ df.historical_entity -local function emigrate(unit, toSite, civ) +---@param unit df.unit +---@param toSite df.world_site +---@param prevEnt df.historical_entity +---@param civ df.historical_entity +local function emigrate(unit, toSite, prevEnt, civ) local histFig = df.historical_figure.find(unit.hist_figure_id) if not histFig then print("Could not find associated historical figure!") return end - local fortEnt = df.global.plotinfo.main.fortress_entity unit_link_utils.markUnitForEmigration(unit, civ.id, true) -- remove current job @@ -119,7 +124,7 @@ local function emigrate(unit, toSite, civ) removeMandates(unit) unit_link_utils.removeUnitAssociations(unit) - unit_link_utils.removeHistFigFromEntity(histFig, fortEnt) + unit_link_utils.removeHistFigFromEntity(histFig, prevEnt) -- have unit join new site government local siteGov = df.historical_entity.find(toSite.cur_owner_id) @@ -151,8 +156,27 @@ local function isSoldier(unit) return unit.military.squad_id ~= -1 end +---@param unit df.unit +---@param fortEnt df.historical_entity +---@param includeElected boolean +local function isAdministrator(unit, fortEnt, includeElected) + ---@diagnostic disable-next-line: missing-parameter + local nps = dfhack.units.getNoblePositions(unit) or {} + + ---@diagnostic disable-next-line: param-type-mismatch + for _, np in ipairs(nps) do + -- Elected officials can be chosen again + local isAdmin = np.entity.id == fortEnt.id + if not includeElected then isAdmin = isAdmin and not np.position.flags.ELECTED end + if isAdmin then return true end + end + return false +end + ---@param nobleList { unit: df.unit, site: df.world_site }[] -local function listNoblesFound(nobleList) +---@param fort df.world_site +---@param fortEnt df.historical_entity +local function listNoblesFound(nobleList, fort, fortEnt) for _, record in ipairs(nobleList) do local unit = record.unit local site = record.site @@ -166,6 +190,9 @@ local function listNoblesFound(nobleList) or "unknown squad" unitMsg = "! "..unitMsg.." - soldier in "..squadName + elseif isAdministrator(unit, fortEnt, true) then + local fortName = dfhack.df2console(dfhack.translation.translateName(fort.name, true)) + unitMsg = "! "..unitMsg.." - administrator of "..fortName else local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) unitMsg = " "..unitMsg.." - to "..siteName @@ -184,9 +211,12 @@ local function printNoNobles() end local function main() - local fort = dfhack.world.getCurrentSite() + ---@diagnostic disable-next-line: assign-type-mismatch + local fort = dfhack.world.getCurrentSite() ---@type df.world_site if not fort then qerror("could not find current site") end + local fortEnt = df.global.plotinfo.main.fortress_entity + local civ = df.historical_entity.find(df.global.plotinfo.civ_id) if not civ then qerror("could not find current civ") end @@ -205,7 +235,7 @@ local function main() end if options.list then - listNoblesFound(freeloaders) + listNoblesFound(freeloaders, fort, fortEnt) return end @@ -215,11 +245,13 @@ local function main() local nobleName = dfhack.units.getReadableName(noble) if inSpecialJob(noble) then - print("[!] "..nobleName.." is busy! Leave alone for now.") + print("! "..nobleName.." is busy! Leave alone for now.") elseif isSoldier(noble) then - print("[!] "..nobleName.." is in a squad! Unassign the unit before proceeding.") + print("! "..nobleName.." is in a squad! Unassign the unit and try again.") + elseif isAdministrator(noble, fortEnt, false) then + print("! "..nobleName.." is an administrator! Unassign the unit and try again.") else - emigrate(noble, site, civ) + emigrate(noble, site, fortEnt, civ) end end end From 5501751450404dab1ccddf7e70c210b27f34a6b7 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 00:13:01 +0800 Subject: [PATCH 33/45] Add signposts for navigation --- internal/emigration/emigrate-nobles.lua | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 2d769d9791..236bb01889 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -140,6 +140,10 @@ local function emigrate(unit, toSite, prevEnt, civ) dfhack.gui.showAnnouncement(line, COLOR_WHITE) end +------------------------ +-- [[ GUARD CHECKS ]] -- +------------------------ + ---@param unit df.unit local function inSpecialJob(unit) local job = unit.job.current_job @@ -173,6 +177,10 @@ local function isAdministrator(unit, fortEnt, includeElected) return false end +----------------------- +-- [[ PRINT MODES ]] -- +----------------------- + ---@param nobleList { unit: df.unit, site: df.world_site }[] ---@param fort df.world_site ---@param fortEnt df.historical_entity @@ -210,6 +218,10 @@ local function printNoNobles() end end +------------------------- +-- [[ MAIN FUNCTION ]] -- +------------------------- + local function main() ---@diagnostic disable-next-line: assign-type-mismatch local fort = dfhack.world.getCurrentSite() ---@type df.world_site @@ -290,10 +302,6 @@ local function resetOptions() options.list = false end ------------------------------- --- [[ SCRIPT STARTS HERE ]] -- ------------------------------- - function run(args) argparse.processArgsGetopt(args, { {"a", "all", handler=function() options.all = true end}, From dd2718ea491b784fec590edbe2096754964b6d41 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 00:27:48 +0800 Subject: [PATCH 34/45] Change isAdministrator --- internal/emigration/emigrate-nobles.lua | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 236bb01889..5753b73b72 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -162,17 +162,13 @@ end ---@param unit df.unit ---@param fortEnt df.historical_entity ----@param includeElected boolean -local function isAdministrator(unit, fortEnt, includeElected) +local function isAdministrator(unit, fortEnt) ---@diagnostic disable-next-line: missing-parameter local nps = dfhack.units.getNoblePositions(unit) or {} ---@diagnostic disable-next-line: param-type-mismatch for _, np in ipairs(nps) do - -- Elected officials can be chosen again - local isAdmin = np.entity.id == fortEnt.id - if not includeElected then isAdmin = isAdmin and not np.position.flags.ELECTED end - if isAdmin then return true end + if np.entity.id == fortEnt.id then return true end end return false end @@ -182,9 +178,8 @@ end ----------------------- ---@param nobleList { unit: df.unit, site: df.world_site }[] ----@param fort df.world_site ---@param fortEnt df.historical_entity -local function listNoblesFound(nobleList, fort, fortEnt) +local function listNoblesFound(nobleList, fortEnt) for _, record in ipairs(nobleList) do local unit = record.unit local site = record.site @@ -198,9 +193,8 @@ local function listNoblesFound(nobleList, fort, fortEnt) or "unknown squad" unitMsg = "! "..unitMsg.." - soldier in "..squadName - elseif isAdministrator(unit, fortEnt, true) then - local fortName = dfhack.df2console(dfhack.translation.translateName(fort.name, true)) - unitMsg = "! "..unitMsg.." - administrator of "..fortName + elseif isAdministrator(unit, fortEnt) then + unitMsg = "! "..unitMsg.." - administrator of this fort" else local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) unitMsg = " "..unitMsg.." - to "..siteName @@ -247,7 +241,7 @@ local function main() end if options.list then - listNoblesFound(freeloaders, fort, fortEnt) + listNoblesFound(freeloaders, fortEnt) return end @@ -260,7 +254,7 @@ local function main() print("! "..nobleName.." is busy! Leave alone for now.") elseif isSoldier(noble) then print("! "..nobleName.." is in a squad! Unassign the unit and try again.") - elseif isAdministrator(noble, fortEnt, false) then + elseif isAdministrator(noble, fortEnt) then print("! "..nobleName.." is an administrator! Unassign the unit and try again.") else emigrate(noble, site, fortEnt, civ) From 23728b8c6c671425eee670c40189367cecd81f61 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 00:41:00 +0800 Subject: [PATCH 35/45] Add support for other civ monarchs, change print messages --- internal/emigration/emigrate-nobles.lua | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 5753b73b72..5d48c3312f 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -54,7 +54,6 @@ local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) local nps = dfhack.units.getNoblePositions(unit) or {} local noblePos = nil for _, np in ipairs(nps) do - -- TODO: also check if civ is not your fort? Some site govs have IS_LAW_MAKER positions if np.position.flags.IS_LAW_MAKER then noblePos = np break @@ -63,12 +62,8 @@ local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) if not noblePos then return end -- unit is not nobility - -- TODO: support other races that may not use MONARCH as position code - -- entity.type == df.historical_entity_type.Civilization - -- position.flags.RULES_FROM_LOCATION - -- Monarchs do not seem to have an world_site associated to them (?) - if noblePos.position.code == "MONARCH" then + if noblePos.position.flags.RULES_FROM_LOCATION and noblePos.entity.id == civ.id then local capital = findCapital(civ) if capital and capital.id ~= thisSite.id then table.insert(nobleList, {unit = unit, site = capital}) @@ -136,7 +131,7 @@ local function emigrate(unit, toSite, prevEnt, civ) local siteName = dfhack.df2console(dfhack.translation.translateName(toSite.name, true)) local govName = dfhack.df2console(dfhack.translation.translateName(siteGov.name, true)) local line = unitName .. " has left to join " ..govName.. " as lord of " .. siteName .. "." - print("[+] "..dfhack.df2console(line)) + print("+ "..dfhack.df2console(line)) dfhack.gui.showAnnouncement(line, COLOR_WHITE) end @@ -184,19 +179,19 @@ local function listNoblesFound(nobleList, fortEnt) local unit = record.unit local site = record.site - local nobleName = dfhack.df2console(dfhack.units.getReadableName(unit)) + local nobleName = dfhack.units.getReadableName(unit) local unitMsg = unit.id..": "..nobleName if isSoldier(unit) then local squad = df.squad.find(unit.military.squad_id) local squadName = squad - and dfhack.df2console(dfhack.translation.translateName(squad.name, true)) + and dfhack.translation.translateName(squad.name, true) or "unknown squad" unitMsg = "! "..unitMsg.." - soldier in "..squadName elseif isAdministrator(unit, fortEnt) then - unitMsg = "! "..unitMsg.." - administrator of this fort" + unitMsg = "! "..unitMsg.." - fort administrator" else - local siteName = dfhack.df2console(dfhack.translation.translateName(site.name, true)) + local siteName = dfhack.translation.translateName(site.name, true) unitMsg = " "..unitMsg.." - to "..siteName end @@ -253,9 +248,9 @@ local function main() if inSpecialJob(noble) then print("! "..nobleName.." is busy! Leave alone for now.") elseif isSoldier(noble) then - print("! "..nobleName.." is in a squad! Unassign the unit and try again.") + print("! "..nobleName.." is in a squad! Unassign unit and try again.") elseif isAdministrator(noble, fortEnt) then - print("! "..nobleName.." is an administrator! Unassign the unit and try again.") + print("! "..nobleName.." is an administrator! Unassign unit and try again.") else emigrate(noble, site, fortEnt, civ) end From 161207ebfdebd340bd7f31d5dc9b6f7c1210f169 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 21:17:00 +0800 Subject: [PATCH 36/45] Add support for evicting noble mayors --- internal/emigration/emigrate-nobles.lua | 66 ++++++++++++++++++++----- internal/emigration/unit-link-utils.lua | 16 +++++- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 5d48c3312f..6c1d98c54f 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -97,7 +97,8 @@ end ---@param toSite df.world_site ---@param prevEnt df.historical_entity ---@param civ df.historical_entity -local function emigrate(unit, toSite, prevEnt, civ) +---@param removeMayor boolean +local function emigrate(unit, toSite, prevEnt, civ, removeMayor) local histFig = df.historical_figure.find(unit.hist_figure_id) if not histFig then print("Could not find associated historical figure!") @@ -119,7 +120,7 @@ local function emigrate(unit, toSite, prevEnt, civ) removeMandates(unit) unit_link_utils.removeUnitAssociations(unit) - unit_link_utils.removeHistFigFromEntity(histFig, prevEnt) + unit_link_utils.removeHistFigFromEntity(histFig, prevEnt, removeMayor) -- have unit join new site government local siteGov = df.historical_entity.find(toSite.cur_owner_id) @@ -155,17 +156,34 @@ local function isSoldier(unit) return unit.military.squad_id ~= -1 end +-- just an enum +local AdminType = { + NOT_ADMIN = { sym = " " }, + IS_ELECTED = { sym = "*" }, + IS_ADMIN = { sym = "!" } +} + ---@param unit df.unit ---@param fortEnt df.historical_entity -local function isAdministrator(unit, fortEnt) +local function getAdminType(unit, fortEnt) ---@diagnostic disable-next-line: missing-parameter local nps = dfhack.units.getNoblePositions(unit) or {} + local result = AdminType.NOT_ADMIN ---@diagnostic disable-next-line: param-type-mismatch for _, np in ipairs(nps) do - if np.entity.id == fortEnt.id then return true end + if np.entity.id ~= fortEnt.id then goto continue end + if np.position.flags.ELECTED then + result = AdminType.IS_ELECTED + goto continue + end + + -- Mayors cannot be evicted if they are also appointed administrators (e.g. manager) + result = AdminType.IS_ADMIN + break + ::continue:: end - return false + return result end ----------------------- @@ -179,6 +197,10 @@ local function listNoblesFound(nobleList, fortEnt) local unit = record.unit local site = record.site + -- avoid scoping errors + local adminType = nil + local siteName = "" + local nobleName = dfhack.units.getReadableName(unit) local unitMsg = unit.id..": "..nobleName if isSoldier(unit) then @@ -188,13 +210,22 @@ local function listNoblesFound(nobleList, fortEnt) or "unknown squad" unitMsg = "! "..unitMsg.." - soldier in "..squadName - elseif isAdministrator(unit, fortEnt) then - unitMsg = "! "..unitMsg.." - fort administrator" - else - local siteName = dfhack.translation.translateName(site.name, true) - unitMsg = " "..unitMsg.." - to "..siteName + goto print end + adminType = getAdminType(unit, fortEnt) + if adminType ~= AdminType.NOT_ADMIN then + local status = adminType == AdminType.IS_ADMIN + and "fort administrator" -- isAdmin + or "elected official" -- isElected + unitMsg = adminType.sym.." "..unitMsg.." - "..status + goto print + end + + siteName = dfhack.translation.translateName(site.name, true) + unitMsg = " "..unitMsg.." - to "..siteName + + ::print:: print(unitMsg) end end @@ -243,17 +274,26 @@ local function main() for _, record in ipairs(freeloaders) do local noble = record.unit local site = record.site + local adminType = nil local nobleName = dfhack.units.getReadableName(noble) if inSpecialJob(noble) then print("! "..nobleName.." is busy! Leave alone for now.") + goto continue elseif isSoldier(noble) then print("! "..nobleName.." is in a squad! Unassign unit and try again.") - elseif isAdministrator(noble, fortEnt) then + goto continue + end + + adminType = getAdminType(noble, fortEnt) + if adminType == AdminType.IS_ADMIN then print("! "..nobleName.." is an administrator! Unassign unit and try again.") - else - emigrate(noble, site, fortEnt, civ) + goto continue end + + local isElected = adminType == AdminType.IS_ELECTED + emigrate(noble, site, fortEnt, civ, isElected) + ::continue:: end end diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 2188bb7117..6dfe81550d 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -1,8 +1,9 @@ --@module = true ----@param histFig df.historical_figure +---@param histFig df.historical_figure ---@param oldEntity df.historical_entity -function removeHistFigFromEntity(histFig, oldEntity) +---@param removeMayor boolean +function removeHistFigFromEntity(histFig, oldEntity, removeMayor) if not histFig or not oldEntity then return end local histFigId = histFig.id @@ -28,6 +29,17 @@ function removeHistFigFromEntity(histFig, oldEntity) end end + -- remove mayor assignment if exists + if removeMayor then + local nps = dfhack.units.getNoblePositions(histFig) or {} + for _,pos in ipairs(nps) do + if pos.entity.id == oldEntity.id and pos.position.flags.ELECTED then + pos.assignment.histfig = -1 + pos.assignment.histfig2 = -1 + end + end + end + -- remove the old entity link and create new one to indicate former membership histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = oldEntity.id, link_strength = 100}) for k,v in ipairs(histFig.entity_links) do From fb8d15e67251e69551dfaa099c8a12b0f1175756 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Mon, 10 Feb 2025 22:55:17 +0800 Subject: [PATCH 37/45] Add symbol dropping logic --- internal/emigration/emigrate-nobles.lua | 9 ++-- internal/emigration/unit-link-utils.lua | 55 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 6c1d98c54f..ba6bdebb5b 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -47,7 +47,7 @@ local function findCapital(civ) end ---@param unit df.unit ----@param nobleList { unit: df.unit, site: df.world_site }[] +---@param nobleList { unit: df.unit, site: df.world_site, id: number }[] ---@param thisSite df.world_site ---@param civ df.historical_entity local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) @@ -66,7 +66,7 @@ local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) if noblePos.position.flags.RULES_FROM_LOCATION and noblePos.entity.id == civ.id then local capital = findCapital(civ) if capital and capital.id ~= thisSite.id then - table.insert(nobleList, {unit = unit, site = capital}) + table.insert(nobleList, {unit = unit, site = capital, id = noblePos.assignment.id}) end return end @@ -77,7 +77,7 @@ local function addNobleOfOtherSite(unit, nobleList, thisSite, civ) if not site then qerror("could not find land of "..name) end if site.id == thisSite.id then return end -- noble rules current fort - table.insert(nobleList, {unit = unit, site = site}) + table.insert(nobleList, {unit = unit, site = site, id = noblePos.assignment.id}) end ---@param unit df.unit @@ -252,7 +252,7 @@ local function main() local civ = df.historical_entity.find(df.global.plotinfo.civ_id) if not civ then qerror("could not find current civ") end - ---@type { unit: df.unit, site: df.world_site }[] + ---@type { unit: df.unit, site: df.world_site, id: number }[] local freeloaders = {} for _, unit in ipairs(dfhack.units.getCitizens()) do if options.unitId ~= -1 and unit.id ~= options.unitId then goto continue end @@ -293,6 +293,7 @@ local function main() local isElected = adminType == AdminType.IS_ELECTED emigrate(noble, site, fortEnt, civ, isElected) + unit_link_utils.unassignSymbols(record.id, civ, fort) ::continue:: end end diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 6dfe81550d..557f938db3 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -134,3 +134,58 @@ function markUnitForEmigration(unit, civId, leaveNow) unit.flags1.merchant = true end end + +---@param item df.item +local function getPos(item) + local x, y, z = dfhack.items.getPosition(item) + if not x or not y or not z then + return nil + end + + if dfhack.maps.isTileVisible(x, y, z) then + return xyz2pos(x, y, z) + end +end + +---@param assignmentId number +---@param entity df.historical_entity +---@param site df.world_site +function unassignSymbols(assignmentId, entity, site) + local claims = entity.artifact_claims + local artifacts = df.global.world.artifacts.all + + for i=#claims-1,0,-1 do + local claim = claims[i] + if claim.claim_type ~= df.artifact_claim_type.Symbol then goto continue end + if claim.symbol_claim_id ~= assignmentId then goto continue end + + local artifact = artifacts[claim.artifact_id] + local item = artifact.item + local artifactName = dfhack.translation.translateName(artifact.name) + + -- we can probably keep artifact.entity_claims since we still hold it + local itemPos = getPos(item) + local success = false + if not itemPos then + if artifact.site == site.id then + print(" ! "..artifactName.." cannot be found!") + goto removeClaim + else + print(" ! "..artifactName.." is not in this site!") + goto continue + end + end + + success = dfhack.items.moveToGround(item, itemPos) + if success then print(" + dropped "..artifactName) + else print(" ! could not drop "..artifactName) + end + + -- they do not seem to "own" their artifacts, no additional cleaning seems necessary + + ::removeClaim:: + claims:erase(i) + claim:delete() + ::continue:: + end +end From 5569bb070516c335ca8ff3c9337a1ddc988f5e28 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 00:04:24 +0800 Subject: [PATCH 38/45] Update TODO --- internal/emigration/emigrate-nobles.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index ba6bdebb5b..07826db2a7 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -3,7 +3,7 @@ --[[ TODO: * Feature: have rightful ruler immigrate to fort if off-site - * QoL: make sure items are unassigned + * Noble of other civ residing in fort? ]]-- local argparse = require("argparse") From 16b4fe94505581d16b417531a4d96c6b2a6c4205 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 00:47:04 +0800 Subject: [PATCH 39/45] Update docs/emigration.rst --- docs/emigration.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/emigration.rst b/docs/emigration.rst index d04e07e178..9d8c0503c6 100644 --- a/docs/emigration.rst +++ b/docs/emigration.rst @@ -18,13 +18,9 @@ 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 will not be emigrated. -Remove them from the squad before retrying. - -.. warning:: - - Emigrated nobles will not surrender any symbols of office before leaving. - Unassign your artefacts before calling ``emigration nobles``. +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 have a ``*`` icon when listed. Usage ----- @@ -38,12 +34,17 @@ Usage 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 + 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 + 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 + Emigrate a noble matching the specified unit ID that does not rule your fortress. Options ------- From bce191d0d1e87a6948e11dfb229bc5222b73df99 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 01:34:20 +0800 Subject: [PATCH 40/45] Fix bad mayor assignment logic --- internal/emigration/unit-link-utils.lua | 41 +++++++++++++++++++------ 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 557f938db3..16b7a2a2d1 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -1,5 +1,36 @@ --@module = true +---@param histFig df.historical_figure +---@param oldEntity df.historical_entity +local function unassignMayor(histFig, oldEntity) + local assignmentId = -1 + local nps = dfhack.units.getNoblePositions(histFig) or {} + for _,pos in ipairs(nps) do + if pos.entity.id == oldEntity.id and pos.position.flags.ELECTED then + pos.assignment.histfig = -1 + pos.assignment.histfig2 = -1 + assignmentId = pos.assignment.id + end + end + if assignmentId == -1 then qerror("could not find mayor assignment!") end + + local startYear = -1 -- remove mayor assignment + for k,v in ipairs(histFig.entity_links) do + if v.entity_id == oldEntity.id + and df.histfig_entity_link_positionst:is_instance(v) + and v.assignment_id == assignmentId + then + startYear = v.start_year + histFig.entity_links:erase(k) + v:delete() + break + end + end + if startYear == -1 then qerror("could not find entity link!") end + + histFig.entity_links:insert('#', {new = df.histfig_entity_link_former_positionst, assignment_id = assignmentId, start_year = startYear, entity_id = oldEntity.id, end_year = df.global.cur_year, link_strength = 100 }) +end + ---@param histFig df.historical_figure ---@param oldEntity df.historical_entity ---@param removeMayor boolean @@ -30,15 +61,7 @@ function removeHistFigFromEntity(histFig, oldEntity, removeMayor) end -- remove mayor assignment if exists - if removeMayor then - local nps = dfhack.units.getNoblePositions(histFig) or {} - for _,pos in ipairs(nps) do - if pos.entity.id == oldEntity.id and pos.position.flags.ELECTED then - pos.assignment.histfig = -1 - pos.assignment.histfig2 = -1 - end - end - end + if removeMayor then unassignMayor(histFig, oldEntity) end -- remove the old entity link and create new one to indicate former membership histFig.entity_links:insert("#", {new = df.histfig_entity_link_former_memberst, entity_id = oldEntity.id, link_strength = 100}) From 3c71aeb0d891d7d26a9501e48d407ece56f77701 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 20:34:12 +0800 Subject: [PATCH 41/45] Update for new structures, add missing event --- internal/emigration/emigrate-nobles.lua | 2 +- internal/emigration/unit-link-utils.lua | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 07826db2a7..8e6efd3746 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -82,7 +82,7 @@ end ---@param unit df.unit local function removeMandates(unit) - local mandates = df.global.world.mandates + local mandates = df.global.world.mandates.all for i=#mandates-1,0,-1 do local mandate = mandates[i] if mandate.unit and mandate.unit.id == unit.id then diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 16b7a2a2d1..e2585cff83 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -4,12 +4,14 @@ ---@param oldEntity df.historical_entity local function unassignMayor(histFig, oldEntity) local assignmentId = -1 + local positionId = -1 local nps = dfhack.units.getNoblePositions(histFig) or {} for _,pos in ipairs(nps) do if pos.entity.id == oldEntity.id and pos.position.flags.ELECTED then pos.assignment.histfig = -1 pos.assignment.histfig2 = -1 assignmentId = pos.assignment.id + positionId = pos.position.id end end if assignmentId == -1 then qerror("could not find mayor assignment!") end @@ -29,6 +31,10 @@ local function unassignMayor(histFig, oldEntity) if startYear == -1 then qerror("could not find entity link!") end histFig.entity_links:insert('#', {new = df.histfig_entity_link_former_positionst, assignment_id = assignmentId, start_year = startYear, entity_id = oldEntity.id, end_year = df.global.cur_year, link_strength = 100 }) + + local hfEventId = df.global.hist_event_next_id + df.global.hist_event_next_id = df.global.hist_event_next_id+1 + df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = oldEntity.id, histfig = histFig.id, link_type = 11, position_id = positionId}) end ---@param histFig df.historical_figure From bd58a05c8e0fa04a26adcb001c65056cad12c8d3 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 21:55:53 +0800 Subject: [PATCH 42/45] Fix incorrect history event --- internal/emigration/unit-link-utils.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index e2585cff83..35e98e2990 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -30,11 +30,27 @@ local function unassignMayor(histFig, oldEntity) end if startYear == -1 then qerror("could not find entity link!") end - histFig.entity_links:insert('#', {new = df.histfig_entity_link_former_positionst, assignment_id = assignmentId, start_year = startYear, entity_id = oldEntity.id, end_year = df.global.cur_year, link_strength = 100 }) + histFig.entity_links:insert('#', { + new = df.histfig_entity_link_former_positionst, + assignment_id = assignmentId, + start_year = startYear, + entity_id = oldEntity.id, + end_year = df.global.cur_year, + link_strength = 100 + }) local hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = oldEntity.id, histfig = histFig.id, link_type = 11, position_id = positionId}) + df.global.world.history.events:insert("#", { + new = df.history_event_remove_hf_entity_linkst, + year = df.global.cur_year, + seconds = df.global.cur_year_tick, + id = hfEventId, + civ = oldEntity.id, + histfig = histFig.id, + link_type = 10, + position_id = positionId + }) end ---@param histFig df.historical_figure From a69dc66bff0b763b12b0a361e6a4a1ec3b08ded4 Mon Sep 17 00:00:00 2001 From: yg-ong Date: Tue, 11 Feb 2025 23:15:44 +0800 Subject: [PATCH 43/45] Update styling of new links and events --- internal/emigration/emigrate-nobles.lua | 1 - internal/emigration/unit-link-utils.lua | 29 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/emigration/emigrate-nobles.lua b/internal/emigration/emigrate-nobles.lua index 8e6efd3746..f4ef605de0 100644 --- a/internal/emigration/emigrate-nobles.lua +++ b/internal/emigration/emigrate-nobles.lua @@ -3,7 +3,6 @@ --[[ TODO: * Feature: have rightful ruler immigrate to fort if off-site - * Noble of other civ residing in fort? ]]-- local argparse = require("argparse") diff --git a/internal/emigration/unit-link-utils.lua b/internal/emigration/unit-link-utils.lua index 35e98e2990..e3bc294d46 100644 --- a/internal/emigration/unit-link-utils.lua +++ b/internal/emigration/unit-link-utils.lua @@ -48,7 +48,7 @@ local function unassignMayor(histFig, oldEntity) id = hfEventId, civ = oldEntity.id, histfig = histFig.id, - link_type = 10, + link_type = df.histfig_entity_link_type.POSITION, position_id = positionId }) end @@ -105,21 +105,42 @@ function addHistFigToSite(histFig, siteId, siteGov) local histFigId = histFig.id -- add new site gov to histfig links - histFig.entity_links:insert("#", {new = df.histfig_entity_link_memberst, entity_id = siteGov.id, link_strength = 100}) + histFig.entity_links:insert("#", { + new = df.histfig_entity_link_memberst, + entity_id = siteGov.id, + link_strength = 100 + }) -- add histfig to new site gov siteGov.histfig_ids:insert('#', histFigId) siteGov.hist_figures:insert('#', histFig) local hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_add_hf_entity_linkst, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, civ = siteGov.id, histfig = histFigId, link_type = 0}) + df.global.world.history.events:insert("#", { + new = df.history_event_add_hf_entity_linkst, + year = df.global.cur_year, + seconds = df.global.cur_year_tick, + id = hfEventId, + civ = siteGov.id, + histfig = histFigId, + link_type = df.histfig_entity_link_type.MEMBER + }) if siteId <= -1 then return end -- skip site join event -- create event indicating histfig moved to site hfEventId = df.global.hist_event_next_id df.global.hist_event_next_id = df.global.hist_event_next_id+1 - df.global.world.history.events:insert("#", {new = df.history_event_change_hf_statest, year = df.global.cur_year, seconds = df.global.cur_year_tick, id = hfEventId, hfid = histFigId, state = 1, reason = -1, site = siteId}) + df.global.world.history.events:insert("#", { + new = df.history_event_change_hf_statest, + year = df.global.cur_year, + seconds = df.global.cur_year_tick, + id = hfEventId, + hfid = histFigId, + state = df.whereabouts_type.settler, + reason = df.history_event_reason.none, + site = siteId + }) end ---@param unit df.unit From 1905346b8ccc78d984cb56c7580a0b000f2e0c12 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 7 Mar 2025 16:30:55 -0800 Subject: [PATCH 44/45] Update changelog.txt --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 0b46206fd2..2aa9160b12 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,6 +42,7 @@ Template for new versions: ## New Features - `advtools`: new ``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 +- `emigration`: new command for sending barons of other sites back to the sites that they rule over ## Fixes - `position`: support for adv mode look cursor From b5e818cf591fa7fe7208d00bdedb999b3ec28a55 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 8 Mar 2025 01:23:28 -0800 Subject: [PATCH 45/45] doc edits --- changelog.txt | 2 +- docs/emigration.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 2aa9160b12..2ee599c3a0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -42,7 +42,7 @@ Template for new versions: ## New Features - `advtools`: new ``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 -- `emigration`: new command for sending barons of other sites back to the sites that they rule over +- `emigration`: new ``nobles`` command for sending "freeloader" barons back to the sites that they rule over ## Fixes - `position`: support for adv mode look cursor diff --git a/docs/emigration.rst b/docs/emigration.rst index 9d8c0503c6..074c8ebc96 100644 --- a/docs/emigration.rst +++ b/docs/emigration.rst @@ -20,7 +20,7 @@ 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 have a ``*`` icon when listed. +(i.e. mayors) may be emigrated, but will be marked with a ``*`` icon when listed. Usage -----