|
| 1 | +-- Show fort's petitions, pending and fulfilled. |
| 2 | +--[====[ |
| 3 | +
|
| 4 | +gui/petitions |
| 5 | +============= |
| 6 | +Show fort's petitions, pending and fulfilled. |
| 7 | +
|
| 8 | +For best experience add following to your ``dfhack*.init``:: |
| 9 | +
|
| 10 | + keybinding add Alt-P@dwarfmode/Default gui/petitions |
| 11 | +
|
| 12 | +]====] |
| 13 | + |
| 14 | +local gui = require 'gui' |
| 15 | +local widgets = require 'gui.widgets' |
| 16 | +local utils = require 'utils' |
| 17 | + |
| 18 | +-- local args = utils.invert({...}) |
| 19 | + |
| 20 | +--[[ |
| 21 | +[lua]# @ df.agreement_details_type |
| 22 | +<type: agreement_details_type> |
| 23 | +0 = JoinParty |
| 24 | +1 = DemonicBinding |
| 25 | +2 = Residency |
| 26 | +3 = Citizenship |
| 27 | +4 = Parley |
| 28 | +5 = PositionCorruption |
| 29 | +6 = PlotStealArtifact |
| 30 | +7 = PromisePosition |
| 31 | +8 = PlotAssassination |
| 32 | +9 = PlotAbduct |
| 33 | +10 = PlotSabotage |
| 34 | +11 = PlotConviction |
| 35 | +12 = Location |
| 36 | +13 = PlotInfiltrationCoup |
| 37 | +14 = PlotFrameTreason |
| 38 | +15 = PlotInduceWar |
| 39 | +]] |
| 40 | + |
| 41 | +if not dfhack.world.isFortressMode() then return end |
| 42 | + |
| 43 | +-- from gui/unit-info-viewer.lua |
| 44 | +do -- for code folding |
| 45 | +-------------------------------------------------- |
| 46 | +---------------------- Time ---------------------- |
| 47 | +-------------------------------------------------- |
| 48 | +local TU_PER_DAY = 1200 |
| 49 | +--[[ |
| 50 | +if advmode then TU_PER_DAY = 86400 ? or only for cur_year_tick? |
| 51 | +advmod_TU / 72 = ticks |
| 52 | +--]] |
| 53 | +local TU_PER_MONTH = TU_PER_DAY * 28 |
| 54 | +local TU_PER_YEAR = TU_PER_MONTH * 12 |
| 55 | + |
| 56 | +local MONTHS = { |
| 57 | + 'Granite', |
| 58 | + 'Slate', |
| 59 | + 'Felsite', |
| 60 | + 'Hematite', |
| 61 | + 'Malachite', |
| 62 | + 'Galena', |
| 63 | + 'Limestone', |
| 64 | + 'Sandstone', |
| 65 | + 'Timber', |
| 66 | + 'Moonstone', |
| 67 | + 'Opal', |
| 68 | + 'Obsidian', |
| 69 | +} |
| 70 | +Time = defclass(Time) |
| 71 | +function Time:init(args) |
| 72 | + self.year = args.year or 0 |
| 73 | + self.ticks = args.ticks or 0 |
| 74 | +end |
| 75 | +function Time:getDays() -- >>float<< Days as age (including years) |
| 76 | + return self.year * 336 + (self.ticks / TU_PER_DAY) |
| 77 | +end |
| 78 | +function Time:getDayInMonth() |
| 79 | + return math.floor ( (self.ticks % TU_PER_MONTH) / TU_PER_DAY ) + 1 |
| 80 | +end |
| 81 | +function Time:getMonths() -- >>int<< Months as age (not including years) |
| 82 | + return math.floor (self.ticks / TU_PER_MONTH) |
| 83 | +end |
| 84 | +function Time:getMonthStr() -- Month as date |
| 85 | + return MONTHS[self:getMonths()+1] or 'error' |
| 86 | +end |
| 87 | +function Time:getDayStr() -- Day as date |
| 88 | + local d = math.floor ( (self.ticks % TU_PER_MONTH) / TU_PER_DAY ) + 1 |
| 89 | + if d == 11 or d == 12 or d == 13 then |
| 90 | + d = tostring(d)..'th' |
| 91 | + elseif d % 10 == 1 then |
| 92 | + d = tostring(d)..'st' |
| 93 | + elseif d % 10 == 2 then |
| 94 | + d = tostring(d)..'nd' |
| 95 | + elseif d % 10 == 3 then |
| 96 | + d = tostring(d)..'rd' |
| 97 | + else |
| 98 | + d = tostring(d)..'th' |
| 99 | + end |
| 100 | + return d |
| 101 | +end |
| 102 | +--function Time:__add() |
| 103 | +--end |
| 104 | +function Time:__sub(other) |
| 105 | + if DEBUG then print(self.year,self.ticks) end |
| 106 | + if DEBUG then print(other.year,other.ticks) end |
| 107 | + if self.ticks < other.ticks then |
| 108 | + return Time{ year = (self.year - other.year - 1) , ticks = (TU_PER_YEAR + self.ticks - other.ticks) } |
| 109 | + else |
| 110 | + return Time{ year = (self.year - other.year) , ticks = (self.ticks - other.ticks) } |
| 111 | + end |
| 112 | +end |
| 113 | +-------------------------------------------------- |
| 114 | +-------------------------------------------------- |
| 115 | +end |
| 116 | + |
| 117 | +local we = df.global.ui.group_id |
| 118 | + |
| 119 | +local function getAgreementDetails(a) |
| 120 | + local sb = {} -- StringBuilder |
| 121 | + |
| 122 | + sb[#sb+1] = {text = "Agreement #" ..a.id, pen = COLOR_RED} |
| 123 | + sb[#sb+1] = NEWLINE |
| 124 | + |
| 125 | + local us = "Us" |
| 126 | + local them = "Them" |
| 127 | + for i, p in ipairs(a.parties) do |
| 128 | + local e_descr = {} |
| 129 | + local our = false |
| 130 | + for _, e_id in ipairs(p.entity_ids) do |
| 131 | + local e = df.global.world.entities.all[e_id] |
| 132 | + e_descr[#e_descr+1] = table.concat{"The ", df.historical_entity_type[e.type], " ", dfhack.TranslateName(e.name, true)} |
| 133 | + if we == e_id then our = true end |
| 134 | + end |
| 135 | + if our then |
| 136 | + us = table.concat(e_descr, ", ") |
| 137 | + else |
| 138 | + them = table.concat(e_descr, ", ") |
| 139 | + end |
| 140 | + end |
| 141 | + sb[#sb+1] = them |
| 142 | + sb[#sb+1] = NEWLINE |
| 143 | + sb[#sb+1] = " petitioned" |
| 144 | + sb[#sb+1] = NEWLINE |
| 145 | + sb[#sb+1] = us |
| 146 | + sb[#sb+1] = NEWLINE |
| 147 | + for _, d in ipairs (a.details) do |
| 148 | + local petition_date = Time{year = d.year, ticks = d.year_tick} |
| 149 | + local petition_date_str = petition_date:getDayStr()..' of '..petition_date:getMonthStr()..' in the year '..tostring(petition_date.year) |
| 150 | + local cur_date = Time{year = df.global.cur_year, ticks = df.global.cur_year_tick} |
| 151 | + sb[#sb+1] = ("On " .. petition_date_str) |
| 152 | + sb[#sb+1] = NEWLINE |
| 153 | + local diff = (cur_date - petition_date) |
| 154 | + if diff:getDays() < 1.0 then |
| 155 | + sb[#sb+1] = ("(this was today)") |
| 156 | + elseif diff:getMonths() == 0 then |
| 157 | + sb[#sb+1] = ("(this was " .. math.floor( diff:getDays() ) .. " days ago)" ) |
| 158 | + else |
| 159 | + sb[#sb+1] = ("(this was " .. diff:getMonths() .. " months and " .. diff:getDayInMonth() .. " days ago)" ) |
| 160 | + end |
| 161 | + sb[#sb+1] = NEWLINE |
| 162 | + |
| 163 | + sb[#sb+1] = ("Petition type: " .. df.agreement_details_type[d.type]) |
| 164 | + sb[#sb+1] = NEWLINE |
| 165 | + if d.type == df.agreement_details_type.Location then |
| 166 | + local details = d.data.Location |
| 167 | + sb[#sb+1] = "Provide a " |
| 168 | + sb[#sb+1] = {text = df.abstract_building_type[details.type], pen = COLOR_LIGHTGREEN} |
| 169 | + sb[#sb+1] = " of tier " .. details.tier |
| 170 | + if details.deity_type ~= -1 then |
| 171 | + sb[#sb+1] = " of a " |
| 172 | + -- None/Deity/Religion |
| 173 | + sb[#sb+1] = {text = df.temple_deity_type[details.deity_type], pen = COLOR_LIGHTGREEN} |
| 174 | + else |
| 175 | + sb[#sb+1] = " for " |
| 176 | + sb[#sb+1] = {text = df.profession[details.profession], pen = COLOR_LIGHTGREEN} |
| 177 | + end |
| 178 | + sb[#sb+1] = NEWLINE |
| 179 | + end |
| 180 | + end |
| 181 | + |
| 182 | + local petition = {} |
| 183 | + |
| 184 | + if a.flags.petition_not_accepted then |
| 185 | + sb[#sb+1] = {text = "This petition wasn't accepted yet!", pen = COLOR_YELLOW} |
| 186 | + petition.status = 'PENDING' |
| 187 | + elseif a.flags.convicted_accepted then |
| 188 | + sb[#sb+1] = {text = "This petition was fulfilled!", pen = COLOR_GREEN} |
| 189 | + petition.status = 'FULFILLED' |
| 190 | + else |
| 191 | + petition.status = 'ACCEPTED' |
| 192 | + end |
| 193 | + |
| 194 | + petition.text = sb |
| 195 | + |
| 196 | + return petition |
| 197 | +end |
| 198 | + |
| 199 | +local getAgreements = function() |
| 200 | + local list = {} |
| 201 | + |
| 202 | + local ags = df.global.world.agreements.all |
| 203 | + for i, a in ipairs(ags) do |
| 204 | + for _, p in ipairs(a.parties) do |
| 205 | + for _, e in ipairs(p.entity_ids) do |
| 206 | + if e == we then |
| 207 | + list[#list+1] = getAgreementDetails(a) |
| 208 | + end |
| 209 | + end |
| 210 | + end |
| 211 | + end |
| 212 | + |
| 213 | + return list |
| 214 | +end |
| 215 | + |
| 216 | +local petitions = defclass(petitions, gui.FramedScreen) |
| 217 | +petitions.ATTRS = { |
| 218 | + frame_style = gui.GREY_LINE_FRAME, |
| 219 | + frame_title = 'Petitions', |
| 220 | + frame_width = 21, -- is calculated in :refresh |
| 221 | + min_frame_width = 21, |
| 222 | + frame_height = 16, |
| 223 | + frame_inset = 0, |
| 224 | + focus_path = 'petitions', |
| 225 | +} |
| 226 | + |
| 227 | +function petitions:init(args) |
| 228 | + self.list = args.list |
| 229 | + -- self.fulfilled = true |
| 230 | + self:addviews{ |
| 231 | + widgets.Label{ |
| 232 | + view_id = 'text', |
| 233 | + frame_inset = 0, |
| 234 | + show_scroll_icons = 'right', |
| 235 | + }, |
| 236 | + } |
| 237 | + |
| 238 | + self:refresh() |
| 239 | +end |
| 240 | + |
| 241 | +function petitions:refresh() |
| 242 | + local lines = {} |
| 243 | + -- list of petitions |
| 244 | + for _, p in ipairs(self.list) do |
| 245 | + if not self.fulfilled and p.status == 'FULFILLED' then goto continue end |
| 246 | + -- each petition is a status and a text |
| 247 | + for _, tok in ipairs(p.text) do |
| 248 | + -- where text is a list of tokens |
| 249 | + table.insert(lines, tok) |
| 250 | + end |
| 251 | + table.insert(lines, NEWLINE) |
| 252 | + ::continue:: |
| 253 | + end |
| 254 | + table.remove(lines, #lines) -- remove last NEWLINE |
| 255 | + |
| 256 | + local label = self.subviews.text |
| 257 | + label:setText(lines) |
| 258 | + |
| 259 | + -- changing text doesn't automatically change scroll position |
| 260 | + if label.frame_body then |
| 261 | + local last_visible_line = label.start_line_num + label.frame_body.height - 1 |
| 262 | + if last_visible_line > label:getTextHeight() then |
| 263 | + label.start_line_num = math.max(label:getTextHeight() - label.frame_body.height + 1, 1) |
| 264 | + end |
| 265 | + end |
| 266 | + |
| 267 | + self.frame_width = math.max(label:getTextWidth()+1, self.min_frame_width) |
| 268 | + self.frame_width = math.min(df.global.gps.dimx - 2, self.frame_width) |
| 269 | + self:onResize(dfhack.screen.getWindowSize()) -- applies new frame_width |
| 270 | +end |
| 271 | + |
| 272 | +function petitions:onRenderFrame(painter, frame) |
| 273 | + petitions.super.onRenderFrame(self, painter, frame) |
| 274 | + |
| 275 | + painter:seek(frame.x1+2, frame.y1 + frame.height-1):key_string('CUSTOM_F', "toggle fulfilled") |
| 276 | +end |
| 277 | + |
| 278 | +function petitions:onInput(keys) |
| 279 | + if petitions.super.onInput(self, keys) then return end |
| 280 | + |
| 281 | + if keys.LEAVESCREEN or keys.SELECT then |
| 282 | + self:dismiss() |
| 283 | + elseif keys.CUSTOM_F then |
| 284 | + self.fulfilled = not self.fulfilled |
| 285 | + self:refresh() |
| 286 | + end |
| 287 | +end |
| 288 | + |
| 289 | +df.global.pause_state = true |
| 290 | +petitions{list=getAgreements()}:show() |
0 commit comments