From 5a61d563d86d08d0143251ac40c8a08b1885577f Mon Sep 17 00:00:00 2001
From: Joshua Barretto <joshua.s.barretto@gmail.com>
Date: Wed, 28 Aug 2019 19:32:44 +0100
Subject: [PATCH] Added modular building generation

---
 assets/voxygen/shaders/postprocess-frag.glsl  |   2 +-
 assets/voxygen/shaders/terrain-vert.glsl      |   2 +-
 .../world/module/human/balcony_upstairs.vox   | Bin 0 -> 1416 bytes
 assets/world/module/human/base-center.vox     | Bin 1760 -> 0 bytes
 .../world/module/human/base-corner-blank.vox  | Bin 2092 -> 0 bytes
 assets/world/module/human/base-edge-blank.vox | Bin 1940 -> 0 bytes
 assets/world/module/human/base-edge-door.vox  | Bin 1924 -> 0 bytes
 .../world/module/human/base-edge-window.vox   | Bin 1924 -> 0 bytes
 .../world/module/human/base-inner-blank.vox   | Bin 1884 -> 0 bytes
 assets/world/module/human/chimney_roof.vox    | Bin 0 -> 1916 bytes
 assets/world/module/human/corner_ground.vox   | Bin 0 -> 4012 bytes
 assets/world/module/human/corner_roof.vox     | Bin 0 -> 1676 bytes
 assets/world/module/human/corner_upstairs.vox | Bin 0 -> 4012 bytes
 assets/world/module/human/door_big.vox        | Bin 0 -> 47099 bytes
 assets/world/module/human/door_ground.vox     | Bin 0 -> 3992 bytes
 assets/world/module/human/example.vox         | Bin 8764 -> 0 bytes
 assets/world/module/human/floor_ground.vox    | Bin 0 -> 4012 bytes
 assets/world/module/human/floor_roof.vox      | Bin 0 -> 4012 bytes
 assets/world/module/human/floor_upstairs.vox  | Bin 0 -> 4012 bytes
 assets/world/module/human/stair_ground.vox    | Bin 0 -> 2040 bytes
 assets/world/module/human/wall_ground.vox     | Bin 0 -> 4012 bytes
 assets/world/module/human/wall_roof.vox       | Bin 0 -> 4012 bytes
 assets/world/module/human/wall_upstairs.vox   | Bin 0 -> 4012 bytes
 assets/world/module/human/window_ground.vox   | Bin 0 -> 4012 bytes
 assets/world/module/human/window_upstairs.vox | Bin 0 -> 4012 bytes
 server-cli/.gitignore                         |   1 +
 world/src/block/natural.rs                    |   1 +
 world/src/column/mod.rs                       |   8 +
 world/src/generator/mod.rs                    |  20 +
 world/src/generator/{town.rs => town/mod.rs}  | 623 +++++++++++++++++-
 world/src/generator/town/util.rs              |  42 ++
 world/src/generator/town/vol.rs               | 203 ++++++
 world/src/sim/mod.rs                          |   7 +-
 world/src/util/grid.rs                        |  23 +-
 34 files changed, 909 insertions(+), 23 deletions(-)
 create mode 100644 assets/world/module/human/balcony_upstairs.vox
 delete mode 100644 assets/world/module/human/base-center.vox
 delete mode 100644 assets/world/module/human/base-corner-blank.vox
 delete mode 100644 assets/world/module/human/base-edge-blank.vox
 delete mode 100644 assets/world/module/human/base-edge-door.vox
 delete mode 100644 assets/world/module/human/base-edge-window.vox
 delete mode 100644 assets/world/module/human/base-inner-blank.vox
 create mode 100644 assets/world/module/human/chimney_roof.vox
 create mode 100644 assets/world/module/human/corner_ground.vox
 create mode 100644 assets/world/module/human/corner_roof.vox
 create mode 100644 assets/world/module/human/corner_upstairs.vox
 create mode 100644 assets/world/module/human/door_big.vox
 create mode 100644 assets/world/module/human/door_ground.vox
 delete mode 100644 assets/world/module/human/example.vox
 create mode 100644 assets/world/module/human/floor_ground.vox
 create mode 100644 assets/world/module/human/floor_roof.vox
 create mode 100644 assets/world/module/human/floor_upstairs.vox
 create mode 100644 assets/world/module/human/stair_ground.vox
 create mode 100644 assets/world/module/human/wall_ground.vox
 create mode 100644 assets/world/module/human/wall_roof.vox
 create mode 100644 assets/world/module/human/wall_upstairs.vox
 create mode 100644 assets/world/module/human/window_ground.vox
 create mode 100644 assets/world/module/human/window_upstairs.vox
 create mode 100644 server-cli/.gitignore
 rename world/src/generator/{town.rs => town/mod.rs} (50%)
 create mode 100644 world/src/generator/town/util.rs
 create mode 100644 world/src/generator/town/vol.rs

diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl
index a9316ac993..f653f1d30c 100644
--- a/assets/voxygen/shaders/postprocess-frag.glsl
+++ b/assets/voxygen/shaders/postprocess-frag.glsl
@@ -170,7 +170,7 @@ void main() {
 	hsva_color.y *= 1.45;
 	hsva_color.z *= 0.85;
 	//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
-	vec4 final_color = fxaa_color; 
+	vec4 final_color = fxaa_color;
     //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
 
 	tgt_color = vec4(final_color.rgb, 1);
diff --git a/assets/voxygen/shaders/terrain-vert.glsl b/assets/voxygen/shaders/terrain-vert.glsl
index 14fe299613..bbfe953324 100644
--- a/assets/voxygen/shaders/terrain-vert.glsl
+++ b/assets/voxygen/shaders/terrain-vert.glsl
@@ -47,4 +47,4 @@ void main() {
 		proj_mat *
 		view_mat *
 		vec4(f_pos, 1);
-}
\ No newline at end of file
+}
diff --git a/assets/world/module/human/balcony_upstairs.vox b/assets/world/module/human/balcony_upstairs.vox
new file mode 100644
index 0000000000000000000000000000000000000000..e06d2de36364167dd23adfc1bec4e406c72f63d9
GIT binary patch
literal 1416
zcmeH`J4ysW5QhKmndxc46Bvkrk>U$P5KMO1K_>8lf~=bjOtz7W;1Ns=j06KyFCcgT
zy@3~MzgiO$6&KC6;iIa%tNyAfrq7NBOLvkEdd(q5mxXlN4C{H@u;xB7I3G5BH#ycZ
zCVJoIQC-Y^o0$#1jb=-A1vZnq9FXh+ln~92mgid!B1hy$r%vd3aR&h+$gJm%*a&5G
zZGZw|W?Iz1&%lGTb0fDtumK9&LuNjGSdSjxfm~ZcfC6H$o*i|qe>B0_9!;U=IT!h|
ze)&#WZ{&zxL@l8&>WTWIrYDw>anBhw-6R(u&6EAAN8DsN=hCz!*0i~^ypOr>EH0#G
zWjT%Zw$r%3`{O+CZ!XgF<8^v@x=nAd4?o7w=KQ<=du?@;HrB^!YxC+)W?NNNs_Xho
OEgoU|Pwb6;qpx@PjXo;?

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/base-center.vox b/assets/world/module/human/base-center.vox
deleted file mode 100644
index 14ed7d12dd4879e4c826f8342006fc708573579a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1760
zcmeIxziU)M5Ww-7o&7O_e?f|1VHGhN1i|u74>?2=5+X_Zyv8ai%s-JLAP6Z0u}zu(
zAZe}Ay}xGtPJ-oG9Hz^I4>SAT?!0%id!IM=o_rUPkCXW)g8Z%`Ta&jRatyc>JM)9t
z<J*zQBjR6s`v>#WbobyFA*&8r^iW{rp7pS09kS?9iG{I|m&85BhQvK3vUpI|NUSr~
z8Ea6;GcgfWWlS(+6jZdx8}k}9p}a#Ab79CRsA%9<4T*1PjeAgIE({q36)kd}XBbk<
z%|sY73Mv|u=dChEffnY%kWo<4V$5?xig}=YBfTDJ=V+k+V*%SSHaOUf(INZ>kKe)f
zIfQpRgt`u)o<~i-$01H_Bp5OZDjFP#{wci^)Joqg{UjO^xe_(8w}Ctp4MidB7wZza
zQzykZ&VjQI&icx@6nY-@oVwi4DCCvcVjj6OC&&3gA{X{v<1A3$BJ$1#=N%sJV;?K~
z*-$w<4cqUh6MAxcai;pRI@hvmAJ*OV{q^cfpRYabyBll$_}!a+arCNx-CtkW*Yne(
z{`1>Ozq&l@H`hNF&U@(Za_9Q_voHO{`f=ZQdGcR+JDpDZY&Kh1i$7t%Pw^l9M}7GN
Dt*?sV

diff --git a/assets/world/module/human/base-corner-blank.vox b/assets/world/module/human/base-corner-blank.vox
deleted file mode 100644
index 7523e5ed3d36fb7907d75ffa5ae7dc9a03b77c2f..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 2092
zcmeIxy=qiJ6u|MB^D*ZLK7d$=g;m685CqGtF0zOpC`yv<7D*#XZc><+un+{nLa?{=
z4fGAH+y}9~{%3<_EP{4k_;F_LoSD6QX6EISt*h@#DUUW6j|t^cmhyD--b3pho{Hy-
zo%zL8uapbK-?p}Q7C-cK;3pxZjUsIj)58#_?Spu48*Lk;^|l^1I1CCA9VGgg>^G4E
zCmK<SA~HzoV3K34Whqb?91Q_wTqFlk8&hJeLM{{rM?<W{*vN_C9jcNGg~8Dfz0J5m
zAwAcKP#7Eyfy#I^`fyYy7Yc);A$p&2h4eg7z9qF@3MlE4`Y#;~0k&nyW-Jqh|Bxtr
zU!w5aiNd;x!g|@)<a-h&SsMa{!O;*%NK(6?h7AsbB7^raI`vBSBv*UZYn)@Cv9}3j
zzZ-pDy>s3<@7ic^G$h3OS%^Vd&mh|OxYDoedEIw31QOE7F*#^a_f+ei!L{x-VjyXY
zljE#obzhAfxRFB)ByBNrnjDCe<8{~IUiTU?khC+&Pv*gC8&M%yhiHs9+QIKAwFu_%
zO_H4V<cv1fCCA|8HKGLnS2^Fo7`X;#o@oDHjCrp|&p~omf;-dTFes9H6V$B1jXO|>
zO1)|oBB~Nmjfm<*)J8-NB5D%ZW9Ca0aOapK<O{h%o{(egrLlLuV&k(1vrW#|>Z!F(
z=QZ)1ckcgrr**$`<?^&xyEeVpSeuUT-<>`m-JV`=ub)}h4{wg9Pw!5quV3Cy-@ko4
sGw*+W&v&kGTz@s)TtA*RZk_y<+RkRPX+EEyxfYjj`j`40{log@7hrX=oB#j-

diff --git a/assets/world/module/human/base-edge-blank.vox b/assets/world/module/human/base-edge-blank.vox
deleted file mode 100644
index f93dd6e635997a8abc3fef072a383a9dd93b958d..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1940
zcmeIxy=oOf6u|L0Uo%JW2`t3IDq=JUf}Oc~2^aALMM+Wyl18F8DNO4FSO|h(A=q2`
z2KoSk-9F9u-wl@6B50R|ALq=>+1b5k?!I`uedS{*<>BVy5uv<or99cZ_rS*im*ClA
zcYbjim2!dj$M(+d;;x-8{3JB3ONF3mkOz5?JGqlPxxKCkI1CCwQ$4JzuY%}ABPvm*
zaV|y5pfETB6%D4zGpIpoBT@^4BT&&MZCR;ljX8)>3xgw2(Z-y0he5|$C&J(eR5S?d
ztujVHf?60Hfr>VcSvTld2iiB#>p?+F2l_t*DmaoM!C?%E!f!|v-Y-#jcA~IvqOf0%
zd-6VslI#tI!4arvNEDU5q6&i}P|=WkLBEo-$mz_SJUPpxk7Mpj<D4twnC61HV6HA5
zfr^B%kHX*(70y@=4URxXL!y`*2x^l9BRaxlzY-ggyf`&bmwh8Tg1izNlDvfM3pCh=
z=m_#kY)JA<>XZ8r<W4l`IA2+>?7_Go8~5=}g?WLpW<*DjS7KpZpwt@C5#-7{2mSGh
z(;r_x-I{H3j?*O~{j56IS?@i+@3il?FJJ15wX6O4##%qVf2V&vy4Bz8tgr0rr*}vF
z%ZHQx{oDKg>&NGnb?5y(+yDCdwU_<I`f=a5dGb$sJDbh=d_G^f7aw8&pT=+WFZ-9@
Dw$q^S

diff --git a/assets/world/module/human/base-edge-door.vox b/assets/world/module/human/base-edge-door.vox
deleted file mode 100644
index 951f3b201ecba3e2b8a62416cf057f51fc168417..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1924
zcmeH`y=oOf6oBXa%^bleun-HYh%rGBEOYfD7x4#0Nm7QJG!osUFfU;t2!e%RZ|NIo
zX=k@jGk$x6Wh{brS@@VU-<+M@J!kgilkICCODT`G7LOUq&`No_b?>216E1=0i{1I<
z)vT0DjDOhP*<Ebd>B3KjrgcydXd1*p9K=rS#7=B)s@gJD4jUM;;sl!NVO3pLm1Tet
ztLkgPyXGKLMg;?hfC>$ab)9k`wK0+l1`Yug+KM(*a$3WI7`b5J5Ky5_G3yQnI^{Ye
z7&rt}XdtY&${YeD$OQw3fC_Dzvu>bM9#FoKT8|2pbfo@cKm}SdCeVyAq3{g}h4)J+
zJUgMVZ$e?eoc82>5=yc+C>S^dRA`V;RQ8H07&rt}XpnnBy^>xeeKK|Wm-I5J;}rW~
z8uTrPSh>Ijt}Yz{D&*>HH2Tz7YlDMu26S}}gj{+qLT`oM3cVG+Pxb>8(jnv^D`SI{
zgTMtYaDfY);uNPi#VM{CIE3uC%Ge-f-|>!jyyG43c)<%^@PZe-;48l3E570@zTypU
zc*7gs@P;q=f-m@jFZhCg_F%R}-&U81^z+iW&U^30eW!K5bM;DJtX=OfHrM*`{k#3^
z(e3_rXZ_5&etLJ*zkE39-@m=@zkYl^Gw-6l=R4OoZ@lWa){pz<#>qda?QAyd^ZESD
Qz4!_H|77|{|FVDi4Vj{!)Bpeg

diff --git a/assets/world/module/human/base-edge-window.vox b/assets/world/module/human/base-edge-window.vox
deleted file mode 100644
index a96fcb21da86d32f984b56e5f709da66d36130ee..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1924
zcmeIxy=qiJ6u|MB^ErY~U?CP(5o3ZNSY~yRMf^Zfl9XYSMxvV(<|QlyL9h_)Eqwzm
z?d<N;jQ_jAaxE6^a^c50Gw0mdyEFH`e6oA(V=3j)&g?OvEV7iRJNF*iIN+*yKHHyO
zUXDt+MEqlSZ-2I>=Y^kylvoHVQk>k$o!rQc+{pD!h|V1bg)mo<BF7=}+KEO~q7dtH
z+!nQ#L7*@=0tuDIZ_tA#Nu(DFgCmejxd?hPnL|~2p)fcCIp)+23K@Hi2!+8BNL1<>
zjNzzGFBAqxAjdIvg^Yb*`v&%U5U`~K`#(4W9LdnYVGIq0|Iko)zJ|hYHx$-wD6H4U
zHF=(f(pVb;g~1U>G!&@^MFa|iBamnZ&VDt{qH#_tHgD{6B9^%=RD2$ceL3gUow?wQ
zgR>55!QA+6B<GaWa$%@aIpcb)FgOB<hNAVTBC;MS(coGSgP3UK#pr=pJyfE>$%B|^
z<i+b@g*fXF4Ne}!L?dsF{>FW9aw95aoDb^38jK5Cav#rBn6D6ON;Eim5DWDRp;w6p
zCl{Xi*@MXr=eE3Rt$kgcYu$S<?mOT2J6Est*~az$Vtb>X-oM+wp4{$l_cmAV>!)`o
z{mX~5{{7qg{_Dr*m3<fcyWV?!^Tw-wYxA^kZ=L;<y`4-ZeL9`4T#Jve|4-vL`j_>~
EZ$Y1)Q~&?~

diff --git a/assets/world/module/human/base-inner-blank.vox b/assets/world/module/human/base-inner-blank.vox
deleted file mode 100644
index ce27fa56d22a873ee452575dd4fcca8eeabb3329..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1884
zcmeIxzlzgA5Ww-7+1=R@d;$xxusZSd5CqFag9h;rigI^dilmX;<*x8v!a@)P3&Gyf
zCs5zO?)Nn7cXL?AB4~Gk4>P+vyT4y%lh@DouYDAeC!^_8f;_4s&qwzk=RV-0csV_s
zTwaevE)oCOKRBF5I$!ussOBrzSh>WiS|P9Gl{}NPCQhD?-O|jJb?G&%NncOL)okfz
zGZ$*qj$9b4E?snKN$09E7!ooH9JS|F=eg=4A*`;{s6|3XfupYRURGzdG6zl6A|a!|
zQG3pMMuPR)OhiIPfullMug(|+TBt=rMuDUDG3yD|>p=SkdOa9u=|KO70tZ(z1h|YL
zAbf{_@P71v5S|?n&J76XML(1G2}p1@3<((pj*5WMKc#m9cU=^xpTq<sS7MkQ1@|ZG
zyx%c*ZFv^lZ|(amX2Cgbg}JhlU>}10Zq6L{&t418Wj>>jJF)gU@+kcromJCiK$0#L
z&iRR$QTQ!5u_DMNQv=I7PsEHu?!<~9m%_WiI6o0HN`Dry!UW13u@I9{1A)Sf-U!cu
zC=rFZ#5f}{&Uo=~JmNmqA8OjR8lP?VUhF&X``xQo+H~i7d$qUIP9NNBUr+9|w+Fi$
z`}*nKN&E8Qto``*zWw_7d1Kv0f4BQx-@Ngr-P%2Ed$-U2NpHvFahpsg8_(iT*#4*f
JAN|Yu@*Cw3nE3zz

diff --git a/assets/world/module/human/chimney_roof.vox b/assets/world/module/human/chimney_roof.vox
new file mode 100644
index 0000000000000000000000000000000000000000..e8e49926a4339631562d3f6ca7557dda14887b1b
GIT binary patch
literal 1916
zcmeH`yNZ-S5QV$DZY}r(24Y~OcmojylN}sn0u>Zw-P8$$joidXFflL^3`~6i!3WTw
zBNMa#Y1*&G#6%Yt&Gv`G={kL?y1J(4>7)G%?@B2T_BIcp@*+xkvN>3v&4BOBFW7%}
zu(>TexqO4Xwb8X~4OY{|RfjrIT}qTuJ1AxJ^tJN5n~iWE4J~>MGh2$|iL!0IY#kIM
zc49OKf)>;jk)>DmYNc)+ojTTSbm};DtJM*?7cjBmH@D&GqaP0+Md<}kh|sr=B4t?E
z(9qGO4N+ODaW0<H%4qc66urx6y^Wf8Ah*$<X8k-*flw2#82PQz7d7xOBKGJXZq5u~
zaT%WHbGO2L1h%{<d&(>$K@UNGpa-=yk+~t9$854kAb2c$!Kd`1XqNlHfn*F6kE9-L
zM8pt!_3lHme<!bVf8(yi!HhdScXB%3o@e7X&hON!l{%o{b2MWuK!Kx0-j_K!?6AQG
zE3B}<0yntrVqH{pF(AAl;kOdCMR^-)z8GthgyTbl1(swyvh(1BMe*$XL0;g2EvN%n
zOKPl5I{Uynp3WXjEX11N9T-r4Puqc>q~UAiHoUBX0WsFVlC`1%t7t%214*C@+T;7H
zJ>KE&<1NpVV)C6d_cZV4+;5#fH#e6q&cnMm=h3}8$IkQpo0s$R$Jg`Ar?>O_*AK_~
rPv`u1@B7N-!+GuM(Y$f})jyf-YPFi{_4>E9_!G|miTscLp?>)R`v$U~

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/corner_ground.vox b/assets/world/module/human/corner_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..ce8b11c4dff312dffd469d33af5822d7814cc04d
GIT binary patch
literal 4012
zcmeH}yQ(Es5QbOXOBCF(ffyJm-arJwWDXwW1T%tyjFX-Tgn`}LNqhto10&JE)E5wZ
z0DS|qb6%$XYMYqo;Go%T_^GPzueG{+t?tGC?Bn~de7DxRd*|vNVSWGNTAyA$xO`y>
ze$V|y_dkDd^~LjNyl!+Yk-m0WHzIW{k|>ZPLwemN58F|oM1@)&7QEmEFL=QVp74Yx
zTvV<TFYyvD@B%OJ0v_;yi^#R(IiBM=p5r;5!3}P3gB#q2Gq<CdYoJ1nv{0i&fgG7!
z+gi6K>(<4Ort8M3W1bsN=n;B_F~WFZeBc*-jdDb9%(%(NW3D1Lyoo8cNz9yfbaEvx
z(c>Z-F_2H~>t-3(^>r&Y{jsIQQRm)S2b;A(ff5yJ<dV#hn=&qmMcqi5Lqaz(=I2^c
zTq|b&xgR^)<VjwrZ8NSf>6^_xly5%w(SEGC2cAQc$g@cWbD~C(o8(+CHsu+FK4l&C
zT<1|{9X&VRs@ruQOTW%|JSFeky4OviNK~w2vKK@nTA^kw@SD2fMJ#yIpVW!x^eDB$
z1z+HcxF(K=1U1jrJZG^sMa)?v_DyH+re>Xb_6G&M!5TPm={#3)&vi<kFT_uVnmsM{
z)bLzt3zXy~g;zXhox~XzC{e+>j>ec~y;Ag4%-84nf;obDSr^xnBzxCHoBi^mxHF%h
zBv?P6y?`rgt`q&_(%e^bFU`Eoel&A8b2np~aYSqOi>TB^>^|kPLm^v%9Oc2u^;{dd
zlMgTQB+o<O$=O$!!3^fG>1$ZQ3YM@u<hfVi3Rk$n4eqdpHLPF-OYSG&0vEW#73Snk
z-t-i89-6+OM^Mt!^dx@a_xZX)K75|Bi8D@!!={$QvN$Y7j?7UWmJB0T@)(cY$<0t5
z7S~+@6PUsjmau>Y%wY~QMNL1P`&j}PxWW})j7hx23%tN{JjXNcjr(5Q^AecA6xNI>
z#uQ@`Kk)-U@O{RR^K2a8^wQV#6@5it;w4^8-}vx`7rfv(jJ(Nfa@L=@6FCa*EtsQ*
zktey0NABdo^9FM@tY8I8Si%%$FoQYFVZk~C*K2sg3tsSqCtTqQH@Lwa?$b}*)J0v>
zN3GOGZQ~PrI8lkg+9q-o>`kz4HH<vTZ9H-(5B4}%uZ9(@U<pf@!VG3GhdC_R|6m;(
z-tdAKJmCpfxWWx?aEJT!Q#W-{*Yr^<wNcyn#2!vmV)C983Y5HO$ve=)$cx;^BM<WA
zeNFbhshhf}i`wXMn8N}Vu;iU@@+L2GrN`*8nG^2tfG6KW6N{+$if{Oa@9-FZJea}^
z=2H`GA}ZeZMv02=rFfT{*u)r4p5(<h*T`W33s_PY-)JL)JKSd+JYmIm+(_{Z&+r^C
z@Dg5p<Bb$g@oeVD3%ndoZPZR3Q<EFHPfoOnDEPh;ISS5%;M=WX<VkMhkvn;CmIU8v
z!wOcgge6R21~Zt$92T56!S~wmh8Mix2~W7f6>e~YJKU$Ax~Yr0rjJ^wjoQX1_Hd#S
zgL5X4qu`th&X5{Lp5!(jxswOyU2t|ZtY8I8Si%%$FoQYFVZm7#oGA@&c)<&v@PsQ|
z;RZLj!+rXxo4TlL`lyxKsBL^=4<{-yIPVfU3eL;mT&rQ^Np9njJ9%&h2j^SE3RbX$
zB}`!kGnm617M#t&8QAcK7rfvJPq@MrZg7J;+^3(qsf)U%k6NjX+QujLaH0}F`RL*f
z|NG8A{_E~ftDWD|j(s-n&vk$A<(KxW*IwNpeemx7_`~;~xSl_J^X2}_Phao9{`~F!
z`)@x!vH#gR|GW45jn^OTZ@u|=f9LJ5{*$%6xVYFaFE9UgE<VEj|I~b=|1kgh0~`?O
AjsO4v

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/corner_roof.vox b/assets/world/module/human/corner_roof.vox
new file mode 100644
index 0000000000000000000000000000000000000000..dea1a89941406c87b2408b119f59207dd21428cc
GIT binary patch
literal 1676
zcmeH`J&IL95QXb@*G~(sz(5R)6#qa3!DJpj^aUy?$T+Dlo5)FY2POuZ2nMFJfdLnw
zLFI0!{c221ba2pYE*uV3ed=^o-Fu%u*}e2pL>}#I9z*iRMV@W;*5@nWIrj^8U+irj
zw&S#XfV{OFDqDlqG+i~-0W2Z83?P!DbWL6kvkdpdB4b1vu{fHLZ5y(6AV%!O<Q#Nz
zfnV=E$HLjN;Jf6&$A;y=$MIQg$*8k{i4DEErI(I+4^L5gK@;UwJ$6Y8OAXD|TXyaE
za4#BZl8wHbk{><WXt~r3)HeFl@~`_8bUe{^qrNTlg$Ei|#2Nj=&DjCmy{Fgx%qHxo
z!&Y;0rtC5b^bphs`M^sHRU7zo-l|8?(OC6@PU%I-S)Bt1y<(tfdgY^KL}aH|AD+GX
zcj^Z78?zDzJ0A2rs2ONS-HqP3f8ez&_y9r2r@{{K1`3>9)V%D;VTTPiSYd@b+~Ec{
z`00by4)5jo;oZ*DV)C4}_N?yb-tSzzFgI5&&;5J1=fVBEC+_ppyI1qe;oJH3=>7cu
u?emHHvpxUa_kHc^{=9MhVBWg<`k(A}wOY;fdi`53e!}@bQUB;4{N)F6k9-sW

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/corner_upstairs.vox b/assets/world/module/human/corner_upstairs.vox
new file mode 100644
index 0000000000000000000000000000000000000000..b296e22caf83c4fba65fb40b53af835db69224d3
GIT binary patch
literal 4012
zcmeH}y{aWe5P-Y>OB6h@ffyJm{(%UB$u3^x1}Z4Xy6M?KSeQ9(;v<+C7>Nd^zJTBZ
z=o^^%G~-vt#6%Yt&AjkYRbTbY?dh5Bdq4T`-V5KXwcfvT`vGBn`|MgD-`>A@W(t0u
z`V;p)y?^`J(^tKI)3rp`S)|TlM2CQcf?nsg^`IRB5(;`fD16}yU--foKJkf9{Nnng
zm-Lce&<lD&FYtj6yvRB`J*VgNoSxHjdWJW=;SFzi!z=ez=BUh7nX@u?Uo(H%#blEL
z*Ihz4N!@v3o--kMHoP>soEN`)k*oHp8N-sC3tIXj&A7B(3dC6t<6gei^hD2jjdqTS
z<-5)AFyEyif@@=oaZX;KzAx5$vDP{MIzBd0dL8?k_1C<Jf#)Ux36=DTE*DAJ*Sw=9
zw|PG1<jl|PBEkLV-MMdT-k-7t${3~RMZY$&VqQe0E@Ib3nU69r1BZZwf(8}YT6|B|
zswMXzH_=@sw-nn#>|)TOtj&1t1%4gR8W2-_Q?AqN_~QE7oclUaxh7>?>`vdsx!;Vh
zOG#{Y{XN0&oqj?vALdD3T%Yud@lD>$qa-hRok!peW~_4G`8`5^o=3Q^FgMny=lwxK
zfP;bdMR~Ud4ojQ$=#zn3pK87tqK_kLgZC+^i@K?sI;fr6sEt|+f^oRQ6|QlO3!LK|
zXE?)YG8UJ(!WFJ@feW1D9A`6*JjrYN<U#J^rv}wx{>A)@d6N^h!Ryr20gf}A;j|!%
zXRXr;J1vKVQ1H{TapX>3(<4vv5Qx)KoZ$@TIL9@vaD_`;;v!E=@QPQw;SKNj#x<^R
zg-aZBYUarS2K1Dk(ldI@do^`a7j@3uS%Z1L>oxR*p3<9|HC#>3vo6-id3K+B2yUL6
zcd~Gc^|Toyz}g#gQ10zKLx6$v+|E369C?uI^vI3eSp#Qog-cxG0v9-?R%)X*YG)0a
zafL5@;u9ZuC0BAIcb>f&(~K#6;S(QtCEE1yjW6mPKaRX6g7ZED44n7ryvH0z9^^Vb
zawB)%r}O>_m$<|QE^tb%)JAR8&ii!UUo*Do6}_aF^nzZ{Q);C)YMZf)YsMA6@QKeE
zN7U)#8(-8lHL;1#cNt*de5=m)nB&NUT&G8F<j!~Oe20ZgT;c*3IHgu<qc&>i8*j!H
zzVL}peBhN_$&K9k)|)ZSn8Fu6@qt&OO&{O*qR#Q-$ZH}5dndsm*k8dOsd3~<Zqp-o
z@?g&e`=W7$D_r6dr#QnI&T)<l_F&^0SGd9@PH~DeoZ%c7?7^mP>Y`3+rB-UA_URLA
zJkh3}9D@Cv;1HYv!Je&g<VkMRBX{!PJP7t_;|f=}#3fE~hBKVw92cAkjcZ)t3YR#=
zDb8?)b6jv9G<8!Kby6#}QX92TpIGCGHudC?oErrJ$$68U4?T{&$bEX`L7tplP2S{1
zZuFemse?K>!<tw`rDybP?tu$ja%MHLh)PfC89m1ZE;*-~SVYAe-l=2!IBKKz$%!@*
zlC!BGAUUg&v#ZC=c~0)rBM<WAyi3lwrf%w@uK63l7{@s-aDhwC%qDO0ntXANKth0n
zfr0@4Wip1n3JC!Y1_}ZTda(~6A;7^vL0~~IzBxz;a4=91SkQ}i3JC!Y1_}ZTda<66
z5a3{-Ah4hp&j$$s4h9MW3wrUl77_v+3>0Mbc@aN)@9rJ`x4ZuEKj-nJ9Q-`#wf|rD
z$GX4u{By_cOD`S|-+A+R^zPe_UC-~n`uzCmhcAzxfBgFR^_TA-JO2N5{%7C!D=$Ah
kUVHV?@y6?4{3~mF_wL=}=H})v@5LrO{!3<${>}Z@?^D~f*#H0l

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/door_big.vox b/assets/world/module/human/door_big.vox
new file mode 100644
index 0000000000000000000000000000000000000000..a6197d5344945d6c1cf50afd7612752d98307b2d
GIT binary patch
literal 47099
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

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/door_ground.vox b/assets/world/module/human/door_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..f5465e94b316d9d9eea26dd534df7f7a754fd4e6
GIT binary patch
literal 3992
zcmeI!y{c_j6~OU1#+YBC;I1tsg;m5a5J9lXB^S9x6rxB>drlWfj%j=W!N$T$u(0(7
z1Ro&2f!N&hGTYypHa0OBw9A1%bIku3Ypyxw+Iye%i=V#t#vfX1@4WTuCx-T$ueSEH
zSMR_4$~E!#rGMkSU%vn9=U+a${Ucl384nFR@~L4*ebl0!+K$H+`)OiF|1frYZqYy5
z)n4xy^6fcRdmY`px92SRD|TI5@fg?q%Fp(C@#~t;_FTq}<eG7-ZPUF*dtKI<{4j3w
ze$B5M`4;2DHsWzy^O#TL>}x-DV<9ibb)DKCkLCI>Z`V4Q7+87jS53v^%=Ww%^fJW#
z%GvKW-}23UT8o?0a!>mWqnf6+xnE$1^TUqi{MJ*+ZEL1=QXJ}+YxByXV_;&P(Y~C^
zR6ndATsIDDlrdd9#eK_TBDlJqM@xGv^xL}bcib@Fx9*zOrg+`zT1&pWPm04cN)$Q<
zCKkf;NEA8-CKkeTNEA8-CKkfECkh<{6AR(I^Li%e7?@ZH=bR{X3{0$x7!NBt>LJ7-
z4sk`ljl+ECF&`!hE9PSw!#H9*c1#rV#NF7rGL|vjBgOd`hj>WOvd}TGd^aXW+8+vO
z9$2TY-zw@h#dGIvVPNjM?K7`iKTgf=T&Zyl*Tq9T_uxH4WAep!^I<;slpAvM7IG<9
za-AF8+)IASubVHkno@JEKh#V$OU+iZ)ND1SrmGohrkbVZ%3rxIckN-yExF4LZgM_H
zXMXKtnP0gjzQ%o9OYbvp+c>Xyh|MR)HSgx!#NhhnisL5+-}&M<KQZ{u7oYs#I#*nB
zak=F9;*$@)@trR|`QY2NHkVv*?Wz^GD>lyMj4?2mXR@68ah{8~pPXkRp3Adb&RyL$
zU*^la)O4<MgB#pj&Q`p|OPp#-O`SD8XMUWTtsiGEmvZHrlIwDf4?g(hldt2uoaey>
zm&f@%5XXtP^Vb&!=G52J*KyZxhQ3ZtU*b9SHT9)#n=kWaUTQkmxxo!?PJM~Dc!^U@
zsj0K3zW8x!^d*;aDOav3xh~iE;Db*-`8r--TyVkVarzR+iGALNJ}|L{9*2D!w{aY=
zU-26H9QJD5#x6J14Y|opZVi2)<hooxYd90R$tPcY@!d6@AAIo1CtrM>_3e`nKKSI5
zFZGRUTyVkVae5WUiJf=X7baHMSJ#(u8^`hb60feWt}o*@cDbQ$$W3l?tLqCT*X8<I
zLtk=}Prmr#yK6c>_~4UIzW6%p+dm(C@X04%>KoU%;DXEJ^d*iH+eg_mcImI&|IGC$
z-euqBQ*N3s^Ii7C4Q`xzXTj~#58wID54DqDeDcK?-_M%Pfe$|U<csg>2jBSMgHOKB
zJumP7CKp_Cb!zo3&J*kNjQUFZ#ZLP_u^!W&#gkj&o$hHqPK}<;cUqV0+)y*Q#qHDw
zUwl97=#O9g_WKobDOYmcHA8O7Ex9Haaw%8mp8AxVC#P3F`EpI?n|YY0d7Zf)#d%^q
z8uMK14A1CyHT>qH(EZ;V*v&hRcch6S?#42Dy?hSi_71h0|C!j{Sys!(X&mS%B!b`G
zXBH*~Itq!vzTR~(G0;&+1a@<dm>B3NBm&zz(Za+)M<Ef|-j^082099f!1ivnFfp$8
zEEE!fcYpliE$_kSFCy-rm%G2ud+it3{p;L+@b$0VU%mN_`v*Vz{{7>(fB2c_`N!Y=
z>i*Y1|MvdLUw(i8```Zbne|_s^MCjG{?<1?xPRx{AK$<C-CzGt&i2KN7x$MhUw-OZ
QzJ&MxC)aoMKdj&W3GkEHV*mgE

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/example.vox b/assets/world/module/human/example.vox
deleted file mode 100644
index 47fd218a963f35128006615d03fee1b30ea89ddf..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 8764
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
DrGVB*

diff --git a/assets/world/module/human/floor_ground.vox b/assets/world/module/human/floor_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..d231795f22ce605dd21ed1b8d78520988d95beca
GIT binary patch
literal 4012
zcmeI!zpAfS5r^?v|7KQ*U}_6VVWq?<h#=VH;6YB22nrHYrw~$^#!IlVuo5k7y#c`s
z&^xf(`(}QA=A^OFV9+ib9^UoNJm2-MHTxXcKmW;xZ~s0b-hc1a2OjZ<uSWdr)kiPC
z(l37A_!mC>#YeAx`R40#Jk5wR;(6?NIuXxj#`B)aiHfJ?eZ}~A;~-JESVJ8LiNeL2
z>NuFC=UKQ|OAiN$!o?oGwLNi=C|s<gj)O$uVmA6ye)hL~d-nNKABlsFg*fhs<-YU8
zQ*7_ccb#$L^jT53h{ba*BYK)<GI}ojX;xt_!#>fo(>KlM?Rl!_6;IdmWISJ->xsJY
zl%CsjVYiDi=k%P;I2!jF?ls(NxYuy6@%mozoPIC6_jUH`JcZ>wd+jVoJ^SgL<s8|+
zQ+K`Iue%qv_v|@%mb>hpL*Lyq>{Z;+dv?bCuYH#FHjl-h?hwv9`ibHE;e6qo;k@D8
z;r#w#eH=aap5=81Oe}01BnlVf%(uoy&bPvP_WQC{n_Vw!y@}Pm>0N(fb^m(jOf1fB
zXQ=Te7H{){H?eq|ciIbe?!@A5?%^)p<{e)0f;X{vn|FB0+q}a|Uhx)Za}Fmt#W|eh
z6z6g_=Wvo!oXbg0ac-YWKlMxh_8xk-zoB00x9jbDzy0pH_i}ceWhToVtf#fw>3Ul0
z9ZbFK{!rr_Ois2}YTSd#O$=_eA9^weQzkKFs&iLQ_F&2;hHN2ZFP8kllur!#Ldah%
zdmK!AB!)c-VULR`n;5c%kiD2Pi6K)6ncLsz7J_^Gdz?aWZhLyS`)U_wZL`4MX1UG7
zdRVJv*N?ScV(C@)LyePIoN6!CxQWFrOzy?zCl<dj`L{ErCzn`qg(-J2<d#ff%3O@j
zTp78<k}FKPiy;?sQ|4mGgv^#nESbWTxfn9h-{J;$aSD@jF*wmqpF#a%s7Gh5qt~8$
z9rMCIW^~NddRVKSt{-c?gQ=I@4>iug<YarP#yyza#NZYd|6uYHgI}E~J-LG^ml$$|
zkh_?2i6K`Ax!c(*D}ONM6GOfb^0&V)R|vU_C37%k5<{jCGPiTwi^)k0P9ZqA_g5=~
z+U>ga#olR-IB8aCR@T#6?Q}h@^$w<9c7LdG4kjntD>d%H<R%8U+7CUMgDI03GS#`O
zCwnku6GOHTvKLGKV9F<kd?Dm7mOT!pJrctng|NrPluZoTLdafBnZ%GOgv{;la|^+}
z{XI@0IJZ5$+kLf*b;|6J<?dyc*2`LLcfG9jHiq8m{#4^^49;oqs&O|4_aM00{?L=z
z7%~STlbCWFL+&8t64klTm)#h$2O*m%*&%lja*2|=a#L<&$Q^`SqU5ez$RtYU$_(y7
za1+J7{SD6TGwCHt?{=@V-fzEq&U4KRdClmWsr9l}TU{?}y@k-*-Jfclh2ZS=t{QhC
zxErV4q0V0j{>I@C{zC9K4nNxuJ(-1&**G#O6a0<CPk!+Se<AoAhoAi7A8vAsd%3~e
zIK1Q)@A8sYyxV8iPyN!ry^r4Q@2Qvi?Rxv(Z@+u~^v5sW^Y`ZKAAiq1z9<hrUo`t=
z?z6dn|Lb3Sy!z%h9-sX1dyh|l^n=fx=O2IjtH)pe{F}$${_?xWKmPuw&-K5Y^MCj6
s`<-un@_6^#pFY0(onQY?X8YpBi^t2CFaPac{u4g_pY;FH|FD1j6C6m#wg3PC

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/floor_roof.vox b/assets/world/module/human/floor_roof.vox
new file mode 100644
index 0000000000000000000000000000000000000000..e55181a7903775a2f6d21a71eb56384f7bbbbd19
GIT binary patch
literal 4012
zcmeI!y~=LY6~*!OG3Qz<M2NLTNMWVq3qn9h<H3WRA_f%1Sf3(b6`Nc_8jDnt6t><#
zAQupe)2GYTJo(LKW0QyyJ6{;=ImY<!wdb?X^K#zzfA_&pK5ec2_RVMSxU|3isI~W=
zefadHGJbyW6@Bpg51;+vhc|Bj9<80zo}b;G@47ZxW`33L=E~AlzT{bX(2(hue(RS$
z^-=wlVK!!A60<Og3CzT7%)%^8Vgi$xz)VcT3|hFvB`$Ci7r2RQxIrD4xWG(|PxstM
zdp>auH>l$RGcgS_s2Ahxh8xuD*_ehI)axw$pk8O`2KDMHGl%Qx`^Bcy_KW436f`S4
z`$dy?^Pp`$!<7~#1vT7s?X)n%^-LVCy4E@FPhHTYlm|`fw2IkO=a@TY$WOSR7G*I@
zURQq9Ij%vIdd<}(Usv}J+2J=W8lKT<Qc%*OO^1f_J535oTD0lVa7L#|K}jq7%PVO0
zm(#q<PqS=SzRkK#p*z(N8D>*pPH{(u-4xhEv)C@jZwmaOiBJ5dz#p3UQGC<6XH&T6
z&~(pmPv8$t{J?j7;x`5U(8LdX$4~6Qc5K52?$E>yT*o!sz;#@!o%KWC^{vj)wR)bs
z%Uk8e^R2w=-oyK$GrX_i^O(~tJGA77vhv_{hf<gFsC>$Ghf){1(9P<GPJbx%p`ZGx
zAJum~=1^h+Gcf}*Fuj<D!5vCm;3jV1I<DgyZs86kE^re!a2?lh4VU`RPyNt$eba}0
z%7?tmTkRnmvMy_N7T@_+jJeh9y3h2UruQ_>;k}d(KKS%5rYvMBOWE@Ir@YvdFZuF$
zr#$2-Uq08Ag{;n4_MftlrL27L$=96bx-S2Y&ZSLj+hx1|X7(_j+u7B;wq5wZ1)uye
zAM-X3-Q*`9x3iaQ$cAjn8sGWOkL#z*K6hTeE6%4yx$LmqcQem4@7vkaoXa!$iJN@z
zhu{3>7vK584}RX>zUFOix~}W|xLsxT;rs3FllGxSnf6KN+{`}aeLMS@Gws7q+~k8l
z{N^{m_|6Z0@bmWeF>iCzbzSGj?JBbm-*3Ns=bcYU5q1dd%{<e*Zf8$(gy-=CH~7hK
ze({S>zVV&!{NNA2`OUA}d%n5f&aUQt^J48YJ(rSVde-%xaWgwk_nL=pnzwnG(;U<D
z`F`)SAAk7GFMZzLe&%sIyP4O`>)hh&%sQuL`waU~QVjcC@4TDYXSmNibkn@e%beyI
z&f)vL&p!O&H^20Gd;6Hj?d)S-H?MPxuQThMn(fo=LrKx?bG`F!W}ogp^UzK6HZOCU
zqdSN1_dfgZhu{3t=k4ud9=EfPdELCuExyjIb87bc+w4F=v)QfTZe|B_-p;P(+1$r>
zT;~VB_~et1+xvc)yLs!DZt;0L&y;Q1mMz&aAM?Jmefsen8pmfiKC>$yl})oesLPYO
zJgW<xKB()HntqYLIz0=M7+g@tB{kfljoZa63@)hSk{WK&#vR-#ZsV{)9h=m!i#GOP
z=fU8DIxeZ<7H!<Y4NOqSBsI*UjX9XEOKQ4BTX*VNvPD~ViZ`E{UEh8C$(#O;yg&Z$
zetcU!{CwMczrXLx_x|f2zw&tY+N+O`e)Frx$8WvyweRz9pZxLhkI(=7_~+mM^7z*m
zpM5R={d@jq-|wHk{?X&-Kl}Lc%U}HI|MG61JbChX`t<34J<BiQ<GWP-j{cAH+kXIw
Cv%%Q_

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/floor_upstairs.vox b/assets/world/module/human/floor_upstairs.vox
new file mode 100644
index 0000000000000000000000000000000000000000..9a4a55bad6c5badeb9f68e0f6c420a35b0c32f59
GIT binary patch
literal 4012
zcmeI!zp8EJ6^8LKzP}?Fh;@o!kt)HMfCz$(2M=<JB#=N*>r(`*Vv|cqlOmO9VVfHe
zya2s}bh(<9-_XV;O2l+-c$j0p?=$9_>tk=&fBOA9FMSvhzkT!B?_A=epG5re*?Uia
z+&6xH^flgj_q}I-e&NeF{yif0jpwW2I$fu0xt8nh`_N(I;G!^7j*Wwh!W?pJ9M3qe
z7lk=>*f_YTp8Io_4jTs-g}LR}IJl_3+PVGCM~{tzi^70!`FwDP=M0}Syu<L0zV`F5
zALj6kkVpHRMA%6?(R=#AO*_d?_u2RSY_Fd=^Tjc+^2IdIzE87kcD~KJiO{X?L&lj1
z&T8+-xD&x$*xck!1b1P#d*%Fz;4f_c;RknNb5Fa)<xd2EVe=3F@;CQzFE_ap!Clzg
z!@b<%9`5B9H+T!1cX*dq{Y*~vd+Ltv>Z-f`9NCps`{wG|b?)JOaE9|5zK?mDWvlaR
z)-9xNcRysDh2-q^O2%DC?nZD=`>C_Dkajl0&cU#^koGph-ofp?(A!-|yBlHm;Mz@l
z8)5I@+ADj*-a^{j2zv+DUfByf2iH#7N$y5)4=%U*GdR`nq&v8}>aKU)SKqs?J)ITK
zbe_{0nWtH{JHKY##?YPahm5l^IH$dmaW@9{Ai1~w)Y;h>b`H|c#k99E>>Z@Ni?CO{
z8yx!^!~Q|qzX<z<VgDfQUxfX_w7W6v9;Driuv`5Ob}qtBVQ>$UdlB4fpHuyO-9_lC
zdvoEO<y>%=b6Vctyv(xOd6{(=OIO`b8Rueg3Y&Mac!k;CkaI5<w=lVjSD3ufUh=pX
zi(8o7!JWLp%l3lDy;$7B<PL6f2RFIF-Q0`CEllp<CO5dr4esI;CTDPx6Ycc-$%j1U
z(Vyc!-A8-o=-Kt&?c7k?Id0$Iyv?%0kr#I7%e}6!bz8nVpK@Jc>y~cmW_LrUFKqqN
zPyN)7?o-bxY|i3L&fpAAwi6s)Ve=Mm@&+$?$qU}$6*h12CU5YP7rfwY{nAhU(5F81
zOFrd8p7QATkPTVNqMzkH-A6m-=-KtwagN7X9cN`eX7`2N_uIX!uw|>WC6gDne933$
zQ?4s)-O^3n=x*rrg{@!usUQ06KJ}c!<}A+S3{G;Qo#5~ao40tAH+abl-r*EBXK^NH
zaFP?8ty{XO8@kkmZpo*7$WtEu`LZEPS@d(<r~7Ef96h_9eSL47>w8~kW}ar*?fjZ`
z7fV;&4;klTajLzNaW59PFxyQz|6=hAlfU^Fi(i=h(SGRcTr4|<X=m73{KDi9e)2c}
zV(|-;KlsU?+`&z*f8v}I2a`8=$qU}#B`^A&^+TWf=;!F7KTn?W=)8UR(f6)rmNUUB
z=TgqaoMzeWe4BL_OIO{8jB~L#)!vbDFBZ2jxtn{jxP{s7mGdtazcBfOzqp0T9qksE
zf3f(5$shdWPwwC*cXKZmw=lVbo7~_IZgPXWc!kLuyyQhclN0@(x}i&5bl0CFOIfsU
zj-Flr@b;58{V)0DkN^1|-<5}-@4EZL-dE@T+RuLac=n5*Ki+@i)yD^Kz5cD|`SIWX
z^7!nZe|voX=|3L-`r?ys^?x|$|Lynt<zK%4c;#0gJbv@*zkV-g`{c=!$J3`z|Le2-
P5kCH(^xx<=JU{*eX1<5=

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/stair_ground.vox b/assets/world/module/human/stair_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..cb742e0dbeeed44471112c1a660a82865ac682bc
GIT binary patch
literal 2040
zcmeIxJ&F`j5Ww-OSFfs`;0X-Gz)0~6L=a4NaF7X9P>^-AhRrr|6OUkGU?dordI7})
z=nc$zVngrH{%cH3bWt?hh9C7lx?fk-^gMaEf8n)=+~3<gAjsDgdAvDTpREbknV;-G
zJ=lD(-LiZmxXM<$%r3I^CfnX*Zh@59n49Qfha->}Fl8N%Kw`ie>u|JWkC+%RV-Jo%
zVxTMU>O?pKi2-xg;RqxKT4BHOe5-fy?pFB)`v^EX4C2%Yqu%B+OJ{DpmmE91az$c5
zjQhx|3FwJdU%0=~E1@sSIa0l2pEhng(X;v`^B{W2cEAS_Ib;rECmMR)i=@v%Y~-kg
zx|F(<x|F(<x^!2UY=hsW*SX|A$R}aUqsHXotVc!i<P(^qep6~4%m=k6I`gPL5R+PZ
z^`UAf3TGwvYMoinQ2*c@W8CS7aet!*Az!TsC4V75Ax|M+A#WjnKCDBjzE>{?HK4)J
z;RqxKG}_c)&}0mQhOus}+oaAI2KCOkGwzdi#xQ7i#?IKMh(N^-1Lw9jWsWJ}$Vk?6
z@?9M4+vGi_bk^a{8Ay1o)gHXgM^<c{&$+>~YP=tY4o4uNjXfAT9D#%?doXl30*S`?
z9+m5u9^G5*@$K#|zK!K?EnI*5?CEnq`hM&Dxn*<d;&OE7=5l=Z_KE#`^YYp9@!gB%
z)B9J;m(Oocy#KV%fA)P}xqP%-yL!Cbxc>Z~^mescE$j9AmuK-OT>hu}kN)O-`3|ZX
Br{e$s

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/wall_ground.vox b/assets/world/module/human/wall_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..89844fe4851c0ce5e60c65834d61bb6fb88f508b
GIT binary patch
literal 4012
zcmeI!zpAfC6~^&d^JlFI2HdrUSXd<(6+{qha>yZ^LWKw+rcH`KVDFPOUV=@ER1%A{
z-hhw`h<9Lj_RakAdB?^^LqhDb;bGo+p6{A>X4d<I{q`T;`N>~fYrlK_+3yYQvmdth
z$7k<8efgUBd+9&)&Y#|W_Q4O1-Cp$8BHD{+=kc*edyL*5tF^~XsK?&hi#9!Hdy(d0
z?6lNO8kbi%iY?pNrDm8@zsAY8M)3QU6=7TCLr{;rZ?QZt&&%_?kC>k4LyP^q#gGm2
zYwY(u3S+$}d71ZzzT^F%b}71=crU0IpBnXR9^2acz9*j=^&amv3hIkb?UK7_!<_o1
z>6sc0``YQQoMk(EInSnHU)vsvk`_%GwC$lNY0;!X+a8LN7EKzo?V%`X(WG(7zP3FS
zB`umXXxnE|(xOS@8l7#>qNJit(_Jju?xrYd(WF5;_Cw0cdDGeQ`abk1^=ij_UV2p5
znXgRWwjK@lzx1SxLw}||En`yAj(T3_4DQUB*sZVW8=2SW8!78#jQbqEHP7>`J09xH
z=fgJ6>)mRG_tt8U%f7bSdG@yKV_74;JMUAE(zlsc&wRhdpR{GeIIiC5<+|!=U3>A}
zF5mB@aq04{F|o^fla{q6jZ12l-^+BS<vyn0*68bQQm4@;<=r~o<mX+SY|2wMsP2-t
zu!}c&lh=5`3ts1S-sqk7UCC0GvL#!xDVws!Z~Wi~zw<kPt0`H@QkJqMTc>8IDSW4k
zos`%?*Zw+PYj?WxK_%;S%{%S8Ih=zA&EdSmyN>5<p8Lt%iRU?-b6A6y*yJtV;svks
zI&bg>Zw}`h-X$wp$x@cGC0nv43t7mztjmUMoH}RX7r*$)&r{>R_`wf;=l8=qqj{Ov
z;oOmVn8(ST`S|X{IF~a`XD}|~Fb?Bf?qbq%hUvZhJe<4YFaF|pem`~Yk3aZ>KbL!)
zw7g^LJ+5$R-NYp>aSOL_3%78A3tY!_T*r0Xzzy8M4cuJ%Fr5{@@r5sZ;R~Pm#3w%S
z3%~FU-|&GCeBcA$@g3js9Y62`KbQVZcZT2ijo<jf7ryX?PkiDRe&HLw;Tyi;10VRn
z2fpJwe&EMhU!Ub$zU3t^dCK#wr{D4>Z}O0byvzHkcNgYmUWa#9^DvK-JHzqq%{WVM
zQy-1XIE=$MOV5(ZH<0?p&%@~vfAJT;^ZTjO6aL^2{w#NvRK9_9Pq@ORbrYAk#4X&y
zE!@HdE^r;!aUIuj12=F3H*mA;J+J-y2GU)r+v-YPsVjA*F4d*FRF~?Ox}|QZTk4v+
zrY_Wlx=<JDy1K5etLy59x}k2U8|Qs`gx~m$FMQz(U--l)KJj_pqi6VrZ}`9mKJbC>
z_>S-Re%9AV`Ic{a$xB}HI_v4Hyvdt9<RK4voO*pW-{$-9`fc9KoB7y!M4iUg^X+bq
zr}v)YWcuKFY<=C%!CTnHo4m<uyx;||^Ez*A_rOnn@)v*cC%^F;FL=T0ys_;QU$T;=
zEM-eJWzD>qhj~A&UgL^i{N%60xzCg93%~ez{A>MdPuHl2&EBcgXx`tP>3H7fIZozX
zo=3ZQXW=dE;!WP<HD2(7*Lj^cT9CcTQkJqMTe2yevc_-x;0M3+JAX92Qdjblr#$6L
zzT{Khc#Rjl;Pq4EJHeN%WGPG8I(*>W_u-shUh?AQ$**??U3<Qs_uX4>K6%~$(C5$p
zq_=OYo4;>c`+eQN@BP<5{?YB(D?hz`_}gFIK7QjjUwfZ_`{bkB7k~fD?aP0Bdi&Qu
zKmS_(`+NRo-}ld6{qXkl*FL`e@)v*pPVV-}lP9;QPoMtRwfqQg|4&`N(f_f2`wHu}
B+yVdq

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/wall_roof.vox b/assets/world/module/human/wall_roof.vox
new file mode 100644
index 0000000000000000000000000000000000000000..de7dbfbc7717eb17568f4dddba11b65dfb4e6fe7
GIT binary patch
literal 4012
zcmeI!zpAZC5ys)}>gwte1y5`s21bhiKm@^L4s(zbsGuO@q-O$QlH(*^f{B5VXkh9M
z2ws2&D>w6Mwoh*p6CE5hn+-2j^?Tp`zV2RY!T#xw-uu#T#~43&=k*Vj@w?BD@sro@
zzxv!A{C(zM^xn_jfBo~%KI8HCc&?8z=VL@X##)b&`51dYM%81S^N5&ldW=WQv`ovi
zOv|)P%Pg5GGi9dCl$kP9X39*NlsPg-=ExkGBXeYq%#qnLB~vmL@xh))em!E=WAF1Q
znUdKuTV~GvSaZBdTC%m}WJX{z`J74F@#au@PGvn(X38v?^_*GWIo>p7*he|jea;$h
zT#@%r_Yfb%jJN4M`(~EAkqOTk)=dgpGV|U?IG6pM>5TVW%YAmg=x14Hp6PzpcBb+i
zcW~F355Bx3>@}QeIr~bw`|{q@=dE~R+Z^t&?me#iK6e}LU!CTuk63RblJ?sj^)_iy
z;e{7o_P(FbsD`uBqEq98rlPaynAKMI<@(Z}q{h`&lec-BS6E@koi;6A^71}|w|Rvh
z_t_Nu#b5m7Cx7#cH+jJe-r}wMF5bSM1#j{uFL=RQyu2Ph@QF`6@O8O)n5TKR&z*u6
zC2cA?S9`uFYC1(ZU+l~DrEj&>b7;*M$=le*3M;JDR!`3ti=X_w&%rldcwfbQ5&Xqp
z{NyKp^Clj6;0s@P;sc-f!~+j}U5*WGViSwY^g};yf8~9-!=A#K!oDcO9x(mV*Y)&U
zpNCf1iwqXn!WMS0!V25i#<Iiy<nf6I9{9o=Kls55FMQuTHn53JEN&KS>|h70o6`^d
zyuQBm=khz#uH`(-o|gU4Ue1K+Q-7|fFa2)H<xDcz!V*iYv4b6~u)_9Q&LocqzVL-7
z-dJM?J6K^mmUCIdo4nu!Z}F1X=DyFJaxz0^ViOCQC6m0mK0fYRebBCS&UDUnPTJ|5
zm_GIAdiv7urcCFQ!4{TSVvQZ_V1*U7S30LW9{9o+o_J%8otri9NN1GCCmwjnE?)B5
zyr&F*@aOe;i<fv^4_j*7`E2Le_O$JXYC99A-}=6u{^)CO=i!^vyv?oe>*cn8zImCG
zx3OB=UU_)n3r~FG1E2WB<31Ztd<PF7mtzY{Y)`X(=~G{{E~D<U&$8cg4!ye7Qd8aP
zQuFdE`!QcnzjxUOb~ENQ?<{-3FEgikFYM0x)Gx}Z(xx1Ki^Fey_}=I@E%Sb6?i}9~
zcv0Z>>^aBqxl!<oCVrkh&mYcC!7rNpLxbPXnImHl6mmsV?$8<sKe)S~$vfAde$)0a
z)bU*J^XUGD?svr6aldQscE}Ikp>aQ#`?>i{j&;YnR{Od7ZCT&<cV>NC-}n1?KO5`Y
z`nJBUZ|jHqnlys$zSOA>cRKGqyW@F3gZtTB#gTOmnv14c9crkd&Ln3Y>bPfCL!H}9
zL9_G})ZTfhy|3-%s7Gz@ZF@QDQQP~^m3I_19s4+*U23VVwtDQPsOflrQPc76qNd~B
zb>Dw$wC{U+URy`Aeb+Jl*7x=FM_);!eg9>!#5T6EHn;hhkGXccD_MNui6_4C*0=uX
ztJ>Yl%7xtGEnf0Auhpa0Y4<NHH|0Vu<Q6~qo8RhD%l~3SZpcl!DHn2!pZtCQS5wc;
z@dtnMgTL^++&s-;UU$|{ecWE1>W{zw@*V$UJpcJ0`RSAD$=@ek`{}xW-urjH_=Ts}
zU-|OWhu{15(?{=q_haw#_rLzd)1UwNtEa#G={HY*`|BS*Hvj28|99W-uYL8yr*C}y
kqo;3u^OygVyM6ic<<qNIum0_`{0N`^pSr)%|M2|r5Av1RhyVZp

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/wall_upstairs.vox b/assets/world/module/human/wall_upstairs.vox
new file mode 100644
index 0000000000000000000000000000000000000000..bbcf8d6110ff735beb45801ae19a02e865900439
GIT binary patch
literal 4012
zcmeI!zp8ZQ702;st><rukW;4!7FLlY3Lzk*@x_<$7Aiy#V|y$D!knW`F2TmaN=#wv
z4FtRZy@PbQn#pI4O`0f*b`C6__4|I;v-j+MX7-%7-+br!Pg`rRzxwPAOZ)6st-bZ^
z-KWo0@cPC7)H{EB_u1cndBg2L*;-_~pV1$qxA$VGsg~O6wD%5v@Pi-x;0NFM#y7t4
zjc<J63txDv%q?!|CpWptO?=`Lk7)OU8{FUqH@LxdyyG43c*i@QRr<t|%i7!8{c$~8
zt=o0QrAJ(ja>e&6`Xyssw%j8hW{ijC+}-t`SP#dHTXMhEtYbtxj@w*bmbrbaei_`c
zw_DS(^0v-Pznvo=b}YxMWA4kW<HfMQ&bu$oY;#Quk2+7Cx2`|trPVdaGv>om{kCUb
zvhIOnj2PZezqGm+x0(Hr-rsQCal0nRulpMNq1C;)UGH$Lahc9>oMTwKO114hk(aPP
z>%G|*&L43eQ#I#r)`<Do<LIZ^dLPvfefM5#uG^E3D~|h_=EJoXuX}$kyUn}ZhFW(=
zzizqfhramMFa5fkH<xmkyIlUrTi)`z8@HLj5=*ROLq776x4iDUxzhp*EU}Ia{n2mz
zx=S;YAAIMNAAIMN4}S2SPd@n0Cm(#{JD+^;jZZ%Kb~}FPQ(t?Jwf2RzSY_v)-k<7f
z47I%HV*07ia{8_xUG2Er*uoYzv55tiSYjRP*hqEUD{ga}TioI%H@U_I7hH15b*|Sw
z{NNkk_`+B1!5bcU;E89=%SYbwt~t5MAxC-ZDQ<1nobI~{l|wCmr(*i9Z{_r%&#tz=
zH#V?=b*y7eZgR*WXR71#VRmwp8{FVJ*SRJ)IpmPH*=2T_o!sOGH)__}tjEkYv&}3s
z%gp3vIsD)o-}u5;arwww-j%J#+NaO0O4Wy2KJUf!Lm%bzsqbBF-wAAD6C2pT8Wvb!
zi6z!ko$i|K;TAW!$qjCB4R3hhfhV5$v1fCeTioI%H*1gF<dCCg%^h<a-}u5;%~@M{
z{NP*Om958`!}myq%C4sGT`_&vw{rT>XR76Uiw$gG9qU+=n;de;xjj?NO?=`5A9%-`
zzUf1s$8*KpGPlf4ZgPX`T(gGtnAv8wnPp~~ncS$E^4#Gzx4Fg5;_{KVyeeCFt*TGY
z6qTwEwLF80>4!eb=~Lgk+Mb!%#3nYdfi*0!z!FQWr#hau+~O8DxycP~a1C#G;DINe
z__1emn_JxCCO2!3+~km>X3ZUQ8{hcCSIt>ldHmp8-j%J#+NbBYO4Wy2p83V}Lm%bz
zsqbBFzYEyJCN{8vH7u~e5=*S7I?J`&!!2%dlN;RN8s6~015Z5hW6$O`x46YkZq^>T
z$stG0nmgt;zVU^xnzOd@_`$clD_f6sOut{Mt1;B_n^sIe^;u5e^`oo3U5goPVH2BJ
zV2LHxv5t*Y$L}b&xy>zZag&=|<AMt=x#T+6Yaf2_jc<J6tM=dx4?OV1v*zU^Z+X|8
z+~km>y!8~fHtU+lwfJpTYpCTny_kOMM>+k}udX&PA9>4L?q&uyu!&7<r8?&1BX4=j
zX||i~b!={MlbbbbJ=QRjX3|VI)6EQSaE%Ktxa5-STrY<=Jn+C1&*E~ELypSUX8r45
zp1kURyW7kEM%N!&SFazs_vd}z-1k5I=GWI}zkT8Q{-6JN{qVI{zVm+m^WzV$Uw-k?
z^{an<a{c$$pMU54&-eNJeZGJ9;``S>{Qkr1%P;-oN4d98o;<leefsqO9?O^T`cqbY
JN5AFx?Z1S3qIduR

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/window_ground.vox b/assets/world/module/human/window_ground.vox
new file mode 100644
index 0000000000000000000000000000000000000000..ebb946a9680d1cf8b48ffc1d5cb6d6489c647265
GIT binary patch
literal 4012
zcmeI!KdY_9702;4vu4eVB#>QOh=r9wlRyvxX}shjw@^VrjP1RJAe?h!lb4VtMXHb#
zY4ZjIUx2=YbmzXA?Pqoxn<$Z_^T3x`>-$^JpFR6|Hs_=FKYsPE5%KQZPu{b{-+mVH
z;ge4u|MWEeUiuav|NfIFpZ?^g<Dp0F)}HPWFLp;#XYK8&;8{b*8oaqY=!dnNNeq2q
zo;hs9axB)#(bai+#|>*?o*o-9&7&hRP?%WQh_>#d%we~E->r#(!o<Qx40Gs63=}37
zHX_ZSBQa2zSlG8UUM&03THa@mht|g3^mz)a?Yrso)cH(iqK*5WXLdi2R(Gi`^*GM_
zZgn5lcJ|JGhBfVb_gSXCT0G3=dad6VKaj28&2t{i+j(8nwbHeRx#5_8=Fc2fn|I+H
zyUqJ{Tk|f>Z1=s*t(InnweH$>&uGiNpiS#^E$nbTeJv|ICNIX;V<H__T({1MhhZkq
z96VVM!#S2UonxV6%eMKgPFZvO(vP#ss?44}unq*azLj&UyLDR^+Sxxe`x3YPwc3~R
z9rbgJXJy!<X!H4M>-p9<`xEvc>_NNjPu=x4p7pjo*UK}#o)vmaed<%6&!&F1vJOnF
zr(NIGZT3rBsDA0{IQ>?0ar{ur0x9dVK%Ol1@<7UmJY_@HWr36pS;>a1WXTV{_{kSP
z`N0=I`NdCu@x@Pm@ta@#<|n`Sef*i#@6%hOyFWzFOTX2r9+y;iuBoHfW$5!ZGq~dB
zrMPi(#Z7KqCRaB%xy9|v;^xgQZeO;3>F4b_%XN-RZgA&X%e9UhTyf`Gi$AWo$t~{m
zQ-7{|>ZLyG_I<ebLi$|lIQ>*}fzAi=_@$QxQkMKUe)F<G%7$#nO4en8lnq(QN;YN5
z55D-xPk!-(FMjfiU;O5apZwxCzt4HN#qBez-lw)k`93Cw@8Q(Tdiu0-{kW~>JKy-u
zC*SzaC*S$x2jBVR2cP`li%)*=b-eFl$VxV4LsqhRvZj}pe9D)+<Ws)nLtf{*l`r|0
zPx+E>dC8Y-pJVxb{OQ$gdA2rYxp&VUx;~C8H6KVmj~g5xNPh6+_~d1Qlnq(QO4en8
zlnq(QrfkZRAAIqXU;H}$Jg2gfP1%xd**;m5$xA-vOTOiUuk$?fi{Iy3+|uvUTif#9
zY|QENdZ*C!aa^hSK=OIq;P^oDgCEByFAJn>$VyhSE(@e=$VxV4Q<nVTi=X`B*YV!7
zAuHLGE!mdslQr3SN99w#<Xb-YI`1mK_<gR$E&V>dwN1YX3*~n}9jD)FE{-2+Ss-Oy
z7RZyOULHvKkf&_Ox-5{gAuHLCl`Q$e7eD#pCqMY&C%^c~FTVK6FMjij-~8kkzmGq&
z`h9xqnA?4zwrg+quHI@cj~{ATV#+#7o|y7}W|A|PnC3c`xrS-3W0`9xvx#Z8W0`H(
zW&_J?!!{e3W;>SIhHW;^yqP%Xl{IWxoLn8Jk9Jv@C=4Vz8Uj1~zGI>=kmzU#?C`sW
ziNZjlqam=v?*S$X1Bs4?zz**ZCJF<Ij)uSv&m1NS1Bs4?zz*LZCJF<Ij)uSv`+|wW
zK%%3;f7q7~-g)%4|5t8*{{OrFP_F)d=&?T@_wRjw>y?+UPhNlR`q^)Pef|7*zxm$%
z{QIB(aQ*V1f4YA4#b2)f`uZQ=JOAT8|98*#8$bW-`inO|zy9i%fBc{9?W0GJu8$u-
T{*UMK5?=qGoOkp)&L7_Z3qjZX

literal 0
HcmV?d00001

diff --git a/assets/world/module/human/window_upstairs.vox b/assets/world/module/human/window_upstairs.vox
new file mode 100644
index 0000000000000000000000000000000000000000..309fc417ec97439b05e71b217abbb99fc70dcfca
GIT binary patch
literal 4012
zcmeI!yNae+8HVBYZq`N=G&T?eBgGwvAec;JBOS~L3NlVw6$k^}?Ia$8iGh)5VCo47
z9)O;KnMYGzt4vIEaL{ZD9-j5y_qSVL{k8e&kKX_CZ^sz#zVqrmHGcQ`F@EyugO{JX
zgRjs0OWyz42d{qq*=Icdo@2xukE6$9$S5u2Xc^owAMV`sk}cfEH@@+WZ+znmU--fU
zUpL(1CO5gsO?=|>-p9JR=9=p}bIlcAc;ST?UiTawl6>;XC!d$`nd4zaJ(g~nca|;Z
zChF~p{dz>aS+O2wtjCra<Ds6{;~^tnN9;El`|XZ;y}2EYtv4f5w$z&wiA~qJ_e|%z
zU*zL<{niof8kcRyPRG`p9qa9SK7TyK^R@0Z?=|9Wc&2dtai8PddcIaVXTCn3sboI)
zeg1aLeXek?8_fPf(z9grV|ou6Q<mrJ$7Ma@xX*c;QqnSI$(AEMybGD)qw=sMWQ$MG
z!@27nUpTU4FF9s?^t{}9S?`HSV-uU$GVjKv@W%U|hc9dwC&?98Tyv9K_)cDePd@p&
z_uwbLxWNS%Tyn)VH?PA79(dw~_vPj=r@8KI9oBi*I&RBeX|HLIX@9cK`sjJN^|IbA
z2iHe8H@V3zY+<`NDXzJ`*Wf0%@SVIQUwrY+H$VBs1(#fM#nrtRH@U$D7hH1571!6{
z0}nj##OreNFo!wstmC%qRrX%?UiL2AtdE|TTQBR~a&UcgbCa9g!WOoRlj54|dkt=K
z3*X60^2Hb5eDjlETyV)HS6tnDag!TdaKR;)Tyf3K;JLvC7hH11^>x_50!yqevyOXR
zIkWA*?N3(Qx7m7IpOW47i(?Dh*v3xEY5V25dC%n*x4F#~*IaXxo800Smt1khHP`n(
z+~k5wF1g~0Yp%H&ZNHA;gHJyB;*0O=xxob&Tyn|Pb=bfH3vB42*O{Jqy8pC?X>VrT
zm+7@Iy>(%2J+0To_11~kJ;yxFt2w=OaLE<d*u+-xdh5XlpM3F+Pkg0g^wxt9KKbGs
zpVwc92cCH0jm^NZz!EF0FEfWZ&2?w%xYv+%miMr{hvhvi@4>A5GQAe2w=S%$r}dh+
z-a7HR=a{E?HK(@@F1g|wo7gH|Z$0?nlP|vUiLZ2w-g@xCCtrNy^ZM)Xz!NXLFEfWZ
z&2?w%u+F>Iaa;D<_OR`7+lOqkK6+kmy{vc3!S&J2O>S}vTi7m6ifgX#HMq$wd?zo-
z7hiny%};)D!6lbmadq#-O>S_(1(#fM#r1Xgzyl9F@w(hR%wf(u>$oj@9(z6Zc<fJ>
zSsy(ww_et}WaIki<|a3}g)MBS99|z?j%mmAy_RF9V-~l#<cce<x#lJ}x!{sZuDH7Q
z<C+^>aKR;)Tyb?BKJdT;PdqO-4|AB~&U)M){w7LFNz0TaNA`33n=$;Ik|`}EDIr77
z@Vj1?Olc`e2^n&R-;J_lN=r#f$dEJqyvvd)EhQ--L(cGdl_gVJN>W0GoZ<T_OQy7x
zq=XDP!}m&-Olc`e2^sR^AHI0U|Jcu8{<nMjq<Ql7NzeWCynnv;?|$ivPp`iEm8TEC
z|DC6ge(=4IeV@Po^)H_O{KsEC{pC-;dHUO5|M0QLfBK&PyU+L6zxLtNH^1@G)3?9%
g%m2x{eevSO)617H|LwK>5I+4sbALwv!}Z5M02N%Gf&c&j

literal 0
HcmV?d00001

diff --git a/server-cli/.gitignore b/server-cli/.gitignore
new file mode 100644
index 0000000000..dc83325b30
--- /dev/null
+++ b/server-cli/.gitignore
@@ -0,0 +1 @@
+settings.ron
diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs
index 79fde45685..54c6f27306 100644
--- a/world/src/block/natural.rs
+++ b/world/src/block/natural.rs
@@ -30,6 +30,7 @@ pub fn structure_gen<'a>(
     if (st_sample.tree_density as f64) < random_seed
         || st_sample.alt < st_sample.water_level
         || st_sample.spawn_rate < 0.5
+        || !st_sample.spawn_rules.trees
     {
         return None;
     }
diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs
index 34bac0c714..04c1903ae0 100644
--- a/world/src/column/mod.rs
+++ b/world/src/column/mod.rs
@@ -1,6 +1,7 @@
 use crate::{
     all::ForestKind,
     block::StructureMeta,
+    generator::{Generator, SpawnRules, TownGen},
     sim::{LocationInfo, SimChunk, WorldSim},
     util::{RandomPerm, Sampler, UnitChooser},
     World, CONFIG,
@@ -518,6 +519,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
             location: sim_chunk.location.as_ref(),
 
             chunk: sim_chunk,
+            spawn_rules: sim_chunk
+                .structures
+                .town
+                .as_ref()
+                .map(|town| TownGen.spawn_rules(town, wpos))
+                .unwrap_or(SpawnRules::default()),
         })
     }
 }
@@ -547,6 +554,7 @@ pub struct ColumnSample<'a> {
     pub location: Option<&'a LocationInfo>,
 
     pub chunk: &'a SimChunk,
+    pub spawn_rules: SpawnRules,
 }
 
 #[derive(Copy, Clone)]
diff --git a/world/src/generator/mod.rs b/world/src/generator/mod.rs
index b24d0b3d84..b658a4b41b 100644
--- a/world/src/generator/mod.rs
+++ b/world/src/generator/mod.rs
@@ -7,8 +7,28 @@ use crate::{column::ColumnSample, util::Sampler};
 use common::terrain::Block;
 use vek::*;
 
+#[derive(Copy, Clone, Debug)]
+pub struct SpawnRules {
+    pub trees: bool,
+}
+
+impl Default for SpawnRules {
+    fn default() -> Self {
+        Self { trees: true }
+    }
+}
+
+impl SpawnRules {
+    pub fn and(self, other: Self) -> Self {
+        Self {
+            trees: self.trees && other.trees,
+        }
+    }
+}
+
 pub trait Generator<'a, T: 'a>:
     Sampler<'a, Index = (&'a T, Vec3<i32>, &'a ColumnSample<'a>, f32), Sample = Option<Block>>
 {
     fn get_z_limits(&self, state: &'a T, wpos: Vec2<i32>, sample: &ColumnSample) -> (f32, f32);
+    fn spawn_rules(&self, town: &'a TownState, wpos: Vec2<i32>) -> SpawnRules;
 }
diff --git a/world/src/generator/town.rs b/world/src/generator/town/mod.rs
similarity index 50%
rename from world/src/generator/town.rs
rename to world/src/generator/town/mod.rs
index 73bde112f6..997bfde100 100644
--- a/world/src/generator/town.rs
+++ b/world/src/generator/town/mod.rs
@@ -1,4 +1,7 @@
-use super::Generator;
+mod util;
+mod vol;
+
+use super::{Generator, SpawnRules};
 use crate::{
     block::block_from_structure,
     column::{ColumnGen, ColumnSample},
@@ -8,14 +11,617 @@ use crate::{
 use common::{
     assets,
     terrain::{Block, BlockKind, Structure},
-    vol::{ReadVol, Vox},
+    vol::{ReadVol, Vox, WriteVol},
 };
+use hashbrown::HashSet;
 use lazy_static::lazy_static;
 use rand::prelude::*;
 use rand_chacha::ChaChaRng;
-use std::{collections::HashSet, sync::Arc};
+use std::{ops::Add, sync::Arc};
 use vek::*;
 
+use self::vol::{ColumnKind, Module, TownCell, TownColumn, TownVol};
+
+const CELL_SIZE: i32 = 9;
+const CELL_HEIGHT: i32 = 9;
+
+pub struct TownGen;
+
+impl<'a> Sampler<'a> for TownGen {
+    type Index = (&'a TownState, Vec3<i32>, &'a ColumnSample<'a>, f32);
+    type Sample = Option<Block>;
+
+    fn get(&self, (town, wpos, sample, height): Self::Index) -> Self::Sample {
+        let cell_pos = (wpos - town.center)
+            .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| {
+                e.div_euclid(sz)
+            })
+            .add(Vec3::from(town.vol.size() / 2));
+        let inner_pos = (wpos - town.center)
+            .map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| {
+                e.rem_euclid(sz)
+            });
+
+        match town.vol.get(cell_pos).unwrap_or(&TownCell::Empty) {
+            TownCell::Empty => None,
+            TownCell::Park => None,
+            TownCell::Rock => Some(Block::new(BlockKind::Normal, Rgb::broadcast(100))),
+            TownCell::Wall => Some(Block::new(BlockKind::Normal, Rgb::broadcast(175))),
+            TownCell::Road => {
+                if (wpos.z as f32) < height - 1.0 {
+                    Some(Block::new(
+                        BlockKind::Normal,
+                        Lerp::lerp(
+                            Rgb::new(150.0, 140.0, 50.0),
+                            Rgb::new(100.0, 95.0, 30.0),
+                            sample.marble_small,
+                        )
+                        .map(|e| e as u8),
+                    ))
+                } else {
+                    Some(Block::empty())
+                }
+            }
+            TownCell::House { idx, module } => {
+                if let Some(module) = module {
+                    let transform = [
+                        (Vec2::new(0, 0), Vec2::unit_x(), Vec2::unit_y()),
+                        (Vec2::new(0, 1), -Vec2::unit_y(), Vec2::unit_x()),
+                        (Vec2::new(1, 1), -Vec2::unit_x(), -Vec2::unit_y()),
+                        (Vec2::new(1, 0), Vec2::unit_y(), -Vec2::unit_x()),
+                    ];
+
+                    MODULES[module.vol_idx]
+                        .0
+                        .get(
+                            Vec3::from(
+                                transform[module.dir].0 * (CELL_SIZE - 1)
+                                    + transform[module.dir].1 * inner_pos.x
+                                    + transform[module.dir].2 * inner_pos.y,
+                            ) + Vec3::unit_z() * inner_pos.z,
+                        )
+                        .ok()
+                        .and_then(|sb| {
+                            block_from_structure(
+                                *sb,
+                                BlockKind::Normal,
+                                wpos,
+                                wpos.into(),
+                                0,
+                                sample,
+                            )
+                        })
+                } else {
+                    Some(Block::new(BlockKind::Normal, town.houses[*idx].color))
+                }
+            }
+        }
+    }
+}
+
+impl<'a> Generator<'a, TownState> for TownGen {
+    fn get_z_limits(
+        &self,
+        town: &'a TownState,
+        wpos: Vec2<i32>,
+        sample: &ColumnSample,
+    ) -> (f32, f32) {
+        (sample.alt - 32.0, sample.alt + 75.0)
+    }
+
+    fn spawn_rules(&self, town: &'a TownState, wpos: Vec2<i32>) -> SpawnRules {
+        SpawnRules { trees: false }
+    }
+}
+
+struct House {
+    color: Rgb<u8>,
+}
+
+pub struct TownState {
+    center: Vec3<i32>,
+    radius: i32,
+    vol: TownVol,
+    houses: Vec<House>,
+}
+
+impl TownState {
+    pub fn generate(center: Vec2<i32>, gen: &mut ColumnGen, rng: &mut impl Rng) -> Option<Self> {
+        let radius = rng.gen_range(12, 24) * 9;
+        let size = Vec2::broadcast(radius * 2 / 9 - 2);
+
+        let alt = gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) as i32;
+
+        let mut vol = TownVol::generate_from(
+            size,
+            |pos| {
+                let wpos = center + (pos - size / 2) * CELL_SIZE + CELL_SIZE / 2;
+                let rel_alt = gen.get(wpos).map(|sample| sample.alt).unwrap_or(0.0) as i32
+                    + CELL_HEIGHT / 2
+                    - alt;
+
+                let col = TownColumn {
+                    ground: rel_alt.div_euclid(CELL_HEIGHT),
+                    kind: None,
+                };
+
+                (col.ground, col)
+            },
+            |(col, pos)| {
+                if pos.z >= col.ground {
+                    TownCell::Empty
+                } else {
+                    TownCell::Rock
+                }
+            },
+        );
+
+        // Generation passes
+        vol.setup(rng);
+        vol.gen_roads(rng, 30);
+        //vol.gen_parks(rng, 8);
+        vol.emplace_columns();
+        let houses = vol.gen_houses(rng, 60);
+        vol.gen_walls();
+        vol.resolve_modules(rng);
+
+        Some(Self {
+            center: Vec3::new(center.x, center.y, alt),
+            radius,
+            vol,
+            houses,
+        })
+    }
+
+    pub fn center(&self) -> Vec3<i32> {
+        self.center
+    }
+
+    pub fn radius(&self) -> i32 {
+        self.radius
+    }
+}
+
+impl TownVol {
+    fn floodfill(
+        &self,
+        mut opens: HashSet<Vec2<i32>>,
+        mut f: impl FnMut(Vec2<i32>, &TownColumn) -> bool,
+    ) -> HashSet<Vec2<i32>> {
+        let mut closed = HashSet::new();
+
+        while opens.len() > 0 {
+            let mut new_opens = HashSet::new();
+
+            for open in opens.iter() {
+                for i in -1..2 {
+                    for j in -1..2 {
+                        let pos = *open + Vec2::new(i, j);
+
+                        if let Some(col) = self.col(pos) {
+                            if !closed.contains(&pos) && !opens.contains(&pos) && f(pos, col) {
+                                new_opens.insert(pos);
+                            }
+                        }
+                    }
+                }
+            }
+
+            closed = closed.union(&opens).copied().collect();
+            opens = new_opens;
+        }
+
+        closed
+    }
+
+    fn setup(&mut self, rng: &mut impl Rng) {
+        // Place a single road tile at first
+        let root_road = self
+            .size()
+            .map(|sz| (sz / 8) * 2 + rng.gen_range(0, sz / 4) * 2);
+        self.set_col_kind(root_road, Some(ColumnKind::Road));
+    }
+
+    fn gen_roads(&mut self, rng: &mut impl Rng, n: usize) {
+        const ATTEMPTS: usize = 5;
+
+        let mut junctions = HashSet::new();
+        junctions.insert(self.choose_column(rng, |_, col| col.is_road()).unwrap());
+
+        for road in 0..n {
+            for _ in 0..ATTEMPTS {
+                let start = *junctions.iter().choose(rng).unwrap();
+                //let start = self.choose_column(rng, |pos, col| pos.map(|e| e % 2 == 0).reduce_and() && col.is_road()).unwrap();
+                let dir = util::gen_dir(rng);
+
+                // If the direction we want to paint a path in is obstructed, abandon this attempt
+                if self
+                    .col(start + dir)
+                    .map(|col| !col.is_empty())
+                    .unwrap_or(true)
+                {
+                    continue;
+                }
+
+                // How long should this road be?
+                let len = rng.gen_range(1, 10) * 2 + 1;
+
+                // Paint the road until we hit an obstacle
+                let success = (1..len)
+                    .map(|i| start + dir * i)
+                    .try_for_each(|pos| {
+                        if self.col(pos).map(|col| col.is_empty()).unwrap_or(false) {
+                            self.set_col_kind(pos, Some(ColumnKind::Road));
+                            Ok(())
+                        } else {
+                            junctions.insert(pos);
+                            Err(())
+                        }
+                    })
+                    .is_ok();
+
+                if success {
+                    junctions.insert(start + dir * (len - 1));
+                }
+
+                break;
+            }
+        }
+    }
+
+    fn gen_parks(&mut self, rng: &mut impl Rng, n: usize) {
+        const ATTEMPTS: usize = 5;
+
+        for _ in 0..n {
+            for _ in 0..ATTEMPTS {
+                let start = self
+                    .choose_column(rng, |pos, col| {
+                        col.is_empty()
+                            && (0..4).any(|i| {
+                                self.col(pos + util::dir(i))
+                                    .map(|col| col.is_road())
+                                    .unwrap_or(false)
+                            })
+                    })
+                    .unwrap();
+
+                let mut energy = 50;
+                let mut park = self.floodfill([start].iter().copied().collect(), |_, col| {
+                    if col.is_empty() && energy > 0 {
+                        energy -= 1;
+                        true
+                    } else {
+                        false
+                    }
+                });
+
+                if park.len() < 4 {
+                    continue;
+                }
+
+                for cell in park {
+                    self.set_col_kind(cell, Some(ColumnKind::Internal));
+                    let col = self.col(cell).unwrap();
+                    let ground = col.ground;
+                    for z in 0..2 {
+                        self.set(Vec3::new(cell.x, cell.y, ground + z), TownCell::Park);
+                    }
+                }
+
+                break;
+            }
+        }
+    }
+
+    fn gen_walls(&mut self) {
+        let mut outer = HashSet::new();
+        for i in 0..self.size().x {
+            outer.insert(Vec2::new(i, 0));
+            outer.insert(Vec2::new(i, self.size().y - 1));
+        }
+        for j in 0..self.size().y {
+            outer.insert(Vec2::new(0, j));
+            outer.insert(Vec2::new(self.size().x - 1, j));
+        }
+
+        let mut outer = self.floodfill(outer, |_, col| col.is_empty());
+
+        let mut walls = HashSet::new();
+        self.floodfill([self.size() / 2].iter().copied().collect(), |pos, _| {
+            if outer.contains(&pos) {
+                walls.insert(pos);
+                false
+            } else {
+                true
+            }
+        });
+
+        while let Some(wall) = walls
+            .iter()
+            .filter(|pos| {
+                (0..4)
+                    .filter(|i| walls.contains(&(**pos + util::dir(*i))))
+                    .count()
+                    < 2
+            })
+            .next()
+        {
+            let wall = *wall;
+            walls.remove(&wall);
+        }
+
+        for wall in walls.iter() {
+            let col = self.col(*wall).unwrap();
+            let ground = col.ground;
+            for z in -1..2 {
+                self.set(Vec3::new(wall.x, wall.y, ground + z), TownCell::Wall);
+            }
+        }
+    }
+
+    fn emplace_columns(&mut self) {
+        for i in 0..self.size().x {
+            for j in 0..self.size().y {
+                let col = self.col(Vec2::new(i, j)).unwrap();
+                let ground = col.ground;
+
+                match col.kind {
+                    None => {}
+                    Some(ColumnKind::Internal) => {}
+                    Some(ColumnKind::External) => {}
+                    Some(ColumnKind::Road) => {
+                        for z in -1..2 {
+                            self.set(Vec3::new(i, j, ground + z), TownCell::Road);
+                        }
+                    }
+                    _ => unimplemented!(),
+                }
+            }
+        }
+    }
+
+    fn gen_houses(&mut self, rng: &mut impl Rng, n: usize) -> Vec<House> {
+        const ATTEMPTS: usize = 20;
+
+        let mut houses = Vec::new();
+        for _ in 0..n {
+            for _ in 0..ATTEMPTS {
+                let entrance = {
+                    let start = self.choose_cell(rng, |_, cell| cell.is_road()).unwrap();
+                    let dir = Vec3::from(util::gen_dir(rng));
+
+                    if self
+                        .get(start + dir)
+                        .map(|col| !col.is_empty())
+                        .unwrap_or(true)
+                        || self
+                            .get(start + dir - Vec3::unit_z())
+                            .map(|col| !col.is_foundation())
+                            .unwrap_or(true)
+                    {
+                        continue;
+                    } else {
+                        start + dir
+                    }
+                };
+
+                let mut cells: HashSet<_> = Some(entrance).into_iter().collect();
+
+                let mut energy = 1000;
+                while energy > 0 {
+                    energy -= 1;
+
+                    let parent = *cells.iter().choose(rng).unwrap();
+                    let dir = util::UNITS_3D
+                        .choose_weighted(rng, |pos| 1 + pos.z.max(0))
+                        .unwrap();
+
+                    if self
+                        .get(parent + dir)
+                        .map(|cell| cell.is_empty())
+                        .unwrap_or(false)
+                        && self
+                            .get(parent + dir - Vec3::unit_z())
+                            .map(|cell| {
+                                cell.is_foundation()
+                                    || cells.contains(&(parent + dir - Vec3::unit_z()))
+                            })
+                            .unwrap_or(false)
+                    {
+                        cells.insert(parent + dir);
+                        energy -= 10;
+                    }
+                }
+
+                // Remove cells that are too isolated
+                loop {
+                    let cells_copy = cells.clone();
+
+                    let mut any_removed = false;
+                    cells.retain(|pos| {
+                        let neighbour_count = (0..6)
+                            .filter(|i| {
+                                let neighbour = pos + util::dir_3d(*i);
+                                cells_copy.contains(&neighbour)
+                            })
+                            .count();
+
+                        if neighbour_count < 3 {
+                            any_removed = true;
+                            false
+                        } else {
+                            true
+                        }
+                    });
+
+                    if !any_removed {
+                        break;
+                    }
+                }
+
+                // Get rid of houses that are too small
+                if cells.len() < 6 {
+                    continue;
+                }
+
+                for cell in cells {
+                    self.set(
+                        cell,
+                        TownCell::House {
+                            idx: houses.len(),
+                            module: None,
+                        },
+                    );
+                    self.set_col_kind(Vec2::from(cell), Some(ColumnKind::Internal));
+                }
+
+                houses.push(House {
+                    color: Rgb::new(rng.gen(), rng.gen(), rng.gen()),
+                });
+            }
+        }
+
+        houses
+    }
+
+    fn resolve_modules(&mut self, rng: &mut impl Rng) {
+        fn classify(cell: &TownCell, this_house: usize) -> ModuleKind {
+            match cell {
+                TownCell::House { idx, .. } if *idx == this_house => ModuleKind::This,
+                _ => ModuleKind::That,
+            }
+        }
+
+        for x in 0..self.size().x {
+            for y in 0..self.size().y {
+                'cell: for z in self.col_range(Vec2::new(x, y)).unwrap() {
+                    let pos = Vec3::new(x, y, z);
+                    let this_idx = if let Ok(TownCell::House { idx, module }) = self.get(pos) {
+                        idx
+                    } else {
+                        continue;
+                    };
+
+                    let mut signature = [ModuleKind::That; 6];
+                    for i in 0..6 {
+                        signature[i] = self
+                            .get(pos + util::dir_3d(i))
+                            .map(|cell| classify(cell, *this_idx))
+                            .unwrap_or(ModuleKind::That);
+                    }
+
+                    let module = MODULES
+                        .iter()
+                        .enumerate()
+                        .filter_map(|(i, module)| {
+                            let perms = [[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], [1, 2, 3, 0]];
+
+                            let mut rotated_signature = [ModuleKind::That; 6];
+                            for (dir, perm) in perms.iter().enumerate() {
+                                rotated_signature[perm[0]] = signature[0];
+                                rotated_signature[perm[1]] = signature[1];
+                                rotated_signature[perm[2]] = signature[2];
+                                rotated_signature[perm[3]] = signature[3];
+                                rotated_signature[4] = signature[4];
+                                rotated_signature[5] = signature[5];
+
+                                if &module.1[0..6] == &rotated_signature[0..6] {
+                                    return Some(Module { vol_idx: i, dir });
+                                }
+                            }
+
+                            None
+                        })
+                        .choose(rng);
+
+                    self.set(
+                        pos,
+                        TownCell::House {
+                            idx: *this_idx,
+                            module,
+                        },
+                    );
+                }
+            }
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq)]
+pub enum ModuleKind {
+    This,
+    That,
+}
+
+lazy_static! {
+    pub static ref MODULES: Vec<(Arc<Structure>, [ModuleKind; 6])> = {
+        use ModuleKind::*;
+        vec![
+            (
+                assets::load("world.module.human.floor_ground").unwrap(),
+                [This, This, This, This, This, That],
+            ),
+            (
+                assets::load("world.module.human.stair_ground").unwrap(),
+                [This, This, This, This, This, That],
+            ),
+            (
+                assets::load("world.module.human.corner_ground").unwrap(),
+                [This, This, That, That, This, That],
+            ),
+            (
+                assets::load("world.module.human.wall_ground").unwrap(),
+                [This, This, This, That, This, That],
+            ),
+            (
+                assets::load("world.module.human.door_ground").unwrap(),
+                [This, This, This, That, This, That],
+            ),
+            (
+                assets::load("world.module.human.window_ground").unwrap(),
+                [This, This, This, That, This, That],
+            ),
+            (
+                assets::load("world.module.human.floor_roof").unwrap(),
+                [This, This, This, This, That, This],
+            ),
+            (
+                assets::load("world.module.human.corner_roof").unwrap(),
+                [This, This, That, That, That, This],
+            ),
+            (
+                assets::load("world.module.human.chimney_roof").unwrap(),
+                [This, This, That, That, That, This],
+            ),
+            (
+                assets::load("world.module.human.wall_roof").unwrap(),
+                [This, This, This, That, That, This],
+            ),
+            (
+                assets::load("world.module.human.floor_upstairs").unwrap(),
+                [This, This, This, This, This, This],
+            ),
+            (
+                assets::load("world.module.human.balcony_upstairs").unwrap(),
+                [This, This, This, This, This, This],
+            ),
+            (
+                assets::load("world.module.human.corner_upstairs").unwrap(),
+                [This, This, That, That, This, This],
+            ),
+            (
+                assets::load("world.module.human.wall_upstairs").unwrap(),
+                [This, This, This, That, This, This],
+            ),
+            (
+                assets::load("world.module.human.window_upstairs").unwrap(),
+                [This, This, This, That, This, This],
+            ),
+        ]
+    };
+}
+
+/*
 const CELL_SIZE: i32 = 11;
 
 static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x100F4E37);
@@ -113,7 +719,7 @@ impl TownState {
             return None;
         }
 
-        let radius = 192;
+        let radius = 200;
 
         let mut grid = Grid::new(
             TownCell::Empty,
@@ -144,7 +750,7 @@ impl TownState {
                     let idx = rng.gen_range(0, 4);
                     Vec2::new(dirs[idx], dirs[idx + 1])
                 };
-                let road_len = 2 + rng.gen_range(1, 3) * 2 + 1;
+                let road_len = 2 + rng.gen_range(1, 5) * 2 + 1;
 
                 // Make sure we aren't trying to create a road where a road already exists!
                 match grid.get(start_pos + road_dir) {
@@ -175,7 +781,7 @@ impl TownState {
         };
 
         // Create roads
-        for _ in 0..25 {
+        for _ in 0..radius.pow(2) / 2000 {
             create_road();
         }
 
@@ -317,7 +923,7 @@ impl TownState {
             break;
         };
 
-        for _ in 0..40 {
+        for _ in 0..radius.pow(2) / 1000 {
             place_house();
         }
 
@@ -467,7 +1073,7 @@ impl TownState {
         };
 
         for _ in 0..100 {
-            variate_walls();
+            //variate_walls();
         }
 
         /*
@@ -662,3 +1268,4 @@ impl<'a> Generator<'a, TownState> for TownGen {
         (sample.alt - 32.0, sample.alt + 75.0)
     }
 }
+*/
diff --git a/world/src/generator/town/util.rs b/world/src/generator/town/util.rs
new file mode 100644
index 0000000000..99cce9c784
--- /dev/null
+++ b/world/src/generator/town/util.rs
@@ -0,0 +1,42 @@
+use rand::prelude::*;
+use vek::*;
+
+pub const UNITS: [Vec2<i32>; 4] = [
+    Vec2 { x: 1, y: 0 },
+    Vec2 { x: 0, y: 1 },
+    Vec2 { x: -1, y: 0 },
+    Vec2 { x: 0, y: -1 },
+];
+
+pub fn dir(i: usize) -> Vec2<i32> {
+    UNITS[i % 4]
+}
+
+pub fn unit(i: usize) -> (Vec2<i32>, Vec2<i32>) {
+    (UNITS[i % 4], UNITS[(i + 1) % 4])
+}
+
+pub fn gen_unit(rng: &mut impl Rng) -> (Vec2<i32>, Vec2<i32>) {
+    unit(rng.gen_range(0, 4))
+}
+
+pub fn gen_dir(rng: &mut impl Rng) -> Vec2<i32> {
+    UNITS[rng.gen_range(0, 4)]
+}
+
+pub const UNITS_3D: [Vec3<i32>; 6] = [
+    Vec3 { x: 1, y: 0, z: 0 },
+    Vec3 { x: 0, y: 1, z: 0 },
+    Vec3 { x: -1, y: 0, z: 0 },
+    Vec3 { x: 0, y: -1, z: 0 },
+    Vec3 { x: 0, y: 0, z: 1 },
+    Vec3 { x: 0, y: 0, z: -1 },
+];
+
+pub fn dir_3d(i: usize) -> Vec3<i32> {
+    UNITS_3D[i % 6]
+}
+
+pub fn gen_dir_3d(rng: &mut impl Rng) -> Vec3<i32> {
+    UNITS_3D[rng.gen_range(0, 6)]
+}
diff --git a/world/src/generator/town/vol.rs b/world/src/generator/town/vol.rs
new file mode 100644
index 0000000000..cde6b2fb3f
--- /dev/null
+++ b/world/src/generator/town/vol.rs
@@ -0,0 +1,203 @@
+use crate::util::Grid;
+use common::vol::{BaseVol, ReadVol, Vox, WriteVol};
+use rand::prelude::*;
+use std::ops::Range;
+use vek::*;
+
+#[derive(Clone)]
+pub enum ColumnKind {
+    Road,
+    Wall,
+    Internal,
+    External, // Outside the boundary wall
+}
+
+#[derive(Clone, Default)]
+pub struct TownColumn {
+    pub ground: i32,
+    pub kind: Option<ColumnKind>,
+}
+
+impl TownColumn {
+    pub fn is_empty(&self) -> bool {
+        self.kind.is_none()
+    }
+
+    pub fn is_road(&self) -> bool {
+        self.kind
+            .as_ref()
+            .map(|kind| match kind {
+                ColumnKind::Road => true,
+                _ => false,
+            })
+            .unwrap_or(false)
+    }
+}
+
+#[derive(Clone)]
+pub struct Module {
+    pub vol_idx: usize,
+    pub dir: usize,
+}
+
+#[derive(Clone)]
+pub enum TownCell {
+    Empty,
+    Park,
+    Rock,
+    Road,
+    Wall,
+    House { idx: usize, module: Option<Module> },
+}
+
+impl TownCell {
+    pub fn is_road(&self) -> bool {
+        match self {
+            TownCell::Road => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_foundation(&self) -> bool {
+        match self {
+            TownCell::Rock => true,
+            _ => false,
+        }
+    }
+}
+
+impl Vox for TownCell {
+    fn empty() -> Self {
+        TownCell::Empty
+    }
+
+    fn is_empty(&self) -> bool {
+        match self {
+            TownCell::Empty => true,
+            _ => false,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub enum TownError {
+    OutOfBounds,
+}
+
+const HEIGHT: usize = 24;
+const UNDERGROUND_DEPTH: i32 = 5;
+
+type GridItem = (i32, TownColumn, Vec<TownCell>);
+
+pub struct TownVol {
+    grid: Grid<GridItem>,
+}
+
+impl TownVol {
+    pub fn generate_from(
+        size: Vec2<i32>,
+        mut f: impl FnMut(Vec2<i32>) -> (i32, TownColumn),
+        mut g: impl FnMut((&TownColumn, Vec3<i32>)) -> TownCell,
+    ) -> Self {
+        let mut this = Self {
+            grid: Grid::new(
+                (0, TownColumn::default(), vec![TownCell::Empty; HEIGHT]),
+                size,
+            ),
+        };
+
+        for (pos, (base, col, cells)) in this.grid.iter_mut() {
+            let column = f(pos);
+            *base = column.0;
+            *col = column.1;
+            for z in 0..HEIGHT {
+                cells[z] = g((
+                    col,
+                    Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + z as i32),
+                ));
+            }
+        }
+
+        this
+    }
+
+    pub fn size(&self) -> Vec2<i32> {
+        self.grid.size()
+    }
+
+    pub fn set_col_kind(&mut self, pos: Vec2<i32>, kind: Option<ColumnKind>) {
+        self.grid.get_mut(pos).map(|col| col.1.kind = kind);
+    }
+
+    pub fn col(&self, pos: Vec2<i32>) -> Option<&TownColumn> {
+        self.grid.get(pos).map(|col| &col.1)
+    }
+
+    pub fn col_range(&self, pos: Vec2<i32>) -> Option<Range<i32>> {
+        self.grid.get(pos).map(|col| {
+            let lower = col.0 - UNDERGROUND_DEPTH;
+            lower..lower + HEIGHT as i32
+        })
+    }
+
+    pub fn choose_column(
+        &self,
+        rng: &mut impl Rng,
+        mut f: impl FnMut(Vec2<i32>, &TownColumn) -> bool,
+    ) -> Option<Vec2<i32>> {
+        self.grid
+            .iter()
+            .filter(|(pos, col)| f(*pos, &col.1))
+            .choose(rng)
+            .map(|(pos, _)| pos)
+    }
+
+    pub fn choose_cell(
+        &self,
+        rng: &mut impl Rng,
+        mut f: impl FnMut(Vec3<i32>, &TownCell) -> bool,
+    ) -> Option<Vec3<i32>> {
+        self.grid
+            .iter()
+            .map(|(pos, (base, _, cells))| {
+                cells.iter().enumerate().map(move |(i, cell)| {
+                    (
+                        Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + i as i32),
+                        cell,
+                    )
+                })
+            })
+            .flatten()
+            .filter(|(pos, cell)| f(*pos, *cell))
+            .choose(rng)
+            .map(|(pos, _)| pos)
+    }
+}
+
+impl BaseVol for TownVol {
+    type Vox = TownCell;
+    type Err = TownError;
+}
+
+impl ReadVol for TownVol {
+    fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Err> {
+        match self.grid.get(Vec2::from(pos)) {
+            Some((base, _, cells)) => cells
+                .get((pos.z + UNDERGROUND_DEPTH - *base) as usize)
+                .ok_or(TownError::OutOfBounds),
+            None => Err(TownError::OutOfBounds),
+        }
+    }
+}
+
+impl WriteVol for TownVol {
+    fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Err> {
+        match self.grid.get_mut(Vec2::from(pos)) {
+            Some((base, _, cells)) => cells
+                .get_mut((pos.z + UNDERGROUND_DEPTH - *base) as usize)
+                .map(|cell| *cell = vox)
+                .ok_or(TownError::OutOfBounds),
+            None => Err(TownError::OutOfBounds),
+        }
+    }
+}
diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs
index 0725fc7b97..5360e000c6 100644
--- a/world/src/sim/mod.rs
+++ b/world/src/sim/mod.rs
@@ -438,17 +438,18 @@ impl WorldSim {
                     .iter()
                     .min_by_key(|(pos, seed)| wpos.distance_squared(*pos));
 
-                if let Some((pos, seed)) = town {
+                if let Some((pos, _)) = town {
                     let maybe_town = maybe_towns
                         .entry(*pos)
                         .or_insert_with(|| {
-                            TownState::generate(*pos, *seed, &mut ColumnGen::new(self), &mut rng)
+                            TownState::generate(*pos, &mut ColumnGen::new(self), &mut rng)
                                 .map(|t| Arc::new(t))
                         })
                         .as_mut()
                         // Only care if we're close to the town
                         .filter(|town| {
-                            town.center.distance_squared(wpos) < town.radius.add(64).pow(2)
+                            Vec2::from(town.center()).distance_squared(wpos)
+                                < town.radius().add(64).pow(2)
                         })
                         .cloned();
 
diff --git a/world/src/util/grid.rs b/world/src/util/grid.rs
index a30ca989a8..188f96c3ef 100644
--- a/world/src/util/grid.rs
+++ b/world/src/util/grid.rs
@@ -40,16 +40,19 @@ impl<T: Clone> Grid<T> {
     }
 
     pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &T)> + '_ {
-        (0..self.size.x)
-            .map(move |x| {
-                (0..self.size.y).map(move |y| {
-                    (
-                        Vec2::new(x, y),
-                        &self.cells[self.idx(Vec2::new(x, y)).unwrap()],
-                    )
-                })
-            })
-            .flatten()
+        let w = self.size.x;
+        self.cells
+            .iter()
+            .enumerate()
+            .map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell))
+    }
+
+    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut T)> + '_ {
+        let w = self.size.x;
+        self.cells
+            .iter_mut()
+            .enumerate()
+            .map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell))
     }
 
     pub fn iter_area(