Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local helptext = [=[
- relations-indicator
- ===================
- v1.1
- Displays the pregnancy, fertility and romantic status of unit(s).
- For singular unit, in (v)iew-(g)eneral mode.
- For multiple units, in Citizens, Pets/Livestock and Others lists.
- By default, displays just pregnancies in those,
- as well as binding itself to be called there via keybinding plugin.
- Can be called with following arguments
- Relating to instructions
- -help, help, ?, -?
- Displays this text and exits
- explain colors
- Explains the meanings of various colors used and exits
- bindings
- Lists the keybindings relations-indicator uses with bind and exits
- bind
- adds context-specific keybindings
- unbind
- removes context-specific keybindings and exits]=]
- -- ======================================== --
- -- Color shades --
- -- ======================================== --
- local COLOR_DARKGRAY = 8 -- British spelling
- local COLOR_LIGHTGRAY = 7 -- ditto
- local straightMaleShade = COLOR_LIGHTCYAN -- feels like this should be pregnant color, but tweak uses green
- local straightFemaleShade = COLOR_LIGHTMAGENTA -- DT colors. Could alternatively flicker with symbol.
- local gayMaleShade = COLOR_YELLOW -- Blue makes more sense, but dark blue on black is hard to see.
- local gayFemaleShade = COLOR_LIGHTRED -- originally love or straight female color.
- local pregnantColor = COLOR_LIGHTGREEN -- base tweak color, might wap with yellow
- local infertileColor = COLOR_LIGHTGRAY
- local offsiteShade = COLOR_WHITE -- issue: orientation-offsite? Blinking can solve this, ofc.
- local deadColor = COLOR_DARKGRAY -- still, should avoid blinking with symbols if possible.
- -- 8 shades, but I only have 7 available. Plus default blue is not so great.
- local function colorExplanation()
- function printlnc(text, color)
- dfhack.color(color)
- dfhack.println(text)
- dfhack.color(COLOR_RESET)
- end
- dfhack.println("relations-indicator marks the following traits with following colour:")
- dfhack.print("Straight Male ") printlnc("Cyan", straightMaleShade)
- dfhack.print("Gay Male ") printlnc("Yellow", gayMaleShade)
- dfhack.print("Straight Female ") printlnc("Magenta", straightFemaleShade)
- dfhack.print("Gay Female ") printlnc("Red", gayFemaleShade)
- dfhack.print("Pregnant ") printlnc("Green", pregnantColor)
- dfhack.println("")
- dfhack.println("For the first four, darker shade indicates unwillingness to marry.")
- dfhack.println("")
- dfhack.println("The below three by default replace the first four in top-down hiearchy:")
- dfhack.println("")
- dfhack.print("Dead partner ") printlnc("Dark gray ", deadColor)
- dfhack.print("Infertile/aromantic or infertile/aromantic hetero partner ") printlnc("Light gray", infertileColor)
- dfhack.print("Offsite partner ") printlnc("White", offsiteShade)
- end
- -- ======================================== --
- -- Indicator Symbols --
- -- ======================================== --
- local loversSymbol = "\148"
- local marriedSymbol = "\3"
- local singleMaleSymbol = "\11"
- local singleFemaleSymbol = "\12"
- local fertileBirdSymbol = "\8" -- used only in unitlist
- local pregnantCreatureSymbol = "\20" -- also only in unitlist
- local diedS, diedE = "\197", "\197" --Used just for viewing a single unit.
- local offsite = "..." --Also for just a single unit only.
- local blinkingdelay = 650
- --How often does it blink between colors and/or symbols.
- -- ======================================== --
- -- Affection thresholds --
- -- ======================================== --
- local friendlinessthreshold = 0
- local loversthreshold = 14
- local marriagethreshold = -40
- -- ======================================== --
- -- Keybindings used --
- -- ======================================== --
- local keybinding_list = {}
- table.insert(keybinding_list, "U@dwarfmode/Default relations-indicator")
- table.insert(keybinding_list, "V@dwarfmode/Default relations-indicator")
- table.insert(keybinding_list, "Z@unitlist/Citizens relations-indicator")
- table.insert(keybinding_list, "Z@dfhack/unitlabors relations-indicator")
- table.insert(keybinding_list, "Z@unitlist/Livestock relations-indicator")
- table.insert(keybinding_list, "Z@unitlist/Citizens relations-indicator")
- table.insert(keybinding_list, "Z@unitlist/Others relations-indicator")
- table.insert(keybinding_list, "Z@layer_unit_relationship relations-indicator")
- local function relationsIndicatorPowerState(enable)
- for bindingi=1, #keybinding_list do
- dfhack.run_command_silent("keybinding " .. (enable and "add " or "clear ") .. keybinding_list[bindingi])
- end
- end
- local args = {...}
- local argsline = table.concat(args, " ")
- --utils.processArgs is neat but requires - and not necessary here
- if argsline:find("help") or
- argsline:find("?") then
- dfhack.println(helptext)
- qerror("")
- --Not, strictly speaking, a proper exit but it'll do
- end
- if argsline:find("explain colors") then colorExplanation() qerror("") end
- if argsline:find("bindings") then
- for bindingi=1, #keybinding_list do
- dfhack.println(keybinding_list[bindingi])
- end
- dfhack.println("(Internal) Z@dfhack/lua/manipulator relations-indicator")
- qerror("")
- end
- if argsline:find("unbind") then relationsIndicatorPowerState(false) qerror("") end
- if argsline:find("bind") then
- relationsIndicatorPowerState(true)
- end
- -- ======================================== --
- -- Loading required modules --
- -- ======================================== --
- local gui = require 'gui'
- local screenconstructor = dfhack.script_environment("gui/indicator_screen")
- -- ======================================== --
- -- Utility functions --
- -- ======================================== --
- local function getLen(data)
- -- Can't # a hashed table, must use pairs.
- local len = 0
- for i, val in pairs(data) do
- len = len +1
- end
- return len
- end
- function blinkergenerator(tableofblinkers)
- -- takes numerically indexed table of values
- -- returns either single value if table has a single value, or function that alternates which one it returns
- -- local manyblinkers = #tableofblinkers
- if #tableofblinkers == 1 then
- return tableofblinkers[1]
- else
- function blinkingfunction()
- local blinkertable = tableofblinkers
- local blinkernr = #tableofblinkers
- return blinkertable[1+math.floor(dfhack.getTickCount()/blinkingdelay) % blinkernr]
- end
- return blinkingfunction
- end
- end
- function getFlag(object, flag)
- -- Utility function for safely requesting info from userdata
- -- Returns nil if the object doesn't have flag attribute, else returns it's value
- -- Because well, ordinarily, {}[flag] returns nil.
- -- However, if object is unit - or some other type, it may instead throw an error
- local a = {}
- if not object or not flag then return nil end
- --Crash is still possible for attempting to pairs a nil
- for index, value in pairs(object) do
- a[index] = value
- end
- local returnvalue = a[flag]
- a = nil --lua automatically garbage cleans tables without variable that links to them.
- return returnvalue
- end
- function getBottomMostViewscreenWithFocus(text, targetscreen)
- --Finds and returns the screen closest to root screen whose path includes text
- --duplicated from getScreen, admittedly.
- if targetscreen and
- dfhack.gui.getFocusString(targetscreen):find(text) then
- return targetscreen
- elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this
- return getBottomMostViewscreenWithFocus(text, targetscreen.child)
- end
- -- if there's no child, it didn't find a screen with text in focus and returns nil
- end
- function writeoverTable(modifiedtable, copiedtable)
- --Takes two tables as input
- --Removes all the values in first table
- --Then places all the values in second table into it
- --Returns the first table after that
- for index, value in pairs(modifiedtable) do
- modifiedtable[index] = nil
- end
- for index, value in pairs(copiedtable) do
- modifiedtable[index] = value
- end
- return modifiedtable
- end
- function hasKeyOverlap (searchtable, searchingtable)
- -- Looks for a key in searchtable whose name contains the name of a key in searchtable
- -- returns true if finds it.
- for aindex, avalue in pairs(searchingtable) do
- for bindex, bvalue in pairs(searchtable) do
- if tostring(bindex):find(tostring(aindex)) then
- return true
- end
- end
- end
- end
- -- ======================================== --
- -- Tier 1 (df) functions --
- -- ======================================== --
- function isItBird(unit)
- return (df.global.world.raws.creatures.all[unit.race].caste[0].flags.LAYS_EGGS)
- --because it lists all available flags as true or false doesn't need to invoke getFlag
- end
- function isItSmart(unit)
- if (df.global.world.raws.creatures.all[unit.race].caste[0].flags.CAN_LEARN and
- not df.global.world.raws.creatures.all[unit.race].caste[0].flags.SLOW_LEARNER) then
- return true
- else
- return false
- end
- end
- function getGenderInInfertileColor(unit)
- local symbolColor = {}
- symbolColor.text = unit.sex == 1 and
- singleMaleSymbol or
- ( unit.sex == 0 and
- singleFemaleSymbol or "")
- symbolColor.color = infertileColor
- return symbolColor
- end
- -- ======================================== --
- -- 43.05 vs earlier structure differences --
- -- ======================================== --
- function getPregnancyTimer(unit)
- -- Takes local unit, returns time until birth in steps.
- -- In case of historical unit always returns -1; don't know about their pregnancy structures.
- -- utilizes getFlag
- if getFlag(unit, "info") then return -1 end -- so assume they're always not pregnant. They usually aren't
- return (getFlag(unit, "pregnancy_timer") or getFlag(unit.relations, "pregnancy_timer"))
- --Differences between 43.05 and earlier dfhack.
- end
- function getLocalRelationship(unit, SpouseOrLover)
- --Takes local unit, boolean
- -- returns spouse id in the case of true for second value,
- -- lover in the case of false or if nil is used and spouse isn't present.
- -- utilizes getFlag
- if getFlag(unit, "info") then return nil end
- --Not intended to be used on historical figure structure, as that is different.
- --Also, using nil when number is expected will throw an error, so this points out those mistakes
- local is4305p = getFlag(unit, "relationship_ids") and true or false
- local relationships = is4305p and unit.relationship_ids or unit.relations
- local spousevalue = is4305p and relationships.Spouse or relationships.spouse_id
- local lovervalue = is4305p and relationships.Lover or relationships.lover_id
- --Again, differences between 43.05 and earlier dfhack.
- --Further issue: 43.03 uses spouse_id, 43.05 uses Spouse
- --This is not as extensible, but since I only get spouse or lover for now...
- if SpouseOrLover == true then return spousevalue end
- if SpouseOrLover == false then return lovervalue end
- if spousevalue > -1 then
- return spousevalue
- else
- return lovervalue
- end
- end
- -- ======================================== --
- -- Tier 2 functions --
- -- ======================================== --
- function isItGelded(unit)
- -- Either local or historical unit
- -- returns true or nil
- -- utilizes getFlag
- if getFlag(unit, "status") then
- --local unit
- if unit.flags3.gelded then
- --usually only pets and guys are gelded, but can be set on anyone
- return true
- elseif unit.curse.add_tags2.STERILE then
- --occurs for vampires and husks
- return true
- elseif not getFlag(unit.status, "current_soul") then
- --occurs for animated corpses
- --Could also use unit.curse.add_tags1.OPPOSED_TO_LIFE
- --Though I'm not certain you need a soul to breed, lack of soul messes up checking personality
- return true
- end
- elseif getFlag(unit, "info") then
- --historical unit
- if (getFlag(unit.info, "wounds") and getFlag(unit.info, "wounds").anon_3 ==1 ) then
- --suspected gelding flag. 0 is dead, -1 is default?
- return true
- elseif (getFlag(unit.info,"curse") and getFlag(unit.info.curse, "active_interactions") ) then
- for i, interaction in pairs(unit.info.curse.active_interactions) do
- --Here, could just check that it's name has VAMPIRE in it in vanilla, but could have modded vamps
- --Interestingly, soul is not mentioned in historical unit data. Presumably it is hiding. Fallback plan
- for j, text in pairs(interaction.str) do
- if text:find("STERILE") or
- text:find("OPPOSED_TO_LIFE") then
- --side effect: False positive on syndromes that remove those tags.
- --ex: modded-in gonads that remove sterility from otherwise-sterile creature
- --TODO: fix
- return true
- end
- end
- end
- end
- end
- return nil
- end
- function getPregnancyText(unit)
- -- Takes local unit, returns single line string that is "" for no pregnancy
- -- utilizes getFlag, getPregnancyTimer
- if not getFlag(unit, "status") or
- unit.caste > 0 then
- return ""
- else
- local howfar = getPregnancyTimer(unit)
- local pregnancy_duration = df.global.world.raws.creatures.all[unit.race].caste[0].flags.CAN_LEARN and 9 or 6
- if howfar < 1 then return "" else
- local returnstring = ""
- if isItBird(unit) then
- if (howfar/33600)>1 then returnstring = "Was wooed " .. tostring(math.floor(pregnancy_duration-howfar/33600)) .. " months ago" end
- if (howfar/33600)<1 then returnstring = "Last seed withers in " .. tostring(math.floor(howfar/1200)) .. " days " end
- if (howfar/33600)>(pregnancy_duration-1) then returnstring = "Was wooed recently" end
- else
- if (howfar/33600)>0 then returnstring = "Sick in the morning recently" end
- if (howfar/33600)<(pregnancy_duration-1) then returnstring = "Missed a period" end
- if (howfar/33600)<(pregnancy_duration-2) then returnstring = tostring(math.floor(pregnancy_duration-howfar/33600)) .. " months pregnant" end
- if (howfar/33600)<1 then returnstring = "Will give birth in " .. tostring(math.floor(howfar/1200)) .. " days" end
- end
- return returnstring;
- end
- end
- end
- function getSpouseAndLover(unit)
- --Takes a local unit, returns either local or historical spouse and lover of that unit if present
- --if the spouse/lover is historically set but culled (single parents who can marry), returns false for those
- --else, returns for that value
- -- utilizes getLocalRelationship, df.historical_figure.find
- local historical_unit = df.historical_figure.find(unit.hist_figure_id)
- local spouse,lover, unithistoricalrelations
- local spouseid = getLocalRelationship(unit, true)
- local loverid = getLocalRelationship(unit, false)
- if spouseid > -1 then
- spouse = df.unit.find(spouseid)
- --shortcut
- end
- if not spouse and --shortcut failed due spouse never arriving, or having left the site
- historical_unit then --pets brought on embark aren't historical
- --got to dig into historical unit values
- unithistoricalrelations = historical_unit.histfig_links
- for index, relation in pairs(unithistoricalrelations) do
- --no local spouse? Mark both global spouse and lover
- if (relation._type == df.histfig_hf_link_spousest) then
- spouse=df.historical_figure.find(relation.target_hf)
- if not spouse then spouse = false
- --there was an id, but there wasn't a histfig with that id (due culling)
- end
- -- small distinction between nil and false: is it better to have loved and lost, or never loved?
- elseif (relation._type == df.histfig_hf_link_loverst) then
- lover=df.historical_figure.find(relation.target_hf)
- if not lover then lover = false
- end
- end
- end
- end
- if loverid > -1 then
- lover=df.unit.find(loverid) --can be nil for offsite lover
- if not lover then lover = false end --false instead of nil to indicate having checked
- end
- if not lover and historical_unit then
- --No local lover? Maybe lover is global
- unithistoricalrelations = historical_unit.histfig_links
- for index, relation in pairs(unithistoricalrelations) do
- if (relation._type == df.histfig_hf_link_loverst) then
- lover=df.historical_figure.find(relation.target_hf)
- if not lover then lover = false end
- end
- end
- end
- return spouse, lover
- end
- function areCompatible(unitA,unitB)
- --Checks if two local units make compatible pair and returns true if they are, false if not
- --Utilizes getFlag, requires them to be historical to check relationships
- -- Do I check if one is a child?
- -- I think not. Arranged marriages can be planned a decade before they happen.
- -- Still, age is most common disqualifying factor
- if unitA.race ~= unitB.race then return false end
- --multi-racial fortress are nice, but humans still can't into dwarves; not like this.
- local is4305p = getFlag(unitA, "relationship_ids") and true or false
- local relationshipsA = is4305p and unitA or unitA.relations
- local relationshipsB = is4305p and unitB or unitB.relations
- --age is stored in relations for 4303 but on base level in 43.05, so...
- local ageA = relationshipsA.birth_year+relationshipsA.birth_time/403200
- local ageB = relationshipsB.birth_year+relationshipsB.birth_time/403200
- --exact age matters
- if (ageA-ageB) > 10 or (ageB-ageA) > 10 then
- --over 10 year age difference
- return false
- end
- -- Lets check if one of them is married.
- -- If they are, can do hanky panky with spouse alone
- local spouseA, loverA = getSpouseAndLover(unitA)
- local spouseB, loverB = getSpouseAndLover(unitB)
- if spouseA or loverA or
- spouseB or loverB then
- if spouseA == unitB or
- loverA == unitB then
- return true
- else
- return false
- end
- end
- --Lets check if they have compatible orientations for marrying each other.
- local attractionA = getAttraction(unitA)
- local attractionB = getAttraction(unitB)
- if not ((2 == attractionA[(unitB.sex == 1 and "guy" or "girl")]) and
- (2 == attractionB[(unitA.sex == 1 and "guy" or "girl")])) then
- -- Admittedly, this means that someone who only romances has no suitable pairings.
- return false
- end
- --Lets check if personalities are compatible.
- local unwillingToFriendA, unwillingToLoveA, unwillingToMarryA = getAromantism(unitA)
- local unwillingToFriendB, unwillingToLoveB, unwillingToMarryB = getAromantism(unitB)
- if unwillingToFriendA or unwillingToLoveA or unwillingToMarryA or
- unwillingToFriendB or unwillingToLoveB or unwillingToMarryB then
- --If either one as baggage about progressing through a relationship, no babies
- return false
- end
- --Checking for relationships requires digging into historical unit values.
- local hfA = unitA.hist_figure_id > -1 and df.historical_figure.find(unitA.hist_figure_id) or nil
- local hfB = unitB.hist_figure_id > -1 and df.historical_figure.find(unitB.hist_figure_id) or nil
- if hfA and hfB then --basic sanity check.
- -- Function to check for being a sibling.
- -- Half-siblings...Possible with hacking, and I bet they block
- function gethfParent(hfunit, retrieveMother)
- --Returns historical mother or father of a historical unit if possible
- --otherwise returns nil
- for index, relationship_link in pairs(hfunit.histfig_links) do
- if retrieveMother and relationship_link._type == df.histfig_hf_link_motherst or
- (not retrieveMother and relationship_link._type == df.histfig_hf_link_fatherst) then
- return df.historical_figure.find(relationship_link.target_hf)
- end
- end
- end
- local momA = gethfParent(hfA, true)
- local momB = gethfParent(hfB, true)
- local dadA = gethfParent(hfA)
- local dadB = gethfParent(hfB)
- if momA and momB and momA == momB or --existence of moms must be checked since nil == nil
- (dadA and dadB and dadA == dadB) then
- --siblings or half-siblings are not allowed
- return false
- end
- --Function to check for grudge:
- -- (As it is not used outside parent function, not encapsulating elsewhere despite size)
- function hasGrudgeTowards(hfUnitChecked, hfUnitFuckThisCreatureInParticular)
- -- print("Checking for grudge between " .. dfhack.TranslateName(hfUnitChecked.name) .. " and " .. dfhack.TranslateName(hfUnitFuckThisCreatureInParticular.name))
- -- Triple-loops checking info.relationships.list[#].anon_3[#].
- -- Admittedly, doing this repeatedly for every unit is inefficient.
- -- Better would be finding all grudges in fortress at start and cross-checking that.
- if hfUnitChecked.info.relationships then
- --Invaders, for instance, may have it absent
- --Though I wonder if it is even possible to marry off invaders, even after peace settlement
- for index, relationship in pairs (hfUnitChecked.info.relationships.list) do
- if hfUnitFuckThisCreatureInParticular.id == relationship.histfig_id then
- --Found a relationship between the two units. Now for grudge!
- local attitude
- if getFlag(relationship,'anon3') ~= nil then attitude = relationship.anon3 else attitude = relationship.attitude end
- for feelingindex, feelingtype in pairs(attitude) do
- --A dwarf can have multiple feelings/relationship types with someone.
- if feelingtype == 2 then
- --[[List of options I've noticed with changing that value:
- 0: Hero
- 1: Friend
- 2: Grudge
- 3: Bonded
- 6: Good for Business
- 7: Friendly Terms? (unsure)
- 10: Comrade
- 17: Loyal Soldier
- 18: Considers Monster (hm, could be interesting RP-wise)
- 26: Protector of the Weak
- Others seemed to default to Friendly terms
- with just few points on 7 as second relation.
- Perhaps anon_1 and anon_5 may also matter.
- --]]
- return true
- end
- end
- --Found unit without grudge.
- attitude = nil
- return false
- end
- end
- end
- end
- if hasGrudgeTowards(hfA, hfB) or
- hasGrudgeTowards(hfB, hfA) then
- --Either one having a grudge? Welp, no marriage for you two.
- return false
- end
- end
- -- No other disqualifing factors? It's a go.
- return true
- end
- function getInclinationIfPresent(unit, inclinationnumber)
- --takes ensouled unit and numerical index of inclination
- --returns the value of inclination or 0 or -1000 in case of divorce from local and seeing the world.
- -- utilizes getFlag
- local values
- if getFlag(unit,"status") then
- values = unit.status.current_soul.personality.values
- elseif getFlag(unit.info,"personality") then
- --can be nil for local units who have never updated their hfunit
- --comes up in the case of divorce.
- values = unit.info.personality.values
- else
- return -1000 --buggy placeholder: divorced partners are super-incapable of progressing their relationship.
- end
- -- Do need to check hfunits, since both parties of a ship must be willing to embark on the waters of marriage
- for index, value in pairs(values) do
- if value.type == inclinationnumber then
- return value.strength
- end
- end
- return 0
- end
- function getAttraction(unit)
- --unit can't be nil. The nothingness where there should be something doesn't have sex, y'see.
- --Outputs a table of levels of sexual attraction an unit has.
- -- utilizes getFlag
- local attraction = {guy = 0, girl = 0}
- local orientation
- if unit.sex~=-1 then
- if getFlag(unit, "status") then
- --local unit
- orientation= getFlag(unit.status, "current_soul") and unit.status.current_soul.orientation_flags or false
- --alas, creatures can be soulless.
- else
- --historical unit
- orientation = unit.orientation_flags
- end
- end
- if orientation then
- if orientation.romance_male then
- attraction.guy = 1
- elseif orientation.marry_male then
- attraction.guy = 2
- end
- if orientation.romance_female then
- attraction.girl = 1
- elseif orientation.marry_female then
- attraction.girl = 2
- end
- end
- return attraction
- end
- -- ======================================== --
- -- Tier 3 functions --
- -- ======================================== --
- function getAromantism(unit)
- --Takes local unit
- --returns following series of values
- local unwillingToFriend, unwillingToLove, unwillingToMarry
- --utilizes getFlag, getIncinationIfPresent
- --utilizes these internally:
- local smittedness, friendly, bonding
- --failure conditions : hating friendship and having no eligble friends (not certain, might be enough to just have one-sided relation), hating romance, hating marriage.
- -- unit.status.current_soul.personality.values.
- -- Type 29: romance, type 2: family, type 3: friendship, type 18: harmony.
- -- hfunit.info.personality.values - nil for embark dwarves, present on visitors, like visitors can have nil spouse relation but histfig value set
- -- poults born on-site from embark turkeys don't have hfid or more than 0 values.
- -- unit.status.current_soul.personality.traits .LOVE_PROPENSITY (if this is high and romance is low can still marry, Putnam suggests from 2015 suggests it is almost the only trait that matters)
- -- unknown: Type 3 - friendship, .LUST_PROPENSITY, FRIENDLINESS
- -- unknown: how much traits must differ in general for marriage to happen instead of a grudge.
- -- also, grudges could be prevented or perhaps later removed by social skills, shared skills and preferences.
- --local smittedness, friendly, bonding, unwillingToFriend, unWillingToLove, unwillingToMarry
- smittedness = unit.status.current_soul.personality.traits.LOVE_PROPENSITY
- -- again, always present on units that are on-site; but obviously histfigs will have to be handled differently
- friendly =unit.status.current_soul.personality.traits.FRIENDLINESS
- --FRIENDLINESS. I think I've seen ever dyed-in-the-wool quarrels have friends.
- bonding = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE -- how easily they make connections
- -- per Sarias's research in 2013, dwarves must be friends before becoming lovers.
- -- local cheerfulness -- happier dwarves make relationships more easily, buuut everyone is at -99999 anyway
- -- local lustfulness -- science required to see if it affects whether relationship can form
- -- local trust -- relationships should be about trust, but lying scumbags can get married too irl. Who knows?
- -- Eventually, for specific units would have to check how well they match up and return granual value.
- unwillingToFriend =(getInclinationIfPresent(unit, 3)+friendly+bonding) < friendlinessthreshold and true or false
- -- avg 100, min -50, max 250
- -- currently requires ~roughly two lowest possible and 1 second lowest possible values.
- -- Starting seven do have friends, which can override this.
- -- While I've failed to friend off dwarves due personality,
- -- those dwarves have managed friendships with at least 1 other person,
- -- and several times have managed a marriage with someone else.
- -- 18-year old Erush Shoduksǎkzul has 3 friends, having 1 lower FRIENDSHIP, bonding
- unwillingToLove = (getInclinationIfPresent(unit, 29)+smittedness) < loversthreshold and true or false
- --not using bonding, maybe should. They already have emotional bond with others, though.
- --50 is average. 14 might be too low, allowing second lowest value on 1 with other average
- --20 is maximum for non-mentioned propensity and hating even the idea of romance, but can sometimes prevent two 1 worse than average values
- -- What numercal indicators can I fit into 1 tile, anyway? Blinking, I guess. TWBT would enable gradual color
- -- blinking has problems at low fps, but viewing unit and unit list have game paused.
- -- Tests should be done with 50 fps/10 gfps, since those are lowest maximums I know of.
- -- 30 GFPS can blend-ish, but 10 is blinky. Maybe use -blinking_at input with default value only_if_above_29
- -- should check if it blinks on returning to low fps.
- unwillingToMarry = getInclinationIfPresent(unit, 2) < marriagethreshold and true or false
- --as long as they don't find family loathsome, it's a-ok.
- return unwillingToFriend, unwillingToLove, unwillingToMarry
- end
- function getSpouseAndLoverLink(unit)
- -- Currently takes local unit only
- -- Returns eight values: spouse and lover names, spouse and lover sites, spouse and lover aliveness, spouse and lover gender
- -- Doesn't check the hf relationships if the hf_only unit doesn't have a spouse in histfig links
- -- might be an issue if a visitor comes on map, romances someone, and then leaves. Never heard of that happening, but hey
- -- utilizes getFlag, getSpouseAndLover, dfhack.units.isAlive, dfhack.TranslateName, df.historical_figure.find
- local spouse,lover
- local spousesite, loversite = df.global.ui.site_id, df.global.ui.site_id
- --blanket current site, unless I assign them differently later
- --this is fine as I have indicator for off-site spouse, not on-site spouse
- local spouselives, loverlives
- spouse, lover = getSpouseAndLover(unit)
- if spouse and getFlag(spouse,"info") then spousesite = spouse.info.unk_14.site end
- if lover and getFlag(lover,"info") then loversite = lover.info.unk_14.site end
- local spousename, lovername
- if spouse == false then
- for index, relation in pairs(df.historical_figure.find(unit.hist_figure_id).histfig_links) do
- --Spouse has been culled? Maybe they're single parent.
- if (relation._type == df.histfig_hf_link_childst) then
- spousename = "Single parent"
- spouselives = true --More like that they're not dead. Visual thing.
- end
- end
- elseif spouse then
- spousename = dfhack.TranslateName(spouse.name)
- -- here, as spouse without name doesn't have gender either
- if getFlag(spouse, "flags1") then --local unit
- spouselives = dfhack.units.isAlive(spouse)
- else
- spouselives = spouse.died_year < 0
- end
- end
- if lover then
- lovername = dfhack.TranslateName(lover.name)
- if getFlag(lover, "flags1") then --local unit
- loverlives = dfhack.units.isAlive(lover)
- else
- loverlives = lover.died_year < 0
- end
- end
- -- lovers can't have children, so it's entirely pointless to speak of lost love.
- return spousename, lovername, spousesite, loversite, spouselives, loverlives, spouse, lover
- end
- function getSymbolizedAnimalPreference(unit, unwrapped)
- --Returns symbolized pregnancy if animal is pregnant.
- --Else, returns gender symbol, color: string and number
- --color is a function whose return value blinks between modes if appropriate.
- --unwrapped doesn't pass the colors through blinkergenerator.
- --utilizes getAttraction, isItBird, isItGelded
- local attraction = getAttraction(unit)
- local symbolColor = {text, color}
- local prefColorTable
- if getPregnancyTimer(unit) > 0 then
- symbolColor.text = isItBird(unit) and fertileBirdSymbol or pregnantCreatureSymbol
- symbolColor.color = pregnantColor
- else
- symbolColor.text = unit.sex == 1 and
- singleMaleSymbol or
- ( unit.sex == 0 and
- singleFemaleSymbol or "")
- if unit.sex == -1 or --flesh balls
- (attraction.guy == 0 and attraction.girl == 0) or --asexual
- isItGelded(unit) then --some tomcats
- symbolColor.color = infertileColor
- return symbolColor
- --strictly speaking, not necessary, due light gray being default color
- end
- prefColorTable = {}
- if unit.sex == 0 then
- if attraction.guy > 0 then table.insert(prefColorTable, straightFemaleShade) end
- if attraction.girl > 0 then table.insert(prefColorTable, gayFemaleShade) end
- else
- if attraction.girl > 0 then table.insert(prefColorTable, straightMaleShade) end
- if attraction.guy > 0 then table.insert(prefColorTable, gayMaleShade) end
- end
- end
- if unwrapped then
- if prefColorTable and #prefColorTable > 0 then
- symbolColor.color = prefColorTable
- end
- return symbolColor
- else
- if prefColorTable then symbolColor.color = blinkergenerator(prefColorTable) end
- if getPregnancyTimer(unit) > 0 then
- symbolColor.onhovertext = {
- color = pregnantColor,
- text = tostring(math.floor((isItSmart(unit) and 9 or 6) -getPregnancyTimer(unit)/33600))
- }
- end
- return symbolColor
- end
- end
- function getSymbolizedSpouse(unit)
- -- Currently takes local unit only
- -- Returns {} with text and color which are string or function and number or function
- -- utilizes getSpouseAndLoverLink, getAttraction, getInclinationIfPresent, isItSmart,isItBird, isItGelded
- local spousename, lovername, spousesite, loversite, spouselives, loverlives, spouse, lover = getSpouseAndLoverLink(unit)
- local symbolColor = {text, color}
- local attraction = getAttraction(unit)
- --plain sexual attraction table
- --it'd be more compact to code if instead of guy and girl values would use 0 and 1 indices
- --could call attraction[unit.sex] == 2 to see if they're willing to engage in gay marriage, for example
- --however, code is more self-explanatory with variables having names that explain what they're for
- local unwillingToFriend, unwillingToLove, unwillingToMarry
- if (attraction.guy+attraction.girl) == 0 then
- unwillingToFriend, unwillingToLove, unwillingToMarry = true, true, true
- else
- unwillingToFriend, unwillingToLove, unwillingToMarry = getAromantism(unit)
- end
- --series of disqualifying boolean values; though if orientation is already zero better not check
- local symbolTable = {}
- local colorTable = {}
- if getPregnancyTimer(unit) > 0 and
- isItSmart(unit) then --necessary due otherwise doubling up on the indicator with animal preferences.
- --Normally, would lose nothing by having pregnancy highest hiearchy, could just check first and skip the above
- --However, in cases of modding or father dying in battle (such as in fucduck's elven outpost) info is lost
- if isItBird(unit) then
- --possible with bird-women adventurers joining the fortress
- table.insert(symbolTable,fertileBirdSymbol)
- else
- table.insert(symbolTable,pregnantCreatureSymbol)
- end
- table.insert(colorTable,pregnantColor)
- end
- if not isItSmart(unit) then --it's an animal
- local animalprefs = getSymbolizedAnimalPreference(unit,true)
- table.insert(symbolTable, animalprefs.text)
- if type(animalprefs.color) == "table" then
- table.insert(colorTable, animalprefs.color[0])
- table.insert(colorTable, animalprefs.color[1]) --two gender prefs at most.
- table.insert(symbolTable, animalprefs.text) --going to mess up timing otherwise.
- else
- table.insert(colorTable, animalprefs.color)
- end
- end
- if not lovername and
- (not spousename or spousename == "Single parent") then
- if isItSmart(unit) then --otherwise already handled earlier, don't need to add anything.
- table.insert(symbolTable,
- ((unit.sex == 0) and singleFemaleSymbol or (unit.sex == 1 and singleMaleSymbol or "")))
- --creatures without gender don't get a symbol.
- if (attraction.guy == 0 and attraction.girl == 0) or --asexual
- isItGelded(unit) or -- gelded. Aw.
- unwillingToLove or --aromantic. Requires soul.
- sex == -1 then --creatures like iron men and bronze colossi indicate that genderless creatures can't breed
- table.insert(colorTable, infertileColor)
- else
- if unit.sex == 0 then
- if attraction.girl > 0 then
- table.insert(colorTable,
- (gayFemaleShade - 8*( (unwillingToMarry or attraction.girl == 1) and 1 or 0 )))
- --darker shade for lover-only relationships
- end
- if attraction.guy > 0 then
- table.insert(colorTable,
- (straightFemaleShade - 8*( (unwillingToMarry or attraction.guy == 1) and 1 or 0 )))
- end
- else
- if attraction.girl > 0 then
- table.insert(colorTable,
- (straightMaleShade - 8*( (unwillingToMarry or attraction.girl == 1) and 1 or 0 )))
- end
- if attraction.guy > 0 then
- table.insert(colorTable,
- (gayMaleShade - 8*( (unwillingToMarry or attraction.guy == 1) and 1 or 0 )))
- end
- end
- if #colorTable>#symbolTable and --Our table has more colors than symbols. Noprobs,
- #symbolTable>1 then --unless there's pregnant single present
- table.insert(symbolTable, symbolTable[#symbolTable])
- --Pregnant singles screw up timing, unless we double up on last symbol.
- end
- end
- end
- else
- if spousename and spousename ~= "Single parent" then
- table.insert(symbolTable, marriedSymbol)
- --table for on-site, alive, not infertile spouse
- -- Using hiearchy:
- -- Dead > Infertile (unless gay) > Offsite > Normal bright color.
- local spousecolor = (unit.sex == 0) and
- (spouse.sex==1 and straightFemaleShade or gayFemaleShade) or
- (spouse.sex==0 and straightMaleShade or gayMaleShade)
- -- live marriage
- spousecolor = df.global.ui.site_id == spousesite and spousecolor or offsiteShade
- --spouse is offsite. Can have problems with divorcing.
- spousecolor = (isItGelded(unit) or isItGelded(spouse)) and
- not (unit.sex == spouse.sex)
- and infertileColor or spousecolor
- --spouse is infertile and not gay-only marriage
- spousecolor = spouselives and spousecolor or deadColor
- table.insert(colorTable, spousecolor)
- end
- if lovername then
- table.insert(symbolTable, loversSymbol)
- -- Two types of lovers: ones willing to progress to marriage, ones unwilling
- -- The unwilling ones get darker shade
- -- Both parties must be willing
- -- Lovers and spouses may also be off-site, dead or infertile.
- -- While can display all with blinky things, should minimize needless UI churn.
- -- Dead > Infertile (unless gay) > Offsite > Unwilling to progress to marriage > Normal bright color.
- local lovercolor = (unit.sex == 0) and
- (lover.sex==0 and gayFemaleShade or straightFemaleShade) or
- (lover.sex==0 and straightMaleShade or gayMaleShade)
- --baseline is willing to marry
- lovercolor = (attraction[((lover.sex==1) and "guy" or "girl")] < 2 or
- getAttraction(lover)[((unit.sex==1) and "guy" or "girl")] < 2 or
- unwillingToMarry or
- (getInclinationIfPresent(lover, 2) < marriagethreshold and true or false)) and
- (lovercolor - 8) or lovercolor
- -- if the unit or their lover has personality or attraction failure, the relationship will not progress
- lovercolor = df.global.ui.site_id == loversite and lovercolor or offsiteShade
- --lover is offsite. Can have problems with divorcing.
- --Happens mostly in case of visitors or married migrants.
- --Issue: Either a possible false hope of offsite lover eventually arriving.
- -- or a false indicator of lover being on-site.
- --Blinking and blurring could solve this, but not hiearchy.
- lovercolor = (isItGelded(unit) or isItGelded(lover)) and
- not (unit.sex == lover.sex)
- and infertileColor or lovercolor
- --lover is infertile and not gay-only marriage
- lovercolor = loverlives and lovercolor or deadColor
- --lover is dead
- table.insert(colorTable, lovercolor)
- end
- end
- symbolColor.text = blinkergenerator(symbolTable)
- symbolColor.color = blinkergenerator(colorTable)
- if getPregnancyTimer(unit) > 0 then
- symbolColor.onhovertext = {
- color = pregnantColor,
- text = tostring(math.floor((isItSmart(unit) and 9 or 6) -getPregnancyTimer(unit)/33600))
- --Needs to be converted to string since #text is called for width
- }
- end
- return symbolColor
- end
- function getSpouseText(unit)
- -- Takes local unit, returns single line string that is "" if there's no romantic relation
- local spousename, lovername, spousesite, loversite, spouselives, loverlives = getSpouseAndLoverLink(unit)
- --An unit can have both lover and spouse in vanilla with retirement shenanigans
- local returnstring = ""
- if spousename then
- --spouse matters more so goes first.
- returnstring = returnstring .. marriedSymbol
- if spousesite ~= df.global.ui.site_id then
- returnstring = returnstring .. offsite
- end
- if spouselives then
- returnstring = returnstring .. spousename
- else
- returnstring = returnstring .. diedS .. spousename ..diedE
- end
- end
- if lovername then
- returnstring = returnstring .. loversSymbol
- if loversite ~= df.global.ui.site_id then
- returnstring = returnstring .. offsite
- end
- if loverlives then
- returnstring = returnstring .. lovername
- else
- returnstring = returnstring .. diedS .. lovername ..diedE
- end
- end
- return returnstring
- end
- function getSuitableMatches(unit, unitlist, joblist)
- --Takes a local unit, local unitlist and local joblist
- --Returns an unitlist that includes unit and then all it's suitable candidates.
- --And joblist that has only those same indices, as unitlist viewscreen uses that data.
- --utilizes areCompatible
- local matchlist, jobmatchlist = {}, {}
- --The unit we've checking always comes first
- for index=0, #unitlist-1 do
- if (unit == unitlist[index]) or -- self
- areCompatible(unit, unitlist[index]) then --suitable match
- matchlist[index] = true
- jobmatchlist[index] = true
- end
- end
- return matchlist, jobmatchlist
- end
- -- ======================================== --
- -- Dynamic text {} output functions --
- -- ======================================== --
- function getViewUnitPairs(unit)
- local returnpairs = {}
- local pregnantRichText = {}
- pregnantRichText.text = getPregnancyText(unit)
- --bit of fluff for pregnancy
- pregnantRichText.color = pregnantColor
- table.insert(returnpairs, pregnantRichText)
- --First line is for pregnancy, second line is for spouse/lover
- local spouseRichText = {}
- --Also gets lover text
- spouseRichText.text = getSpouseText(unit)
- function nabNonPregnantColor(unit)
- --I want spouse text to be coloured appropriately for the relationship.
- local previouscolor, returncolor
- local basecolor = getSymbolizedSpouse(unit).color
- if type(basecolor) == "number" then
- return basecolor
- else
- previouscolor = getSymbolizedSpouse(unit).color()
- function returnfunction()
- --In case the romantic relationship is blinky - typically that means pregnancy and/or lover.
- local unit = unit
- returncolor = getSymbolizedSpouse(unit).color()
- --Might as well use code already in place
- if returncolor == pregnantColor then
- --Of course, if the unit is pregnant, that's shown above, not here.
- --Visual bug: Can still start out as pregnant color.
- return previouscolor
- else
- previouscolor = returncolor
- return returncolor
- end
- end
- return returnfunction
- end
- end
- if spouseRichText.text then
- spouseRichText.color = nabNonPregnantColor(unit)
- end
- table.insert(returnpairs, spouseRichText)
- return returnpairs
- end
- local HavePrinted, oldCitizens, oldJobs, oldIndices = false
- function showUnitPairs(unit)
- local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
- if not HavePrinted then
- oldCitizens, oldJobs, oldIndices = {}, {}, {}
- local index = 0
- while getFlag(unitscreen.units.Citizens, index) do
- if (unit == unitscreen.units.Citizens[index]) or -- self
- areCompatible(unit, unitscreen.units.Citizens[index]) then
- index = 1+index
- else
- table.insert(oldCitizens, unitscreen.units.Citizens[index])
- table.insert(oldIndices, index)
- oldJobs[#oldIndices] = unitscreen.jobs.Citizens[index]
- unitscreen.units.Citizens:erase(index)
- unitscreen.jobs.Citizens:erase(index)
- end
- end
- HavePrinted = true
- for ci = 0, #unitscreen.units.Citizens -1 do
- if (unit == unitscreen.units.Citizens[ci]) then
- unitscreen.cursor_pos.Citizens = ci
- break;
- end
- end
- end
- end
- function hideUnitPairs()
- if HavePrinted then
- local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
- for i=#oldCitizens, 1, -1 do
- unitscreen.units.Citizens:insert(oldIndices[i], oldCitizens[i])
- unitscreen.jobs.Citizens:insert(oldIndices[i], oldJobs[i])
- end
- HavePrinted = false
- end
- end
- local pagelength, currentpage, visitorpopupdims, symbolizedList = df.global.gps.dimy - 9, 0, {x = -30, y = 4}
- -- pagelength needs to be exposed due manipulators having two lines shorter pages than standard view.
- -- Also due resizing.
- -- currentpage needs to be exposed due traversing the lists.
- function getUnitListPairs()
- local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
- --note: Counts from 0, unlike lua tables
- local returntable = {}
- local cursorposition, unitlist, iter
- if unitscreen.page == 0 then --Citizen list
- cursorposition = unitscreen.cursor_pos.Citizens
- currentpage = math.floor(cursorposition / pagelength)
- cursorposition = cursorposition % pagelength --cursor position within a page
- unitlist = unitscreen.units.Citizens
- for iter = (0+currentpage*pagelength),
- ( (((1+currentpage)*pagelength-1)<(#unitlist -1)) and
- ((1+currentpage)*pagelength-1) or
- (#unitlist -1)) do
- table.insert(returntable, getSymbolizedSpouse(unitlist[iter]))
- returntable[#returntable].onclick = function()
- local tile = nil
- if dfhack.gui.getCurFocus():find("unitlist") then tile = dfhack.screen.readTile(39,df.global.gps.dimy-2) end
- --search plugin support
- if not tile or (tile and tile.ch == 95 and tile.fg == 2 or tile.ch == 0) then
- if HavePrinted then hideUnitPairs() else
- showUnitPairs(unitlist[iter]) end
- writeoverTable(symbolizedList, getUnitListPairs())
- end
- end
- end
- elseif unitscreen.page == 1 or unitscreen.page == 2 then --Livestock or Others
- local pageName = (unitscreen.page == 1) and "Livestock" or "Others"
- cursorposition = unitscreen.cursor_pos[pageName]
- currentpage = math.floor(cursorposition / pagelength)
- cursorposition = cursorposition % pagelength --cursor position within a page
- unitlist = unitscreen.units[pageName]
- for iter = (0+currentpage*pagelength),
- ( (((1+currentpage)*pagelength-1)<(#unitlist -1)) and
- ((1+currentpage)*pagelength-1) or
- (#unitlist -1)) do
- local unit = unitlist[iter]
- -- What goes on with combination of pet and intelligent?
- -- Tests reveals failure to bear children and love for even histfigged intelligent dogs
- -- Perhaps only dumb pets of non-your civ can screw.
- -- Of course, might want accurate indicator then anyway, as you might make them your civ members
- --Near as I can tell, for historical figures:
- -- pet 1 0
- -- smart 1 - Fertile
- -- 0 Fer Fertile(trogs, trolls)
- if isItSmart(unit) then
- if (df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET or
- df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET_EXOTIC)
- -- Intelligent pets seem unable to breed
- or unit.hist_figure_id < 0 then
- --I think marriage requires being historical,
- -- collaborated by historical turkeys being able to marry during retirement
- table.insert(returntable,getGenderInInfertileColor(unit))
- end
- if unit.hist_figure_id > -1 then
- table.insert(returntable, getSymbolizedSpouse(unit))
- returntable[#returntable].onclick = function()
- --Something to display visitor dating pool
- --creates several tables per call, but one doesn't usually call it.
- local fortressmatches = getSuitableMatches(unit, unitscreen.units.Citizens, unitscreen.jobs.Others)
- --Lets find the candidates for a given visitor
- -- this is a table of index, true values, not units.
- -- print("entered onclick " .. getLen(fortressmatches))
- if getLen(fortressmatches) > 0 then
- local fortressmatchlist = {}
- for index, value in pairs(fortressmatches) do
- table.insert(fortressmatchlist, getSymbolizedSpouse(unitscreen.units.Citizens[index]))
- fortressmatchlist[#fortressmatchlist].notEndOfLine = true
- table.insert(fortressmatchlist, {text = dfhack.TranslateName(unitscreen.units.Citizens[index].name)})
- --Lets convert the unit to nicely colored name to display.
- end
- print(#fortressmatchlist)
- local popupscreen = screenconstructor.getScreen(
- fortressmatchlist,
- {x = math.floor((df.global.gps.dimx-screenconstructor.getLongestLength(fortressmatchlist,"text"))/2),
- y = math.floor((df.global.gps.dimy-screenconstructor.getHeight(fortressmatchlist))/2)},
- {x = math.floor((df.global.gps.dimx-screenconstructor.getLongestLength(fortressmatchlist,"text"))/2 -1),
- y = math.floor((df.global.gps.dimy-screenconstructor.getHeight(fortressmatchlist))/2 -1),
- width = 2+ screenconstructor.getLongestLength(fortressmatchlist,"text"),
- height = 2+ screenconstructor.getHeight(fortressmatchlist)}
- )
- popupscreen:show()
- popupscreen.onInput = function() popupscreen:dismiss() end
- --There's no input on which I wont want to dismiss the screen.
- end
- end
- end
- else
- --It doesn't matter if a pet/troglodyte has killed someone or not, they'll breed either way.
- if unit.hist_figure_id > -1 then
- --Nonetheless, historical pets can marry in retired forts
- table.insert(returntable, getSymbolizedSpouse(unit))
- --getSymbolizedSpouse calls on below functions for not smart creatures
- else
- table.insert(returntable, getSymbolizedAnimalPreference(unit))
- end
- end
- --[[ Deprecated logic based on previously believed data.
- if unit.hist_figure_id > 0 then
- --Can be married. Visitor, murderer, pet...
- if isItSmart(unit) and
- (df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET or
- df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET_EXOTIC) then
- --
- table.insert(returntable,getGenderInInfertileColor(unit))
- end
- --Distinction between being smart or not (aka free love) is handled inside.
- table.insert(returntable, getSymbolizedSpouse(unit))
- else
- if isItSmart(unit) then
- --Wild animal men and non-historical gremlins
- --Infertile until they're able to marry - like after having killed someone important
- table.insert(returntable,getGenderInInfertileColor(unit))
- else
- --normal turkeys brought on embark and wild animals.
- table.insert(returntable, getSymbolizedAnimalPreference(unit))
- end
- end]]--
- end
- end
- return returntable
- end
- -- ======================================== --
- -- Initialization and placement --
- -- ======================================== --
- local unitscreen = getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child.child)
- --gets unitlist viewscreen
- local viewscreen
- if unitscreen then
- symbolizedList = getUnitListPairs()
- local list_rect = {x = 1, y = 4, width = 1, height = pagelength}
- local newscreen = screenconstructor.getScreen(symbolizedList, list_rect, nil)
- newscreen:show()
- local listtable = {}
- listtable[0] = "Citizens"
- listtable[1] = "Livestock"
- listtable[2] = "Others"
- listtable[3] = "Dead"
- local oldwhichlist,lenlist, doNotUseBase, manitimeout, manicursorpos = listtable[unitscreen.page]
- local manipulatorkeytable = {}
- local upkeys = {CURSOR_UP = true}
- local downkeys = {CURSOR_DOWN = true}
- newscreen.onclick = function()
- local tile = nil
- if dfhack.gui.getCurFocus():find("unitlist") then tile = dfhack.screen.readTile(39,df.global.gps.dimy-2) end
- --search plugin support
- --if prevents failing to work in manipulator/main
- if not tile or (tile and tile.ch == 95 and tile.fg == 2 or tile.ch == 0) then
- if HavePrinted then hideUnitPairs() end --restoring units on random clicks
- writeoverTable(symbolizedList, getUnitListPairs()) --restoring appearance too
- end
- end
- local baseonInput = newscreen.onInput
- local function onListInput(self, keys)
- lenlist = #(unitscreen.units[oldwhichlist])
- if keys.LEAVESCREEN and 2 == dfhack.screen.readTile(29,df.global.gps.dimy-2).fg then
- --Search plugin support. Tbh, would have been ultimately easier to disable dismiss_on_zoom
- local dismissfunc = self.dismiss
- self.dismiss = function () end
- dfhack.timeout(1, "frames", function() self.dismiss = dismissfunc end )
- end
- if not doNotUseBase then baseonInput(self, keys) end
- local manipulatorscript = getBottomMostViewscreenWithFocus("dfhack/lua/manipulator",df.global.gview.view.child.child)
- --Lua manipulator viewscreen is present
- local whichlist = listtable[unitscreen.page]
- --duplicated from indicator_screen (ugh). Feels like there should be a way to better determine this.
- if (currentpage ~= math.floor(unitscreen.cursor_pos[whichlist]/ pagelength) and whichlist ~= "Dead") or
- --up-down paging
- (oldwhichlist and oldwhichlist ~= whichlist) or --left-right paging
- (lenlist ~= #(unitscreen.units[oldwhichlist])) then --search plugin support
- oldwhichlist = whichlist
- doNotUseBase = true
- lenlist = #(unitscreen.units[oldwhichlist])
- writeoverTable(symbolizedList, getUnitListPairs())
- dfhack.timeout(2, "frames", function() doNotUseBase = false end)
- --Something weird happens with writeoverTable here, where it sometimes parses input twice.
- --In the absence of other solutions, merely avoid relaying input for two frames.
- end
- function mv_cursor(keys)
- -- Function for the purpose of moving cursor alongside the manipulator.
- -- They don't do this natively - a bugging disrepacy.
- if keys.CURSOR_UP or keys.CURSOR_DOWN then
- unitscreen.cursor_pos[whichlist] = --set new cursor position
- (unitscreen.cursor_pos[whichlist] +(keys.CURSOR_DOWN and 1 or -1)) --to 1 up or down from previous
- % #(unitscreen.units[whichlist]) --with overflow accounted for.
- end
- end
- --manipulator/main.lua scrolls differently than default interface, got to handle it
- --TODO: fix the mess with numpad keys and manipulator/main.lua
- if manipulatorscript and manipulatorscript.breakdown_level ~= 2 then
- --Finds manipulator here on both Alt-L and Escape back out
- --breakdown level check prevents that.
- if #(unitscreen.units[whichlist]) > (df.global.gps.dimy -11) then
- --multi-page manipulator unitlist handling
- if pagelength ~= lenlist then
- --Instead of using a sublist that is refreshed manipulator/main uses whole list that is moved
- pagelength = lenlist
- writeoverTable(symbolizedList, getUnitListPairs())
- self:adjustDims(true,nil,nil,nil, pagelength)
- self.frame_body.clip_y2 = df.global.gps.dimy - 8
- manicursorpos = unitscreen.cursor_pos[whichlist] > (df.global.gps.dimy - 12) and
- (df.global.gps.dimy - 12) or unitscreen.cursor_pos[whichlist]
- self.frame_body.y1 = 4 - (unitscreen.cursor_pos[whichlist] > (df.global.gps.dimy - 12) and
- unitscreen.cursor_pos[whichlist] - (df.global.gps.dimy - 12) or
- 0)
- end
- --scrolling occurs if manipulator's cursor position has reached the edge and tries to keep going.
- --Manipulator's initial cursor position is either current cursor position or bottom, whichever is smaller
- --Successive changes can divorce the two, so need to have internal check.
- --Two functions to follow the cursor position of manipulator
- function manidown()
- if manicursorpos ~= (df.global.gps.dimy - 12) then
- manicursorpos = 1 + manicursorpos
- elseif manicursorpos == (df.global.gps.dimy - 12) then
- if (unitscreen.cursor_pos[whichlist] +1 ) ~= #(unitscreen.units[whichlist]) then
- self.frame_body.y1 = -1 + self.frame_body.y1
- else
- self.frame_body.y1 = 4
- manicursorpos = 0
- end
- end
- end
- function maniup()
- if manicursorpos ~= 0 then
- manicursorpos = -1 + manicursorpos
- elseif manicursorpos == 0 then
- if unitscreen.cursor_pos[whichlist] ~= 0 then
- self.frame_body.y1 = 1 + self.frame_body.y1
- else
- self.frame_body.y1 = (df.global.gps.dimy -7) - #(unitscreen.units[whichlist])
- manicursorpos = (df.global.gps.dimy - 12)
- end
- end
- end
- --manipulator/main allows shift+up/down scrolling, which has unique behaviour
- if hasKeyOverlap(keys, downkeys) then
- if not hasKeyOverlap(keys, {["_FAST"] = true}) then
- manidown()
- else
- for i=1, 10 do
- if (unitscreen.cursor_pos[whichlist] +1 ) ~= #(unitscreen.units[whichlist]) then
- manidown()
- mv_cursor(downkeys)
- else
- if i == 1 then
- manidown()
- mv_cursor(downkeys)
- end
- break;
- end
- end
- end
- end
- if hasKeyOverlap(keys, upkeys) then
- if not hasKeyOverlap(keys, {["_FAST"] = true}) then
- maniup()
- else
- for i=1, 10 do
- if unitscreen.cursor_pos[whichlist] ~= 0 then
- maniup()
- mv_cursor(upkeys)
- else
- if i == 1 then
- maniup()
- mv_cursor(upkeys)
- end
- break;
- end
- end
- end
- end
- end
- mv_cursor(keys) --adjust outside cursor, does nothing on shift-scrolling
- if df.global.gps.mouse_x == 1 and --clicked on the indicator line
- (keys._MOUSE_L or keys._MOUSE_L_DOWN) then
- if manipulatorscript and not manitimeout then
- manitimeout = true
- --timeout necessary due otherwise causing errors with multiple rapid commands
- self._native.parent.breakdown_level = 2
- self._native.parent.parent.child = self._native.parent.child
- self._native.parent = self._native.parent.parent
- dfhack.timeout(2, "frames", function()
- dfhack.run_command("gui/indicator_screen execute_hook manipulator/main")
- end)
- dfhack.timeout(2, "frames", function() manitimeout = false end)
- end
- end
- elseif manipulatorscript and manipulatorscript.breakdown_level == 2 then
- if keys.LEAVESCREEN then
- hideUnitPairs()
- dfhack.run_command("relations-indicator")
- end
- if keys.UNITJOB_ZOOM_CRE then
- dfhack.timeout(4, "frames", function() dfhack.run_command("relations-indicator") end)
- end
- end
- end
- newscreen.onInput = onListInput
- local baseOnResize = newscreen.onResize
- function onListResize(self)
- -- Unlike with View-unit, the data might change depending on the size of the window.
- baseOnResize(self)
- if pagelength ~= (df.global.gps.dimy - 9) then
- --If window length changed, better refresh the data.
- pagelength = df.global.gps.dimy - 9
- writeoverTable(symbolizedList, getUnitListPairs())
- self:adjustDims(true,list_rect.x, list_rect.y,list_rect.width, pagelength)
- --Not adjusting height here would result in situation where making screen shorter works, but taller not.
- end
- end
- newscreen.onResize = onListResize
- -- ======================================== --
- -- View-unit section --
- -- ======================================== --
- else
- local function viewunit()
- viewscreen = dfhack.gui.getCurViewscreen()
- local unit = dfhack.gui.getSelectedUnit()
- local symbolizedSingle = getViewUnitPairs(unit)
- local view_rect = {x = (-30 -(screenconstructor.isWideView() and 24 or 0)), y = 17, width = 28, height = 2}
- local newscreen = screenconstructor.getScreen(symbolizedSingle, view_rect, nil)
- newscreen:show()
- if not dfhack.gui.getFocusString(viewscreen)
- :find("dwarfmode/ViewUnits/Some/General") then
- --Can enter in inventory view with v if one previously exited inventory view
- newscreen:removeFromView() --Gotta hide in that case
- end
- local baseonInput = newscreen.onInput
- local function onViewInput(self, keys)
- --handling changing the menu width and units:
- --Capturing the state before it changes:
- local oldUnit, oldScreen
- local sameUnit, sameScreen = true, true
- --Tab changing menu width is handled below.
- --storing pre-keypress identifiers for unit and viewscreen state
- if not keys.CHANGETAB then
- oldUnit = df.global.ui_selected_unit
- oldScreen = dfhack.gui.getFocusString(viewscreen)
- --Merely checking viewscreen match will not work, given that sideview only modifies existing screen
- end
- baseonInput(self,keys) --Doing baseline housekeeping and passing input to parent
- --Finding out if anything changed after parent got the input
- if not keys.CHANGETAB then
- sameUnit = (oldUnit == df.global.ui_selected_unit)
- --could also use dfhack.gui.getSelectedUnit()
- sameScreen = (oldScreen == dfhack.gui.getFocusString(viewscreen))
- end
- if keys.CHANGETAB then
- --Tabbing moves around the sideview, so got to readjust screen position.
- view_rect.x = -30 -(screenconstructor.isWideView() and 24 or 0)
- self:adjustDims(true, view_rect.x)
- --unlike text tables, position tables aren't dynamic, to allow them to be incomplete
- end
- --If unit changed, got to replace the indicator text
- if not sameUnit then
- writeoverTable(symbolizedSingle,getViewUnitPairs(df.global.world.units.active[df.global.ui_selected_unit]))
- elseif not sameScreen then
- --Don't want to display the screen if there isn't an unit present, but don't want to spam blinks either
- if not dfhack.gui.getFocusString(viewscreen)
- :find("dwarfmode/ViewUnits/Some/General") then
- --Different screen doesn't mean it's different in same way - need to check here too.
- self:removeFromView()
- else
- --It's general, so better fix it...Thoug well - should change mostly nothing
- self:adjustDims(true, view_rect.x, view_rect.y, view_rect.width, view_rect.height)
- self.signature = true
- end
- end
- end
- newscreen.onInput = onViewInput
- end
- if dfhack.gui.getCurFocus():find("dwarfmode/ViewUnits/Some")
- then viewunit()
- else dfhack.timeout(2, "frames", function()
- if getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
- viewunit()
- end
- end)
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement