From 5ea26d9cae7ae43da8afdac905c7b3ab96f9991d Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 29 Nov 2012 16:27:51 +0400
Subject: [PATCH 01/32] Only show the advanced new constraint dialog on
Shift-Enter.
Upon reflection it is a bit too scary to be always shown.
---
Lua API.html | 6 ++++++
Lua API.rst | 6 ++++++
Readme.html | 4 ++--
Readme.rst | 6 ++----
images/workflow-new1.png | Bin 5662 -> 6674 bytes
library/lua/gui/dialogs.lua | 22 +++++++++++++++++++++-
library/lua/gui/widgets.lua | 19 +++++++++++++++++++
scripts/gui/workflow.lua | 20 ++++++++++++--------
8 files changed, 68 insertions(+), 15 deletions(-)
diff --git a/Lua API.html b/Lua API.html
index 04af5d672..f42905d01 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -2853,6 +2853,9 @@ this may be extended with mouse click support.
on_submit: | Enter key callback; if specified, the list reacts to the key
and calls it as on_submit(index,choice). |
+on_submit2: | Shift-Enter key callback; if specified, the list reacts to the key
+and calls it as on_submit2(index,choice). |
+
row_height: | Height of every row in text lines. |
icon_width: | If not nil, the specified number of character columns
@@ -2908,6 +2911,9 @@ with the following fields:
list:submit()
Call the on_submit callback, as if the Enter key was handled.
+list:submit2()
+Call the on_submit2 callback, as if the Shift-Enter key was handled.
+
diff --git a/Lua API.rst b/Lua API.rst
index 4087ff0aa..d42a348e4 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -2777,6 +2777,8 @@ It has the following attributes:
:on_select: Selection change callback; called as ``on_select(index,choice)``.
:on_submit: Enter key callback; if specified, the list reacts to the key
and calls it as ``on_submit(index,choice)``.
+:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key
+ and calls it as ``on_submit2(index,choice)``.
:row_height: Height of every row in text lines.
:icon_width: If not *nil*, the specified number of character columns
are reserved to the left of the list item for the icons.
@@ -2826,6 +2828,10 @@ The list supports the following methods:
Call the ``on_submit`` callback, as if the Enter key was handled.
+* ``list:submit2()``
+
+ Call the ``on_submit2`` callback, as if the Shift-Enter key was handled.
+
FilteredList class
------------------
diff --git a/Readme.html b/Readme.html
index cdc4dd631..deea72bef 100644
--- a/Readme.html
+++ b/Readme.html
@@ -3045,11 +3045,11 @@ the job material first using job
as described in workflow documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints.
-After selecting one of the presented outputs, the interface proceeds to the
+ If you select one of the outputs with Enter, the matching constraint is simply
+added to the list. If you use Shift-Enter, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
-If you don't need advanced settings, you can just press 'y' to confirm creation.
diff --git a/Readme.rst b/Readme.rst
index b9844debd..a214a6ecb 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -2309,15 +2309,13 @@ can be used for troubleshooting jobs that don't match the right constraints.
.. image:: images/workflow-new1.png
-After selecting one of the presented outputs, the interface proceeds to the
+If you select one of the outputs with Enter, the matching constraint is simply
+added to the list. If you use Shift-Enter, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
.. image:: images/workflow-new2.png
-If you don't need advanced settings, you can just press 'y' to confirm creation.
-
-
gui/assign-rack
===============
diff --git a/images/workflow-new1.png b/images/workflow-new1.png
index 25b498bca23326f9e5ea05471dfcdb275f4e4734..50d0e1f421ac9de1abf87545c8d7ade31bd4cd4a 100644
GIT binary patch
literal 6674
zcmZ{Jc{tQx^#5%3ZBl%) jwhT{eX7YqXNK4R8xaL85+2?P=nH!(J_yFI>~-}>N-#^^g1of~Tzep?V2
z_cNX+eNg{#4YaAJ=!08nCNCZCqufCE)X6MPuiHJ6hZdeqUR_*x_BCi_Rv-*>iXti7
zyH{)cn^$5<{@fkCqpo1{G~CH0V(Xh8rN=_
zT+5f^#h~}daJZjDXAKm7sO2WHxPKi|$jz=sx-|Py8$5R0_u%fY!~h9Mf>!&hYu{&@
z?x(QD)bB5RDMPvOmR5m}xlCJ>@F~CE&B6yN6gN&81uf_`?ZA1BYnzvilE%S)=CR2=
z34(V6u4A%9Fz(wlC$v*56%O}NGz+cP+N3266tDt8gmaXqk60nrkh$Mp2BID@07+xU
zdgD1&W5zKUg%$0zEJ+pn0hc_#5GQ-=MfayDiTphm*qMgw_|pC;b>E={ME+iVfC5r8
zitxb5rbO=rMi-pJ2d5RN{P=L?#N_NPDX)O1re&PAbnG*b89`_is^O
zW}k^Di9PWw5qW~WYcum)2c>x0u3&<<=Yv8xE;$1U3o2Q^VrdRsREJwkcGsx^%OQki3X62%o#9ji
zz>dxE`R|Y$k3;n-h>Fwju6T@s_SIHuE*`gzcN%e_jeKKB_KN*E`J+8gv?N}R#Wp3J
z9PM~kQ@|j(0dmvK^oe@zf($RN9PjL3^4gA`D+71AGA4I9Hao^Dapw~X%mE2I+9<9z
zrrRG|-#zWbuOS6qC}7AHlpjf&$B-2~8+aZj9Q1JXgt3IIs{F7K{BN|%((Z?Z$f0*1#PzR-y|DrV&HksW
zTF(M}F%y}y_io!~wNMmIgj)C_dM0t#cGNRh{e+spQ?#03(C+Nbz0;tW08V-@inHe{
z^niYOICS$)8MN`wk{M{|kah6^`nDCvrCJ*NRahcv%6S12fu`8u*szK%F!X3>HE*r#xotb4|`W9@_*mtTOSB|S)p
z%@pe;CIH8F2v}KJ#o?U+_{du?j>CJ);Yx@K@rMH$*0XV
zL(xnaXJwLU>0>#ZOtdB&p&V%$oSG3hDUH1*e3~JfA*UD5ye<>N4nNGs#&&HlS^`Mk
z#y*#_J`-nlVoeWI
zhnwTR3dvTOME0zNTgmPp{%xKQf2m+bqdJWcDQZ==iuM?nZ#ip|Fm#g5l2_~O$?y3U
zmF!%NtARKv<0$Tg99nK)JlUHrT%WC`2d_>JGJNa-)hy0_QzIXK%KsQzI*U4xLB+Nr
zS!QTbOTP`u1y)JxZ_56_1}^95X}^uxU*>samQblptIy_}Id>#X$qo_+b$49wd=0
z_FHdf`V8E(;`ZJ^1TJgvjQBHF!|8BS7a$vjd%8R;!^Z#LL>S3w52{@QE22Np9D&K7
zD)}>euXgRqukTN{X)}s-b!#QI5QDL?fY1Un2VpKOzg?`AOACn3KlMa(SV^8fAf2$~
zn81MtQ)2Il&Ou;a@3cMZ=Zwq&E;76PDlog#2pELTMwA^DU-jBXT|
zFp`JnDGw6*`ZOYE)6oE0nPEowg-ndy9;;Kf|*
z$hDzSV-khui|v;qaL)RaVa7DYo}3rO-t+#oBA2XmCiM^TtSR;d8dzw8b<(6b3&B0x{EdXVEWI52WPE(Q#R9BIn7ET
z9@c9R<)SP<%=LZHy@IJpq*^k?2#CuUu(8;DN4#5Ax0j^1<(r_zigctB
zj3Y)zo`43vM&QDmSBi*CkYfaEcyBJka5p|&11+59)s)X6DUSYk@&TE#2*F2@rhL&4
zFSG0K`ZY)WD9gu7=+4n@FGHqte~+{Bi$YSxIsQusXthI9m6pP0W$g+^4_))1@SOS}
zC_S<;sRe0B+mOn5FRf)f-|)b@?5#9d(->K7?8=o68T?kr4HtFF+63y(I9V}IS!
z ndSutI+wq?L>;|4SHYBN4v*FpKcpqle>iz}@3X)#T^x@JfSg92J2eYpkd8prf@a
zve;U!&=8pW+k*BN%Hw9=gO8Tr-b?dK;n++-uWQbDqPUc|G)ba$QFirQ)U#Y=MlrB)=ump6z&JR20UOhsLSr~DX!Eo
zwzInv_FUWP3&&vbH>lQX+=x}6W=MI9Zsjfh2P)T)#-h?*3O+zx)Fj&;rAo6!cv4R-{ZDgy>nds0B`x1I6($+(D`NFw>An-TtN$qzbFD~T)ANIH%m@KdRbgV-T}EV2RJK6;GD6ZNk5Pstv+Zlhi8{Tt
zls6Wz^wTbY<6?IPG6c;?sq6Q`QCNO=dB5VPd)RvUp(6v?A@1HqM;^ZShiSyx9xr!8
zGwJ9`+Yw1zVMeB4na}GM`>Y_GuGxrYS$YOT7(+{1{ZpW^AKmw}U9+E`985`e^5{h0
z=zt%tXX{7r-g788%{_x^>G|cnq~^;AYD715JD}ZE`0aN|cxwH@-xsb&10???LH=uP
zDnU7O+RKz#$W&{QszOO;TAJ(8UKKQK+kOAHks0tZ^d%<%;HtuWR{86YBCv(h_-+MHsB^qYfnO_L*V5z+O%SL(Lvr66>pn;D){a9w{5yUEhj6^-FtJfiZnke~K
zx5X`5BI&f!i4U}tcV(j&+m@thvx$41YsaruyPuBFnAjtdBi$*R=f(~DJl;>XN3`@`
zTM_y}bIi|Hw4~iAN2(s{lvl8M*J&7fh2~&B&&~biKBGxsKd8*-cI6LL*z2>v4wv}9
zW6a8Dc+4K2=M8F4^09bSmOk&p9SCGWAY4y#pa{2zSX7lm5uO9c^W~aC`_nn0i9*?#
z5ybX7U!|*R+P5#=3S(X`|AvGprUm~dg)tB>Cyd^ZFuomijaSss24x1NG48e^bEE@J
zvF&B=QW(d=_bIDq@v+}p%)*B?x32x5-4MHozc4cxSKk=Oa(}Xk
za<3sv2B*yQ0pBL^aBH8Bs_65ogHH2tM?izLXG?_B!HF18oQctq&Wdex$s3U0CHYn9
ztDR=pgTY-XK=&W7H=p#K&&ulBaT9cSCi197Rq3ultn|tqoz|nN$E3611q+%AGn6U6
zXs@HID@7MJqS45)M3Nn{;G4dHl|BWZ0^kgQ8yjVNsZbM&S{y!&j1N5S&a}lTGB}*A
zV)7OC;6)SKg)}NvmUl|9v#FJ7nbWAx2p|k6w@en2jf`MAtH-K}gfItMoE$w~Ab0(O
ziMCE@>%5|gE<3RfNpF=&*yODAUjDMmju5Kmj`VOekE06w@Q9TNzxVrty;
zk-%c!hcAcc+Z!(1eLtod2TsBG3q{ANi(n^&sZqxmjA3L9IBrdJku@hYTUMc)t0XV>
zq~k{UMwv8@6|R(?eR+^WWSJQmK6sUh{1ZJ-eFnL^5^$0(mw`7RM3CTJhC|l2va2frbn*H2+-r8b=m{rfx{
zZ2VT@#rLz__iw%k7loYM(bL$C{j68Qc^1Ey6EHlnCVgm;q=EOG5ZbDQV&ph_B#9fM
zWgnhz)R5;yiL0+KKvYD4{lyh#S_6d1BKCn2vi_qxMRoCvt(Z!fM
z{NzNW@yzj!gOSlYM}O79Ilb`?=!f{Izok35MDt+=jaaSX!!_n54>Obl5a66+a2uf>
zb5d=X6&GZE^YMwYR4&9~^F!i&HiThPHunrVwUBE;^Jsi3VRY?_?IGVAQ|uqb05euY
zv4ChLlBgrJ@bexUN6e`Q*68kELnAMMImOcqXpyk_k;;DG-3!5&M22AeP03OY4oj{d#f7WRlTr{Q5oJ;veEvWP7t~bx}K!Gsa20Acc
zIC2^X>fVsB7L;t;MM;9XZw-c`M@ASBdZ!CK1Z-eU`CELFo&=hn$G_0yeR=AF=95wT
z%E~E-Yt?~X;kD93!>g27_1Rm;&hWlRGc!%zg|ZAOQ+y!5*Q9ySHup@?-=^d2dVTXfhGKmOPmZ(eEX{)MM&U!^yV5Q|{4kyh
z3fWKZv@Y?fdLXOb+=1sd6s1P2)m~j@-CVU1o&RK*#n!#1bXqHfD}iz0S840!!;kEW
zBwxsYuY^+4~bkfUP^fgA-y+5tw8+s>4US?%M=zh!$=8
z$f9oj7Fn%*hybpC6pimb7i^l}r)*<|j$<1TUFtY-5Cs*|fBQ%o?pYNF>wX+Du%*tc
z{i*1LsW!;(nvVk~^%lK%jweQg7{lg+Yg6v5`~c!lGSa()5RZqbH1d725pw}jnyKP<
zmyi3cpyd!AQQ+(AGp^$Dh#)4`VLe{1K+p>ub@^uS-hL)195V5
z)g)hzGw-Yr-UDLsroIMGh5LuoW0l@J7R7#s&hCIjJA13^tn&x{aiVyPcN_H?uYU?Y^!c
z>FZz##?8gLm#tUX1$gaBlD0g46ViK@5*{2U*a2D=CLn8$(Z@-ntNNw8p?>PvFJ
zX+4ZmL30XG&enTdB&&(-E*HvhT)%UAf8(ClVJ$3
zpY_gw@)}z2x;`%qKSa$^?wPXJp0n
zKlFAT`auqF6A!|j1}!{#ewE8uZ+}L(SM@(2bi{a;76+wl3gT;7)*&Aj9R@%8dR9cL
z%BDcO+dRI7UZO85^Im6zox)dG3?WW^zwsWeTFaMM19wwqB_C@^^pdE4{glU8Pb{IP
zFJ+KH%WrxYk46!=fzuloJ);styD%4O;XIhtv{!{u>^Xh$CtHz|eYoOXa#4^(AX?-8
z)HL~!-#+TCgvM6*SpY^V@!?ifWInq8x7(Z(m}Vy|L?wEWX{?0E)J6mVg-9rmedD;h
zFk{3|&cO)!DdKPtl_b6~FSDI38Gxf_Lk|HK`uA+-Ac#!6XNn@;C=u_od0)MKL@_C<
zCKB9G1}hFtgW)9s>a4sdM^6j!7YrUyZDFV)0e47wfa|$XsLUgsS%!=W@ES0;$>GGX
zqK%)nM9JB1(#!$c&&*!Cz#dfk$xztP)pTMBrPqmZ1a%z7&DI-KF~3hhQa8^#+{C-z
z^wRPS@?sK@ijuO5g0ixLil&|NS*^3`T1uMoN@ul{l=h15j{dI!0oU<<-eLdufaD`B
Q!oMj@&RZH+8M@v5A9!#IKL7v#
literal 5662
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
X3+v6wDh=8C=tWR0Y|S5=xySw=1!(tQ
diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua
index 0a79b4c3e..fb9b8fd63 100644
--- a/library/lua/gui/dialogs.lua
+++ b/library/lua/gui/dialogs.lua
@@ -152,7 +152,9 @@ ListBox.ATTRS{
with_filter = false,
cursor_pen = DEFAULT_NIL,
select_pen = DEFAULT_NIL,
- on_select = DEFAULT_NIL
+ on_select = DEFAULT_NIL,
+ on_select2 = DEFAULT_NIL,
+ select2_hint = DEFAULT_NIL,
}
function ListBox:preinit(info)
@@ -168,6 +170,16 @@ function ListBox:init(info)
list_widget = widgets.FilteredList
end
+ local on_submit2
+ if self.select2_hint or self.on_select2 then
+ on_submit2 = function(sel, obj)
+ self:dismiss()
+ if self.on_select2 then self.on_select2(sel, obj) end
+ local cb = obj.on_select2
+ if cb then cb(obj, sel) end
+ end
+ end
+
self:addviews{
list_widget{
view_id = 'list',
@@ -182,11 +194,19 @@ function ListBox:init(info)
local cb = obj.on_select or obj[2]
if cb then cb(obj, sel) end
end,
+ on_submit2 = on_submit2,
frame = { l = 0, r = 0 },
}
}
end
+function ListBox:onRenderFrame(dc,rect)
+ ListBox.super.onRenderFrame(self,dc,rect)
+ if self.select2_hint then
+ dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY)
+ end
+end
+
function ListBox:getWantedFrameSize()
local mw, mh = InputBox.super.getWantedFrameSize(self)
local list = self.subviews.list
diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua
index 67090e114..145300c59 100644
--- a/library/lua/gui/widgets.lua
+++ b/library/lua/gui/widgets.lua
@@ -384,6 +384,7 @@ List.ATTRS{
inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
+ on_submit2 = DEFAULT_NIL,
row_height = 1,
scroll_keys = STANDARDSCROLL,
icon_width = DEFAULT_NIL,
@@ -542,10 +543,19 @@ function List:submit()
end
end
+function List:submit2()
+ if self.on_submit2 and #self.choices > 0 then
+ self.on_submit2(self:getSelected())
+ end
+end
+
function List:onInput(keys)
if self.on_submit and keys.SELECT then
self:submit()
return true
+ elseif self.on_submit2 and keys.SEC_SELECT then
+ self:submit2()
+ return true
else
for k,v in pairs(self.scroll_keys) do
if keys[k] then
@@ -608,6 +618,11 @@ function FilteredList:init(info)
return info.on_submit(self:getSelected())
end
end
+ if info.on_submit2 then
+ self.list.on_submit2 = function()
+ return info.on_submit2(self:getSelected())
+ end
+ end
self.not_found = Label{
visible = false,
text = info.not_found_label or 'No matches',
@@ -634,6 +649,10 @@ function FilteredList:submit()
return self.list:submit()
end
+function FilteredList:submit2()
+ return self.list:submit2()
+end
+
function FilteredList:canSubmit()
return not self.not_found.visible
end
diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua
index 80c05d296..a387e64b9 100644
--- a/scripts/gui/workflow.lua
+++ b/scripts/gui/workflow.lua
@@ -552,18 +552,22 @@ function JobConstraints:onNewConstraint()
table.insert(choices, { text = itemstr..' of '..matstr, obj = cons })
end
- dlg.showListPrompt(
- 'New limit',
- 'Select one of the possible outputs:',
- COLOR_WHITE,
- choices,
- function(idx,item)
+ dlg.ListBox{
+ frame_title = 'New limit',
+ text = 'Select one of the possible outputs:',
+ text_pen = COLOR_WHITE,
+ choices = choices,
+ on_select = function(idx,item)
+ self:saveConstraint(item.obj)
+ end,
+ select2_hint = 'Advanced',
+ on_select2 = function(idx,item)
NewConstraint{
constraint = item.obj,
on_submit = self:callback('saveConstraint')
}:show()
- end
- )
+ end,
+ }:show()
end
function JobConstraints:onDeleteConstraint()
From d7f7538d0148e8a731089034f431dfcd80c3a268 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 29 Nov 2012 10:33:18 +0100
Subject: [PATCH 02/32] ruby: fix Pointer assignment
---
plugins/ruby/ruby-autogen-defs.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb
index 4148659a6..a3e810178 100644
--- a/plugins/ruby/ruby-autogen-defs.rb
+++ b/plugins/ruby/ruby-autogen-defs.rb
@@ -308,7 +308,7 @@ module DFHack
DFHack.memory_write_int32(@_memaddr, v)
end
when nil; DFHack.memory_write_int32(@_memaddr, 0)
- else _get._set(v)
+ else @_tg._at(_getp)._set(v)
end
end
From 184082b379c67989c55d5454cc3a204b40d71360 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 29 Nov 2012 17:11:16 +0100
Subject: [PATCH 03/32] scripts/lever: fix for links to cage/support
---
scripts/lever.rb | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/scripts/lever.rb b/scripts/lever.rb
index 59196f7d2..43aa29b04 100644
--- a/scripts/lever.rb
+++ b/scripts/lever.rb
@@ -47,9 +47,12 @@ def lever_descr(bld, idx=nil)
}.flatten.each { |r|
# linked building description
tg = r.building_tg
- state = tg.gate_flags.closed ? 'closed' : 'opened'
- state << ', closing' if tg.gate_flags.closing
- state << ', opening' if tg.gate_flags.opening
+ state = ''
+ if tg.respond_to?(:gate_flags)
+ state << (tg.gate_flags.closed ? 'closed' : 'opened')
+ state << ", closing (#{tg.timer})" if tg.gate_flags.closing
+ state << ", opening (#{tg.timer})" if tg.gate_flags.opening
+ end
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
From f8dea0e9f95c9d763a03b0b672495b3f9a9a6196 Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Thu, 29 Nov 2012 18:19:53 -0600
Subject: [PATCH 04/32] Autofarm: only try to plant things that have seeds.
---
scripts/autofarm.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb
index 098466745..18f5a9aeb 100644
--- a/scripts/autofarm.rb
+++ b/scripts/autofarm.rb
@@ -18,10 +18,11 @@ class AutoFarm
end
def is_plantable (plant)
+ has_seed = plant.flags[:SEED]
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
- can_plant = plant.flags[season]
+ can_plant = has_seed && plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
From abe027c940ec3efcea7955d45708d2c7e8916295 Mon Sep 17 00:00:00 2001
From: Anuradha Dissanayake
Date: Fri, 30 Nov 2012 22:44:05 +1300
Subject: [PATCH 05/32] Copy changes from ag fork
---
plugins/search.cpp | 154 +++++++++++++++++++++++++++++++++++----------
1 file changed, 120 insertions(+), 34 deletions(-)
diff --git a/plugins/search.cpp b/plugins/search.cpp
index fdc788955..a14397fba 100644
--- a/plugins/search.cpp
+++ b/plugins/search.cpp
@@ -10,6 +10,7 @@
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_unitlistst.h"
#include "df/interface_key.h"
+#include "df/interfacest.h"
using std::set;
using std::vector;
@@ -19,6 +20,7 @@ using namespace DFHack;
using namespace df::enums;
using df::global::gps;
+using df::global::gview;
/*
Search Plugin
@@ -39,6 +41,14 @@ void OutputString(int8_t color, int &x, int y, const std::string &text)
x += text.length();
}
+static bool is_live_screen(const df::viewscreen *screen)
+{
+ for (df::viewscreen *cur = &gview->view; cur; cur = cur->child)
+ if (cur == screen)
+ return true;
+ return false;
+}
+
//
// START: Base Search functionality
//
@@ -60,6 +70,15 @@ public:
track_secondary_values = false;
}
+ bool reset_on_change()
+ {
+ if (valid && is_live_screen(viewscreen))
+ return false;
+
+ reset_all();
+ return true;
+ }
+
// A new keystroke is received in a searchable screen
virtual bool process_input(set *input)
{
@@ -206,12 +225,12 @@ protected:
bool list_has_been_sorted = (sort_list1->size() == reference_list.size()
&& *sort_list1 != reference_list);
- for (int i = 0; i < saved_indexes.size(); i++)
+ for (size_t i = 0; i < saved_indexes.size(); i++)
{
int adjusted_item_index = i;
if (list_has_been_sorted)
{
- for (int j = 0; j < sort_list1->size(); j++)
+ for (size_t j = 0; j < sort_list1->size(); j++)
{
if ((*sort_list1)[j] == reference_list[i])
{
@@ -245,6 +264,9 @@ protected:
update_secondary_values();
*sort_list2 = saved_list2;
}
+
+ saved_list1.clear();
+ saved_list2.clear();
}
store_reference_values();
search_string = "";
@@ -278,7 +300,7 @@ protected:
}
string search_string_l = toLower(search_string);
- for (int i = 0; i < saved_list1.size(); i++ )
+ for (size_t i = 0; i < saved_list1.size(); i++ )
{
T element = saved_list1[i];
string desc = toLower(get_element_description(element));
@@ -307,8 +329,9 @@ protected:
// Display hotkey message
void print_search_option(int x, int y = -1) const
{
+ auto dim = Screen::getWindowSize();
if (y == -1)
- y = gps->dimy - 2;
+ y = dim.y - 2;
OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key));
OutputString((entry_mode) ? 10 : 15, x, y, ": Search");
@@ -346,7 +369,12 @@ struct search_hook : T
DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input))
{
- module.init(this);
+ if (!module.init(this))
+ {
+ INTERPOSE_NEXT(feed)(input);
+ return;
+ }
+
if (!module.process_input(input))
{
INTERPOSE_NEXT(feed)(input);
@@ -357,9 +385,10 @@ struct search_hook : T
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
- module.init(this);
+ bool ok = module.init(this);
INTERPOSE_NEXT(render)();
- module.render();
+ if (ok)
+ module.render();
}
};
@@ -382,11 +411,12 @@ public:
virtual void render() const
{
if (!viewscreen->in_group_mode)
- print_search_option(1);
+ print_search_option(2);
else
{
- int x = 1;
- OutputString(15, x, gps->dimy - 2, "Tab to enable Search");
+ auto dim = Screen::getWindowSize();
+ int x = 2, y = dim.y - 2;
+ OutputString(15, x, y, "Tab to enable Search");
}
}
@@ -402,13 +432,18 @@ public:
search_parent::do_post_update_check();
}
- virtual void init(df::viewscreen_storesst *screen)
+ bool init(df::viewscreen_storesst *screen)
{
+ if (screen != viewscreen && !reset_on_change())
+ return false;
+
if (!valid)
{
viewscreen = screen;
search_parent::init(&screen->item_cursor, &screen->items);
}
+
+ return true;
}
@@ -440,8 +475,8 @@ private:
typedef search_hook stocks_search_hook;
-IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed);
-IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render);
//
// END: Stocks screen search
@@ -461,28 +496,33 @@ public:
print_search_option(28);
}
- virtual void init(df::viewscreen_unitlistst *screen)
+ bool init(df::viewscreen_unitlistst *screen)
{
+ if (screen != viewscreen && !reset_on_change())
+ return false;
+
if (!valid)
{
viewscreen = screen;
search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]);
}
+
+ return true;
}
private:
virtual string get_element_description(df::unit *element) const
{
string desc = Translation::TranslateName(Units::getVisibleName(element), false);
- if (viewscreen->page == 1)
- desc += Units::getProfessionName(element); // Check animal type too
+ desc += ", " + Units::getProfessionName(element); // Check animal type too
return desc;
}
virtual bool should_check_input(set *input)
{
- if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L))
+ if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) ||
+ (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF)))
{
if (!is_entry_mode())
{
@@ -502,8 +542,8 @@ private:
};
typedef search_hook unitlist_search_hook;
-IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100);
-IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100);
+template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100);
+template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100);
//
// END: Unit screen search
@@ -529,6 +569,29 @@ private:
{
return Items::getDescription(element, 0, true);
}
+
+ virtual bool should_check_input(set *input)
+ {
+ if (is_entry_mode())
+ return true;
+
+ if (input->count(interface_key::TRADE_TRADE) ||
+ input->count(interface_key::TRADE_OFFER) ||
+ input->count(interface_key::TRADE_SEIZE))
+ {
+ // Block the keys if were searching
+ if (!search_string.empty())
+ input->clear();
+
+ // Trying to trade, reset search
+ clear_search();
+ reset_all();
+
+ return false;
+ }
+
+ return true;
+ }
};
@@ -540,20 +603,25 @@ public:
print_search_option(2, 26);
}
- virtual void init(df::viewscreen_tradegoodsst *screen)
+ bool init(df::viewscreen_tradegoodsst *screen)
{
+ if (screen != viewscreen && !reset_on_change())
+ return false;
+
if (!valid)
{
viewscreen = screen;
search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q');
track_secondary_values = true;
}
+
+ return true;
}
};
typedef search_hook trade_search_merc_hook;
-IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed);
-IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render);
class trade_search_fort : public trade_search_base
@@ -564,20 +632,25 @@ public:
print_search_option(42, 26);
}
- virtual void init(df::viewscreen_tradegoodsst *screen)
+ bool init(df::viewscreen_tradegoodsst *screen)
{
+ if (screen != viewscreen && !reset_on_change())
+ return false;
+
if (!valid)
{
viewscreen = screen;
search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w');
track_secondary_values = true;
}
+
+ return true;
}
};
typedef search_hook trade_search_fort_hook;
-IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed);
-IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render);
//
// END: Trade screen search
@@ -589,10 +662,15 @@ DFHACK_PLUGIN("search");
DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands)
{
- if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply()
- || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, render).apply()
- || !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_fort_hook, render).apply()
- || !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || !INTERPOSE_HOOK(stocks_search_hook, render).apply())
+ if (!gps || !gview ||
+ !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() ||
+ !INTERPOSE_HOOK(unitlist_search_hook, render).apply() ||
+ !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() ||
+ !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() ||
+ !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() ||
+ !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() ||
+ !INTERPOSE_HOOK(stocks_search_hook, feed).apply() ||
+ !INTERPOSE_HOOK(stocks_search_hook, render).apply())
out.printerr("Could not insert Search hooks!\n");
return CR_OK;
@@ -613,9 +691,17 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event )
{
- unitlist_search_hook::module.reset_all();
- trade_search_merc_hook::module.reset_all();
- trade_search_fort_hook::module.reset_all();
- stocks_search_hook::module.reset_all();
+ switch (event) {
+ case SC_VIEWSCREEN_CHANGED:
+ unitlist_search_hook::module.reset_on_change();
+ trade_search_merc_hook::module.reset_on_change();
+ trade_search_fort_hook::module.reset_on_change();
+ stocks_search_hook::module.reset_on_change();
+ break;
+
+ default:
+ break;
+ }
+
return CR_OK;
-}
\ No newline at end of file
+}
From 2cb594ba8990e062e7f353d38811b218f631866b Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 30 Nov 2012 14:48:05 +0400
Subject: [PATCH 06/32] Tweak the workflow lua api: include history in output
of listConstraints.
---
plugins/lua/workflow.lua | 3 +-
plugins/workflow.cpp | 98 +++++++++++++++++++++++++---------------
2 files changed, 63 insertions(+), 38 deletions(-)
diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua
index 19ca0a84a..b3bcf3d08 100644
--- a/plugins/lua/workflow.lua
+++ b/plugins/lua/workflow.lua
@@ -8,10 +8,11 @@ local utils = require 'utils'
* isEnabled()
* setEnabled(enable)
- * listConstraints([job]) -> {...}
+ * listConstraints([job[,with_history]]) -> {{...},...}
* findConstraint(token) -> {...} or nil
* setConstraint(token[, by_count, goal, gap]) -> {...}
* deleteConstraint(token) -> true/false
+ * getCountHistory(token) -> {{...},...} or nil
--]]
diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp
index 05fdca55b..12174af5c 100644
--- a/plugins/workflow.cpp
+++ b/plugins/workflow.cpp
@@ -291,6 +291,15 @@ int ProtectedJob::cur_tick_idx = 0;
typedef std::map, bool> TMaterialCache;
+static const size_t MAX_HISTORY_SIZE = 28;
+
+enum HistoryItem {
+ HIST_COUNT = 0,
+ HIST_AMOUNT,
+ HIST_INUSE_COUNT,
+ HIST_INUSE_AMOUNT
+};
+
struct ItemConstraint {
PersistentDataItem config;
PersistentDataItem history;
@@ -360,36 +369,29 @@ public:
size_t history_size() {
return history.data_size() / hist_entry_size;
}
- size_t history_base(int idx) {
+ int history_value(int idx, HistoryItem item) {
size_t hsize = history_size();
- return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size;
- }
- int history_count(int idx) {
- return history.get_int28(history_base(idx) + 0*int28_size);
- }
- int history_amount(int idx) {
- return history.get_int28(history_base(idx) + 1*int28_size);
- }
- int history_inuse_count(int idx) {
- return history.get_int28(history_base(idx) + 2*int28_size);
- }
- int history_inuse_amount(int idx) {
- return history.get_int28(history_base(idx) + 3*int28_size);
+ size_t base = ((history.ival(0)+1+idx) % hsize) * hist_entry_size;
+ return history.get_int28(base + item*int28_size);
}
+ int history_count(int idx) { return history_value(idx, HIST_COUNT); }
+ int history_amount(int idx) { return history_value(idx, HIST_AMOUNT); }
+ int history_inuse_count(int idx) { return history_value(idx, HIST_INUSE_COUNT); }
+ int history_inuse_amount(int idx) { return history_value(idx, HIST_INUSE_AMOUNT); }
void updateHistory()
{
size_t buffer_size = history_size();
- if (buffer_size < 28)
- history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size);
+ if (buffer_size < MAX_HISTORY_SIZE && size_t(history.ival(0)+1) == buffer_size)
+ history.ensure_data(hist_entry_size*++buffer_size);
history.ival(0) = (history.ival(0)+1) % buffer_size;
size_t base = history.ival(0) * hist_entry_size;
- history.set_int28(base + 0*int28_size, item_count);
- history.set_int28(base + 1*int28_size, item_amount);
- history.set_int28(base + 2*int28_size, item_inuse_count);
- history.set_int28(base + 3*int28_size, item_inuse_amount);
+ history.set_int28(base + HIST_COUNT*int28_size, item_count);
+ history.set_int28(base + HIST_AMOUNT*int28_size, item_amount);
+ history.set_int28(base + HIST_INUSE_COUNT*int28_size, item_inuse_count);
+ history.set_int28(base + HIST_INUSE_AMOUNT*int28_size, item_inuse_amount);
}
};
@@ -1384,6 +1386,25 @@ static void setEnabled(color_ostream &out, bool enable)
}
}
+static void push_count_history(lua_State *L, ItemConstraint *icv)
+{
+ size_t hsize = icv->history_size();
+
+ lua_createtable(L, hsize, 0);
+
+ for (size_t i = 0; i < hsize; i++)
+ {
+ lua_createtable(L, 0, 4);
+
+ Lua::SetField(L, icv->history_amount(i), -1, "cur_amount");
+ Lua::SetField(L, icv->history_count(i), -1, "cur_count");
+ Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount");
+ Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count");
+
+ lua_rawseti(L, -2, i+1);
+ }
+}
+
static void push_constraint(lua_State *L, ItemConstraint *cv)
{
lua_newtable(L);
@@ -1430,19 +1451,31 @@ static void push_constraint(lua_State *L, ItemConstraint *cv)
lua_newtable(L);
+ bool resumed = false, want_resumed = false;
+
for (size_t i = 0, j = 0; i < cv->jobs.size(); i++)
{
if (!cv->jobs[i]->isLive()) continue;
Lua::PushDFObject(L, cv->jobs[i]->actual_job);
lua_rawseti(L, -2, ++j);
+
+ if (cv->jobs[i]->want_resumed) {
+ want_resumed = true;
+ resumed = resumed || cv->jobs[i]->isActuallyResumed();
+ }
}
lua_setfield(L, ctable, "jobs");
+
+ if (want_resumed && !resumed)
+ Lua::SetField(L, true, ctable, "is_delayed");
}
static int listConstraints(lua_State *L)
{
+ lua_settop(L, 2);
auto job = Lua::CheckDFObject(L, 1);
+ bool with_history = lua_toboolean(L, 2);
lua_pushnil(L);
@@ -1467,6 +1500,13 @@ static int listConstraints(lua_State *L)
for (size_t i = 0; i < vec.size(); i++)
{
push_constraint(L, vec[i]);
+
+ if (with_history)
+ {
+ push_count_history(L, vec[i]);
+ lua_setfield(L, -2, "history");
+ }
+
lua_rawseti(L, -2, i+1);
}
@@ -1525,23 +1565,7 @@ static int getCountHistory(lua_State *L)
ItemConstraint *icv = get_constraint(out, token, NULL, false);
if (icv)
- {
- size_t hsize = icv->history_size();
-
- lua_createtable(L, hsize, 0);
-
- for (int i = hsize-1; i >= 0; i--)
- {
- lua_createtable(L, 0, 4);
-
- Lua::SetField(L, icv->history_amount(i), -1, "cur_amount");
- Lua::SetField(L, icv->history_count(i), -1, "cur_count");
- Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount");
- Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count");
-
- lua_rawseti(L, -2, hsize-i); // reverse order
- }
- }
+ push_count_history(L, icv);
else
lua_pushnil(L);
From 210c1650ecb19a9407779b37ff334f28dca859a7 Mon Sep 17 00:00:00 2001
From: Anuradha Dissanayake
Date: Sat, 1 Dec 2012 01:01:04 +1300
Subject: [PATCH 07/32] Add stockpile screen searching capability
---
plugins/search.cpp | 122 ++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 109 insertions(+), 13 deletions(-)
diff --git a/plugins/search.cpp b/plugins/search.cpp
index a14397fba..cf1798dcf 100644
--- a/plugins/search.cpp
+++ b/plugins/search.cpp
@@ -7,10 +7,12 @@
//#include "df/viewscreen_petst.h"
#include "df/viewscreen_storesst.h"
+#include "df/viewscreen_layer_stockpilest.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_unitlistst.h"
#include "df/interface_key.h"
#include "df/interfacest.h"
+#include "df/layer_object_listst.h"
using std::set;
using std::vector;
@@ -79,6 +81,11 @@ public:
return true;
}
+ bool is_valid()
+ {
+ return valid;
+ }
+
// A new keystroke is received in a searchable screen
virtual bool process_input(set *input)
{
@@ -164,9 +171,9 @@ protected:
const S *viewscreen;
vector saved_list1, reference_list;
vector saved_list2;
+ vector *sort_list2;
vector saved_indexes;
- bool valid;
bool redo_search;
bool track_secondary_values;
string search_string;
@@ -240,12 +247,17 @@ protected:
}
}
- saved_list2[saved_indexes[i]] = (*sort_list2)[adjusted_item_index];
+ update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index);
}
saved_indexes.clear();
}
}
+ virtual void update_saved_secondary_list_item(size_t i, size_t j)
+ {
+ saved_list2[i] = (*sort_list2)[j];
+ }
+
// Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering
void store_reference_values()
{
@@ -254,7 +266,7 @@ protected:
}
// Shortcut to clear the search immediately
- void clear_search()
+ virtual void clear_search()
{
if (saved_list1.size() > 0)
{
@@ -273,7 +285,7 @@ protected:
}
// The actual sort
- void do_search()
+ virtual void do_search()
{
if (search_string.length() == 0)
{
@@ -318,7 +330,8 @@ protected:
store_reference_values(); //Keep a copy, in case user sorts new list
- *cursor_pos = 0;
+ if (cursor_pos)
+ *cursor_pos = 0;
}
virtual bool should_check_input(set *input)
@@ -346,10 +359,9 @@ protected:
private:
vector *sort_list1;
- vector *sort_list2;
int *cursor_pos;
char select_key;
-
+ bool valid;
bool entry_mode;
df::interface_key select_token;
@@ -359,7 +371,8 @@ private:
};
template search_parent *search_parent ::lock = NULL;
-// Parent struct for the hooks
+// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V
+// but different static modules
template
struct search_hook : T
{
@@ -437,7 +450,7 @@ public:
if (screen != viewscreen && !reset_on_change())
return false;
- if (!valid)
+ if (!is_valid())
{
viewscreen = screen;
search_parent::init(&screen->item_cursor, &screen->items);
@@ -501,7 +514,7 @@ public:
if (screen != viewscreen && !reset_on_change())
return false;
- if (!valid)
+ if (!is_valid())
{
viewscreen = screen;
search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]);
@@ -608,7 +621,7 @@ public:
if (screen != viewscreen && !reset_on_change())
return false;
- if (!valid)
+ if (!is_valid())
{
viewscreen = screen;
search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q');
@@ -637,7 +650,7 @@ public:
if (screen != viewscreen && !reset_on_change())
return false;
- if (!valid)
+ if (!is_valid())
{
viewscreen = screen;
search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w');
@@ -657,6 +670,84 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render);
//
+//
+// START: Stockpile screen search
+//
+
+class stockpile_search : public search_parent
+{
+public:
+ void update_saved_secondary_list_item(size_t i, size_t j)
+ {
+ *saved_list2[i] = *(*sort_list2)[j];
+ }
+
+ string get_element_description(string *element) const
+ {
+ return *element;
+ }
+
+ void render() const
+ {
+ print_search_option(2, 25);
+ }
+
+ static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx)
+ {
+ return virtual_cast(vector_get(layer->layer_objects,idx));
+ }
+
+ bool init(df::viewscreen_layer_stockpilest *screen)
+ {
+ if (screen != viewscreen && !reset_on_change())
+ return false;
+
+ auto list3 = getLayerList(screen, 2);
+ if (!list3->active)
+ {
+ if (is_valid())
+ {
+ clear_search();
+ reset_all();
+ }
+
+ return false;
+ }
+
+ if (!is_valid())
+ {
+ viewscreen = screen;
+ search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status);
+ track_secondary_values = true;
+ }
+
+ return true;
+ }
+
+ void do_search()
+ {
+ search_parent::do_search();
+ auto list3 = getLayerList(viewscreen, 2);
+ list3->num_entries = viewscreen->item_names.size();
+ }
+
+ void clear_search()
+ {
+ search_parent::clear_search();
+ auto list3 = getLayerList(viewscreen, 2);
+ list3->num_entries = viewscreen->item_names.size();
+ }
+};
+
+typedef search_hook stockpile_search_hook;
+template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed);
+template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render);
+
+//
+// END: Stockpile screen search
+//
+
+
DFHACK_PLUGIN("search");
@@ -670,7 +761,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector
Date: Fri, 30 Nov 2012 15:50:35 +0100
Subject: [PATCH 08/32] deathcause: handle non-dead units
---
scripts/deathcause.rb | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb
index 0ed54d81a..ab3e44a39 100644
--- a/scripts/deathcause.rb
+++ b/scripts/deathcause.rb
@@ -25,12 +25,19 @@ else
puts "Not a historical figure, cannot death find info"
else
events = df.world.history.events
+ found = false
(0...events.length).reverse_each { |i|
- if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim_hf == hf
- display_death_event(events[i])
+ e = events[i]
+ if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
+ display_death_event(e)
+ found = true
break
end
}
+ if not found
+ u = item.unit_tg
+ puts "#{u.name} is not dead yet !" if u and not u.flags1.dead
+ end
end
end
From 0bfe006016d76206c9a05f4c8c0d5aede3dd748e Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 30 Nov 2012 19:10:17 +0400
Subject: [PATCH 09/32] Try to reimplement the inventory monitor by falconne in
lua.
For no other reason than to provide a complete example of lua
interface for a native plugin :)
TODO: paint the graph in the right pane.
---
Lua API.rst | 11 +
library/lua/gui.lua | 10 +-
library/lua/gui/widgets.lua | 41 +++-
scripts/gui/workflow.lua | 410 +++++++++++++++++++++++++++++++++---
4 files changed, 430 insertions(+), 42 deletions(-)
diff --git a/Lua API.rst b/Lua API.rst
index d42a348e4..714a41bfb 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -2710,6 +2710,16 @@ containing newlines, or a table with the following possible fields:
Specifies a pen to paint as one tile before the main part of the token.
+* ``token.width = ...``
+
+ If specified either as a value or a callback, the text field is padded
+ or truncated to the specified number.
+
+* ``token.pad_char = '?'``
+
+ If specified together with ``width``, the padding area is filled with
+ this character instead of just being skipped over.
+
* ``token.key = '...'``
Specifies the keycode associated with the token. The string description
@@ -2842,6 +2852,7 @@ In addition to passing through all attributes supported by List, it
supports:
:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field.
+:edit_below: If true, the edit field is placed below the list instead of above.
:not_found_label: Specifies the text of the label shown when no items match the filter.
The list choices may include the following attributes:
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index 2145cfad1..99bf9263c 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -112,10 +112,14 @@ function inset_frame(rect, inset, gap)
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
end
-function compute_frame_body(wavail, havail, spec, inset, gap)
+function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame)
gap = gap or 0
local l,t,r,b = parse_inset(inset)
- local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b)
+ local xgap,ygap = 0,0
+ if inner_frame then
+ xgap,ygap = gap*2+l+r, gap*2+t+b
+ end
+ local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap)
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
return rect, body
end
@@ -623,7 +627,7 @@ end
function FramedScreen:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height
local fw, fh = self:getWantedFrameSize(parent_rect)
- return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1)
+ return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true)
end
function FramedScreen:onRenderFrame(dc, rect)
diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua
index 145300c59..ebbffbbbd 100644
--- a/library/lua/gui/widgets.lua
+++ b/library/lua/gui/widgets.lua
@@ -60,6 +60,7 @@ Panel = defclass(Panel, Widget)
Panel.ATTRS {
on_render = DEFAULT_NIL,
+ on_layout = DEFAULT_NIL,
}
function Panel:init(args)
@@ -70,6 +71,10 @@ function Panel:onRenderBody(dc)
if self.on_render then self.on_render(dc) end
end
+function Panel:postComputeFrame(body)
+ if self.on_layout then self.on_layout(body) end
+end
+
-----------
-- Pages --
-----------
@@ -242,7 +247,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
end
if token.text or token.key then
- local text = getval(token.text) or ''
+ local text = ''..(getval(token.text) or '')
local keypen
if dc then
@@ -256,7 +261,23 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
end
end
- x = x + #text
+ local width = getval(token.width)
+ local padstr
+ if width then
+ x = x + width
+ if #text > width then
+ text = string.sub(text,1,width)
+ else
+ if token.pad_char then
+ padstr = string.rep(token.pad_char,width-#text)
+ end
+ if dc and token.rjustify then
+ if padstr then dc:string(padstr) else dc:advance(width-#text) end
+ end
+ end
+ else
+ x = x + #text
+ end
if token.key then
local keystr = gui.getKeyDisplay(token.key)
@@ -281,6 +302,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
dc:string(text)
end
end
+
+ if width and dc and not token.rjustify then
+ if padstr then dc:string(padstr) else dc:advance(width-#text) end
+ end
end
token.x2 = x
@@ -591,10 +616,14 @@ end
FilteredList = defclass(FilteredList, Widget)
+FilteredList.ATTRS {
+ edit_below = false,
+}
+
function FilteredList:init(info)
self.edit = EditField{
text_pen = info.edit_pen or info.cursor_pen,
- frame = { l = info.icon_width, t = 0 },
+ frame = { l = info.icon_width, t = 0, h = 1 },
on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'),
}
@@ -608,6 +637,10 @@ function FilteredList:init(info)
scroll_keys = info.scroll_keys,
icon_width = info.icon_width,
}
+ if self.edit_below then
+ self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
+ self.list.frame = { t = 0, b = 2 }
+ end
if info.on_select then
self.list.on_select = function()
return info.on_select(self:getSelected())
@@ -627,7 +660,7 @@ function FilteredList:init(info)
visible = false,
text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED,
- frame = { l = info.icon_width, t = 2 },
+ frame = { l = info.icon_width, t = self.list.frame.t },
}
self:addviews{ self.edit, self.list, self.not_found }
self:setChoices(info.choices, info.selected)
diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua
index a387e64b9..9a45f6554 100644
--- a/scripts/gui/workflow.lua
+++ b/scripts/gui/workflow.lua
@@ -66,20 +66,64 @@ function is_caste_mat(iobj)
end
function describe_material(iobj)
- local matline = 'any material'
+ local matflags = utils.list_bitfield_flags(iobj.mat_mask)
+ if #matflags > 0 then
+ matflags = 'any '..table.concat(matflags, '/')
+ else
+ matflags = nil
+ end
+
if is_caste_mat(iobj) then
- matline = 'no material'
+ return 'no material'
elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
+ local matline
if info then
matline = info:toString()
else
matline = iobj.mat_type..':'..iobj.mat_index
end
+ return matline, matflags
+ else
+ return matflags or 'any material'
+ end
+end
+
+function current_stock(iobj)
+ if iobj.goal_by_count then
+ return iobj.cur_count
+ else
+ return iobj.cur_amount
end
- return matline
end
+function if_by_count(iobj,bc,ba)
+ if iobj.goal_by_count then
+ return bc
+ else
+ return ba
+ end
+end
+
+function compute_trend(history,field)
+ local count = #history
+ if count == 0 then
+ return 0
+ end
+ local sumX,sumY,sumXY,sumXX = 0,0,0,0
+ for i,v in ipairs(history) do
+ sumX = sumX + i
+ sumY = sumY + v[field]
+ sumXY = sumXY + i*v[field]
+ sumXX = sumXX + i*i
+ end
+ return (count * sumXY - sumX * sumY) / (count * sumXX - sumX * sumX)
+end
+
+------------------------
+-- RANGE EDITOR GROUP --
+------------------------
+
local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }
RangeEditor = defclass(RangeEditor, widgets.Label)
@@ -162,6 +206,10 @@ function RangeEditor:onIncRange(field, delta)
self.save_cb(cons)
end
+---------------------------
+-- NEW CONSTRAINT DIALOG --
+---------------------------
+
NewConstraint = defclass(NewConstraint, gui.FramedScreen)
NewConstraint.focus_path = 'workflow/new'
@@ -177,7 +225,7 @@ NewConstraint.ATTRS {
}
function NewConstraint:init(args)
- self.constraint = args.constraint or {}
+ self.constraint = args.constraint or { item_type = -1 }
rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false })
local matlist = {}
@@ -202,8 +250,16 @@ function NewConstraint:init(args)
frame = { l = 1, t = 2, w = 26 },
text = {
'Type: ',
- { pen = COLOR_LIGHTCYAN,
- text = function() return describe_item_type(self.constraint) end },
+ { pen = function()
+ if self:isValid() then return COLOR_LIGHTCYAN else return COLOR_LIGHTRED end
+ end,
+ text = function()
+ if self:isValid() then
+ return describe_item_type(self.constraint)
+ else
+ return 'item not set'
+ end
+ end },
NEWLINE, ' ',
{ key = 'CUSTOM_T', text = ': Select, ',
on_activate = self:callback('chooseType') },
@@ -277,6 +333,7 @@ function NewConstraint:init(args)
{ key = 'LEAVESCREEN', text = ': Cancel, ',
on_activate = self:callback('dismiss') },
{ key = 'MENU_CONFIRM', key_sep = ': ',
+ enabled = self:callback('isValid'),
text = function()
if self.is_existing then return 'Update' else return 'Create new' end
end,
@@ -295,9 +352,17 @@ function NewConstraint:postinit()
self:onChange()
end
+function NewConstraint:isValid()
+ return self.constraint.item_type >= 0
+end
+
function NewConstraint:onChange()
local token = workflow.constraintToToken(self.constraint)
- local out = workflow.findConstraint(token)
+ local out
+
+ if self:isValid() then
+ out = workflow.findConstraint(token)
+ end
if out then
self.constraint = out
@@ -390,6 +455,288 @@ function NewConstraint:onRangeChange()
cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1))
end
+------------------------------
+-- GLOBAL CONSTRAINT SCREEN --
+------------------------------
+
+ConstraintList = defclass(ConstraintList, gui.FramedScreen)
+
+ConstraintList.focus_path = 'workflow/list'
+
+ConstraintList.ATTRS {
+ frame_title = 'Workflow Status',
+ frame_inset = 0,
+ frame_background = COLOR_BLACK,
+ frame_style = gui.BOUNDARY_FRAME,
+}
+
+function ConstraintList:init(args)
+ local fwidth_cb = self:cb_getfield('fwidth')
+
+ self.fwidth = 20
+ self.sort_by_severity = false
+
+ self:addviews{
+ widgets.Panel{
+ frame = { w = 31, r = 0, h = 6, t = 0 },
+ frame_inset = 1,
+ subviews = {
+ widgets.Label{
+ frame = { l = 0, t = 0 },
+ enabled = self:callback('isAnySelected'),
+ text = {
+ { text = function()
+ local cur = self:getCurConstraint()
+ if cur then
+ return string.format(
+ 'Currently %d (%d in use)',
+ current_stock(cur),
+ if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount)
+ )
+ else
+ return 'No constraint selected'
+ end
+ end }
+ }
+ },
+ RangeEditor{
+ frame = { l = 0, t = 2 },
+ enabled = self:callback('isAnySelected'),
+ get_cb = self:callback('getCurConstraint'),
+ save_cb = self:callback('saveConstraint'),
+ },
+ }
+ },
+ widgets.Widget{
+ active = false,
+ frame = { w = 1, r = 31 },
+ frame_background = gui.BOUNDARY_FRAME.frame_pen,
+ },
+ widgets.Widget{
+ active = false,
+ frame = { w = 31, r = 0, h = 1, t = 6 },
+ frame_background = gui.BOUNDARY_FRAME.frame_pen,
+ },
+ widgets.Panel{
+ frame = { l = 0, r = 32 },
+ frame_inset = 1,
+ on_layout = function(body)
+ self.fwidth = body.width - (12+1+1+7+1+1+1+7)
+ end,
+ subviews = {
+ widgets.Label{
+ frame = { l = 0, t = 0 },
+ text_pen = COLOR_CYAN,
+ text = {
+ { text = 'Item', width = 12 }, ' ',
+ { text = 'Material etc', width = fwidth_cb }, ' ',
+ { text = 'Stock / Limit' },
+ }
+ },
+ widgets.FilteredList{
+ view_id = 'list',
+ frame = { t = 2, b = 2 },
+ edit_below = true,
+ not_found_label = 'No matching constraints',
+ edit_pen = COLOR_LIGHTCYAN,
+ text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK },
+ cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN },
+ },
+ widgets.Label{
+ frame = { b = 0, h = 1 },
+ text = {
+ { key = 'CUSTOM_SHIFT_A', text = ': Add',
+ on_activate = self:callback('onNewConstraint') }, ', ',
+ { key = 'CUSTOM_SHIFT_X', text = ': Delete',
+ on_activate = self:callback('onDeleteConstraint') }, ', ',
+ { key = 'CUSTOM_SHIFT_O', text = ': Severity Order',
+ on_activate = self:callback('onSwitchSort'),
+ pen = function()
+ if self.sort_by_severity then
+ return COLOR_LIGHTCYAN
+ else
+ return COLOR_WHITE
+ end
+ end }, ', ',
+ { key = 'CUSTOM_SHIFT_S', text = ': Search',
+ on_activate = function()
+ self.subviews.list.edit.active = not self.subviews.list.edit.active
+ end,
+ pen = function()
+ if self.subviews.list.edit.active then
+ return COLOR_LIGHTCYAN
+ else
+ return COLOR_WHITE
+ end
+ end }
+ }
+ }
+ }
+ },
+ }
+
+ self.subviews.list.edit.active = false
+
+ self:initListChoices()
+end
+
+function stock_trend_color(cons)
+ local stock = current_stock(cons)
+ if stock >= cons.goal_value - cons.goal_gap then
+ return COLOR_LIGHTGREEN, 0
+ elseif stock <= cons.goal_gap then
+ return COLOR_LIGHTRED, 4
+ elseif stock >= cons.goal_value - 2*cons.goal_gap then
+ return COLOR_GREEN, 1
+ elseif stock <= 2*cons.goal_gap then
+ return COLOR_RED, 3
+ else
+ local trend = if_by_count(cons, cons.trend_count, cons.trend_amount)
+ if trend > 0.3 then
+ return COLOR_GREEN, 1
+ elseif trend < -0.3 then
+ return COLOR_RED, 3
+ else
+ return COLOR_GREY, 2
+ end
+ end
+end
+
+function ConstraintList:initListChoices(clist, sel_token)
+ clist = clist or workflow.listConstraints(nil, true)
+
+ local fwidth_cb = self:cb_getfield('fwidth')
+ local choices = {}
+
+ for i,cons in ipairs(clist) do
+ cons.trend_count = compute_trend(cons.history, 'cur_count')
+ cons.trend_amount = compute_trend(cons.history, 'cur_amount')
+
+ local itemstr = describe_item_type(cons)
+ local matstr,matflagstr = describe_material(cons)
+ if matflagstr then
+ matstr = matflagstr .. ' ' .. matstr
+ end
+
+ if cons.min_quality > 0 or cons.is_local then
+ local lst = {}
+ if cons.is_local then
+ table.insert(lst, 'local')
+ end
+ if cons.min_quality > 0 then
+ table.insert(lst, string.lower(df.item_quality[cons.min_quality]))
+ end
+ matstr = matstr .. ' ('..table.concat(lst,',')..')'
+ end
+
+ local goal_color = COLOR_GREY
+ if #cons.jobs == 0 then
+ goal_color = COLOR_RED
+ elseif cons.is_delayed then
+ goal_color = COLOR_YELLOW
+ end
+
+ table.insert(choices, {
+ text = {
+ { text = itemstr, width = 12, pad_char = ' ' }, ' ',
+ { text = matstr, width = fwidth_cb, pad_char = ' ' }, ' ',
+ { text = curry(current_stock,cons), width = 7, rjustify = true,
+ pen = function() return { fg = stock_trend_color(cons) } end },
+ { text = curry(if_by_count,cons,'S','I'), gap = 1,
+ pen = { fg = COLOR_GREY } },
+ { text = function() return cons.goal_value end, gap = 1,
+ pen = { fg = goal_color } }
+ },
+ severity = select(2, stock_trend_color(cons)),
+ search_key = itemstr .. ' | ' .. matstr,
+ token = cons.token,
+ obj = cons
+ })
+ end
+
+ self:setChoices(choices, sel_token)
+end
+
+function ConstraintList:isAnySelected()
+ return self.subviews.list:getSelected() ~= nil
+end
+
+function ConstraintList:getCurConstraint()
+ local selidx,selobj = self.subviews.list:getSelected()
+ if selobj then return selobj.obj end
+end
+
+function ConstraintList:onSwitchSort()
+ self.sort_by_severity = not self.sort_by_severity
+ self:setChoices(self.subviews.list:getChoices())
+end
+
+function ConstraintList:setChoices(choices, sel_token)
+ if self.sort_by_severity then
+ table.sort(choices, function(a,b)
+ return a.severity > b.severity
+ or (a.severity == b.severity and
+ current_stock(a.obj)/a.obj.goal_value < current_stock(b.obj)/b.obj.goal_value)
+ end)
+ else
+ table.sort(choices, function(a,b) return a.search_key < b.search_key end)
+ end
+
+ local selidx = nil
+ if sel_token then
+ selidx = utils.linear_index(choices, sel_token, 'token')
+ end
+
+ local list = self.subviews.list
+ local filter = list:getFilter()
+
+ list:setChoices(choices, selidx)
+
+ if filter ~= '' then
+ list:setFilter(filter, selidx)
+
+ if selidx and list:getSelected() ~= selidx then
+ list:setFilter('', selidx)
+ end
+ end
+end
+
+function ConstraintList:onInput(keys)
+ if keys.LEAVESCREEN then
+ self:dismiss()
+ else
+ ConstraintList.super.onInput(self, keys)
+ end
+end
+
+function ConstraintList:onNewConstraint()
+ NewConstraint{
+ on_submit = self:callback('saveConstraint')
+ }:show()
+end
+
+function ConstraintList:saveConstraint(cons)
+ local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap)
+ self:initListChoices(nil, out.token)
+end
+
+function ConstraintList:onDeleteConstraint()
+ local cons = self:getCurConstraint()
+ dlg.showYesNoPrompt(
+ 'Delete Constraint',
+ 'Really delete the current constraint?',
+ COLOR_YELLOW,
+ function()
+ workflow.deleteConstraint(cons.token)
+ self:initListChoices()
+ end
+ )
+end
+
+-------------------------------
+-- WORKSHOP JOB INFO OVERLAY --
+-------------------------------
+
JobConstraints = defclass(JobConstraints, guidm.MenuOverlay)
JobConstraints.focus_path = 'workflow/job'
@@ -480,24 +827,12 @@ function JobConstraints:initListChoices(clist, sel_token)
end
itemstr = itemstr .. ' ('..table.concat(lst,',')..')'
end
- local matstr = describe_material(cons)
- local matflagstr = ''
- local matflags = utils.list_bitfield_flags(cons.mat_mask)
- if #matflags > 0 then
- matflags[1] = 'any '..matflags[1]
- if matstr == 'any material' then
- matstr = table.concat(matflags, ', ')
- matflags = {}
- end
- end
- if #matflags > 0 then
- matflagstr = table.concat(matflags, ', ')
- end
+ local matstr,matflagstr = describe_material(cons)
table.insert(choices, {
text = {
goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE,
- ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr
+ ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', (matflagstr or '')
},
token = cons.token,
obj = cons
@@ -593,20 +928,25 @@ function JobConstraints:onInput(keys)
end
end
-if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then
- qerror("This script requires a workshop job selected in the 'q' mode")
-end
+local args = {...}
-local job = dfhack.gui.getSelectedJob()
+if args[1] == 'list' then
+ check_enabled(function() ConstraintList{}:show() end)
+else
+ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then
+ qerror("This script requires a workshop job selected in the 'q' mode")
+ end
-check_enabled(function()
- check_repeat(job, function()
- local clist = workflow.listConstraints(job)
- if not clist then
- dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED)
- return
- end
- JobConstraints{ job = job, clist = clist }:show()
- end)
-end)
+ local job = dfhack.gui.getSelectedJob()
+ check_enabled(function()
+ check_repeat(job, function()
+ local clist = workflow.listConstraints(job)
+ if not clist then
+ dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED)
+ return
+ end
+ JobConstraints{ job = job, clist = clist }:show()
+ end)
+ end)
+end
From 9e30bf0dffcffc428556d49dd4117bc5e94b6a5f Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Fri, 30 Nov 2012 11:05:37 -0600
Subject: [PATCH 10/32] Autofarm: use player's actual seed stocks as basis for
plantable seeds, instead of player entity's hypothetically plantable seeds.
Avoids designating a plot for planting with seeds the player doesn't have.
---
scripts/autofarm.rb | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb
index 18f5a9aeb..9d39bc29a 100644
--- a/scripts/autofarm.rb
+++ b/scripts/autofarm.rb
@@ -29,7 +29,18 @@ class AutoFarm
def find_plantable_plants
plantable = {}
- for i in 0..df.ui.tasks.known_plants.length-1
+ counts = {}
+
+ df.world.items.other[:SEEDS].each { |i|
+ if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
+ !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
+ !i.flags.trader && !i.flags.in_building && !i.flags.construction &&
+ !i.flags.artifact1 && plantable.has_key? (i.mat_index))
+ counts[i.mat_index] = counts[i.mat_index] + i.stack_size
+ end
+ }
+
+ counts.keys.each { |i|
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
@@ -37,7 +48,8 @@ class AutoFarm
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
- end
+ }
+
return plantable
end
From 021d08970922f897b8d37c117524418b91c62041 Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Fri, 30 Nov 2012 20:25:19 -0600
Subject: [PATCH 11/32] sync structures
---
library/xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/library/xml b/library/xml
index 42e26b368..22b01b80a 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc
+Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921
From e5f509a994d7c071d84595bd8d01f5ce78196dca Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Fri, 30 Nov 2012 20:51:40 -0600
Subject: [PATCH 12/32] autofarm: sync with changes to structures for
df-item.xml
---
scripts/autofarm.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb
index 3839a7ca9..6a7635b90 100644
--- a/scripts/autofarm.rb
+++ b/scripts/autofarm.rb
@@ -35,7 +35,7 @@ class AutoFarm
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
- !i.flags.artifact1 && plantable.has_key? (i.mat_index))
+ !i.flags.artifact)
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
From 05dce0d2f173b452faceef23b5ef33b33a07f6bd Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Fri, 30 Nov 2012 21:24:18 -0600
Subject: [PATCH 13/32] Fix inadvertently prematurely terminated block comment.
---
plugins/lua/workflow.lua | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua
index b3bcf3d08..563e83297 100644
--- a/plugins/lua/workflow.lua
+++ b/plugins/lua/workflow.lua
@@ -8,7 +8,7 @@ local utils = require 'utils'
* isEnabled()
* setEnabled(enable)
- * listConstraints([job[,with_history]]) -> {{...},...}
+ * listConstraints([job[,with_history] ]) -> {{...},...}
* findConstraint(token) -> {...} or nil
* setConstraint(token[, by_count, goal, gap]) -> {...}
* deleteConstraint(token) -> true/false
From 58239e97ed2372c5110db5684a4c228bcb5ebf95 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Sat, 1 Dec 2012 16:50:03 +0400
Subject: [PATCH 14/32] Implement the history graph in the workflow status
screen.
---
Lua API.html | 14 +-
Lua API.rst | 2 +
Readme.html | 27 +++-
Readme.rst | 33 ++++-
dfhack.init-example | 1 +
images/workflow-new1.png | Bin 6674 -> 6775 bytes
images/workflow-new2.png | Bin 6862 -> 7793 bytes
images/workflow-status.png | Bin 0 -> 5118 bytes
images/workflow.png | Bin 4931 -> 5779 bytes
library/lua/gui/widgets.lua | 19 ++-
scripts/gui/workflow.lua | 248 ++++++++++++++++++++++++------------
11 files changed, 248 insertions(+), 96 deletions(-)
create mode 100644 images/workflow-status.png
diff --git a/Lua API.html b/Lua API.html
index f42905d01..f14239a10 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -2787,6 +2787,14 @@ before rendering the token.
token.tile = pen
Specifies a pen to paint as one tile before the main part of the token.
+token.width = ...
+If specified either as a value or a callback, the text field is padded
+or truncated to the specified number.
+
+token.pad_char = '?'
+If specified together with width, the padding area is filled with
+this character instead of just being skipped over.
+
token.key = '...'
Specifies the keycode associated with the token. The string description
of the key binding is added to the text content of the token.
@@ -2848,7 +2856,9 @@ this may be extended with mouse click support.
|
icon_pen: | Default pen for icons. |
-on_select: | Selection change callback; called as on_select(index,choice). |
+
---|
on_select: | Selection change callback; called as on_select(index,choice).
+This is also called with nil arguments if setChoices is called
+with an empty list. |
on_submit: | Enter key callback; if specified, the list reacts to the key
and calls it as on_submit(index,choice). |
@@ -2928,6 +2938,8 @@ supports:
edit_pen: | If specified, used instead of cursor_pen for the edit field. |
+edit_below: | If true, the edit field is placed below the list instead of above. |
+
not_found_label: |
| Specifies the text of the label shown when no items match the filter. |
diff --git a/Lua API.rst b/Lua API.rst
index 714a41bfb..cedc36441 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -2785,6 +2785,8 @@ It has the following attributes:
:inactive_pen: If specified, used for the cursor when the widget is not active.
:icon_pen: Default pen for icons.
:on_select: Selection change callback; called as ``on_select(index,choice)``.
+ This is also called with *nil* arguments if ``setChoices`` is called
+ with an empty list.
:on_submit: Enter key callback; if specified, the list reacts to the key
and calls it as ``on_submit(index,choice)``.
:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key
diff --git a/Readme.html b/Readme.html
index deea72bef..d75be99f4 100644
--- a/Readme.html
+++ b/Readme.html
@@ -3034,11 +3034,11 @@ current job, and their current status.
current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.
-Pressing 'c' switches the current constraint between counting stacks or items.
-Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
-bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting
-items and expanding the range each gives a 5x bonus).
-Pressing 'n' produces a list of possible outputs of this job as guessed by
+
Pressing 'I' switches the current constraint between counting stacks or items.
+Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
+bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting
+items and expanding the range each gives a 2x bonus).
+Pressing 'A' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by choosing one as template. If you
don't see the choice you want in the list, it likely means you have to adjust
the job material first using job item-material or gui/workshop-job,
@@ -3050,6 +3050,23 @@ added to the list. If you use Shift-Enter, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
+Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen)
+opens the overall status screen, which was copied from the C++ implementation
+by falconne for better integration with the rest of the lua script:
+
+This screen shows all currently existing workflow constraints, and allows
+monitoring and/or changing them from one screen. The constraint list can
+be filtered by typing text in the field below.
+The color of the stock level number indicates how "healthy" the stock level
+is, based on current count and trend. Bright green is very good, green is good,
+red is bad, bright red is very bad.
+The limit number is also color-coded. Red means that there are currently no
+workshops producing that item (i.e. no jobs). If it's yellow, that means the
+production has been delayed, possibly due to lack of input materials.
+The chart on the right is a plot of the last 14 days (28 half day plots) worth
+of stock history for the selected item, with the rightmost point representing
+the current stock value. The bright green dashed line is the target
+limit (maximum) and the dark green line is that minus the gap (minimum).
diff --git a/Readme.rst b/Readme.rst
index a214a6ecb..a84691b05 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -2295,12 +2295,12 @@ current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.
-Pressing 'c' switches the current constraint between counting stacks or items.
-Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
-bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting
-items and expanding the range each gives a 5x bonus).
+Pressing 'I' switches the current constraint between counting stacks or items.
+Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
+bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting
+items and expanding the range each gives a 2x bonus).
-Pressing 'n' produces a list of possible outputs of this job as guessed by
+Pressing 'A' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by choosing one as template. If you
don't see the choice you want in the list, it likely means you have to adjust
the job material first using ``job item-material`` or ``gui/workshop-job``,
@@ -2316,6 +2316,29 @@ suit your need, and set the item count range.
.. image:: images/workflow-new2.png
+Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen)
+opens the overall status screen, which was copied from the C++ implementation
+by falconne for better integration with the rest of the lua script:
+
+.. image:: images/workflow-status.png
+
+This screen shows all currently existing workflow constraints, and allows
+monitoring and/or changing them from one screen. The constraint list can
+be filtered by typing text in the field below.
+
+The color of the stock level number indicates how "healthy" the stock level
+is, based on current count and trend. Bright green is very good, green is good,
+red is bad, bright red is very bad.
+
+The limit number is also color-coded. Red means that there are currently no
+workshops producing that item (i.e. no jobs). If it's yellow, that means the
+production has been delayed, possibly due to lack of input materials.
+
+The chart on the right is a plot of the last 14 days (28 half day plots) worth
+of stock history for the selected item, with the rightmost point representing
+the current stock value. The bright green dashed line is the target
+limit (maximum) and the dark green line is that minus the gap (minimum).
+
gui/assign-rack
===============
diff --git a/dfhack.init-example b/dfhack.init-example
index 8fafa4cf4..7617b9f6e 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -91,6 +91,7 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end
keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
+keybinding add Alt-W@overallstatus "gui/workflow status"
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
diff --git a/images/workflow-new1.png b/images/workflow-new1.png
index 50d0e1f421ac9de1abf87545c8d7ade31bd4cd4a..498fc4e80b4eb9736a00429195a9eee6dbd17d14 100644
GIT binary patch
literal 6775
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
-
The search plugin adds search to the Stocks, Trading and Unit List screens.
+
The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.
Searching works the same way as the search option in "Move to Depot" does.
You will see the Search option displayed on screen with a hotkey (usually 's').
@@ -2890,6 +2890,13 @@ filter).
are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade.
+
In the stockpile screen the option only appears if the cursor is in the
+rightmost list:
+
![images/search-stockpile.png](images/search-stockpile.png)
+
Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only
+on items actually shown in the rightmost list, so it is possible to select
+only fat or tallow by forbidding fats, then searching for fat/tallow, and
+using Permit Fats again while the list is filtered.
diff --git a/Readme.rst b/Readme.rst
index a84691b05..afad3ef4c 100644
--- a/Readme.rst
+++ b/Readme.rst
@@ -2074,7 +2074,7 @@ directly to the main dwarf mode screen.
Search
======
-The search plugin adds search to the Stocks, Trading and Unit List screens.
+The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens.
.. image:: images/search.png
@@ -2097,6 +2097,16 @@ are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade.
+In the stockpile screen the option only appears if the cursor is in the
+rightmost list:
+
+.. image:: images/search-stockpile.png
+
+Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only
+on items actually shown in the rightmost list, so it is possible to select
+only fat or tallow by forbidding fats, then searching for fat/tallow, and
+using Permit Fats again while the list is filtered.
+
AutoMaterial
============
diff --git a/images/search-stockpile.png b/images/search-stockpile.png
new file mode 100644
index 0000000000000000000000000000000000000000..37a0e57cd3a74a4f4f7b80d76dcd996cc92da1fa
GIT binary patch
literal 5971
zcmZ`-2{@E*wExB!yD^b9G1g?6P}VGyLH3Ys$`WP
L
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
Date: Sun, 2 Dec 2012 14:43:23 +0400
Subject: [PATCH 16/32] Detect mouse press events for lua.
---
Lua API.html | 5 ++++-
Lua API.rst | 5 ++++-
library/lua/gui.lua | 2 ++
library/modules/Screen.cpp | 14 ++++++++++++--
4 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/Lua API.html b/Lua API.html
index f14239a10..aa41b8b63 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1749,7 +1749,10 @@ options; if multiple interpretations exist, the table will contain multiple keys
Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.
_MOUSE_L, _MOUSE_R
-If the left or right mouse button is pressed.
+If the left or right mouse button is being pressed.
+
+_MOUSE_L_DOWN, _MOUSE_R_DOWN
+If the left or right mouse button was just pressed.
If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.
diff --git a/Lua API.rst b/Lua API.rst
index cedc36441..eaef74997 100644
--- a/Lua API.rst
+++ b/Lua API.rst
@@ -1610,7 +1610,10 @@ Supported callbacks and fields are:
Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.
``_MOUSE_L, _MOUSE_R``
- If the left or right mouse button is pressed.
+ If the left or right mouse button is being pressed.
+
+ ``_MOUSE_L_DOWN, _MOUSE_R_DOWN``
+ If the left or right mouse button was just pressed.
If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key.
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index 99bf9263c..603c7ab44 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -13,6 +13,8 @@ CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
local FAKE_INPUT_KEYS = {
_MOUSE_L = true,
_MOUSE_R = true,
+ _MOUSE_L_DOWN = true,
+ _MOUSE_R_DOWN = true,
_STRING = true,
}
diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp
index cd20bc25e..782bb317d 100644
--- a/library/modules/Screen.cpp
+++ b/library/modules/Screen.cpp
@@ -664,14 +664,24 @@ int dfhack_lua_viewscreen::do_input(lua_State *L)
if (enabler && enabler->tracking_on)
{
- if (enabler->mouse_lbut) {
+ if (enabler->mouse_lbut_down) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L");
}
- if (enabler->mouse_rbut) {
+ if (enabler->mouse_rbut_down) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R");
}
+ if (enabler->mouse_lbut) {
+ lua_pushboolean(L, true);
+ lua_setfield(L, -2, "_MOUSE_L_DOWN");
+ enabler->mouse_lbut = 0;
+ }
+ if (enabler->mouse_rbut) {
+ lua_pushboolean(L, true);
+ lua_setfield(L, -2, "_MOUSE_R_DOWN");
+ enabler->mouse_rbut = 0;
+ }
}
lua_call(L, 2, 0);
From dc7f9f56cd9d9ac6ebda4b59027841458ecbf82c Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Sun, 2 Dec 2012 15:31:43 +0400
Subject: [PATCH 17/32] Implement a low stock level announcement as suggested
by falconne.
---
NEWS | 1 +
plugins/workflow.cpp | 21 +++++++++++++++++++--
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/NEWS b/NEWS
index 4856b06c3..b8ad53830 100644
--- a/NEWS
+++ b/NEWS
@@ -43,6 +43,7 @@ DFHack future
- logic fix: collecting webs produces silk, and ungathered webs are not thread.
- items assigned to squads are considered busy, even if not in inventory.
- shearing and milking jobs are supported, but only with generic MILK or YARN outputs.
+ - workflow announces when the stock level gets very low once a season.
New Fix Armory plugin:
Together with a couple of binary patches and the gui/assign-rack script,
this plugin makes weapon racks, armor stands, chests and cabinets in
diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp
index 12174af5c..b70fd8b0c 100644
--- a/plugins/workflow.cpp
+++ b/plugins/workflow.cpp
@@ -322,6 +322,7 @@ struct ItemConstraint {
bool request_suspend, request_resume;
bool is_active, cant_resume_reported;
+ int low_stock_reported;
TMaterialCache material_cache;
@@ -329,7 +330,7 @@ public:
ItemConstraint()
: is_craft(false), min_quality(item_quality::Ordinary), is_local(false),
weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0),
- is_active(false), cant_resume_reported(false)
+ is_active(false), cant_resume_reported(false), low_stock_reported(-1)
{}
int goalCount() { return config.ival(0); }
@@ -349,6 +350,8 @@ public:
config.ival(2) &= ~1;
}
+ int curItemStock() { return goalByCount() ? item_count : item_amount; }
+
void init(const std::string &str)
{
config.val() = str;
@@ -358,7 +361,7 @@ public:
void computeRequest()
{
- int size = goalByCount() ? item_count : item_amount;
+ int size = curItemStock();
request_resume = (size <= goalCount()-goalGap());
request_suspend = (size >= goalCount());
}
@@ -1323,6 +1326,20 @@ static void update_jobs_by_constraints(color_ostream &out)
else if (ct->mat_mask.whole)
info = bitfield_to_string(ct->mat_mask) + " " + info;
+ if (ct->low_stock_reported != DF_GLOBAL_VALUE(cur_season,-1))
+ {
+ int count = ct->goalCount(), gap = ct->goalGap();
+
+ if (count >= gap*3 && ct->curItemStock() < std::min(gap*2, (count-gap)/2))
+ {
+ ct->low_stock_reported = DF_GLOBAL_VALUE(cur_season,-1);
+
+ Gui::showAnnouncement("Stock level is low: " + info, COLOR_BROWN, true);
+ }
+ else
+ ct->low_stock_reported = -1;
+ }
+
if (is_running != ct->is_active)
{
if (is_running && ct->request_resume)
From 3953112eb936e7afddea09413bf6dfcefa795566 Mon Sep 17 00:00:00 2001
From: jj
Date: Mon, 3 Dec 2012 19:03:07 +0100
Subject: [PATCH 18/32] dump Vegetation::t_plant, fix plant.is_burning
---
library/include/modules/Vegetation.h | 22 ----------------------
library/modules/Vegetation.cpp | 25 -------------------------
plugins/plants.cpp | 4 ++--
3 files changed, 2 insertions(+), 49 deletions(-)
diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h
index 89ba5ff6c..f293ec52c 100644
--- a/library/include/modules/Vegetation.h
+++ b/library/include/modules/Vegetation.h
@@ -40,31 +40,9 @@ namespace Vegetation
{
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
-// "Simplified" copy of plant
-struct t_plant {
- df::language_name name;
- df::plant_flags flags;
- int16_t material;
- df::coord pos;
- int32_t grow_counter;
- uint16_t temperature_1;
- uint16_t temperature_2;
- int32_t is_burning;
- int32_t hitpoints;
- int16_t update_order;
- //std::vector unk1;
- //int32_t unk2;
- //uint16_t temperature_3;
- //uint16_t temperature_4;
- //uint16_t temperature_5;
- // Pointer to original object, in case you want to modify it
- df::plant *origin;
-};
-
DFHACK_EXPORT bool isValid();
DFHACK_EXPORT uint32_t getCount();
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
-DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
}
}
#endif
diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp
index f7c4c9b0c..9b14a3cc0 100644
--- a/library/modules/Vegetation.cpp
+++ b/library/modules/Vegetation.cpp
@@ -58,28 +58,3 @@ df::plant * Vegetation::getPlant(const int32_t index)
return NULL;
return world->plants.all[index];
}
-
-bool Vegetation::copyPlant(const int32_t index, t_plant &out)
-{
- if (uint32_t(index) >= getCount())
- return false;
-
- out.origin = world->plants.all[index];
-
- out.name = out.origin->name;
- out.flags = out.origin->flags;
- out.material = out.origin->material;
- out.pos = out.origin->pos;
- out.grow_counter = out.origin->grow_counter;
- out.temperature_1 = out.origin->temperature.whole;
- out.temperature_2 = out.origin->temperature.fraction;
- out.is_burning = out.origin->is_burning;
- out.hitpoints = out.origin->hitpoints;
- out.update_order = out.origin->update_order;
- //out.unk1 = out.origin->anon_1;
- //out.unk2 = out.origin->anon_2;
- //out.temperature_3 = out.origin->temperature_unk;
- //out.temperature_4 = out.origin->min_safe_temp;
- //out.temperature_5 = out.origin->max_safe_temp;
- return true;
-}
diff --git a/plugins/plants.cpp b/plugins/plants.cpp
index 5ab09868f..22e60c0d0 100644
--- a/plugins/plants.cpp
+++ b/plugins/plants.cpp
@@ -113,7 +113,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs
if(shrubs && p->flags.bits.is_shrub || trees && !p->flags.bits.is_shrub)
{
if (what == do_immolate)
- p->is_burning = true;
+ p->damage_flags.bits.is_burning = true;
p->hitpoints = 0;
destroyed ++;
}
@@ -136,7 +136,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs
if(tree->pos.x == x && tree->pos.y == y && tree->pos.z == z)
{
if(what == do_immolate)
- tree->is_burning = true;
+ tree->damage_flags.bits.is_burning = true;
tree->hitpoints = 0;
didit = true;
break;
From 0b80dff09d0c0dbb6dacd469249e738e1cf25978 Mon Sep 17 00:00:00 2001
From: jj
Date: Tue, 4 Dec 2012 17:18:09 +0100
Subject: [PATCH 19/32] ruby: add d-float support
---
plugins/ruby/codegen.pl | 3 +++
plugins/ruby/ruby-autogen-defs.rb | 16 ++++++++++++++++
plugins/ruby/ruby.cpp | 13 +++++++++++++
3 files changed, 32 insertions(+)
diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl
index ff69853af..9d76c6872 100755
--- a/plugins/ruby/codegen.pl
+++ b/plugins/ruby/codegen.pl
@@ -849,6 +849,9 @@ sub render_item_number {
} elsif ($subtype eq 's-float') {
push @lines_rb, 'float';
return;
+ } elsif ($subtype eq 'd-float') {
+ push @lines_rb, 'double';
+ return;
} else {
print "no render number $subtype\n";
return;
diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb
index a3e810178..e657962d5 100644
--- a/plugins/ruby/ruby-autogen-defs.rb
+++ b/plugins/ruby/ruby-autogen-defs.rb
@@ -35,6 +35,9 @@ module DFHack
def float
Float.new
end
+ def double
+ Double.new
+ end
def bit(shift, enum=nil)
BitField.new(shift, 1, enum)
end
@@ -237,6 +240,19 @@ module DFHack
_set(0.0)
end
end
+ class Double < MemStruct
+ def _get
+ DFHack.memory_read_double(@_memaddr)
+ end
+
+ def _set(v)
+ DFHack.memory_write_double(@_memaddr, v)
+ end
+
+ def _cpp_init
+ _set(0.0)
+ end
+ end
class BitField < MemStruct
attr_accessor :_shift, :_len, :_enum
def initialize(shift, len, enum=nil)
diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp
index db94ad650..d75fa2402 100644
--- a/plugins/ruby/ruby.cpp
+++ b/plugins/ruby/ruby.cpp
@@ -578,6 +578,11 @@ static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr)
return rb_float_new(*(float*)rb_num2ulong(addr));
}
+static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr)
+{
+ return rb_float_new(*(double*)rb_num2ulong(addr));
+}
+
// memory writing (buffer)
static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw)
@@ -613,6 +618,12 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
return Qtrue;
}
+static VALUE rb_dfmemory_write_double(VALUE self, VALUE addr, VALUE val)
+{
+ *(double*)rb_num2ulong(addr) = rb_num2dbl(val);
+ return Qtrue;
+}
+
// return memory permissions at address (eg "rx", nil if unmapped)
static VALUE rb_dfmemory_check(VALUE self, VALUE addr)
{
@@ -968,12 +979,14 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1);
rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1);
+ rb_define_singleton_method(rb_cDFHack, "memory_read_double", RUBY_METHOD_FUNC(rb_dfmemory_read_double), 1);
rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
+ rb_define_singleton_method(rb_cDFHack, "memory_write_double", RUBY_METHOD_FUNC(rb_dfmemory_write_double), 2);
rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1);
rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2);
From 74ebe7d2070a4487f9f47377a1a992254167e298 Mon Sep 17 00:00:00 2001
From: jj
Date: Tue, 4 Dec 2012 17:46:13 +0100
Subject: [PATCH 20/32] ruby: add df-static-flagarray
---
plugins/ruby/codegen.pl | 15 +++++++++--
plugins/ruby/ruby-autogen-defs.rb | 45 +++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl
index 9d76c6872..03017a0f5 100755
--- a/plugins/ruby/codegen.pl
+++ b/plugins/ruby/codegen.pl
@@ -698,6 +698,8 @@ sub sizeof {
return 12;
} elsif ($subtype eq 'df-flagarray') {
return 8;
+ } elsif ($subtype eq 'df-static-flagarray') {
+ return $field->getAttribute('count');
} elsif ($subtype eq 'df-array') {
return 8; # XXX 6 ?
} else {
@@ -913,6 +915,7 @@ sub render_item_container {
my $rbmethod = join('_', split('-', $subtype));
my $tg = $item->findnodes('child::ld:item')->[0];
my $indexenum = $item->getAttribute('index-enum');
+ my $count = $item->getAttribute('count');
if ($tg)
{
if ($rbmethod eq 'df_linked_list') {
@@ -929,11 +932,19 @@ sub render_item_container {
elsif ($indexenum)
{
$indexenum = rb_ucase($indexenum);
- push @lines_rb, "$rbmethod($indexenum)";
+ if ($count) {
+ push @lines_rb, "$rbmethod($count, $indexenum)";
+ } else {
+ push @lines_rb, "$rbmethod($indexenum)";
+ }
}
else
{
- push @lines_rb, "$rbmethod";
+ if ($count) {
+ push @lines_rb, "$rbmethod($count)";
+ } else {
+ push @lines_rb, "$rbmethod";
+ }
}
}
diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb
index e657962d5..ffd68bf1e 100644
--- a/plugins/ruby/ruby-autogen-defs.rb
+++ b/plugins/ruby/ruby-autogen-defs.rb
@@ -78,6 +78,9 @@ module DFHack
def df_flagarray(indexenum=nil)
DfFlagarray.new(indexenum)
end
+ def df_static_flagarray(len, indexenum=nil)
+ DfStaticFlagarray.new(len, indexenum)
+ end
def df_array(tglen)
DfArray.new(tglen, yield)
end
@@ -680,6 +683,48 @@ module DFHack
include Enumerable
end
+ class DfStaticFlagarray < MemStruct
+ attr_accessor :_indexenum
+ def initialize(len, indexenum)
+ @len = len*8
+ @_indexenum = indexenum
+ end
+ def length
+ @len
+ end
+ def size ; length ; end
+ def [](idx)
+ idx = _indexenum.int(idx) if _indexenum
+ idx += length if idx < 0
+ return if idx < 0 or idx >= length
+ byte = DFHack.memory_read_int8(@_memaddr + idx/8)
+ (byte & (1 << (idx%8))) > 0
+ end
+ def []=(idx, v)
+ idx = _indexenum.int(idx) if _indexenum
+ idx += length if idx < 0
+ if idx >= length or idx < 0
+ raise 'index out of bounds'
+ else
+ byte = DFHack.memory_read_int8(@_memaddr + idx/8)
+ if (v == nil or v == false or v == 0)
+ byte &= 0xff ^ (1 << (idx%8))
+ else
+ byte |= (1 << (idx%8))
+ end
+ DFHack.memory_write_int8(@_memaddr + idx/8, byte)
+ end
+ end
+ def inspect
+ out = "#'
+ end
+
+ include Enumerable
+ end
class DfArray < Compound
attr_accessor :_tglen, :_tg
def initialize(tglen, tg)
From cd6eb9edd38cf919ba07b158a56656731494a6e7 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 6 Dec 2012 12:00:18 +0400
Subject: [PATCH 21/32] If training ammo is forbidden for all use, don't move
it to combat chests.
---
plugins/fix-armory.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp
index efa9350ff..5a4821b4b 100644
--- a/plugins/fix-armory.cpp
+++ b/plugins/fix-armory.cpp
@@ -110,7 +110,8 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
* 1. Combat ammo and ammo without any allowed use can be stored
* in BOXes marked for Squad Equipment, either directly or via
* containing room. No-allowed-use ammo is assumed to be reserved
- * for emergency combat use, or something like that.
+ * for emergency combat use, or something like that; however if
+ * it is already stored in a training chest, it won't be moved.
* 1a. If assigned to a squad position, that box can be used _only_
* for ammo assigned to that specific _squad_. Otherwise, if
* multiple squads can use this room, they will store their
@@ -158,8 +159,8 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool tr
bool cs = spec->flags.bits.use_combat;
bool ts = spec->flags.bits.use_training;
- // no-use ammo assumed to be combat
- if (((cs || !ts) && combat) || (ts && train))
+ // no-use ammo assumed to fit any category
+ if (((cs || !ts) && combat) || ((ts || !cs) && train))
{
if (binsearch_index(spec->assigned, item->id) >= 0)
return true;
From e1b70d171cacc74a58bbf94b0f1febe8b8cdc85d Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 6 Dec 2012 11:00:19 +0100
Subject: [PATCH 22/32] ruby: tweak is_citizen test
---
plugins/ruby/unit.rb | 52 +++++++++++++++++++++++++++++++++++++++-----
1 file changed, 47 insertions(+), 5 deletions(-)
diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb
index 4c638b1a9..13c3711b0 100644
--- a/plugins/ruby/unit.rb
+++ b/plugins/ruby/unit.rb
@@ -63,12 +63,54 @@ module DFHack
}
end
+ def unit_testflagcurse(u, flag)
+ return false if u.curse.rem_tags1.send(flag)
+ return true if u.curse.add_tags1.send(flag)
+ return false if u.caste < 0
+ u.race_tg.caste[u.caste].flags[flag]
+ end
+
+ def unit_isfortmember(u)
+ # RE from viewscreen_unitlistst ctor
+ return false if df.gamemode != :DWARF or
+ u.mood == :Berserk or
+ unit_testflagcurse(u, :CRAZED) or
+ unit_testflagcurse(u, :OPPOSED_TO_LIFE) or
+ u.unknown8.unk2 or
+ u.flags3.ghostly or
+ u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or
+ u.flags1.forest or
+ u.flags1.merchant or u.flags1.diplomat
+ return true if u.flags1.tame
+ return false if u.flags2.underworld or u.flags2.resident or
+ u.flags2.visitor_uninvited or u.flags2.visitor or
+ u.civ_id == -1 or
+ u.civ_id != df.ui.civ_id
+ true
+ end
+
+ # return the page in viewscreen_unitlist where the unit would appear
+ def unit_category(u)
+ return if u.flags1.left or u.flags1.incoming
+ # return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush
+ return :Dead if u.flags1.dead
+ return :Dead if u.flags3.ghostly # hostile ?
+ return :Others if !unit_isfortmember(u)
+ casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0
+ return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC])
+ return :Citizens if unit_testflagcurse(u, :CAN_SPEAK)
+ :Livestock
+ # some other stuff with ui.race_id ? (jobs only?)
+ end
+
def unit_iscitizen(u)
- u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest and
- !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
- !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
- u.mood != :Berserk
- # TODO check curse ; currently this should keep vampires, but may include werebeasts
+ unit_category(u) == :Citizens
+ end
+
+ def unit_ishostile(u)
+ unit_category(u) == :Others and
+ # TODO
+ true
end
# list workers (citizen, not crazy / child / inmood / noble)
From 9a6eff0370b623f36ae4e4d5f428dc9bbbe20fe0 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 6 Dec 2012 13:00:33 +0100
Subject: [PATCH 23/32] deathcause: allow selection from unitlist screen
---
NEWS | 1 +
scripts/deathcause.rb | 31 +++++++++++++++++++------------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/NEWS b/NEWS
index b8ad53830..9cb34fcb8 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,7 @@ DFHack future
- removebadthoughts: add --dry-run option
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
+ - deathcause: allow selection from the unitlist screen
New tweaks:
- tweak military-training: speed up melee squad training up to 10x (normally 3-5x).
New scripts:
diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb
index ab3e44a39..b85a85ac2 100644
--- a/scripts/deathcause.rb
+++ b/scripts/deathcause.rb
@@ -11,33 +11,40 @@ def display_death_event(e)
end
item = df.item_find(:selected)
+unit = df.unit_find(:selected)
if !item or !item.kind_of?(DFHack::ItemBodyComponent)
item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) }
end
-if !item or !item.kind_of?(DFHack::ItemBodyComponent)
- puts "Please select a corpse in the loo'k' menu"
-else
+if item and item.kind_of?(DFHack::ItemBodyComponent)
hf = item.hist_figure_id
- if hf == -1
- # TODO try to retrieve info from the unit (u = item.unit_tg)
- puts "Not a historical figure, cannot death find info"
+elsif unit
+ hf = unit.hist_figure_id
+end
+
+if not hf
+ puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen"
+
+elsif hf == -1
+ # TODO try to retrieve info from the unit (u = item.unit_tg)
+ puts "Not a historical figure, cannot death find info"
+
+else
+ histfig = df.world.history.figures.binsearch(hf)
+ unit = histfig ? df.unit_find(histfig.unit_id) : nil
+ if unit and not unit.flags1.dead
+ puts "#{unit.name} is not dead yet !"
+
else
events = df.world.history.events
- found = false
(0...events.length).reverse_each { |i|
e = events[i]
if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf
display_death_event(e)
- found = true
break
end
}
- if not found
- u = item.unit_tg
- puts "#{u.name} is not dead yet !" if u and not u.flags1.dead
- end
end
end
From 126c31684ec70e149436b3799326cd8aab5891fe Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 6 Dec 2012 13:43:58 +0100
Subject: [PATCH 24/32] deathcause: ghosts are dead
---
scripts/deathcause.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb
index b85a85ac2..73e29c890 100644
--- a/scripts/deathcause.rb
+++ b/scripts/deathcause.rb
@@ -33,7 +33,7 @@ elsif hf == -1
else
histfig = df.world.history.figures.binsearch(hf)
unit = histfig ? df.unit_find(histfig.unit_id) : nil
- if unit and not unit.flags1.dead
+ if unit and not unit.flags1.dead and not unit.flags3.ghostly
puts "#{unit.name} is not dead yet !"
else
From 885059c887c0da9b8d56b7e5fadc7740f505873f Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Thu, 6 Dec 2012 19:00:48 +0400
Subject: [PATCH 25/32] Add a script to expose the correct season to soundsense
on world load.
---
NEWS | 1 +
dfhack.init-example | 7 +++++++
scripts/soundsense-season.lua | 26 ++++++++++++++++++++++++++
3 files changed, 34 insertions(+)
create mode 100644 scripts/soundsense-season.lua
diff --git a/NEWS b/NEWS
index 9cb34fcb8..a0e01dba1 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,7 @@ DFHack future
- embark: lets you embark anywhere.
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
+ - soundsense-season: writes the correct season to gamelog.txt on world load.
New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
diff --git a/dfhack.init-example b/dfhack.init-example
index 7617b9f6e..1a5aee48f 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -137,6 +137,13 @@ tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
+###########
+# Scripts #
+###########
+
+# write the correct season to gamelog on world load
+soundsense-season
+
#######################################################
# Apply binary patches at runtime #
# #
diff --git a/scripts/soundsense-season.lua b/scripts/soundsense-season.lua
new file mode 100644
index 000000000..6b7d43cfa
--- /dev/null
+++ b/scripts/soundsense-season.lua
@@ -0,0 +1,26 @@
+-- On map load writes the current season to gamelog.txt
+
+local seasons = {
+ [0] = 'Spring',
+ [1] = 'Summer',
+ [2] = 'Autumn',
+ [3] = 'Winter',
+}
+
+local args = {...}
+
+local function write_gamelog(msg)
+ local log = io.open('gamelog.txt', 'a')
+ log:write(msg.."\n")
+ log:close()
+end
+
+if args[1] == 'disable' then
+ dfhack.onStateChange[_ENV] = nil
+else
+ dfhack.onStateChange[_ENV] = function(op)
+ if op == SC_WORLD_LOADED then
+ write_gamelog(seasons[df.global.cur_season]..' has arrived on the calendar.')
+ end
+ end
+end
From ebc2625d970eb0ff6ec871ba9b3301a7004f00c5 Mon Sep 17 00:00:00 2001
From: jj
Date: Thu, 6 Dec 2012 23:46:59 +0100
Subject: [PATCH 26/32] ditch the unused Vegetation module
---
library/CMakeLists.txt | 2 -
library/include/DFHack.h | 1 -
library/include/ModuleFactory.h | 1 -
library/include/modules/Maps.h | 1 -
library/include/modules/Vegetation.h | 48 ----------------------
library/modules/Maps.cpp | 1 +
library/modules/Vegetation.cpp | 60 ----------------------------
plugins/cleaners.cpp | 1 +
plugins/devel/tiles.cpp | 1 -
plugins/getplants.cpp | 2 +-
plugins/liquids.cpp | 1 -
plugins/mapexport/mapexport.cpp | 1 +
plugins/plants.cpp | 8 ++--
plugins/prospector.cpp | 1 +
plugins/tiletypes.cpp | 1 -
15 files changed, 10 insertions(+), 120 deletions(-)
delete mode 100644 library/include/modules/Vegetation.h
delete mode 100644 library/modules/Vegetation.cpp
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index f67b6fe44..784b54c90 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -121,7 +121,6 @@ include/modules/Materials.h
include/modules/Notes.h
include/modules/Screen.h
include/modules/Translation.h
-include/modules/Vegetation.h
include/modules/Vermin.h
include/modules/World.h
include/modules/Graphic.h
@@ -142,7 +141,6 @@ modules/Materials.cpp
modules/Notes.cpp
modules/Screen.cpp
modules/Translation.cpp
-modules/Vegetation.cpp
modules/Vermin.cpp
modules/World.cpp
modules/Graphic.cpp
diff --git a/library/include/DFHack.h b/library/include/DFHack.h
index d606df94b..8a094cf86 100644
--- a/library/include/DFHack.h
+++ b/library/include/DFHack.h
@@ -61,7 +61,6 @@ distribution.
#include "modules/Translation.h"
#include "modules/World.h"
#include "modules/Items.h"
-#include "modules/Vegetation.h"
#include "modules/Maps.h"
#include "modules/Gui.h"
diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h
index 1f3d4222a..87c9a726f 100644
--- a/library/include/ModuleFactory.h
+++ b/library/include/ModuleFactory.h
@@ -33,7 +33,6 @@ namespace DFHack
Module* createGui();
Module* createWorld();
Module* createMaterials();
- Module* createVegetation();
Module* createNotes();
Module* createGraphic();
}
diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h
index 632e8ec13..82f79e94b 100644
--- a/library/include/modules/Maps.h
+++ b/library/include/modules/Maps.h
@@ -32,7 +32,6 @@ distribution.
#include "Export.h"
#include "Module.h"
-#include "modules/Vegetation.h"
#include
#include "BitArray.h"
#include "modules/Materials.h"
diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h
deleted file mode 100644
index f293ec52c..000000000
--- a/library/include/modules/Vegetation.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
-https://github.com/peterix/dfhack
-Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
-
-This software is provided 'as-is', without any express or implied
-warranty. In no event will the authors be held liable for any
-damages arising from the use of this software.
-
-Permission is granted to anyone to use this software for any
-purpose, including commercial applications, and to alter it and
-redistribute it freely, subject to the following restrictions:
-
-1. The origin of this software must not be misrepresented; you must
-not claim that you wrote the original software. If you use this
-software in a product, an acknowledgment in the product documentation
-would be appreciated but is not required.
-
-2. Altered source versions must be plainly marked as such, and
-must not be misrepresented as being the original software.
-
-3. This notice may not be removed or altered from any source
-distribution.
-*/
-
-#pragma once
-#ifndef CL_MOD_VEGETATION
-#define CL_MOD_VEGETATION
-/**
- * \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
- * @ingroup grp_modules
- */
-
-#include "Export.h"
-#include "DataDefs.h"
-#include "df/plant.h"
-
-namespace DFHack
-{
-namespace Vegetation
-{
-const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
-
-DFHACK_EXPORT bool isValid();
-DFHACK_EXPORT uint32_t getCount();
-DFHACK_EXPORT df::plant * getPlant(const int32_t index);
-}
-}
-#endif
diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp
index 363de8064..38f8bfb9f 100644
--- a/library/modules/Maps.cpp
+++ b/library/modules/Maps.cpp
@@ -59,6 +59,7 @@ using namespace std;
#include "df/z_level_flags.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
+#include "df/plant.h"
using namespace DFHack;
using namespace df::enums;
diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp
deleted file mode 100644
index 9b14a3cc0..000000000
--- a/library/modules/Vegetation.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
-https://github.com/peterix/dfhack
-Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
-
-This software is provided 'as-is', without any express or implied
-warranty. In no event will the authors be held liable for any
-damages arising from the use of this software.
-
-Permission is granted to anyone to use this software for any
-purpose, including commercial applications, and to alter it and
-redistribute it freely, subject to the following restrictions:
-
-1. The origin of this software must not be misrepresented; you must
-not claim that you wrote the original software. If you use this
-software in a product, an acknowledgment in the product documentation
-would be appreciated but is not required.
-
-2. Altered source versions must be plainly marked as such, and
-must not be misrepresented as being the original software.
-
-3. This notice may not be removed or altered from any source
-distribution.
-*/
-
-
-#include "Internal.h"
-
-#include
-#include
-#include