Skip to content

Commit 64626c8

Browse files
authored
Add gui/petitions.lua: shows list of fort's petitions (#321)
* Add gui/petitions.lua: shows list of fort's petitions * fix autodocs * use gui.widgets.Label to simplify text scrolling Also, minor visual changes * use `Label.show_scroll_icons` * remove trailing whitespace * Update changelog.txt
1 parent b808050 commit 64626c8

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed

changelog.txt

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ that repo.
1515

1616
## New Scripts
1717

18+
- `gui/petitions`: shows list of fort's petitions
19+
1820
## Fixes
1921

2022
## Misc Improvements

gui/petitions.lua

+290
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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

Comments
 (0)