Skip to content

Commit ee82d10

Browse files
authored
Merge pull request DFHack#57 from grubsteak/patch-1
stamper.lua - gui designation manipulator
2 parents 54a0e03 + 123be9a commit ee82d10

1 file changed

Lines changed: 364 additions & 0 deletions

File tree

gui/stamper.lua

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
--designating tool
2+
3+
--[====[
4+
5+
gui/stamper
6+
===========
7+
allows manipulation of designations by transforms such as translations, reflections, rotations, and inversion.
8+
designations can also be used as brushes to erase other designations and cancel constructions.
9+
10+
]====]
11+
12+
local utils = require "utils"
13+
local gui = require "gui"
14+
local guidm = require "gui.dwarfmode"
15+
local dlg = require "gui.dialogs"
16+
17+
StamperUI = defclass(StamperUI, guidm.MenuOverlay)
18+
19+
StamperUI.ATTRS {
20+
state="none",
21+
buffer=nil,
22+
offsetDirection=0,
23+
cull=true,
24+
blink=false,
25+
option="normal"
26+
}
27+
28+
local digSymbols={" ", "X", "_", 30, ">", "<"}
29+
30+
function StamperUI:init()
31+
self.saved_mode = df.global.ui.main.mode
32+
df.global.ui.main.mode=df.ui_sidebar_mode.LookAround
33+
end
34+
35+
function StamperUI:onDestroy()
36+
df.global.ui.main.mode = self.saved_mode
37+
end
38+
39+
local function paintMapTile(dc, vp, cursor, pos, ...)
40+
if not same_xyz(cursor, pos) then
41+
local stile = vp:tileToScreen(pos)
42+
if stile.z == 0 then
43+
dc:map(true):seek(stile.x,stile.y):char(...):map(false)
44+
end
45+
end
46+
end
47+
48+
local function minToMax(...)
49+
local args={...}
50+
table.sort(args,function(a,b) return a < b end)
51+
return table.unpack(args)
52+
end
53+
54+
local function cullBuffer(data) --there"s probably a memory saving way of doing this
55+
local lowerX=math.huge
56+
local lowerY=math.huge
57+
local upperX=-math.huge
58+
local upperY=-math.huge
59+
for x=0,data.xlen do
60+
for y=0,data.ylen do
61+
if data[x][y].dig>0 then
62+
lowerX=math.min(x,lowerX)
63+
lowerY=math.min(y,lowerY)
64+
upperX=math.max(x,upperX)
65+
upperY=math.max(y,upperY)
66+
end
67+
end
68+
end
69+
if lowerX==math.huge then lowerX=0 end
70+
if lowerY==math.huge then lowerY=0 end
71+
if upperX==-math.huge then upperX=data.xlen end
72+
if upperY==-math.huge then upperY=data.ylen end
73+
local buffer={}
74+
for x=lowerX,upperX do
75+
buffer[x-lowerX]={}
76+
for y=lowerY,upperY do
77+
buffer[x-lowerX][y-lowerY]=data[x][y]
78+
end
79+
end
80+
buffer.xlen=upperX-lowerX
81+
buffer.ylen=upperY-lowerY
82+
return buffer
83+
end
84+
local function getTiles(p1,p2,cull)
85+
if cull==nil then cull=true end
86+
local x1,x2=minToMax(p1.x,p2.x)
87+
local y1,y2=minToMax(p1.y,p2.y)
88+
local xlen=x2-x1
89+
local ylen=y2-y1
90+
assert(p1.z==p2.z, "only tiles from the same Z-level can be copied")
91+
local z=p1.z
92+
local data={}
93+
for k, block in ipairs(df.global.world.map.map_blocks) do
94+
if block.map_pos.z==z then
95+
for block_x, row in ipairs(block.designation) do
96+
local x=block_x+block.map_pos.x
97+
if x>=x1 and x<=x2 then
98+
if not data[x-x1] then
99+
data[x-x1]={}
100+
end
101+
for block_y, tile in ipairs(row) do
102+
local y=block_y+block.map_pos.y
103+
if y>=y1 and y<=y2 then
104+
data[x-x1][y-y1]=copyall(tile)
105+
end
106+
end
107+
end
108+
end
109+
end
110+
end
111+
data.xlen=xlen
112+
data.ylen=ylen
113+
if cull then
114+
return cullBuffer(data)
115+
end
116+
return data
117+
end
118+
119+
function StamperUI:getOffset()
120+
if self.offsetDirection==0 then --southeast
121+
return 0, 0
122+
elseif self.offsetDirection==1 then --northeast
123+
return 0, -self.buffer.ylen
124+
elseif self.offsetDirection==2 then --northwest
125+
return -self.buffer.xlen, -self.buffer.ylen
126+
elseif self.offsetDirection==3 then --southwest
127+
return -self.buffer.xlen, 0
128+
else
129+
error("out of range")
130+
end
131+
end
132+
133+
function StamperUI:setBuffer(tiles)
134+
self.buffer=tiles
135+
end
136+
137+
function StamperUI:transformBuffer(callback)
138+
local newBuffer={}
139+
local xlen=0
140+
local ylen=0
141+
for x=0, self.buffer.xlen do
142+
for y=0, self.buffer.ylen do
143+
local x2,y2=callback(x,y,self.buffer.xlen,self.buffer.ylen,self.buffer[x][y])
144+
xlen=math.max(x2,xlen)
145+
ylen=math.max(y2,ylen)
146+
if not newBuffer[x2] then
147+
newBuffer[x2]={}
148+
end
149+
if not newBuffer[x2][y2] then
150+
newBuffer[x2][y2]=self.buffer[x][y]
151+
end
152+
end
153+
end
154+
newBuffer.xlen=xlen
155+
newBuffer.ylen=ylen
156+
return newBuffer
157+
end
158+
159+
function StamperUI:pasteBuffer(position,option)
160+
local z=position.z
161+
local offsetX,offsetY=self:getOffset()
162+
local x1=position.x+offsetX
163+
local x2=position.x+self.buffer.xlen+offsetX
164+
local y1=position.y+offsetY
165+
local y2=position.y+self.buffer.ylen+offsetY
166+
for k, block in ipairs(df.global.world.map.map_blocks) do
167+
if block.map_pos.z==z then
168+
for block_x, row in ipairs(block.designation) do
169+
local x=block_x+block.map_pos.x
170+
if x>=x1 and x<=x2 then
171+
for block_y, tile in ipairs(row) do
172+
local y=block_y+block.map_pos.y
173+
if y>=y1 and y<=y2 and self.buffer[x-x1][y-y1].dig>0 then
174+
if self.option=="erase" then
175+
tile.dig=0
176+
elseif self.option=="construction" then
177+
dfhack.constructions.designateRemove(x,y,z)
178+
else
179+
tile.dig=self.buffer[x-x1][y-y1].dig
180+
end
181+
end
182+
end
183+
end
184+
end
185+
end
186+
end
187+
end
188+
189+
function StamperUI:invertBuffer() --this modifies the buffer instead of copying it
190+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=0 else tile.dig=1 end return x,y end)
191+
end
192+
193+
function StamperUI:renderOverlay()
194+
local vp=self:getViewport()
195+
local dc = gui.Painter.new(self.df_layout.map)
196+
local visible = gui.blink_visible(500)
197+
198+
if gui.blink_visible(120) and self.marking then
199+
paintMapTile(dc, vp, nil, self.mark, "+", COLOR_LIGHTGREEN)
200+
--perhaps draw a rectangle to the point
201+
elseif not marking and (gui.blink_visible(750) or not self.blink) and self.buffer~=nil and (self.state=="brush" or self.state=="convert") then
202+
--draw over cursor in these circumstances
203+
local offsetX,offsetY=self:getOffset()
204+
for x=0, self.buffer.xlen do
205+
for y=0, self.buffer.ylen do
206+
local tile=self.buffer[x][y]
207+
if tile.dig>0 then
208+
if not (gui.blink_visible(750) and x==-offsetX and y==-offsetY) then
209+
local fg=COLOR_BLACK
210+
local bg=COLOR_CYAN
211+
if self.option=="erase" then
212+
bg=COLOR_RED
213+
fg=COLOR_BLACK
214+
elseif self.option=="construction" then
215+
bg=COLOR_GREEN
216+
fg=COLOR_BLACK
217+
end
218+
local symbol=digSymbols[tile.dig]
219+
if self.option~="normal" then
220+
symbol=" "
221+
end
222+
dc:pen(fg,bg)
223+
paintMapTile(dc, vp, nil, xyz2pos(df.global.cursor.x+x+offsetX,df.global.cursor.y+y+offsetY,df.global.cursor.z), symbol, fg)
224+
end
225+
end
226+
end
227+
end
228+
end
229+
end
230+
231+
function StamperUI:onRenderBody(dc)
232+
self:renderOverlay()
233+
234+
dc:clear():seek(1,1):pen(COLOR_WHITE):string("Stamper - "..self.state:gsub("^%a",function(x)return x:upper()end))
235+
dc:seek(2,3)
236+
237+
if self.state=="brush" then
238+
dc:key_string("CUSTOM_S", "Set Brush",COLOR_GREY)
239+
dc:newline():newline(1)
240+
dc:key_string("CUSTOM_H", "Flip Horizontal",COLOR_GREY):newline(1)
241+
dc:key_string("CUSTOM_V", "Flip Vertical",COLOR_GREY):newline(1)
242+
dc:key_string("CUSTOM_R", "Rotate 90",COLOR_GREY):newline(1)
243+
dc:key_string("CUSTOM_T", "Rotate -90",COLOR_GREY):newline(1)
244+
dc:key_string("CUSTOM_G", "Cycle Corner",COLOR_GREY):newline(1)
245+
dc:key_string("CUSTOM_I", "Invert",COLOR_GREY):newline(1)
246+
dc:key_string("CUSTOM_C", "Convert to...",COLOR_GREY):newline(1)
247+
dc:newline(1)
248+
dc:key_string("CUSTOM_E", (self.option=="erase" and "Erasing" or "Erase"),self.option=="erase" and COLOR_RED or COLOR_GREY):newline(1) --make red
249+
dc:key_string("CUSTOM_X", (self.option=="construction" and "Removing" or "Remove").." Constructions",self.option=="construction" and COLOR_GREEN or COLOR_GREY):newline(1) --make red
250+
dc:newline():newline(1)
251+
dc:key_string("CUSTOM_B", "Blink Brush",self.blink and COLOR_WHITE or COLOR_GREY):newline(1)
252+
dc:newline()
253+
elseif self.state=="mark" then
254+
if self.buffer==nil then
255+
dc:string("Select two corners.")
256+
end
257+
dc:newline():newline(1)
258+
dc:key_string("CUSTOM_P", "Cull Selections",self.cull and COLOR_WHITE or COLOR_GREY)
259+
elseif self.state=="convert" then
260+
dc:key_string("CUSTOM_D","Mine",COLOR_GREY):newline(2)
261+
dc:key_string("CUSTOM_H", "Channel",COLOR_GREY):newline(2)
262+
dc:key_string("CUSTOM_U", "Up Stair",COLOR_GREY):newline(2)
263+
dc:key_string("CUSTOM_J", "Up Stair",COLOR_GREY):newline(2)
264+
dc:key_string("CUSTOM_I", "U/D Stair",COLOR_GREY):newline(2)
265+
dc:key_string("CUSTOM_R", "Up Ramp",COLOR_GREY):newline(2)
266+
dc:newline(1)
267+
dc:string("To undesignate use the erase command",COLOR_WHITE)
268+
end
269+
270+
dc:newline():newline():key_string("LEAVESCREEN", "Back")
271+
end
272+
273+
function StamperUI:onInput(keys)
274+
if df.global.cursor.x==-30000 then
275+
local vp=self:getViewport()
276+
df.global.cursor=xyz2pos(math.floor((vp.x1+math.abs((vp.x2-vp.x1))/2)+.5),math.floor((vp.y1+math.abs((vp.y2-vp.y1)/2))+.5), vp.z)
277+
return
278+
end
279+
280+
if self.state=="brush" then
281+
if keys.CUSTOM_S then
282+
self.state="mark"
283+
elseif keys.CUSTOM_D then
284+
self.state="brush"
285+
elseif keys.CUSTOM_H then
286+
self.buffer=self:transformBuffer(function(x,y,xlen,ylen,tile) return xlen-x, y end)
287+
elseif keys.CUSTOM_V then
288+
self.buffer=self:transformBuffer(function(x,y,xlen,ylen,tile) return x, ylen-y end)
289+
elseif keys.CUSTOM_R then
290+
self.buffer=self:transformBuffer(function(x,y,xlen,ylen,tile) return y, xlen-x end)
291+
self.offsetDirection=(self.offsetDirection+1)%4
292+
elseif keys.CUSTOM_T then
293+
self.buffer=self:transformBuffer(function(x,y,xlen,ylen,tile) return ylen-y,x end)
294+
self.offsetDirection=(self.offsetDirection-1)%4
295+
elseif keys.CUSTOM_G then
296+
self.offsetDirection=(self.offsetDirection+1)%4
297+
elseif keys.CUSTOM_E then
298+
self.option=self.option=="erase" and "normal" or "erase"
299+
elseif keys.CUSTOM_X then
300+
self.option=self.option=="construction" and "normal" or "construction"
301+
elseif keys.CUSTOM_I then
302+
self:invertBuffer()
303+
elseif keys.CUSTOM_C then
304+
self.state="convert"
305+
elseif keys.CUSTOM_B then
306+
self.blink = not self.blink
307+
elseif keys.SELECT then
308+
self:pasteBuffer(copyall(df.global.cursor))
309+
end
310+
elseif self.state=="mark" then
311+
if keys.SELECT then
312+
if self.marking then
313+
--set the table
314+
self.state="brush"
315+
self.marking = false
316+
self:setBuffer(getTiles(self.mark,copyall(df.global.cursor),self.cull))
317+
else
318+
self.marking = true
319+
self.mark = copyall(df.global.cursor)
320+
end
321+
elseif keys.LEAVESCREEN and self.buffer~=nil then
322+
self.state="brush"
323+
return
324+
elseif keys.CUSTOM_P then
325+
self.cull = not self.cull
326+
end
327+
elseif self.state=="convert" then
328+
if keys.LEAVESCREEN then
329+
self.state="brush"
330+
return
331+
elseif keys.CUSTOM_D then
332+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=1 end return x,y end)
333+
self.state="brush"
334+
elseif keys.CUSTOM_H then
335+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=3 end return x,y end)
336+
self.state="brush"
337+
elseif keys.CUSTOM_U then
338+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=6 end return x,y end)
339+
self.state="brush"
340+
elseif keys.CUSTOM_J then
341+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=5 end return x,y end)
342+
self.state="brush"
343+
elseif keys.CUSTOM_I then
344+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=2 end return x,y end)
345+
self.state="brush"
346+
elseif keys.CUSTOM_R then
347+
self:transformBuffer(function(x,y,xlen,ylen,tile) if tile.dig>0 then tile.dig=4 end return x,y end)
348+
self.state="brush"
349+
end
350+
end
351+
352+
if keys.LEAVESCREEN then
353+
self:dismiss()
354+
elseif self:propagateMoveKeys(keys) then
355+
return
356+
end
357+
end
358+
359+
if not (dfhack.gui.getCurFocus():match("^dwarfmode/Default") or dfhack.gui.getCurFocus():match("^dwarfmode/Designate") or dfhack.gui.getCurFocus():match("^dwarfmode/LookAround"))then
360+
qerror("This screen requires the main dwarfmode view or the designation screen")
361+
end
362+
363+
local list = StamperUI{state="mark", blink=false,cull=true}
364+
list:show()

0 commit comments

Comments
 (0)