SHOW:
|
|
- or go back to the newest paste.
| 1 | local helptext = [=[ | |
| 2 | relations-indicator | |
| 3 | =================== | |
| 4 | - | v1.0 |
| 4 | + | v1.1 |
| 5 | Displays the pregnancy, fertility and romantic status of unit(s). | |
| 6 | For singular unit, in (v)iew-(g)eneral mode. | |
| 7 | For multiple units, in Citizens, Pets/Livestock and Others lists. | |
| 8 | By default, displays just pregnancies in those, | |
| 9 | as well as binding itself to be called there via keybinding plugin. | |
| 10 | Can be called with following arguments | |
| 11 | Relating to instructions | |
| 12 | -help, help, ?, -? | |
| 13 | Displays this text and exits | |
| 14 | explain colors | |
| 15 | Explains the meanings of various colors used and exits | |
| 16 | bindings | |
| 17 | Lists the keybindings relations-indicator uses with bind and exits | |
| 18 | bind | |
| 19 | adds context-specific keybindings | |
| 20 | unbind | |
| 21 | removes context-specific keybindings and exits]=] | |
| 22 | ||
| 23 | -- ======================================== -- | |
| 24 | -- Color shades -- | |
| 25 | -- ======================================== -- | |
| 26 | ||
| 27 | local COLOR_DARKGRAY = 8 -- British spelling | |
| 28 | local COLOR_LIGHTGRAY = 7 -- ditto | |
| 29 | ||
| 30 | local straightMaleShade = COLOR_LIGHTCYAN -- feels like this should be pregnant color, but tweak uses green | |
| 31 | local straightFemaleShade = COLOR_LIGHTMAGENTA -- DT colors. Could alternatively flicker with symbol. | |
| 32 | local gayMaleShade = COLOR_YELLOW -- Blue makes more sense, but dark blue on black is hard to see. | |
| 33 | local gayFemaleShade = COLOR_LIGHTRED -- originally love or straight female color. | |
| 34 | local pregnantColor = COLOR_LIGHTGREEN -- base tweak color, might wap with yellow | |
| 35 | local infertileColor = COLOR_LIGHTGRAY | |
| 36 | local offsiteShade = COLOR_WHITE -- issue: orientation-offsite? Blinking can solve this, ofc. | |
| 37 | local deadColor = COLOR_DARKGRAY -- still, should avoid blinking with symbols if possible. | |
| 38 | -- 8 shades, but I only have 7 available. Plus default blue is not so great. | |
| 39 | ||
| 40 | ||
| 41 | local function colorExplanation() | |
| 42 | function printlnc(text, color) | |
| 43 | dfhack.color(color) | |
| 44 | dfhack.println(text) | |
| 45 | dfhack.color(COLOR_RESET) | |
| 46 | end | |
| 47 | dfhack.println("relations-indicator marks the following traits with following colour:")
| |
| 48 | dfhack.print("Straight Male ") printlnc("Cyan", straightMaleShade)
| |
| 49 | dfhack.print("Gay Male ") printlnc("Yellow", gayMaleShade)
| |
| 50 | dfhack.print("Straight Female ") printlnc("Magenta", straightFemaleShade)
| |
| 51 | dfhack.print("Gay Female ") printlnc("Red", gayFemaleShade)
| |
| 52 | dfhack.print("Pregnant ") printlnc("Green", pregnantColor)
| |
| 53 | dfhack.println("")
| |
| 54 | dfhack.println("For the first four, darker shade indicates unwillingness to marry.")
| |
| 55 | dfhack.println("")
| |
| 56 | dfhack.println("The below three by default replace the first four in top-down hiearchy:")
| |
| 57 | dfhack.println("")
| |
| 58 | dfhack.print("Dead partner ") printlnc("Dark gray ", deadColor)
| |
| 59 | dfhack.print("Infertile/aromantic or infertile/aromantic hetero partner ") printlnc("Light gray", infertileColor)
| |
| 60 | dfhack.print("Offsite partner ") printlnc("White", offsiteShade)
| |
| 61 | end | |
| 62 | ||
| 63 | -- ======================================== -- | |
| 64 | -- Indicator Symbols -- | |
| 65 | -- ======================================== -- | |
| 66 | ||
| 67 | local loversSymbol = "\148" | |
| 68 | local marriedSymbol = "\3" | |
| 69 | local singleMaleSymbol = "\11" | |
| 70 | local singleFemaleSymbol = "\12" | |
| 71 | local fertileBirdSymbol = "\8" -- used only in unitlist | |
| 72 | local pregnantCreatureSymbol = "\20" -- also only in unitlist | |
| 73 | local diedS, diedE = "\197", "\197" --Used just for viewing a single unit. | |
| 74 | local offsite = "..." --Also for just a single unit only. | |
| 75 | local blinkingdelay = 650 | |
| 76 | --How often does it blink between colors and/or symbols. | |
| 77 | ||
| 78 | -- ======================================== -- | |
| 79 | -- Affection thresholds -- | |
| 80 | -- ======================================== -- | |
| 81 | ||
| 82 | local friendlinessthreshold = 0 | |
| 83 | local loversthreshold = 14 | |
| 84 | local marriagethreshold = -40 | |
| 85 | ||
| 86 | -- ======================================== -- | |
| 87 | -- Keybindings used -- | |
| 88 | -- ======================================== -- | |
| 89 | ||
| 90 | local keybinding_list = {}
| |
| 91 | table.insert(keybinding_list, "U@dwarfmode/Default relations-indicator") | |
| 92 | ||
| 93 | table.insert(keybinding_list, "V@dwarfmode/Default relations-indicator") | |
| 94 | table.insert(keybinding_list, "Z@unitlist/Citizens relations-indicator") | |
| 95 | table.insert(keybinding_list, "Z@dfhack/unitlabors relations-indicator") | |
| 96 | table.insert(keybinding_list, "Z@unitlist/Livestock relations-indicator") | |
| 97 | table.insert(keybinding_list, "Z@unitlist/Citizens relations-indicator") | |
| 98 | table.insert(keybinding_list, "Z@unitlist/Others relations-indicator") | |
| 99 | ||
| 100 | ||
| 101 | local function relationsIndicatorPowerState(enable) | |
| 102 | for bindingi=1, #keybinding_list do | |
| 103 | dfhack.run_command_silent("keybinding " .. (enable and "add " or "clear ") .. keybinding_list[bindingi])
| |
| 104 | end | |
| 105 | end | |
| 106 | ||
| 107 | local args = {...}
| |
| 108 | ||
| 109 | local argsline = table.concat(args, " ") | |
| 110 | --utils.processArgs is neat but requires - and not necessary here | |
| 111 | if argsline:find("help") or
| |
| 112 | argsline:find("?") then
| |
| 113 | dfhack.println(helptext) | |
| 114 | qerror("")
| |
| 115 | --Not, strictly speaking, a proper exit but it'll do | |
| 116 | end | |
| 117 | if argsline:find("explain colors") then colorExplanation() qerror("") end
| |
| 118 | ||
| 119 | if argsline:find("bindings") then
| |
| 120 | for bindingi=1, #keybinding_list do | |
| 121 | dfhack.println(keybinding_list[bindingi]) | |
| 122 | end | |
| 123 | dfhack.println("(Internal) Z@dfhack/lua/manipulator relations-indicator")
| |
| 124 | qerror("")
| |
| 125 | end | |
| 126 | ||
| 127 | if argsline:find("unbind") then relationsIndicatorPowerState(false) qerror("") end
| |
| 128 | if argsline:find("bind") then
| |
| 129 | relationsIndicatorPowerState(true) | |
| 130 | end | |
| 131 | ||
| 132 | -- ======================================== -- | |
| 133 | -- Loading required modules -- | |
| 134 | -- ======================================== -- | |
| 135 | ||
| 136 | local gui = require 'gui' | |
| 137 | ||
| 138 | local screenconstructor = dfhack.script_environment("gui/indicator_screen")
| |
| 139 | ||
| 140 | -- ======================================== -- | |
| 141 | -- Utility functions -- | |
| 142 | -- ======================================== -- | |
| 143 | ||
| 144 | ||
| 145 | local function getLen(data) | |
| 146 | -- Can't # a hashed table, must use pairs. | |
| 147 | local len = 0 | |
| 148 | for i, val in pairs(data) do | |
| 149 | len = len +1 | |
| 150 | end | |
| 151 | return len | |
| 152 | end | |
| 153 | ||
| 154 | function blinkergenerator(tableofblinkers) | |
| 155 | -- takes numerically indexed table of values | |
| 156 | -- returns either single value if table has a single value, or function that alternates which one it returns | |
| 157 | -- local manyblinkers = #tableofblinkers | |
| 158 | if #tableofblinkers == 1 then | |
| 159 | return tableofblinkers[1] | |
| 160 | else | |
| 161 | function blinkingfunction() | |
| 162 | local blinkertable = tableofblinkers | |
| 163 | local blinkernr = #tableofblinkers | |
| 164 | ||
| 165 | return blinkertable[1+math.floor(dfhack.getTickCount()/blinkingdelay) % blinkernr] | |
| 166 | end | |
| 167 | return blinkingfunction | |
| 168 | end | |
| 169 | end | |
| 170 | ||
| 171 | function getFlag(object, flag) | |
| 172 | -- Utility function for safely requesting info from userdata | |
| 173 | -- Returns nil if the object doesn't have flag attribute, else returns it's value | |
| 174 | -- Because well, ordinarily, {}[flag] returns nil.
| |
| 175 | -- However, if object is unit - or some other type, it may instead throw an error | |
| 176 | local a = {}
| |
| 177 | if not object or not flag then return nil end | |
| 178 | --Crash is still possible for attempting to pairs a nil | |
| 179 | for index, value in pairs(object) do | |
| 180 | a[index] = value | |
| 181 | end | |
| 182 | local returnvalue = a[flag] | |
| 183 | a = nil --lua automatically garbage cleans tables without variable that links to them. | |
| 184 | return returnvalue | |
| 185 | end | |
| 186 | ||
| 187 | function getBottomMostViewscreenWithFocus(text, targetscreen) | |
| 188 | --Finds and returns the screen closest to root screen whose path includes text | |
| 189 | --duplicated from getScreen, admittedly. | |
| 190 | if targetscreen and | |
| 191 | dfhack.gui.getFocusString(targetscreen):find(text) then | |
| 192 | return targetscreen | |
| 193 | elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this | |
| 194 | return getBottomMostViewscreenWithFocus(text, targetscreen.child) | |
| 195 | end | |
| 196 | -- if there's no child, it didn't find a screen with text in focus and returns nil | |
| 197 | end | |
| 198 | ||
| 199 | function writeoverTable(modifiedtable, copiedtable) | |
| 200 | --Takes two tables as input | |
| 201 | --Removes all the values in first table | |
| 202 | --Then places all the values in second table into it | |
| 203 | --Returns the first table after that | |
| 204 | for index, value in pairs(modifiedtable) do | |
| 205 | modifiedtable[index] = nil | |
| 206 | end | |
| 207 | for index, value in pairs(copiedtable) do | |
| 208 | modifiedtable[index] = value | |
| 209 | end | |
| 210 | return modifiedtable | |
| 211 | end | |
| 212 | ||
| 213 | function hasKeyOverlap (searchtable, searchingtable) | |
| 214 | -- Looks for a key in searchtable whose name contains the name of a key in searchtable | |
| 215 | -- returns true if finds it. | |
| 216 | for aindex, avalue in pairs(searchingtable) do | |
| 217 | for bindex, bvalue in pairs(searchtable) do | |
| 218 | if tostring(bindex):find(tostring(aindex)) then | |
| 219 | return true | |
| 220 | end | |
| 221 | end | |
| 222 | end | |
| 223 | end | |
| 224 | ||
| 225 | -- ======================================== -- | |
| 226 | -- Tier 1 (df) functions -- | |
| 227 | -- ======================================== -- | |
| 228 | ||
| 229 | function isItBird(unit) | |
| 230 | return (df.global.world.raws.creatures.all[unit.race].caste[0].flags.LAYS_EGGS) | |
| 231 | --because it lists all available flags as true or false doesn't need to invoke getFlag | |
| 232 | end | |
| 233 | ||
| 234 | function isItSmart(unit) | |
| 235 | if (df.global.world.raws.creatures.all[unit.race].caste[0].flags.CAN_LEARN and | |
| 236 | not df.global.world.raws.creatures.all[unit.race].caste[0].flags.SLOW_LEARNER) then | |
| 237 | return true | |
| 238 | else | |
| 239 | return false | |
| 240 | end | |
| 241 | ||
| 242 | end | |
| 243 | ||
| 244 | function getGenderInInfertileColor(unit) | |
| 245 | local symbolColor = {}
| |
| 246 | symbolColor.text = unit.sex == 1 and | |
| 247 | singleMaleSymbol or | |
| 248 | ( unit.sex == 0 and | |
| 249 | singleFemaleSymbol or "") | |
| 250 | symbolColor.color = infertileColor | |
| 251 | return symbolColor | |
| 252 | end | |
| 253 | -- ======================================== -- | |
| 254 | -- 43.05 vs earlier structure differences -- | |
| 255 | -- ======================================== -- | |
| 256 | ||
| 257 | function getPregnancyTimer(unit) | |
| 258 | -- Takes local unit, returns time until birth in steps. | |
| 259 | -- In case of historical unit always returns -1; don't know about their pregnancy structures. | |
| 260 | -- utilizes getFlag | |
| 261 | if getFlag(unit, "info") then return -1 end -- so assume they're always not pregnant. They usually aren't | |
| 262 | return (getFlag(unit, "pregnancy_timer") or getFlag(unit.relations, "pregnancy_timer")) | |
| 263 | --Differences between 43.05 and earlier dfhack. | |
| 264 | end | |
| 265 | ||
| 266 | function getLocalRelationship(unit, SpouseOrLover) | |
| 267 | --Takes local unit, boolean | |
| 268 | -- returns spouse id in the case of true for second value, | |
| 269 | -- lover in the case of false or if nil is used and spouse isn't present. | |
| 270 | -- utilizes getFlag | |
| 271 | if getFlag(unit, "info") then return nil end | |
| 272 | --Not intended to be used on historical figure structure, as that is different. | |
| 273 | --Also, using nil when number is expected will throw an error, so this points out those mistakes | |
| 274 | local is4305p = getFlag(unit, "relationship_ids") and true or false | |
| 275 | local relationships = is4305p and unit.relationship_ids or unit.relations | |
| 276 | local spousevalue = is4305p and relationships.Spouse or relationships.spouse_id | |
| 277 | local lovervalue = is4305p and relationships.Lover or relationships.lover_id | |
| 278 | --Again, differences between 43.05 and earlier dfhack. | |
| 279 | --Further issue: 43.03 uses spouse_id, 43.05 uses Spouse | |
| 280 | --This is not as extensible, but since I only get spouse or lover for now... | |
| 281 | if SpouseOrLover == true then return spousevalue end | |
| 282 | if SpouseOrLover == false then return lovervalue end | |
| 283 | if spousevalue > -1 then | |
| 284 | return spousevalue | |
| 285 | else | |
| 286 | return lovervalue | |
| 287 | end | |
| 288 | end | |
| 289 | ||
| 290 | -- ======================================== -- | |
| 291 | -- Tier 2 functions -- | |
| 292 | -- ======================================== -- | |
| 293 | ||
| 294 | function isItGelded(unit) | |
| 295 | -- Either local or historical unit | |
| 296 | -- returns true or nil | |
| 297 | -- utilizes getFlag | |
| 298 | if getFlag(unit, "status") then | |
| 299 | --local unit | |
| 300 | if unit.flags3.gelded then | |
| 301 | --usually only pets and guys are gelded, but can be set on anyone | |
| 302 | return true | |
| 303 | elseif unit.curse.add_tags2.STERILE then | |
| 304 | --occurs for vampires and husks | |
| 305 | return true | |
| 306 | elseif not getFlag(unit.status, "current_soul") then | |
| 307 | --occurs for animated corpses | |
| 308 | --Could also use unit.curse.add_tags1.OPPOSED_TO_LIFE | |
| 309 | --Though I'm not certain you need a soul to breed, lack of soul messes up checking personality | |
| 310 | return true | |
| 311 | end | |
| 312 | elseif getFlag(unit, "info") then | |
| 313 | --historical unit | |
| 314 | if (getFlag(unit.info, "wounds") and getFlag(unit.info, "wounds").anon_3 ==1 ) then | |
| 315 | --suspected gelding flag. 0 is dead, -1 is default? | |
| 316 | return true | |
| 317 | elseif (getFlag(unit.info,"curse") and getFlag(unit.info.curse, "active_interactions") ) then | |
| 318 | for i, interaction in pairs(unit.info.curse.active_interactions) do | |
| 319 | --Here, could just check that it's name has VAMPIRE in it in vanilla, but could have modded vamps | |
| 320 | --Interestingly, soul is not mentioned in historical unit data. Presumably it is hiding. Fallback plan | |
| 321 | for j, text in pairs(interaction.str) do | |
| 322 | if text:find("STERILE") or
| |
| 323 | text:find("OPPOSED_TO_LIFE") then
| |
| 324 | --side effect: False positive on syndromes that remove those tags. | |
| 325 | --ex: modded-in gonads that remove sterility from otherwise-sterile creature | |
| 326 | --TODO: fix | |
| 327 | return true | |
| 328 | end | |
| 329 | end | |
| 330 | end | |
| 331 | end | |
| 332 | end | |
| 333 | return nil | |
| 334 | end | |
| 335 | ||
| 336 | function getPregnancyText(unit) | |
| 337 | -- Takes local unit, returns single line string that is "" for no pregnancy | |
| 338 | -- utilizes getFlag, getPregnancyTimer | |
| 339 | if not getFlag(unit, "status") or | |
| 340 | unit.caste > 0 then | |
| 341 | return "" | |
| 342 | else | |
| 343 | local howfar = getPregnancyTimer(unit) | |
| 344 | ||
| 345 | local pregnancy_duration = df.global.world.raws.creatures.all[unit.race].caste[0].flags.CAN_LEARN and 9 or 6 | |
| 346 | if howfar < 1 then return "" else | |
| 347 | local returnstring = "" | |
| 348 | if isItBird(unit) then | |
| 349 | if (howfar/33600)>1 then returnstring = "Was wooed " .. tostring(math.floor(pregnancy_duration-howfar/33600)) .. " months ago" end | |
| 350 | if (howfar/33600)<1 then returnstring = "Last seed withers in " .. tostring(math.floor(howfar/1200)) .. " days " end | |
| 351 | if (howfar/33600)>(pregnancy_duration-1) then returnstring = "Was wooed recently" end | |
| 352 | ||
| 353 | else | |
| 354 | if (howfar/33600)>0 then returnstring = "Sick in the morning recently" end | |
| 355 | if (howfar/33600)<(pregnancy_duration-1) then returnstring = "Missed a period" end | |
| 356 | if (howfar/33600)<(pregnancy_duration-2) then returnstring = tostring(math.floor(pregnancy_duration-howfar/33600)) .. " months pregnant" end | |
| 357 | if (howfar/33600)<1 then returnstring = "Will give birth in " .. tostring(math.floor(howfar/1200)) .. " days" end | |
| 358 | end | |
| 359 | return returnstring; | |
| 360 | end | |
| 361 | end | |
| 362 | end | |
| 363 | ||
| 364 | function getSpouseAndLover(unit) | |
| 365 | --Takes a local unit, returns either local or historical spouse and lover of that unit if present | |
| 366 | --if the spouse/lover is historically set but culled (single parents who can marry), returns false for those | |
| 367 | --else, returns for that value | |
| 368 | -- utilizes getLocalRelationship, df.historical_figure.find | |
| 369 | local historical_unit = df.historical_figure.find(unit.hist_figure_id) | |
| 370 | local spouse,lover, unithistoricalrelations | |
| 371 | local spouseid = getLocalRelationship(unit, true) | |
| 372 | local loverid = getLocalRelationship(unit, false) | |
| 373 | if spouseid > -1 then | |
| 374 | spouse = df.unit.find(spouseid) | |
| 375 | --shortcut | |
| 376 | end | |
| 377 | if not spouse and --shortcut failed due spouse never arriving, or having left the site | |
| 378 | historical_unit then --pets brought on embark aren't historical | |
| 379 | --got to dig into historical unit values | |
| 380 | unithistoricalrelations = historical_unit.histfig_links | |
| 381 | for index, relation in pairs(unithistoricalrelations) do | |
| 382 | --no local spouse? Mark both global spouse and lover | |
| 383 | if (relation._type == df.histfig_hf_link_spousest) then | |
| 384 | spouse=df.historical_figure.find(relation.target_hf) | |
| 385 | if not spouse then spouse = false | |
| 386 | --there was an id, but there wasn't a histfig with that id (due culling) | |
| 387 | end | |
| 388 | -- small distinction between nil and false: is it better to have loved and lost, or never loved? | |
| 389 | elseif (relation._type == df.histfig_hf_link_loverst) then | |
| 390 | lover=df.historical_figure.find(relation.target_hf) | |
| 391 | if not lover then lover = false | |
| 392 | end | |
| 393 | end | |
| 394 | end | |
| 395 | end | |
| 396 | if loverid > -1 then | |
| 397 | lover=df.unit.find(loverid) --can be nil for offsite lover | |
| 398 | if not lover then lover = false end --false instead of nil to indicate having checked | |
| 399 | end | |
| 400 | if not lover and historical_unit then | |
| 401 | --No local lover? Maybe lover is global | |
| 402 | unithistoricalrelations = historical_unit.histfig_links | |
| 403 | for index, relation in pairs(unithistoricalrelations) do | |
| 404 | if (relation._type == df.histfig_hf_link_loverst) then | |
| 405 | lover=df.historical_figure.find(relation.target_hf) | |
| 406 | if not lover then lover = false end | |
| 407 | end | |
| 408 | end | |
| 409 | end | |
| 410 | return spouse, lover | |
| 411 | end | |
| 412 | ||
| 413 | function areCompatible(unitA,unitB) | |
| 414 | --Checks if two local units make compatible pair and returns true if they are, false if not | |
| 415 | --Utilizes getFlag, requires them to be historical to check relationships | |
| 416 | ||
| 417 | -- Do I check if one is a child? | |
| 418 | -- I think not. Arranged marriages can be planned a decade before they happen. | |
| 419 | -- Still, age is most common disqualifying factor | |
| 420 | if unitA.race ~= unitB.race then return false end | |
| 421 | --multi-racial fortress are nice, but humans still can't into dwarves; not like this. | |
| 422 | local is4305p = getFlag(unitA, "relationship_ids") and true or false | |
| 423 | local relationshipsA = is4305p and unitA or unitA.relations | |
| 424 | local relationshipsB = is4305p and unitB or unitB.relations | |
| 425 | --age is stored in relations for 4303 but on base level in 43.05, so... | |
| 426 | local ageA = relationshipsA.birth_year+relationshipsA.birth_time/403200 | |
| 427 | local ageB = relationshipsB.birth_year+relationshipsB.birth_time/403200 | |
| 428 | --exact age matters | |
| 429 | if (ageA-ageB) > 10 or (ageB-ageA) > 10 then | |
| 430 | --over 10 year age difference | |
| 431 | return false | |
| 432 | end | |
| 433 | ||
| 434 | -- Lets check if one of them is married. | |
| 435 | -- If they are, can do hanky panky with spouse alone | |
| 436 | local spouseA, loverA = getSpouseAndLover(unitA) | |
| 437 | local spouseB, loverB = getSpouseAndLover(unitB) | |
| 438 | if spouseA or loverA or | |
| 439 | spouseB or loverB then | |
| 440 | if spouseA == unitB or | |
| 441 | loverA == unitB then | |
| 442 | return true | |
| 443 | else | |
| 444 | return false | |
| 445 | end | |
| 446 | end | |
| 447 | ||
| 448 | --Lets check if they have compatible orientations for marrying each other. | |
| 449 | local attractionA = getAttraction(unitA) | |
| 450 | local attractionB = getAttraction(unitB) | |
| 451 | if not ((2 == attractionA[(unitB.sex == 1 and "guy" or "girl")]) and | |
| 452 | (2 == attractionB[(unitA.sex == 1 and "guy" or "girl")])) then | |
| 453 | -- Admittedly, this means that someone who only romances has no suitable pairings. | |
| 454 | return false | |
| 455 | end | |
| 456 | ||
| 457 | --Lets check if personalities are compatible. | |
| 458 | local unwillingToFriendA, unwillingToLoveA, unwillingToMarryA = getAromantism(unitA) | |
| 459 | local unwillingToFriendB, unwillingToLoveB, unwillingToMarryB = getAromantism(unitB) | |
| 460 | if unwillingToFriendA or unwillingToLoveA or unwillingToMarryA or | |
| 461 | unwillingToFriendB or unwillingToLoveB or unwillingToMarryB then | |
| 462 | --If either one as baggage about progressing through a relationship, no babies | |
| 463 | return false | |
| 464 | end | |
| 465 | --Checking for relationships requires digging into historical unit values. | |
| 466 | local hfA = unitA.hist_figure_id > -1 and df.historical_figure.find(unitA.hist_figure_id) or nil | |
| 467 | local hfB = unitB.hist_figure_id > -1 and df.historical_figure.find(unitB.hist_figure_id) or nil | |
| 468 | if hfA and hfB then --basic sanity check. | |
| 469 | -- Function to check for being a sibling. | |
| 470 | -- Half-siblings...Possible with hacking, and I bet they block | |
| 471 | function gethfParent(hfunit, retrieveMother) | |
| 472 | --Returns historical mother or father of a historical unit if possible | |
| 473 | --otherwise returns nil | |
| 474 | for index, relationship_link in pairs(hfunit.histfig_links) do | |
| 475 | if retrieveMother and relationship_link._type == df.histfig_hf_link_motherst or | |
| 476 | (not retrieveMother and relationship_link._type == df.histfig_hf_link_fatherst) then | |
| 477 | return df.historical_figure.find(relationship_link.target_hf) | |
| 478 | end | |
| 479 | end | |
| 480 | end | |
| 481 | ||
| 482 | local momA = gethfParent(hfA, true) | |
| 483 | local momB = gethfParent(hfB, true) | |
| 484 | local dadA = gethfParent(hfA) | |
| 485 | local dadB = gethfParent(hfB) | |
| 486 | if momA and momB and momA == momB or --existence of moms must be checked since nil == nil | |
| 487 | (dadA and dadB and dadA == dadB) then | |
| 488 | --siblings or half-siblings are not allowed | |
| 489 | return false | |
| 490 | end | |
| 491 | ||
| 492 | --Function to check for grudge: | |
| 493 | -- (As it is not used outside parent function, not encapsulating elsewhere despite size) | |
| 494 | function hasGrudgeTowards(hfUnitChecked, hfUnitFuckThisCreatureInParticular) | |
| 495 | -- print("Checking for grudge between " .. dfhack.TranslateName(hfUnitChecked.name) .. " and " .. dfhack.TranslateName(hfUnitFuckThisCreatureInParticular.name))
| |
| 496 | -- Triple-loops checking info.relationships.list[#].anon_3[#]. | |
| 497 | -- Admittedly, doing this repeatedly for every unit is inefficient. | |
| 498 | -- Better would be finding all grudges in fortress at start and cross-checking that. | |
| 499 | if hfUnitChecked.info.relationships then | |
| 500 | --Invaders, for instance, may have it absent | |
| 501 | --Though I wonder if it is even possible to marry off invaders, even after peace settlement | |
| 502 | for index, relationship in pairs (hfUnitChecked.info.relationships.list) do | |
| 503 | if hfUnitFuckThisCreatureInParticular.id == relationship.histfig_id then | |
| 504 | - | for feelingindex, feelingtype in pairs(relationship.anon_3) do |
| 504 | + | |
| 505 | local attitude | |
| 506 | if getFlag(relationship,'anon3') ~= nil then attitude = relationship.anon3 else attitude = relationship.attitude end | |
| 507 | for feelingindex, feelingtype in pairs(attitude) do | |
| 508 | --A dwarf can have multiple feelings/relationship types with someone. | |
| 509 | if feelingtype == 2 then | |
| 510 | --[[List of options I've noticed with changing that value: | |
| 511 | 0: Hero | |
| 512 | 1: Friend | |
| 513 | 2: Grudge | |
| 514 | 3: Bonded | |
| 515 | 6: Good for Business | |
| 516 | 7: Friendly Terms? (unsure) | |
| 517 | 10: Comrade | |
| 518 | 17: Loyal Soldier | |
| 519 | 18: Considers Monster (hm, could be interesting RP-wise) | |
| 520 | 26: Protector of the Weak | |
| 521 | ||
| 522 | Others seemed to default to Friendly terms | |
| 523 | with just few points on 7 as second relation. | |
| 524 | ||
| 525 | Perhaps anon_1 and anon_5 may also matter. | |
| 526 | --]] | |
| 527 | return true | |
| 528 | end | |
| 529 | end | |
| 530 | --Found unit without grudge. | |
| 531 | attitude = nil | |
| 532 | return false | |
| 533 | end | |
| 534 | end | |
| 535 | end | |
| 536 | end | |
| 537 | ||
| 538 | if hasGrudgeTowards(hfA, hfB) or | |
| 539 | hasGrudgeTowards(hfB, hfA) then | |
| 540 | --Either one having a grudge? Welp, no marriage for you two. | |
| 541 | return false | |
| 542 | end | |
| 543 | end | |
| 544 | -- No other disqualifing factors? It's a go. | |
| 545 | ||
| 546 | return true | |
| 547 | end | |
| 548 | ||
| 549 | function getInclinationIfPresent(unit, inclinationnumber) | |
| 550 | --takes ensouled unit and numerical index of inclination | |
| 551 | --returns the value of inclination or 0 or -1000 in case of divorce from local and seeing the world. | |
| 552 | -- utilizes getFlag | |
| 553 | local values | |
| 554 | if getFlag(unit,"status") then | |
| 555 | values = unit.status.current_soul.personality.values | |
| 556 | elseif getFlag(unit.info,"personality") then | |
| 557 | --can be nil for local units who have never updated their hfunit | |
| 558 | --comes up in the case of divorce. | |
| 559 | values = unit.info.personality.values | |
| 560 | else | |
| 561 | return -1000 --buggy placeholder: divorced partners are super-incapable of progressing their relationship. | |
| 562 | end | |
| 563 | -- Do need to check hfunits, since both parties of a ship must be willing to embark on the waters of marriage | |
| 564 | for index, value in pairs(values) do | |
| 565 | if value.type == inclinationnumber then | |
| 566 | return value.strength | |
| 567 | end | |
| 568 | end | |
| 569 | return 0 | |
| 570 | end | |
| 571 | ||
| 572 | function getAttraction(unit) | |
| 573 | --unit can't be nil. The nothingness where there should be something doesn't have sex, y'see. | |
| 574 | --Outputs a table of levels of sexual attraction an unit has. | |
| 575 | -- utilizes getFlag | |
| 576 | local attraction = {guy = 0, girl = 0}
| |
| 577 | local orientation | |
| 578 | if unit.sex~=-1 then | |
| 579 | if getFlag(unit, "status") then | |
| 580 | --local unit | |
| 581 | orientation= getFlag(unit.status, "current_soul") and unit.status.current_soul.orientation_flags or false | |
| 582 | --alas, creatures can be soulless. | |
| 583 | else | |
| 584 | --historical unit | |
| 585 | orientation = unit.orientation_flags | |
| 586 | ||
| 587 | end | |
| 588 | end | |
| 589 | if orientation then | |
| 590 | if orientation.romance_male then | |
| 591 | attraction.guy = 1 | |
| 592 | elseif orientation.marry_male then | |
| 593 | attraction.guy = 2 | |
| 594 | end | |
| 595 | if orientation.romance_female then | |
| 596 | attraction.girl = 1 | |
| 597 | elseif orientation.marry_female then | |
| 598 | attraction.girl = 2 | |
| 599 | end | |
| 600 | end | |
| 601 | return attraction | |
| 602 | end | |
| 603 | ||
| 604 | -- ======================================== -- | |
| 605 | -- Tier 3 functions -- | |
| 606 | -- ======================================== -- | |
| 607 | ||
| 608 | function getAromantism(unit) | |
| 609 | --Takes local unit | |
| 610 | --returns following series of values | |
| 611 | local unwillingToFriend, unwillingToLove, unwillingToMarry | |
| 612 | --utilizes getFlag, getIncinationIfPresent | |
| 613 | --utilizes these internally: | |
| 614 | local smittedness, friendly, bonding | |
| 615 | ||
| 616 | --failure conditions : hating friendship and having no eligble friends (not certain, might be enough to just have one-sided relation), hating romance, hating marriage. | |
| 617 | -- unit.status.current_soul.personality.values. | |
| 618 | -- Type 29: romance, type 2: family, type 3: friendship, type 18: harmony. | |
| 619 | -- hfunit.info.personality.values - nil for embark dwarves, present on visitors, like visitors can have nil spouse relation but histfig value set | |
| 620 | -- poults born on-site from embark turkeys don't have hfid or more than 0 values. | |
| 621 | -- 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) | |
| 622 | -- unknown: Type 3 - friendship, .LUST_PROPENSITY, FRIENDLINESS | |
| 623 | -- unknown: how much traits must differ in general for marriage to happen instead of a grudge. | |
| 624 | -- also, grudges could be prevented or perhaps later removed by social skills, shared skills and preferences. | |
| 625 | ||
| 626 | ||
| 627 | --local smittedness, friendly, bonding, unwillingToFriend, unWillingToLove, unwillingToMarry | |
| 628 | ||
| 629 | smittedness = unit.status.current_soul.personality.traits.LOVE_PROPENSITY | |
| 630 | -- again, always present on units that are on-site; but obviously histfigs will have to be handled differently | |
| 631 | ||
| 632 | friendly =unit.status.current_soul.personality.traits.FRIENDLINESS | |
| 633 | --FRIENDLINESS. I think I've seen ever dyed-in-the-wool quarrels have friends. | |
| 634 | ||
| 635 | bonding = unit.status.current_soul.personality.traits.EMOTIONALLY_OBSESSIVE -- how easily they make connections | |
| 636 | -- per Sarias's research in 2013, dwarves must be friends before becoming lovers. | |
| 637 | -- local cheerfulness -- happier dwarves make relationships more easily, buuut everyone is at -99999 anyway | |
| 638 | -- local lustfulness -- science required to see if it affects whether relationship can form | |
| 639 | -- local trust -- relationships should be about trust, but lying scumbags can get married too irl. Who knows? | |
| 640 | -- Eventually, for specific units would have to check how well they match up and return granual value. | |
| 641 | ||
| 642 | unwillingToFriend =(getInclinationIfPresent(unit, 3)+friendly+bonding) < friendlinessthreshold and true or false | |
| 643 | -- avg 100, min -50, max 250 | |
| 644 | -- currently requires ~roughly two lowest possible and 1 second lowest possible values. | |
| 645 | -- Starting seven do have friends, which can override this. | |
| 646 | ||
| 647 | -- While I've failed to friend off dwarves due personality, | |
| 648 | -- those dwarves have managed friendships with at least 1 other person, | |
| 649 | -- and several times have managed a marriage with someone else. | |
| 650 | -- 18-year old Erush ShoduksÇŽkzul has 3 friends, having 1 lower FRIENDSHIP, bonding | |
| 651 | ||
| 652 | unwillingToLove = (getInclinationIfPresent(unit, 29)+smittedness) < loversthreshold and true or false | |
| 653 | --not using bonding, maybe should. They already have emotional bond with others, though. | |
| 654 | --50 is average. 14 might be too low, allowing second lowest value on 1 with other average | |
| 655 | --20 is maximum for non-mentioned propensity and hating even the idea of romance, but can sometimes prevent two 1 worse than average values | |
| 656 | ||
| 657 | -- What numercal indicators can I fit into 1 tile, anyway? Blinking, I guess. TWBT would enable gradual color | |
| 658 | -- blinking has problems at low fps, but viewing unit and unit list have game paused. | |
| 659 | -- Tests should be done with 50 fps/10 gfps, since those are lowest maximums I know of. | |
| 660 | -- 30 GFPS can blend-ish, but 10 is blinky. Maybe use -blinking_at input with default value only_if_above_29 | |
| 661 | -- should check if it blinks on returning to low fps. | |
| 662 | unwillingToMarry = getInclinationIfPresent(unit, 2) < marriagethreshold and true or false | |
| 663 | --as long as they don't find family loathsome, it's a-ok. | |
| 664 | ||
| 665 | return unwillingToFriend, unwillingToLove, unwillingToMarry | |
| 666 | end | |
| 667 | ||
| 668 | function getSpouseAndLoverLink(unit) | |
| 669 | -- Currently takes local unit only | |
| 670 | -- Returns eight values: spouse and lover names, spouse and lover sites, spouse and lover aliveness, spouse and lover gender | |
| 671 | -- Doesn't check the hf relationships if the hf_only unit doesn't have a spouse in histfig links | |
| 672 | -- might be an issue if a visitor comes on map, romances someone, and then leaves. Never heard of that happening, but hey | |
| 673 | -- utilizes getFlag, getSpouseAndLover, dfhack.units.isAlive, dfhack.TranslateName, df.historical_figure.find | |
| 674 | local spouse,lover | |
| 675 | local spousesite, loversite = df.global.ui.site_id, df.global.ui.site_id | |
| 676 | --blanket current site, unless I assign them differently later | |
| 677 | --this is fine as I have indicator for off-site spouse, not on-site spouse | |
| 678 | local spouselives, loverlives | |
| 679 | spouse, lover = getSpouseAndLover(unit) | |
| 680 | if spouse and getFlag(spouse,"info") then spousesite = spouse.info.unk_14.site end | |
| 681 | if lover and getFlag(lover,"info") then loversite = lover.info.unk_14.site end | |
| 682 | local spousename, lovername | |
| 683 | if spouse == false then | |
| 684 | for index, relation in pairs(df.historical_figure.find(unit.hist_figure_id).histfig_links) do | |
| 685 | --Spouse has been culled? Maybe they're single parent. | |
| 686 | if (relation._type == df.histfig_hf_link_childst) then | |
| 687 | spousename = "Single parent" | |
| 688 | spouselives = true --More like that they're not dead. Visual thing. | |
| 689 | end | |
| 690 | end | |
| 691 | elseif spouse then | |
| 692 | spousename = dfhack.TranslateName(spouse.name) | |
| 693 | -- here, as spouse without name doesn't have gender either | |
| 694 | if getFlag(spouse, "flags1") then --local unit | |
| 695 | spouselives = dfhack.units.isAlive(spouse) | |
| 696 | else | |
| 697 | spouselives = spouse.died_year < 0 | |
| 698 | end | |
| 699 | end | |
| 700 | if lover then | |
| 701 | lovername = dfhack.TranslateName(lover.name) | |
| 702 | if getFlag(lover, "flags1") then --local unit | |
| 703 | loverlives = dfhack.units.isAlive(lover) | |
| 704 | else | |
| 705 | loverlives = lover.died_year < 0 | |
| 706 | end | |
| 707 | end | |
| 708 | -- lovers can't have children, so it's entirely pointless to speak of lost love. | |
| 709 | return spousename, lovername, spousesite, loversite, spouselives, loverlives, spouse, lover | |
| 710 | end | |
| 711 | ||
| 712 | function getSymbolizedAnimalPreference(unit, unwrapped) | |
| 713 | --Returns symbolized pregnancy if animal is pregnant. | |
| 714 | --Else, returns gender symbol, color: string and number | |
| 715 | --color is a function whose return value blinks between modes if appropriate. | |
| 716 | --unwrapped doesn't pass the colors through blinkergenerator. | |
| 717 | --utilizes getAttraction, isItBird, isItGelded | |
| 718 | local attraction = getAttraction(unit) | |
| 719 | local symbolColor = {text, color}
| |
| 720 | local prefColorTable | |
| 721 | if getPregnancyTimer(unit) > 0 then | |
| 722 | symbolColor.text = isItBird(unit) and fertileBirdSymbol or pregnantCreatureSymbol | |
| 723 | symbolColor.color = pregnantColor | |
| 724 | else | |
| 725 | symbolColor.text = unit.sex == 1 and | |
| 726 | singleMaleSymbol or | |
| 727 | ( unit.sex == 0 and | |
| 728 | singleFemaleSymbol or "") | |
| 729 | if unit.sex == -1 or --flesh balls | |
| 730 | (attraction.guy == 0 and attraction.girl == 0) or --asexual | |
| 731 | isItGelded(unit) then --some tomcats | |
| 732 | symbolColor.color = infertileColor | |
| 733 | return symbolColor | |
| 734 | --strictly speaking, not necessary, due light gray being default color | |
| 735 | end | |
| 736 | prefColorTable = {}
| |
| 737 | if unit.sex == 0 then | |
| 738 | if attraction.guy > 0 then table.insert(prefColorTable, straightFemaleShade) end | |
| 739 | if attraction.girl > 0 then table.insert(prefColorTable, gayFemaleShade) end | |
| 740 | else | |
| 741 | if attraction.girl > 0 then table.insert(prefColorTable, straightMaleShade) end | |
| 742 | if attraction.guy > 0 then table.insert(prefColorTable, gayMaleShade) end | |
| 743 | end | |
| 744 | end | |
| 745 | if unwrapped then | |
| 746 | if prefColorTable and #prefColorTable > 0 then | |
| 747 | symbolColor.color = prefColorTable | |
| 748 | end | |
| 749 | return symbolColor | |
| 750 | else | |
| 751 | if prefColorTable then symbolColor.color = blinkergenerator(prefColorTable) end | |
| 752 | if getPregnancyTimer(unit) > 0 then | |
| 753 | symbolColor.onhovertext = {
| |
| 754 | color = pregnantColor, | |
| 755 | text = tostring(math.floor((isItSmart(unit) and 9 or 6) -getPregnancyTimer(unit)/33600)) | |
| 756 | } | |
| 757 | end | |
| 758 | return symbolColor | |
| 759 | end | |
| 760 | end | |
| 761 | ||
| 762 | function getSymbolizedSpouse(unit) | |
| 763 | -- Currently takes local unit only | |
| 764 | -- Returns {} with text and color which are string or function and number or function
| |
| 765 | -- utilizes getSpouseAndLoverLink, getAttraction, getInclinationIfPresent, isItSmart,isItBird, isItGelded | |
| 766 | local spousename, lovername, spousesite, loversite, spouselives, loverlives, spouse, lover = getSpouseAndLoverLink(unit) | |
| 767 | local symbolColor = {text, color}
| |
| 768 | ||
| 769 | ||
| 770 | local attraction = getAttraction(unit) | |
| 771 | --plain sexual attraction table | |
| 772 | --it'd be more compact to code if instead of guy and girl values would use 0 and 1 indices | |
| 773 | --could call attraction[unit.sex] == 2 to see if they're willing to engage in gay marriage, for example | |
| 774 | --however, code is more self-explanatory with variables having names that explain what they're for | |
| 775 | local unwillingToFriend, unwillingToLove, unwillingToMarry | |
| 776 | if (attraction.guy+attraction.girl) == 0 then | |
| 777 | unwillingToFriend, unwillingToLove, unwillingToMarry = true, true, true | |
| 778 | else | |
| 779 | unwillingToFriend, unwillingToLove, unwillingToMarry = getAromantism(unit) | |
| 780 | end | |
| 781 | --series of disqualifying boolean values; though if orientation is already zero better not check | |
| 782 | ||
| 783 | local symbolTable = {}
| |
| 784 | local colorTable = {}
| |
| 785 | if getPregnancyTimer(unit) > 0 and | |
| 786 | isItSmart(unit) then --necessary due otherwise doubling up on the indicator with animal preferences. | |
| 787 | --Normally, would lose nothing by having pregnancy highest hiearchy, could just check first and skip the above | |
| 788 | --However, in cases of modding or father dying in battle (such as in fucduck's elven outpost) info is lost | |
| 789 | if isItBird(unit) then | |
| 790 | --possible with bird-women adventurers joining the fortress | |
| 791 | table.insert(symbolTable,fertileBirdSymbol) | |
| 792 | else | |
| 793 | table.insert(symbolTable,pregnantCreatureSymbol) | |
| 794 | end | |
| 795 | table.insert(colorTable,pregnantColor) | |
| 796 | end | |
| 797 | ||
| 798 | if not isItSmart(unit) then --it's an animal | |
| 799 | local animalprefs = getSymbolizedAnimalPreference(unit,true) | |
| 800 | table.insert(symbolTable, animalprefs.text) | |
| 801 | if type(animalprefs.color) == "table" then | |
| 802 | table.insert(colorTable, animalprefs.color[0]) | |
| 803 | table.insert(colorTable, animalprefs.color[1]) --two gender prefs at most. | |
| 804 | table.insert(symbolTable, animalprefs.text) --going to mess up timing otherwise. | |
| 805 | else | |
| 806 | table.insert(colorTable, animalprefs.color) | |
| 807 | end | |
| 808 | end | |
| 809 | if not lovername and | |
| 810 | (not spousename or spousename == "Single parent") then | |
| 811 | if isItSmart(unit) then --otherwise already handled earlier, don't need to add anything. | |
| 812 | table.insert(symbolTable, | |
| 813 | ((unit.sex == 0) and singleFemaleSymbol or (unit.sex == 1 and singleMaleSymbol or ""))) | |
| 814 | --creatures without gender don't get a symbol. | |
| 815 | if (attraction.guy == 0 and attraction.girl == 0) or --asexual | |
| 816 | isItGelded(unit) or -- gelded. Aw. | |
| 817 | unwillingToLove or --aromantic. Requires soul. | |
| 818 | sex == -1 then --creatures like iron men and bronze colossi indicate that genderless creatures can't breed | |
| 819 | ||
| 820 | table.insert(colorTable, infertileColor) | |
| 821 | ||
| 822 | else | |
| 823 | if unit.sex == 0 then | |
| 824 | if attraction.girl > 0 then | |
| 825 | table.insert(colorTable, | |
| 826 | (gayFemaleShade - 8*( (unwillingToMarry or attraction.girl == 1) and 1 or 0 ))) | |
| 827 | --darker shade for lover-only relationships | |
| 828 | end | |
| 829 | if attraction.guy > 0 then | |
| 830 | table.insert(colorTable, | |
| 831 | (straightFemaleShade - 8*( (unwillingToMarry or attraction.guy == 1) and 1 or 0 ))) | |
| 832 | end | |
| 833 | else | |
| 834 | if attraction.girl > 0 then | |
| 835 | table.insert(colorTable, | |
| 836 | (straightMaleShade - 8*( (unwillingToMarry or attraction.girl == 1) and 1 or 0 ))) | |
| 837 | end | |
| 838 | if attraction.guy > 0 then | |
| 839 | table.insert(colorTable, | |
| 840 | (gayMaleShade - 8*( (unwillingToMarry or attraction.guy == 1) and 1 or 0 ))) | |
| 841 | end | |
| 842 | end | |
| 843 | if #colorTable>#symbolTable and --Our table has more colors than symbols. Noprobs, | |
| 844 | #symbolTable>1 then --unless there's pregnant single present | |
| 845 | table.insert(symbolTable, symbolTable[#symbolTable]) | |
| 846 | --Pregnant singles screw up timing, unless we double up on last symbol. | |
| 847 | end | |
| 848 | end | |
| 849 | end | |
| 850 | else | |
| 851 | if spousename and spousename ~= "Single parent" then | |
| 852 | table.insert(symbolTable, marriedSymbol) | |
| 853 | --table for on-site, alive, not infertile spouse | |
| 854 | -- Using hiearchy: | |
| 855 | -- Dead > Infertile (unless gay) > Offsite > Normal bright color. | |
| 856 | local spousecolor = (unit.sex == 0) and | |
| 857 | (spouse.sex==1 and straightFemaleShade or gayFemaleShade) or | |
| 858 | (spouse.sex==0 and straightMaleShade or gayMaleShade) | |
| 859 | -- live marriage | |
| 860 | spousecolor = df.global.ui.site_id == spousesite and spousecolor or offsiteShade | |
| 861 | --spouse is offsite. Can have problems with divorcing. | |
| 862 | spousecolor = (isItGelded(unit) or isItGelded(spouse)) and | |
| 863 | not (unit.sex == spouse.sex) | |
| 864 | and infertileColor or spousecolor | |
| 865 | --spouse is infertile and not gay-only marriage | |
| 866 | spousecolor = spouselives and spousecolor or deadColor | |
| 867 | table.insert(colorTable, spousecolor) | |
| 868 | end | |
| 869 | if lovername then | |
| 870 | table.insert(symbolTable, loversSymbol) | |
| 871 | -- Two types of lovers: ones willing to progress to marriage, ones unwilling | |
| 872 | -- The unwilling ones get darker shade | |
| 873 | -- Both parties must be willing | |
| 874 | -- Lovers and spouses may also be off-site, dead or infertile. | |
| 875 | -- While can display all with blinky things, should minimize needless UI churn. | |
| 876 | -- Dead > Infertile (unless gay) > Offsite > Unwilling to progress to marriage > Normal bright color. | |
| 877 | local lovercolor = (unit.sex == 0) and | |
| 878 | (lover.sex==0 and gayFemaleShade or straightFemaleShade) or | |
| 879 | (lover.sex==0 and straightMaleShade or gayMaleShade) | |
| 880 | --baseline is willing to marry | |
| 881 | lovercolor = (attraction[((lover.sex==1) and "guy" or "girl")] < 2 or | |
| 882 | getAttraction(lover)[((unit.sex==1) and "guy" or "girl")] < 2 or | |
| 883 | unwillingToMarry or | |
| 884 | (getInclinationIfPresent(lover, 2) < marriagethreshold and true or false)) and | |
| 885 | (lovercolor - 8) or lovercolor | |
| 886 | -- if the unit or their lover has personality or attraction failure, the relationship will not progress | |
| 887 | lovercolor = df.global.ui.site_id == loversite and lovercolor or offsiteShade | |
| 888 | --lover is offsite. Can have problems with divorcing. | |
| 889 | --Happens mostly in case of visitors or married migrants. | |
| 890 | --Issue: Either a possible false hope of offsite lover eventually arriving. | |
| 891 | -- or a false indicator of lover being on-site. | |
| 892 | --Blinking and blurring could solve this, but not hiearchy. | |
| 893 | lovercolor = (isItGelded(unit) or isItGelded(lover)) and | |
| 894 | not (unit.sex == lover.sex) | |
| 895 | and infertileColor or lovercolor | |
| 896 | --lover is infertile and not gay-only marriage | |
| 897 | lovercolor = loverlives and lovercolor or deadColor | |
| 898 | --lover is dead | |
| 899 | table.insert(colorTable, lovercolor) | |
| 900 | end | |
| 901 | ||
| 902 | end | |
| 903 | ||
| 904 | symbolColor.text = blinkergenerator(symbolTable) | |
| 905 | ||
| 906 | symbolColor.color = blinkergenerator(colorTable) | |
| 907 | ||
| 908 | if getPregnancyTimer(unit) > 0 then | |
| 909 | symbolColor.onhovertext = {
| |
| 910 | color = pregnantColor, | |
| 911 | text = tostring(math.floor((isItSmart(unit) and 9 or 6) -getPregnancyTimer(unit)/33600)) | |
| 912 | --Needs to be converted to string since #text is called for width | |
| 913 | } | |
| 914 | end | |
| 915 | ||
| 916 | return symbolColor | |
| 917 | ||
| 918 | end | |
| 919 | ||
| 920 | function getSpouseText(unit) | |
| 921 | -- Takes local unit, returns single line string that is "" if there's no romantic relation | |
| 922 | local spousename, lovername, spousesite, loversite, spouselives, loverlives = getSpouseAndLoverLink(unit) | |
| 923 | --An unit can have both lover and spouse in vanilla with retirement shenanigans | |
| 924 | local returnstring = "" | |
| 925 | if spousename then | |
| 926 | --spouse matters more so goes first. | |
| 927 | returnstring = returnstring .. marriedSymbol | |
| 928 | if spousesite ~= df.global.ui.site_id then | |
| 929 | returnstring = returnstring .. offsite | |
| 930 | end | |
| 931 | if spouselives then | |
| 932 | returnstring = returnstring .. spousename | |
| 933 | else | |
| 934 | returnstring = returnstring .. diedS .. spousename ..diedE | |
| 935 | end | |
| 936 | end | |
| 937 | if lovername then | |
| 938 | returnstring = returnstring .. loversSymbol | |
| 939 | if loversite ~= df.global.ui.site_id then | |
| 940 | returnstring = returnstring .. offsite | |
| 941 | end | |
| 942 | if loverlives then | |
| 943 | returnstring = returnstring .. lovername | |
| 944 | else | |
| 945 | returnstring = returnstring .. diedS .. lovername ..diedE | |
| 946 | end | |
| 947 | end | |
| 948 | ||
| 949 | return returnstring | |
| 950 | end | |
| 951 | ||
| 952 | function getSuitableMatches(unit, unitlist, joblist) | |
| 953 | --Takes a local unit, local unitlist and local joblist | |
| 954 | --Returns an unitlist that includes unit and then all it's suitable candidates. | |
| 955 | --And joblist that has only those same indices, as unitlist viewscreen uses that data. | |
| 956 | --utilizes areCompatible | |
| 957 | local matchlist, jobmatchlist = {}, {}
| |
| 958 | --The unit we've checking always comes first | |
| 959 | for index=0, #unitlist-1 do | |
| 960 | if (unit == unitlist[index]) or -- self | |
| 961 | areCompatible(unit, unitlist[index]) then --suitable match | |
| 962 | matchlist[index] = true | |
| 963 | jobmatchlist[index] = true | |
| 964 | end | |
| 965 | end | |
| 966 | return matchlist, jobmatchlist | |
| 967 | end | |
| 968 | ||
| 969 | -- ======================================== -- | |
| 970 | -- Dynamic text {} output functions --
| |
| 971 | -- ======================================== -- | |
| 972 | ||
| 973 | ||
| 974 | function getViewUnitPairs(unit) | |
| 975 | local returnpairs = {}
| |
| 976 | local pregnantRichText = {}
| |
| 977 | pregnantRichText.text = getPregnancyText(unit) | |
| 978 | --bit of fluff for pregnancy | |
| 979 | pregnantRichText.color = pregnantColor | |
| 980 | table.insert(returnpairs, pregnantRichText) | |
| 981 | --First line is for pregnancy, second line is for spouse/lover | |
| 982 | local spouseRichText = {}
| |
| 983 | --Also gets lover text | |
| 984 | spouseRichText.text = getSpouseText(unit) | |
| 985 | function nabNonPregnantColor(unit) | |
| 986 | --I want spouse text to be coloured appropriately for the relationship. | |
| 987 | local previouscolor, returncolor | |
| 988 | local basecolor = getSymbolizedSpouse(unit).color | |
| 989 | if type(basecolor) == "number" then | |
| 990 | return basecolor | |
| 991 | else | |
| 992 | previouscolor = getSymbolizedSpouse(unit).color() | |
| 993 | function returnfunction() | |
| 994 | --In case the romantic relationship is blinky - typically that means pregnancy and/or lover. | |
| 995 | local unit = unit | |
| 996 | returncolor = getSymbolizedSpouse(unit).color() | |
| 997 | --Might as well use code already in place | |
| 998 | if returncolor == pregnantColor then | |
| 999 | --Of course, if the unit is pregnant, that's shown above, not here. | |
| 1000 | --Visual bug: Can still start out as pregnant color. | |
| 1001 | return previouscolor | |
| 1002 | else | |
| 1003 | previouscolor = returncolor | |
| 1004 | return returncolor | |
| 1005 | end | |
| 1006 | end | |
| 1007 | return returnfunction | |
| 1008 | end | |
| 1009 | end | |
| 1010 | ||
| 1011 | if spouseRichText.text then | |
| 1012 | spouseRichText.color = nabNonPregnantColor(unit) | |
| 1013 | end | |
| 1014 | table.insert(returnpairs, spouseRichText) | |
| 1015 | return returnpairs | |
| 1016 | end | |
| 1017 | ||
| 1018 | - | local unittable,jobstable = getSuitableMatches(unit, unitscreen.units.Citizens,unitscreen.jobs.Citizens) |
| 1018 | + | |
| 1019 | function showUnitPairs(unit) | |
| 1020 | local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
| |
| 1021 | if not HavePrinted then | |
| 1022 | --local unittable,jobstable = getSuitableMatches(unit, unitscreen.units.Citizens,unitscreen.jobs.Citizens) | |
| 1023 | --unittable, jobstable never used | |
| 1024 | oldCitizens, oldJobs, oldIndices = {}, {}, {}
| |
| 1025 | local index = 0 | |
| 1026 | while getFlag(unitscreen.units.Citizens, index) do | |
| 1027 | if (unit == unitscreen.units.Citizens[index]) or -- self | |
| 1028 | areCompatible(unit, unitscreen.units.Citizens[index]) then | |
| 1029 | index = 1+index | |
| 1030 | else | |
| 1031 | table.insert(oldCitizens, unitscreen.units.Citizens[index]) | |
| 1032 | table.insert(oldIndices, index) | |
| 1033 | oldJobs[#oldIndices] = unitscreen.jobs.Citizens[index] | |
| 1034 | unitscreen.units.Citizens:erase(index) | |
| 1035 | unitscreen.jobs.Citizens:erase(index) | |
| 1036 | end | |
| 1037 | end | |
| 1038 | HavePrinted = true | |
| 1039 | for ci = 0, #unitscreen.units.Citizens -1 do | |
| 1040 | if (unit == unitscreen.units.Citizens[ci]) then | |
| 1041 | unitscreen.cursor_pos.Citizens = ci | |
| 1042 | break; | |
| 1043 | end | |
| 1044 | end | |
| 1045 | end | |
| 1046 | end | |
| 1047 | ||
| 1048 | function hideUnitPairs() | |
| 1049 | if HavePrinted then | |
| 1050 | local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
| |
| 1051 | for i=#oldCitizens, 1, -1 do | |
| 1052 | unitscreen.units.Citizens:insert(oldIndices[i], oldCitizens[i]) | |
| 1053 | unitscreen.jobs.Citizens:insert(oldIndices[i], oldJobs[i]) | |
| 1054 | end | |
| 1055 | HavePrinted = false | |
| 1056 | end | |
| 1057 | end | |
| 1058 | ||
| 1059 | ||
| 1060 | local pagelength, currentpage, visitorpopupdims, symbolizedList = df.global.gps.dimy - 9, 0, {x = -30, y = 4}
| |
| 1061 | -- pagelength needs to be exposed due manipulators having two lines shorter pages than standard view. | |
| 1062 | -- Also due resizing. | |
| 1063 | -- currentpage needs to be exposed due traversing the lists. | |
| 1064 | function getUnitListPairs() | |
| 1065 | local unitscreen = getBottomMostViewscreenWithFocus("unitlist", df.global.gview.view.child.child)
| |
| 1066 | --note: Counts from 0, unlike lua tables | |
| 1067 | local returntable = {}
| |
| 1068 | local cursorposition, unitlist, iter | |
| 1069 | if unitscreen.page == 0 then --Citizen list | |
| 1070 | cursorposition = unitscreen.cursor_pos.Citizens | |
| 1071 | currentpage = math.floor(cursorposition / pagelength) | |
| 1072 | cursorposition = cursorposition % pagelength --cursor position within a page | |
| 1073 | unitlist = unitscreen.units.Citizens | |
| 1074 | for iter = (0+currentpage*pagelength), | |
| 1075 | ( (((1+currentpage)*pagelength-1)<(#unitlist -1)) and | |
| 1076 | ((1+currentpage)*pagelength-1) or | |
| 1077 | (#unitlist -1)) do | |
| 1078 | table.insert(returntable, getSymbolizedSpouse(unitlist[iter])) | |
| 1079 | returntable[#returntable].onclick = function() | |
| 1080 | local tile = nil | |
| 1081 | if dfhack.gui.getCurFocus():find("unitlist") then tile = dfhack.screen.readTile(39,df.global.gps.dimy-2) end
| |
| 1082 | --search plugin support | |
| 1083 | if not tile or (tile and tile.ch == 95 and tile.fg == 2 or tile.ch == 0) then | |
| 1084 | if HavePrinted then hideUnitPairs() else | |
| 1085 | showUnitPairs(unitlist[iter]) end | |
| 1086 | writeoverTable(symbolizedList, getUnitListPairs()) | |
| 1087 | end | |
| 1088 | end | |
| 1089 | end | |
| 1090 | elseif unitscreen.page == 1 or unitscreen.page == 2 then --Livestock or Others | |
| 1091 | local pageName = (unitscreen.page == 1) and "Livestock" or "Others" | |
| 1092 | cursorposition = unitscreen.cursor_pos[pageName] | |
| 1093 | currentpage = math.floor(cursorposition / pagelength) | |
| 1094 | cursorposition = cursorposition % pagelength --cursor position within a page | |
| 1095 | unitlist = unitscreen.units[pageName] | |
| 1096 | for iter = (0+currentpage*pagelength), | |
| 1097 | ( (((1+currentpage)*pagelength-1)<(#unitlist -1)) and | |
| 1098 | ((1+currentpage)*pagelength-1) or | |
| 1099 | (#unitlist -1)) do | |
| 1100 | local unit = unitlist[iter] | |
| 1101 | -- What goes on with combination of pet and intelligent? | |
| 1102 | -- Tests reveals failure to bear children and love for even histfigged intelligent dogs | |
| 1103 | -- Perhaps only dumb pets of non-your civ can screw. | |
| 1104 | -- Of course, might want accurate indicator then anyway, as you might make them your civ members | |
| 1105 | ||
| 1106 | --Near as I can tell, for historical figures: | |
| 1107 | -- pet 1 0 | |
| 1108 | -- smart 1 - Fertile | |
| 1109 | -- 0 Fer Fertile(trogs, trolls) | |
| 1110 | ||
| 1111 | if isItSmart(unit) then | |
| 1112 | if (df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET or | |
| 1113 | df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET_EXOTIC) | |
| 1114 | -- Intelligent pets seem unable to breed | |
| 1115 | or unit.hist_figure_id < 0 then | |
| 1116 | --I think marriage requires being historical, | |
| 1117 | -- collaborated by historical turkeys being able to marry during retirement | |
| 1118 | table.insert(returntable,getGenderInInfertileColor(unit)) | |
| 1119 | end | |
| 1120 | if unit.hist_figure_id > -1 then | |
| 1121 | table.insert(returntable, getSymbolizedSpouse(unit)) | |
| 1122 | returntable[#returntable].onclick = function() | |
| 1123 | - | print("entered onclick " .. getLen(fortressmatches))
|
| 1123 | + | |
| 1124 | --creates several tables per call, but one doesn't usually call it. | |
| 1125 | local fortressmatches = getSuitableMatches(unit, unitscreen.units.Citizens, unitscreen.jobs.Others) | |
| 1126 | --Lets find the candidates for a given visitor | |
| 1127 | -- this is a table of index, true values, not units. | |
| 1128 | -- print("entered onclick " .. getLen(fortressmatches))
| |
| 1129 | if getLen(fortressmatches) > 0 then | |
| 1130 | local fortressmatchlist = {}
| |
| 1131 | for index, value in pairs(fortressmatches) do | |
| 1132 | table.insert(fortressmatchlist, getSymbolizedSpouse(unitscreen.units.Citizens[index])) | |
| 1133 | fortressmatchlist[#fortressmatchlist].notEndOfLine = true | |
| 1134 | table.insert(fortressmatchlist, {text = dfhack.TranslateName(unitscreen.units.Citizens[index].name)})
| |
| 1135 | --Lets convert the unit to nicely colored name to display. | |
| 1136 | end | |
| 1137 | print(#fortressmatchlist) | |
| 1138 | local popupscreen = screenconstructor.getScreen( | |
| 1139 | fortressmatchlist, | |
| 1140 | {x = math.floor((df.global.gps.dimx-screenconstructor.getLongestLength(fortressmatchlist,"text"))/2),
| |
| 1141 | y = math.floor((df.global.gps.dimy-screenconstructor.getHeight(fortressmatchlist))/2)}, | |
| 1142 | {x = math.floor((df.global.gps.dimx-screenconstructor.getLongestLength(fortressmatchlist,"text"))/2 -1),
| |
| 1143 | y = math.floor((df.global.gps.dimy-screenconstructor.getHeight(fortressmatchlist))/2 -1), | |
| 1144 | width = 2+ screenconstructor.getLongestLength(fortressmatchlist,"text"), | |
| 1145 | height = 2+ screenconstructor.getHeight(fortressmatchlist)} | |
| 1146 | ) | |
| 1147 | popupscreen:show() | |
| 1148 | popupscreen.onInput = function() popupscreen:dismiss() end | |
| 1149 | --There's no input on which I wont want to dismiss the screen. | |
| 1150 | end | |
| 1151 | end | |
| 1152 | end | |
| 1153 | else | |
| 1154 | --It doesn't matter if a pet/troglodyte has killed someone or not, they'll breed either way. | |
| 1155 | if unit.hist_figure_id > -1 then | |
| 1156 | --Nonetheless, historical pets can marry in retired forts | |
| 1157 | table.insert(returntable, getSymbolizedSpouse(unit)) | |
| 1158 | --getSymbolizedSpouse calls on below functions for not smart creatures | |
| 1159 | else | |
| 1160 | table.insert(returntable, getSymbolizedAnimalPreference(unit)) | |
| 1161 | end | |
| 1162 | end | |
| 1163 | ||
| 1164 | --[[ Deprecated logic based on previously believed data. | |
| 1165 | if unit.hist_figure_id > 0 then | |
| 1166 | --Can be married. Visitor, murderer, pet... | |
| 1167 | if isItSmart(unit) and | |
| 1168 | (df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET or | |
| 1169 | df.global.world.raws.creatures.all[unit.race].caste[0].flags.PET_EXOTIC) then | |
| 1170 | -- | |
| 1171 | table.insert(returntable,getGenderInInfertileColor(unit)) | |
| 1172 | ||
| 1173 | end | |
| 1174 | --Distinction between being smart or not (aka free love) is handled inside. | |
| 1175 | table.insert(returntable, getSymbolizedSpouse(unit)) | |
| 1176 | else | |
| 1177 | if isItSmart(unit) then | |
| 1178 | --Wild animal men and non-historical gremlins | |
| 1179 | --Infertile until they're able to marry - like after having killed someone important | |
| 1180 | table.insert(returntable,getGenderInInfertileColor(unit)) | |
| 1181 | else | |
| 1182 | --normal turkeys brought on embark and wild animals. | |
| 1183 | table.insert(returntable, getSymbolizedAnimalPreference(unit)) | |
| 1184 | ||
| 1185 | end | |
| 1186 | end]]-- | |
| 1187 | end | |
| 1188 | ||
| 1189 | end | |
| 1190 | return returntable | |
| 1191 | end | |
| 1192 | ||
| 1193 | ||
| 1194 | -- ======================================== -- | |
| 1195 | -- Initialization and placement -- | |
| 1196 | -- ======================================== -- | |
| 1197 | ||
| 1198 | local unitscreen = getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child.child)
| |
| 1199 | --gets unitlist viewscreen | |
| 1200 | local viewscreen | |
| 1201 | if unitscreen then | |
| 1202 | symbolizedList = getUnitListPairs() | |
| 1203 | local list_rect = {x = 1, y = 4, width = 1, height = pagelength}
| |
| 1204 | local newscreen = screenconstructor.getScreen(symbolizedList, list_rect, nil) | |
| 1205 | newscreen:show() | |
| 1206 | local listtable = {}
| |
| 1207 | listtable[0] = "Citizens" | |
| 1208 | listtable[1] = "Livestock" | |
| 1209 | listtable[2] = "Others" | |
| 1210 | listtable[3] = "Dead" | |
| 1211 | local oldwhichlist,lenlist, doNotUseBase, manitimeout, manicursorpos = listtable[unitscreen.page] | |
| 1212 | ||
| 1213 | local manipulatorkeytable = {}
| |
| 1214 | local upkeys = {CURSOR_UP = true}
| |
| 1215 | local downkeys = {CURSOR_DOWN = true}
| |
| 1216 | newscreen.onclick = function() | |
| 1217 | local tile = nil | |
| 1218 | if dfhack.gui.getCurFocus():find("unitlist") then tile = dfhack.screen.readTile(39,df.global.gps.dimy-2) end
| |
| 1219 | --search plugin support | |
| 1220 | --if prevents failing to work in manipulator/main | |
| 1221 | if not tile or (tile and tile.ch == 95 and tile.fg == 2 or tile.ch == 0) then | |
| 1222 | if HavePrinted then hideUnitPairs() end --restoring units on random clicks | |
| 1223 | writeoverTable(symbolizedList, getUnitListPairs()) --restoring appearance too | |
| 1224 | end | |
| 1225 | end | |
| 1226 | local baseonInput = newscreen.onInput | |
| 1227 | local function onListInput(self, keys) | |
| 1228 | lenlist = #(unitscreen.units[oldwhichlist]) | |
| 1229 | ||
| 1230 | if keys.LEAVESCREEN and 2 == dfhack.screen.readTile(29,df.global.gps.dimy-2).fg then | |
| 1231 | --Search plugin support. Tbh, would have been ultimately easier to disable dismiss_on_zoom | |
| 1232 | local dismissfunc = self.dismiss | |
| 1233 | self.dismiss = function () end | |
| 1234 | dfhack.timeout(1, "frames", function() self.dismiss = dismissfunc end ) | |
| 1235 | end | |
| 1236 | ||
| 1237 | if not doNotUseBase then baseonInput(self, keys) end | |
| 1238 | local manipulatorscript = getBottomMostViewscreenWithFocus("dfhack/lua/manipulator",df.global.gview.view.child.child)
| |
| 1239 | --Lua manipulator viewscreen is present | |
| 1240 | local whichlist = listtable[unitscreen.page] | |
| 1241 | --duplicated from indicator_screen (ugh). Feels like there should be a way to better determine this. | |
| 1242 | ||
| 1243 | if (currentpage ~= math.floor(unitscreen.cursor_pos[whichlist]/ pagelength) and whichlist ~= "Dead") or | |
| 1244 | --up-down paging | |
| 1245 | (oldwhichlist and oldwhichlist ~= whichlist) or --left-right paging | |
| 1246 | (lenlist ~= #(unitscreen.units[oldwhichlist])) then --search plugin support | |
| 1247 | oldwhichlist = whichlist | |
| 1248 | doNotUseBase = true | |
| 1249 | lenlist = #(unitscreen.units[oldwhichlist]) | |
| 1250 | writeoverTable(symbolizedList, getUnitListPairs()) | |
| 1251 | dfhack.timeout(2, "frames", function() doNotUseBase = false end) | |
| 1252 | --Something weird happens with writeoverTable here, where it sometimes parses input twice. | |
| 1253 | --In the absence of other solutions, merely avoid relaying input for two frames. | |
| 1254 | end | |
| 1255 | ||
| 1256 | function mv_cursor(keys) | |
| 1257 | -- Function for the purpose of moving cursor alongside the manipulator. | |
| 1258 | -- They don't do this natively - a bugging disrepacy. | |
| 1259 | if keys.CURSOR_UP or keys.CURSOR_DOWN then | |
| 1260 | unitscreen.cursor_pos[whichlist] = --set new cursor position | |
| 1261 | (unitscreen.cursor_pos[whichlist] +(keys.CURSOR_DOWN and 1 or -1)) --to 1 up or down from previous | |
| 1262 | % #(unitscreen.units[whichlist]) --with overflow accounted for. | |
| 1263 | end | |
| 1264 | end | |
| 1265 | ||
| 1266 | --manipulator/main.lua scrolls differently than default interface, got to handle it | |
| 1267 | --TODO: fix the mess with numpad keys and manipulator/main.lua | |
| 1268 | if manipulatorscript and manipulatorscript.breakdown_level ~= 2 then | |
| 1269 | --Finds manipulator here on both Alt-L and Escape back out | |
| 1270 | --breakdown level check prevents that. | |
| 1271 | if #(unitscreen.units[whichlist]) > (df.global.gps.dimy -11) then | |
| 1272 | --multi-page manipulator unitlist handling | |
| 1273 | if pagelength ~= lenlist then | |
| 1274 | --Instead of using a sublist that is refreshed manipulator/main uses whole list that is moved | |
| 1275 | pagelength = lenlist | |
| 1276 | writeoverTable(symbolizedList, getUnitListPairs()) | |
| 1277 | self:adjustDims(true,nil,nil,nil, pagelength) | |
| 1278 | self.frame_body.clip_y2 = df.global.gps.dimy - 8 | |
| 1279 | manicursorpos = unitscreen.cursor_pos[whichlist] > (df.global.gps.dimy - 12) and | |
| 1280 | (df.global.gps.dimy - 12) or unitscreen.cursor_pos[whichlist] | |
| 1281 | self.frame_body.y1 = 4 - (unitscreen.cursor_pos[whichlist] > (df.global.gps.dimy - 12) and | |
| 1282 | unitscreen.cursor_pos[whichlist] - (df.global.gps.dimy - 12) or | |
| 1283 | 0) | |
| 1284 | end | |
| 1285 | --scrolling occurs if manipulator's cursor position has reached the edge and tries to keep going. | |
| 1286 | --Manipulator's initial cursor position is either current cursor position or bottom, whichever is smaller | |
| 1287 | --Successive changes can divorce the two, so need to have internal check. | |
| 1288 | ||
| 1289 | --Two functions to follow the cursor position of manipulator | |
| 1290 | function manidown() | |
| 1291 | if manicursorpos ~= (df.global.gps.dimy - 12) then | |
| 1292 | manicursorpos = 1 + manicursorpos | |
| 1293 | elseif manicursorpos == (df.global.gps.dimy - 12) then | |
| 1294 | if (unitscreen.cursor_pos[whichlist] +1 ) ~= #(unitscreen.units[whichlist]) then | |
| 1295 | self.frame_body.y1 = -1 + self.frame_body.y1 | |
| 1296 | else | |
| 1297 | self.frame_body.y1 = 4 | |
| 1298 | manicursorpos = 0 | |
| 1299 | end | |
| 1300 | end | |
| 1301 | end | |
| 1302 | function maniup() | |
| 1303 | if manicursorpos ~= 0 then | |
| 1304 | manicursorpos = -1 + manicursorpos | |
| 1305 | elseif manicursorpos == 0 then | |
| 1306 | if unitscreen.cursor_pos[whichlist] ~= 0 then | |
| 1307 | self.frame_body.y1 = 1 + self.frame_body.y1 | |
| 1308 | else | |
| 1309 | self.frame_body.y1 = (df.global.gps.dimy -7) - #(unitscreen.units[whichlist]) | |
| 1310 | manicursorpos = (df.global.gps.dimy - 12) | |
| 1311 | end | |
| 1312 | end | |
| 1313 | end | |
| 1314 | ||
| 1315 | --manipulator/main allows shift+up/down scrolling, which has unique behaviour | |
| 1316 | if hasKeyOverlap(keys, downkeys) then | |
| 1317 | if not hasKeyOverlap(keys, {["_FAST"] = true}) then
| |
| 1318 | manidown() | |
| 1319 | else | |
| 1320 | for i=1, 10 do | |
| 1321 | if (unitscreen.cursor_pos[whichlist] +1 ) ~= #(unitscreen.units[whichlist]) then | |
| 1322 | manidown() | |
| 1323 | mv_cursor(downkeys) | |
| 1324 | else | |
| 1325 | if i == 1 then | |
| 1326 | manidown() | |
| 1327 | mv_cursor(downkeys) | |
| 1328 | end | |
| 1329 | break; | |
| 1330 | end | |
| 1331 | end | |
| 1332 | end | |
| 1333 | end | |
| 1334 | if hasKeyOverlap(keys, upkeys) then | |
| 1335 | if not hasKeyOverlap(keys, {["_FAST"] = true}) then
| |
| 1336 | maniup() | |
| 1337 | else | |
| 1338 | for i=1, 10 do | |
| 1339 | if unitscreen.cursor_pos[whichlist] ~= 0 then | |
| 1340 | maniup() | |
| 1341 | mv_cursor(upkeys) | |
| 1342 | else | |
| 1343 | if i == 1 then | |
| 1344 | maniup() | |
| 1345 | mv_cursor(upkeys) | |
| 1346 | end | |
| 1347 | break; | |
| 1348 | end | |
| 1349 | end | |
| 1350 | end | |
| 1351 | end | |
| 1352 | end | |
| 1353 | mv_cursor(keys) --adjust outside cursor, does nothing on shift-scrolling | |
| 1354 | if df.global.gps.mouse_x == 1 and --clicked on the indicator line | |
| 1355 | (keys._MOUSE_L or keys._MOUSE_L_DOWN) then | |
| 1356 | if manipulatorscript and not manitimeout then | |
| 1357 | manitimeout = true | |
| 1358 | --timeout necessary due otherwise causing errors with multiple rapid commands | |
| 1359 | self._native.parent.breakdown_level = 2 | |
| 1360 | self._native.parent.parent.child = self._native.parent.child | |
| 1361 | self._native.parent = self._native.parent.parent | |
| 1362 | dfhack.timeout(2, "frames", function() | |
| 1363 | dfhack.run_command("gui/indicator_screen execute_hook manipulator/main")
| |
| 1364 | end) | |
| 1365 | dfhack.timeout(2, "frames", function() manitimeout = false end) | |
| 1366 | end | |
| 1367 | end | |
| 1368 | elseif manipulatorscript and manipulatorscript.breakdown_level == 2 then | |
| 1369 | if keys.LEAVESCREEN then | |
| 1370 | hideUnitPairs() | |
| 1371 | dfhack.run_command("relations-indicator")
| |
| 1372 | end | |
| 1373 | if keys.UNITJOB_ZOOM_CRE then | |
| 1374 | dfhack.timeout(4, "frames", function() dfhack.run_command("relations-indicator") end)
| |
| 1375 | end | |
| 1376 | end | |
| 1377 | end | |
| 1378 | newscreen.onInput = onListInput | |
| 1379 | ||
| 1380 | local baseOnResize = newscreen.onResize | |
| 1381 | ||
| 1382 | function onListResize(self) | |
| 1383 | -- Unlike with View-unit, the data might change depending on the size of the window. | |
| 1384 | baseOnResize(self) | |
| 1385 | if pagelength ~= (df.global.gps.dimy - 9) then | |
| 1386 | --If window length changed, better refresh the data. | |
| 1387 | pagelength = df.global.gps.dimy - 9 | |
| 1388 | writeoverTable(symbolizedList, getUnitListPairs()) | |
| 1389 | self:adjustDims(true,list_rect.x, list_rect.y,list_rect.width, pagelength) | |
| 1390 | --Not adjusting height here would result in situation where making screen shorter works, but taller not. | |
| 1391 | end | |
| 1392 | end | |
| 1393 | ||
| 1394 | newscreen.onResize = onListResize | |
| 1395 | ||
| 1396 | -- ======================================== -- | |
| 1397 | -- View-unit section -- | |
| 1398 | -- ======================================== -- | |
| 1399 | ||
| 1400 | ||
| 1401 | else | |
| 1402 | local function viewunit() | |
| 1403 | viewscreen = dfhack.gui.getCurViewscreen() | |
| 1404 | local unit = dfhack.gui.getSelectedUnit() | |
| 1405 | local symbolizedSingle = getViewUnitPairs(unit) | |
| 1406 | local view_rect = {x = (-30 -(screenconstructor.isWideView() and 24 or 0)), y = 17, width = 28, height = 2}
| |
| 1407 | local newscreen = screenconstructor.getScreen(symbolizedSingle, view_rect, nil) | |
| 1408 | newscreen:show() | |
| 1409 | if not dfhack.gui.getFocusString(viewscreen) | |
| 1410 | :find("dwarfmode/ViewUnits/Some/General") then
| |
| 1411 | --Can enter in inventory view with v if one previously exited inventory view | |
| 1412 | newscreen:removeFromView() --Gotta hide in that case | |
| 1413 | end | |
| 1414 | local baseonInput = newscreen.onInput | |
| 1415 | local function onViewInput(self, keys) | |
| 1416 | --handling changing the menu width and units: | |
| 1417 | --Capturing the state before it changes: | |
| 1418 | local oldUnit, oldScreen | |
| 1419 | local sameUnit, sameScreen = true, true | |
| 1420 | --Tab changing menu width is handled below. | |
| 1421 | --storing pre-keypress identifiers for unit and viewscreen state | |
| 1422 | if not keys.CHANGETAB then | |
| 1423 | oldUnit = df.global.ui_selected_unit | |
| 1424 | oldScreen = dfhack.gui.getFocusString(viewscreen) | |
| 1425 | --Merely checking viewscreen match will not work, given that sideview only modifies existing screen | |
| 1426 | end | |
| 1427 | ||
| 1428 | baseonInput(self,keys) --Doing baseline housekeeping and passing input to parent | |
| 1429 | ||
| 1430 | --Finding out if anything changed after parent got the input | |
| 1431 | if not keys.CHANGETAB then | |
| 1432 | sameUnit = (oldUnit == df.global.ui_selected_unit) | |
| 1433 | --could also use dfhack.gui.getSelectedUnit() | |
| 1434 | sameScreen = (oldScreen == dfhack.gui.getFocusString(viewscreen)) | |
| 1435 | end | |
| 1436 | ||
| 1437 | if keys.CHANGETAB then | |
| 1438 | --Tabbing moves around the sideview, so got to readjust screen position. | |
| 1439 | view_rect.x = -30 -(screenconstructor.isWideView() and 24 or 0) | |
| 1440 | self:adjustDims(true, view_rect.x) | |
| 1441 | --unlike text tables, position tables aren't dynamic, to allow them to be incomplete | |
| 1442 | end | |
| 1443 | ||
| 1444 | --If unit changed, got to replace the indicator text | |
| 1445 | if not sameUnit then | |
| 1446 | writeoverTable(symbolizedSingle,getViewUnitPairs(df.global.world.units.active[df.global.ui_selected_unit])) | |
| 1447 | elseif not sameScreen then | |
| 1448 | --Don't want to display the screen if there isn't an unit present, but don't want to spam blinks either | |
| 1449 | if not dfhack.gui.getFocusString(viewscreen) | |
| 1450 | :find("dwarfmode/ViewUnits/Some/General") then
| |
| 1451 | --Different screen doesn't mean it's different in same way - need to check here too. | |
| 1452 | self:removeFromView() | |
| 1453 | else | |
| 1454 | --It's general, so better fix it...Thoug well - should change mostly nothing | |
| 1455 | self:adjustDims(true, view_rect.x, view_rect.y, view_rect.width, view_rect.height) | |
| 1456 | self.signature = true | |
| 1457 | end | |
| 1458 | end | |
| 1459 | ||
| 1460 | end | |
| 1461 | ||
| 1462 | newscreen.onInput = onViewInput | |
| 1463 | ||
| 1464 | end | |
| 1465 | if dfhack.gui.getCurFocus():find("dwarfmode/ViewUnits/Some")
| |
| 1466 | then viewunit() | |
| 1467 | else dfhack.timeout(2, "frames", function() | |
| 1468 | if getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
| |
| 1469 | viewunit() | |
| 1470 | end | |
| 1471 | end) | |
| 1472 | end | |
| 1473 | end |