From 45c3f145e58270cda73dd268a7f68e8709b62da5 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 5 Nov 2015 13:17:26 +1100 Subject: [PATCH 1/3] Add gui/family-affairs --- docs/images/family-affairs.png | Bin 0 -> 3961 bytes scripts/gui/family-affairs.lua | 296 +++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 docs/images/family-affairs.png create mode 100644 scripts/gui/family-affairs.lua diff --git a/docs/images/family-affairs.png b/docs/images/family-affairs.png new file mode 100644 index 0000000000000000000000000000000000000000..faf59390eb85e5e8b3aca9a4633fc7315b1b3ade GIT binary patch literal 3961 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 literal 0 HcmV?d00001 diff --git a/scripts/gui/family-affairs.lua b/scripts/gui/family-affairs.lua new file mode 100644 index 000000000..51525f36e --- /dev/null +++ b/scripts/gui/family-affairs.lua @@ -0,0 +1,296 @@ +-- gui/family-affairs +-- derived from v1.2 @ http://www.bay12forums.com/smf/index.php?topic=147779 +local help = [[=begin + +gui/family-affairs +================== +A user-friendly interface to view romantic relationships, +with the ability to add, remove, or otherwise change them at +your whim - fantastic for depressed dwarves with a dead spouse +(or matchmaking players...). + +The target/s must be alive, sane, and in fortress mode. + +.. image:: /docs/images/family-affairs.png + :align: center + +``gui/family-affairs [unitID]`` + shows GUI for the selected unit, or the specified unit ID + +``gui/family-affairs divorce [unitID]`` + removes all spouse and lover information from the unit + and it's partner, bypassing almost all checks. + +``gui/family-affairs [unitID] [unitID]`` + divorces the two specificed units and their partners, + then arranges for the two units to marry, bypassing + almost all checks. Use with caution. + +=end]] + +local dlg = require ('gui.dialogs') + +function ErrorPopup (msg,color) + if not tostring(msg) then msg = "Error" end + if not color then color = COLOR_LIGHTRED end + dlg.showMessage("Dwarven Family Affairs", msg, color, nil) +end + +function AnnounceAndGamelog (text,l) + if not l then l = true end + dfhack.gui.showAnnouncement(text, _G["COLOR_LIGHTMAGENTA"]) + if l then + local log = io.open('gamelog.txt', 'a') + log:write(text.."\n") + log:close() + end +end + +function ListPrompt (msg, choicelist, bool, yes_func) +dlg.showListPrompt( + "Dwarven Family Affairs", + msg, + COLOR_WHITE, + choicelist, + --called if choice is yes + yes_func, + --called on cancel + function() end, + 15, + bool + ) +end + +function GetMarriageSummary (source) + local familystate = "" + + if source.relations.spouse_id ~= -1 then + if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) then + familystate = dfhack.TranslateName(source.name).." has a spouse ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" + end + if dfhack.units.isSane(df.unit.find(source.relations.spouse_id)) == false then + familystate = dfhack.TranslateName(source.name).."'s spouse is dead or not sane, would you like to choose a new one?" + end + end + + if source.relations.spouse_id == -1 and source.relations.lover_id ~= -1 then + if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) then + familystate = dfhack.TranslateName(source.name).." already has a lover ("..dfhack.TranslateName(df.unit.find(source.relations.spouse_id).name)..")" + end + if dfhack.units.isSane(df.unit.find(source.relations.lover_id)) == false then + familystate = dfhack.TranslateName(source.name).."'s lover is dead or not sane, would you like that love forgotten?" + end + end + + if source.relations.spouse_id == -1 and source.relations.lover_id == -1 then + familystate = dfhack.TranslateName(source.name).." is not involved in romantic relationships with anyone" + end + + if source.relations.pregnancy_timer > 0 then + familystate = familystate.."\nShe is pregnant." + local father = df.historical_figure.find(source.relations.pregnancy_spouse) + if father then + familystate = familystate.." The father is "..dfhack.TranslateName(father.name).."." + end + end + + return familystate +end + +function GetSpouseData (source) + local spouse = df.unit.find(source.relations.spouse_id) + local spouse_hf + if spouse then + spouse_hf = df.historical_figure.find (spouse.hist_figure_id) + end + return spouse,spouse_hf +end + +function GetLoverData (source) + local lover = df.unit.find(source.relations.spouse_id) + local lover_hf + if lover then + lover_hf = df.historical_figure.find (lover.hist_figure_id) + end + return lover,lover_hf +end + +function EraseHFLinksLoverSpouse (hf) + for i = #hf.histfig_links-1,0,-1 do + if hf.histfig_links[i]._type == df.histfig_hf_link_spousest or hf.histfig_links[i]._type == df.histfig_hf_link_loverst then + local todelete = hf.histfig_links[i] + hf.histfig_links:erase(i) + todelete:delete() + end + end +end + +function Divorce (source) + local source_hf = df.historical_figure.find(source.hist_figure_id) + local spouse,spouse_hf = GetSpouseData (source) + local lover,lover_hf = GetLoverData (source) + + source.relations.spouse_id = -1 + source.relations.lover_id = -1 + + if source_hf then + EraseHFLinksLoverSpouse (source_hf) + end + if spouse then + spouse.relations.spouse_id = -1 + spouse.relations.lover_id = -1 + end + if lover then + spouse.relations.spouse_id = -1 + spouse.relations.lover_id = -1 + end + if spouse_hf then + EraseHFLinksLoverSpouse (spouse_hf) + end + if lover_hf then + EraseHFLinksLoverSpouse (lover_hf) + end + + local partner = spouse or lover + if not partner then + AnnounceAndGamelog(dfhack.TranslateName(source.name).." is now single") + else + AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(partner.name).." are now single") + end +end + +function Marriage (source,target) + local source_hf = df.historical_figure.find(source.hist_figure_id) + local target_hf = df.historical_figure.find(target.hist_figure_id) + source.relations.spouse_id = target.id + target.relations.spouse_id = source.id + + local new_link = df.histfig_hf_link_spousest:new() -- adding hf link to source + new_link.target_hf = target_hf.id + new_link.link_strength = 100 + source_hf.histfig_links:insert('#',new_link) + + new_link = df.histfig_hf_link_spousest:new() -- adding hf link to target + new_link.target_hf = source_hf.id + new_link.link_strength = 100 + target_hf.histfig_links:insert('#',new_link) +end + +function ChooseNewSpouse (source) + + if not source then + qerror("no unit") return + end + if (source.profession == 103 or source.profession == 104) then + ErrorPopup("target is too young") return + end + if not (source.relations.spouse_id == -1 and source.relations.lover_id == -1) then + ErrorPopup("target already has a spouse or a lover") + qerror("source already has a spouse or a lover") + return + end + + local choicelist = {} + targetlist = {} + + for k,v in pairs (df.global.world.units.active) do + if dfhack.units.isCitizen(v) + and v.race == source.race + and v.sex ~= source.sex + and v.relations.spouse_id == -1 + and v.relations.lover_id == -1 + and not (v.profession == 103 or v.profession == 104) + then + table.insert(choicelist,dfhack.TranslateName(v.name)..', '..dfhack.units.getProfessionName(v)) + table.insert(targetlist,v) + end + end + + if #choicelist > 0 then + ListPrompt( + "Assign new spouse for "..dfhack.TranslateName(source.name), + choicelist, + true, + function(a,b) + local target = targetlist[a] + Marriage (source,target) + AnnounceAndGamelog(dfhack.TranslateName(source.name).." and "..dfhack.TranslateName(target.name).." have married!") + end) + else + ErrorPopup("No suitable candidates") + end +end + +function MainDialog (source) + + local familystate = GetMarriageSummary(source) + + familystate = familystate.."\nSelect action:" + local choicelist = {} + local on_select = {} + + local child = (source.profession == 103 or source.profession == 104) + local is_single = source.relations.spouse_id == -1 and source.relations.lover_id == -1 + local ready_for_marriage = single and not child + + if not child then + table.insert(choicelist,"Remove romantic relationships (if any)") + table.insert(on_select, Divorce) + if ready_for_marriage then + table.insert(choicelist,"Assign a new spouse") + table.insert(on_select,ChooseNewSpouse) + end + if not ready_for_marriage then + table.insert(choicelist,"[Assign a new spouse]") + table.insert(on_select,function () ErrorPopup ("Existing relationships must be removed if you wish to assign a new spouse.") end) + end + else + table.insert(choicelist,"Leave this child alone") + table.insert(on_select,nil) + end + + ListPrompt(familystate, choicelist, false, + function(a,b) if on_select[a] then on_select[a](source) end end) +end + + +local args = {...} + +if args[1] == "help" or args[1] == "?" then print(helpstr) return end + +if not df.global.gamemode == 0 then + print (helpstr) qerror ("invalid gamemode") return +end + +if args[1] == "divorce" and tonumber(args[2]) then + local unit = df.unit.find(args[2]) + if unit then Divorce (unit) return end +end + +if tonumber(args[1]) and tonumber(args[2]) then + local unit1 = df.unit.find(args[1]) + local unit2 = df.unit.find(args[2]) + if unit1 and unit2 then + Divorce (unit1) + Divorce (unit2) + Marriage (unit1,unit2) + return + end +end + +local selected = dfhack.gui.getSelectedUnit(true) +if tonumber(args[1]) then + selected = df.unit.find(tonumber(args[1])) or selected +end + +if selected then + if dfhack.units.isCitizen(selected) and dfhack.units.isSane(selected) then + MainDialog(selected) + else + qerror("You must select sane fortress citizen.") + return + end +else + print (helpstr) + qerror("select a sane fortress dwarf") +end From 3623659a9b00a750561f83e2d8138d280cbbaa7a Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 5 Nov 2015 16:40:35 +1100 Subject: [PATCH 2/3] Add emigration script --- scripts/emigration.lua | 130 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 scripts/emigration.lua diff --git a/scripts/emigration.lua b/scripts/emigration.lua new file mode 100644 index 000000000..d523177bf --- /dev/null +++ b/scripts/emigration.lua @@ -0,0 +1,130 @@ +--Allow stressed dwarves to emigrate from the fortress +-- For 34.11 by IndigoFenix; update and cleanup by PeridexisErrant +-- old version: http://dffd.bay12games.com/file.php?id=8404 +--[[=begin + +emigration +========== +Allows dwarves to emigrate from the fortress when stressed, +in proportion to how badly stressed they are and adjusted +for who they would have to leave with - a dwarven merchant +being more attractive than leaving alone (or with an elf). +The check is made monthly. + +A happy dwarf (ie with negative stress) will never emigrate. + +Usage: ``emigration enable|disable`` + +=end]] + +local args = {...} +if args[1] == "enable" then + enabled = true +elseif args[1] == "disable" then + enabled = false +end + +function desireToStay(unit,method,civ_id) + -- on a percentage scale + value = 100 - unit.status.current_soul.personality.stress_level / 5000 + if method == 'merchant' or method == 'diplomat' then + if civ_id ~= unit.civ_id then value = value*2 end end + if method == 'wild' then + value = value*5 end + return value +end + +function desert(u,method,civ) + u.relations.following = nil + local line = dfhack.TranslateName(dfhack.units.getVisibleName(u)) .. " has " + if method == 'merchant' then + line = line.."joined the merchants" + u.flags1.merchant = true + u.civ_id = civ + elseif method == 'diplomat' then + line = line.."followed the diplomat" + u.flags1.diplomat = true + u.civ_id = civ + else + line = line.."abandoned the settlement in search of a better life." + u.civ_id = -1 + u.flags1.forest = true + u.animal.leave_countdown = 2 + end + print(line) + dfhack.gui.showAnnouncement(line, COLOR_WHITE) +end + +function canLeave(unit) + for _, skill in pairs(unit.status.current_soul.skills) do + if skill.rating > 14 then return false end + end + if unit.flags1.caged + or u.race ~= df.global.ui.race_id + or u.civ_id ~= df.global.ui.civ_id + or dfhack.units.isDead(u) + or dfhack.units.isOpposedToLife(u) + or u.flags1.merchant + or u.flags1.diplomat + or unit.flags1.chained + or dfhack.units.getNoblePositions(unit) ~= nil + or unit.military.squad_id ~= -1 + or dfhack.units.isCitizen(unit) + or dfhack.units.isSane(unit) + or unit.profession ~= 103 + or not dfhack.units.isDead(unit) + then return false end + return true +end + +function checkForDeserters(method,civ_id) + local allUnits = df.global.world.units.active + for i=#allUnits-1,0,-1 do -- search list in reverse + local u = allUnits[i] + if canLeave(u) and math.random(100) < desireToStay(u,method,civ_id) then + desert(u,method,civ_id) + end + end +end + +function checkmigrationnow() + local merchant_civ_ids = {} + local diplomat_civ_ids = {} + local allUnits = df.global.world.units.active + for i=0, #allUnits-1 do + local unit = allUnits[i] + if dfhack.units.isSane(unit) + and not dfhack.units.isDead(unit) + and not dfhack.units.isOpposedToLife(unit) + and not unit.flags1.tame + then + if unit.flags1.merchant then table.insert(merchant_civ_ids, unit.civ_id) end + if unit.flags1.diplomat then table.insert(diplomat_civ_ids, unit.civ_id) end + end + end + + for _, civ_id in pairs(merchant_civ_ids) do checkForDeserters('merchant', civ_id) end + for _, civ_id in pairs(diplomat_civ_ids) do checkForDeserters('diplomat', civ_id) end + checkForDeserters('wild', -1) +end + +local function event_loop() + checkmigrationnow() + dfhack.timeout(1, 'months', event_loop) +end + +dfhack.onStateChange.loadEmigration = function(code) + if code==SC_MAP_LOADED then + if enabled then + print("Emigration enabled.") + event_loop() + else + print("Emigration disabled.") + end + end +end + +if dfhack.isMapLoaded() then + dfhack.onStateChange.loadEmigration(SC_MAP_LOADED) +end + From efe945cfd91064203d1e8bb3b3c35a70cede666a Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Thu, 5 Nov 2015 21:54:45 +1100 Subject: [PATCH 3/3] Add new scripts to NEWS --- NEWS.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index e966eda6a..ee8c8a4bf 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -82,6 +82,8 @@ New scripts - `pref-adjust`: Adjust all preferences of all dwarves in play - `rejuvenate`: make any "old" dwarf 20 years old - `starvingdead`: make undead weaken after one month on the map, and crumble after six +- `emigration`: stressed dwarves may leave your fortress if they see a chance +- `gui/family-affairs`: investigate and alter romantic relationships New tweaks ----------