From ad1c75d0507df9303bd31dac1f18ec2af05888eb Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 14 Oct 2021 19:15:03 -0400 Subject: [PATCH 1/2] WIP branch merging --- .../sprites/blocks/payload/block-forge.png | Bin 1374 -> 0 bytes .../sprites/blocks/payload/block-loader.png | Bin 1602 -> 0 bytes .../sprites/blocks/payload/block-unloader.png | Bin 895 -> 0 bytes .../blocks/payload/constructor-top.png | Bin 0 -> 1510 bytes .../sprites/blocks/payload/constructor.png | Bin 0 -> 1578 bytes .../blocks/payload/deconstructor-top.png | Bin 0 -> 2030 bytes .../sprites/blocks/payload/deconstructor.png | Bin 0 -> 4242 bytes .../sprites/blocks/payload/factory-in-3.png | Bin 0 -> 655 bytes .../{units => payload}/factory-in-5.png | Bin .../{units => payload}/factory-in-7.png | Bin .../{units => payload}/factory-in-9.png | Bin .../sprites/blocks/payload/factory-out-3.png | Bin 0 -> 609 bytes .../{units => payload}/factory-out-5.png | Bin .../{units => payload}/factory-out-7.png | Bin .../{units => payload}/factory-out-9.png | Bin .../{units => payload}/factory-top-3.png | Bin .../{units => payload}/factory-top-5.png | Bin .../blocks/payload/large-constructor-top.png | Bin 0 -> 2555 bytes .../blocks/payload/large-constructor.png | Bin 0 -> 4197 bytes .../blocks/payload/payload-loader-top.png | Bin 0 -> 1032 bytes .../sprites/blocks/payload/payload-loader.png | Bin 0 -> 1412 bytes .../sprites/blocks/payload/payload-source.png | Bin 1263 -> 8026 bytes .../blocks/payload/payload-unloader-top.png | Bin 0 -> 1033 bytes .../blocks/payload/payload-unloader.png | Bin 0 -> 1417 bytes .../sprites/blocks/units/factory-in-3.png | Bin 1407 -> 0 bytes .../sprites/blocks/units/factory-out-3.png | Bin 651 -> 0 bytes core/assets/bundles/bundle.properties | 14 +- core/src/mindustry/content/Blocks.java | 153 ++++++++----- .../world/blocks/payloads/BlockProducer.java | 21 +- .../world/blocks/payloads/BlockUnloader.java | 67 ------ .../world/blocks/payloads/BuildPayload.java | 18 +- .../{BlockForge.java => Constructor.java} | 11 +- .../world/blocks/payloads/Payload.java | 9 + .../world/blocks/payloads/PayloadBlock.java | 12 +- .../blocks/payloads/PayloadDeconstructor.java | 205 ++++++++++++++++++ .../{BlockLoader.java => PayloadLoader.java} | 44 ++-- .../blocks/payloads/PayloadUnloader.java | 97 +++++++++ .../world/blocks/payloads/UnitPayload.java | 20 ++ 38 files changed, 502 insertions(+), 169 deletions(-) delete mode 100644 core/assets-raw/sprites/blocks/payload/block-forge.png delete mode 100644 core/assets-raw/sprites/blocks/payload/block-loader.png delete mode 100644 core/assets-raw/sprites/blocks/payload/block-unloader.png create mode 100644 core/assets-raw/sprites/blocks/payload/constructor-top.png create mode 100644 core/assets-raw/sprites/blocks/payload/constructor.png create mode 100644 core/assets-raw/sprites/blocks/payload/deconstructor-top.png create mode 100644 core/assets-raw/sprites/blocks/payload/deconstructor.png create mode 100644 core/assets-raw/sprites/blocks/payload/factory-in-3.png rename core/assets-raw/sprites/blocks/{units => payload}/factory-in-5.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-in-7.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-in-9.png (100%) create mode 100644 core/assets-raw/sprites/blocks/payload/factory-out-3.png rename core/assets-raw/sprites/blocks/{units => payload}/factory-out-5.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-out-7.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-out-9.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-top-3.png (100%) rename core/assets-raw/sprites/blocks/{units => payload}/factory-top-5.png (100%) create mode 100644 core/assets-raw/sprites/blocks/payload/large-constructor-top.png create mode 100644 core/assets-raw/sprites/blocks/payload/large-constructor.png create mode 100644 core/assets-raw/sprites/blocks/payload/payload-loader-top.png create mode 100644 core/assets-raw/sprites/blocks/payload/payload-loader.png create mode 100644 core/assets-raw/sprites/blocks/payload/payload-unloader-top.png create mode 100644 core/assets-raw/sprites/blocks/payload/payload-unloader.png delete mode 100644 core/assets-raw/sprites/blocks/units/factory-in-3.png delete mode 100644 core/assets-raw/sprites/blocks/units/factory-out-3.png delete mode 100644 core/src/mindustry/world/blocks/payloads/BlockUnloader.java rename core/src/mindustry/world/blocks/payloads/{BlockForge.java => Constructor.java} (88%) create mode 100644 core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java rename core/src/mindustry/world/blocks/payloads/{BlockLoader.java => PayloadLoader.java} (74%) create mode 100644 core/src/mindustry/world/blocks/payloads/PayloadUnloader.java diff --git a/core/assets-raw/sprites/blocks/payload/block-forge.png b/core/assets-raw/sprites/blocks/payload/block-forge.png deleted file mode 100644 index 6d37c59675a4a4818c15c21234d9b698fee49cc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1374 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&z$)SC z;uumf=j~i;KN&}XHtr*=96F6I4jWXm3)yF1;M}h8SkUqr*EHk%hr|u`F<(;4acE$f zq+7@$v*4Jqul?(bp|;lhTef^V^DXTE{cpRg(_cS&_3P=A)9M=*UH&V5@k_m3otW?b zu-gm_{@?j;73Qqj#>f!1`YVs4=k0f`q8yV#TeiMpPhen3xFw)7i~j*5!wfZ+)z6q& z7#MuGn^HeBDlss;ahS2IEJ!?jwJ8I`8(BG)_4aq|_uLI$wfV07$=CMt?0f$0z414C z>b~{y=l6TgpMSY%XUDJLMH!q0T~k?leg=zdU3P+rqxMA3zX%bIlUgVKPq^?pa{Z~$ z>xQCJ8GNr^I~nu-C`YhR7vqj&Y41NUOgrsbS|F0xlasqIShq!qq0ZaCae@(BweMr& zpjC|%SOs3E6*_C2-1Eftj_NWIr)`3Mrdsc!Rt3i0zq@a5oQ&-Ly?v<_74;tRlfxMq z=1mHZ<)YLW6BElx}0>Io%s5mkG#VhGa%K@eZ&t~73 zV%q+5Ms=_f^MMd+G3iVnADN#4vwJqPI2wyxy&1qQuq9a|j=w`Q?D({_oZ514zs0V< zg6=-nX}A%0_D){&(TjzmES_5>W*@mWtJpjIh1QA`;k8c|teUFvYL+xeki;!6C-Kb-MFM-S7|Pt92hS|_2uj> z-|rT|ag|}q^`>C9?CU=zI4*u=n117=@;`IezkwRhYxaiRV%V~ymgC(18+#%*uoTRZ z>eroBkXc)9{p<0hkUJY^J(gwA{9HXj%D;n9ca&V9oMr8_gImO#bP&Hc4zg@7s$t%O~CNJ>T{% zXWwL#mN%A(Z=OgWSIp3BnACo9Zq_+=9#=a}{Ys8m$%l2re{KA|f;sT2swem2jJFb3 z7&6jpG|onxU^u6De)aw2lQrsV|1Q^2UBK3KML(i&$-<4yv)6KIHD0(?;}f;^yuyZU zj5A(osC0g8E}1T-{mpF#mt)ZfH{aiHKF#^N{I_eBV~py-Nip+w%%87tjd!jPi@p9F z0hi;9!3_eZnZ;YycCUUt{R;1u=P5^@tY0?uoaNK7hZ|ldJl_-KKSk>!-(MNSE+@5V zI{9Arnq%X7r?y`V)aG zS4k!IveyKa#eLb3y|~KOVY5oawEGg%e>J~qJJ-KU_O$u@ua-%MX9eO!U$;&BHCdKD zqkGq9pVt%H{C3Js{24rHuXebQ5A%Z`FHQPPR(LrYa5r4GI4$7AG{NAE7E3XMO7A9) pgE9@i7X+9p)9>iaxpU*8ychRvn{^!u5)2Fs44$rjF6*2UngFBMX+i)1 diff --git a/core/assets-raw/sprites/blocks/payload/block-loader.png b/core/assets-raw/sprites/blocks/payload/block-loader.png deleted file mode 100644 index 08c851a0cd73d2f233a3063cc24787b57eb0773f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1602 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^TsI6Yk)Ln`9l-nH#ha}{EH zU@1RihjL$o1ylL~CkqQ1hH9A|VqzMOrXlu|>jP(oPK^&fmR#x*lCmcw?8}-m`FqrPZE3o?pMR z>)Ljg=aKh=cKi3IF)Zzuv8;R4EV?$o^W5fr?(gjO?qd3&?A|~BM}q6?=tZB`ye~Cr zdB?D=zemRY<3Wg4`9hVJr_3*2Y8LvP*Ll5okzxsZ!F74Zy|tV6m-l^jcX4{brC|P{ zM_|q5ud3fp{<^oXOv;EVP{g;H_^9W}4i1~Gb`OWO3@2A`-PdRq%cSaV2o_6~68`h_OntnU= zODca!CgYBj&d=FzY)|bp{Wk4J_@lWdstnV!KT4Z^bF*B3?4H=~;yqK_m0lFeYtzrpTQoKGxc|)d?bE*p88iH!bbJ4sZFT3}_h{Xp`dI(FnOsae^9`pBadywH{+K7zUl+;H z`PI_yRp^gC8Gb$fb>2)KHFsX!*?*+@bo1*?3_|idwZ5<;|8x?}yKulKZX z{%`)H))2#CcDgmnJ94+w_uRL6y?dtDPpT1YP_%H+yApL~*VP^Q+4-+Stm_OLYB}U$ z^g^P~&F_y$-L`d=?0%^O$J#s?(mzhLZMW*S-tumF0YlpZr}WPUH~q`m`gr;~bD0Bn zEaFy^kLzuG@3Zysb&!Bq6ZhPs%G+<$PrLOo`nxorLg7N=iaT-VEStYi`hMvXW5pGX z=VzboeX@5;34`2cjpyf{?0vHL3yZ;t>XX$kITI#;R0$tYc(3|?=~8A7o3tsP(>}+2 zii@&qII{R#(K*RI)AvkoW2*S)`sP@@V0E47$MtV#-&x0Er805ce)wuVu zPiMa#z>wMS^;6*bW8a@teDP+w!4|Rqd2-y;_oqI7xXiS{@mhY3OYdIyv?~_hPt;Ub zSL*Y!KUfr4=YP%J5{&P8Z)bb3z35G6QCy#`;JHcPFXb}+m@D*q(VXz4c>U)msxvJb zjx3G(3U4ZR-!|{QuetI4iJso?7r!tFP4CmmKc7>le|(;A)$IS=2X*hcEoYgq znBbTMhyMt-S>AkE9!_hxX(WO{%)fDA>jkQ i+XUt@57 diff --git a/core/assets-raw/sprites/blocks/payload/block-unloader.png b/core/assets-raw/sprites/blocks/payload/block-unloader.png deleted file mode 100644 index 2ee0b89d9bae55ce804c89c1df79800af95cf419..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 895 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^S>-8@|!Ln>~)y=z!>$AE`5 z;GULdP1BMyqK^uve-te0@yU`uP^xwRjGXA~i)+eVIe#$Nt&HFBX`RNywXR)DI=;RB z`Th6n{pPbieg3Gq#US=|&@HA9&);`5Xfe(Z@Z&CEW7+ngo@s_;qu~#8#x$1%-hZ(T zGX-We@8>+wRFKsmcj(jhd*9ceK73t&_R|;7FCMq9vb`stxj8I}^Is1`Tua)@BT|1N z8ErV;W~k14aGoc@bpiVag$1wrT^8(TjTF4_)m?Fk`JrgX505JQn>qdkwtUHZu~)(6 zKl{R0?#d;MRqQVmYkvQGTz|9p((Ci?Qx-pe;L5>%!|6ku4ReFIBI^S&rdN+78EiN* z8M0Zv95rT$5Da3R_Gs@94;GidR~S|>xF{&RUdFP5!zFcY2m=q}$`##A3qIAI2{^xj zA#(4c7glX-H;Ne-%iGH^6efoD7KB7IvA*yv3%3hN6+N$zC~<&w;jfd^EPp&?@J^J7 zOW?KRJ_NLR6yl)d=@*2b;%^D5)6 z{n*Jk?a-fsDTR6Weq3a(5I@efz~F}@2a`Yp1B(KK>7iF`^EOJaV&8a(-H_K+gwHj@ zU|oTM-{Fg%hcC{WpgCv4#VIKdrz}x>%B}u%tIpDd$S$!oQT;QQE*){4u9j7}Zrbwf z%3X06Bv>5QYrIt5`!nv+0XIKii2RL#0otGN)c-8K3Yf%5_s1(7} za7I~jX7#5@&$M;=C72#&FkCok)6V`u=?vF|B5o&zGaM5xaV!={WA=F27A96=bpFjH zX8{L8r`eW2lohtzY(C$2;haECT1(lq2b+~R7DzZU9OaOgzAN2U%WqW1z`(%Z>FVdQ I&MBb@0H!XKJOBUy diff --git a/core/assets-raw/sprites/blocks/payload/constructor-top.png b/core/assets-raw/sprites/blocks/payload/constructor-top.png new file mode 100644 index 0000000000000000000000000000000000000000..39a3ca79f354cd47c5902cee3907a1b6e6bf2960 GIT binary patch literal 1510 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&z`DTG z#WAE}&fB@R*&&W1ZRhpEx>60CCUkU7@YBiBJ#s6^<5hy{NBJl6E^AauFWu%$O4R0f zDR!hoiBVb8^=-^*VfD!~E#-@!o!eXfe63Jt`T0HPYu=yPIeGf->d#NFih8`fc=-3^ zC-Nq&UtgWwZTZ#w^368_ef#B7?N_{9vtynwBZCx6wCA2#kNA0;dY8!g-i?=PvB}&s z^Vk0i3NMy?eImoqz~;D0M!$)HL5gKvk!=SPLxzAxJ1^#g zZ*|YjRBe1PyI)G1<$lc+mZtlm?7fd1b`-x<`RSP4EMP2uv{67xy?OHj@$=g!Kl}Uh zgH&D3?-_p#v>qsS22YE~Z|L$3n&j3Vb?EE;`F{2L3jZ3#>@2hXdC)nZ^HP^c7UxPy zrOZC=gQoIRn;H)Cly-Fm`>rbcU8ES0nlAadB)eM9NM5w*!S$Afjvt=?l;W6UwAuIO z%@}_(S8=Bthc4DBQCB>;-)Wm#cs-tYw z=}aor-zom&_04y6pAP2N>vVtX+i|+`Y*PF0`XwI&&QJBT3i(^KLbYaR#p^HWS?^!` zcrCbdyNJ+s)r-`zBTtSknwyt?)~C$u`g6t4 zfp#qc-%~ghuh;ncg!}Vtx@L7V*!kT(K~u9YzA+p-FK18QX~}*_;OfzGg|zO)5-kg7 z^>;aLh>DoC&aalk*6O>l##zFPwONjKkY@N(5K z;#e*seP)B|6eIZtw&-M=S27M>s>>?(c`;pEVa~AY&$%7`0#(cjfmazqrb#wj**JBh z=JzFS+Ly&Pa0uv}W!JdC+AG<#^(RB?u?9mYjYK<#gN_wG0$wxu11iK!*f> zA_r!ib&qs5VBWy-Y_X2wmduP)Q--Nmr5R$E*-f7JNPFcy28I`GP0wD`c$V@lNE12` zo`3IbaC%_2jPIHvvnjhoVt1+jNDpLu6}#LhoAJ`+w5{vc?$UM4`ls-&gYksE{S|@b zN>lbM2z(>4V@cT6CWXw_)2p>z&#f1eY+35N%dK4H@0lROZ?9%$h3h&dKT}w@a$EPU zqOBpUEo>84El_=ZLAzSu*_+i@_HnNX3}5-XTP^34XcniG#YW}+S?^U=%vkXy`>K#x zpyuhMBJP~beQIKPK874ugQf*~{?=H#_O4FSJ~c5B(aHP=wKC^d@N_)on>weZZqrV~ ziSJ}R=jq!{{rl+Wx0YQ>ulqPR3SXYp@^o2eG~Qj-{@n)cI!Zd=fc<)Yb~5sIeyG9u?s*8UVYmSuOU{lRy( zAdWZTlFM2Ij^){LDIVFC#L05)w2h_ItB_#r)#3~MX8o*+bU5+PDJ}3U`vX ctsnTU*^O6C5M1WPz`(%Z>FVdQ&MBb@07O!;r2qf` literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/constructor.png b/core/assets-raw/sprites/blocks/payload/constructor.png new file mode 100644 index 0000000000000000000000000000000000000000..8f68ec170879938cc45ddfed92a486ead4294580 GIT binary patch literal 1578 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&!1~0~ z#WAE}&fB@RelorSZQT=KEyUuX zAZV$0v@vw`!tJ|vt=jb~y)N$M<@WMr)w}McFRNZwpZ2Zp?!P*FE89jXtGd0xORMen z=uC>|EM0w-ElzHJm%K%LWR&vO)Qth`3;|M3=Zs4;1g!on7HJW>dSrq^$=~`XZ2_D5 zY(Kpzwx)VMBYs=XXm=IpP=?$^@cznC3_xQqOvvwUv>f;X?7D(~mEg z&7UBapIx|6?%Q;R5AIX%|KBn#^{029y}dp6ufu^KxExy7vGo0vS}Nu7*7@n$&@5j; z)(6%etDhBr%jIQ?WZlXB@o%o`>s84gxA8suxnY0EQ%}vX*!Cm4n2NNIM@BxLF-yCV zmqB{=;>*Go_V&s03St_^IeH_-erEM)w;o`6ua?OX ztoTwyh<^;nMYkDQAqCP`HS({0FW9z+fnoN6qOIclYJR6)&)9m)Mf&Lzw;8*H%wGTI z|1UquYwF!6dMDTq>i(FZFhytg&D;!mrxgcgIheiv{kGon@|VkAb^cRd79Y6y(sRp# zAmyc*!RO*mZz@jZdhqSI|Ft?PWlNCAx}Dbq&M*|5b zU&@jcxj(yqeo=Rq5~I$&YXyI*HhiwGZ*b23C-KDTL+J!ffiYYNp+uAI!&0>|H<(OgF@+qBrbuV)Blkma2d7`n)*rvE}*jHI=Ea{qNEp(eAd^x0c`f`*G`WWsTNf zSDKWQuH1Ihn5(Pb)#Gz@!q%S*6Q4IsTi-P= zpUpohy?Kj3P}r^=nWmX4nL8^zQ_b{Wym}V3rT&zc`28|psk~cP{%X$jJM+lNBl6`U z!%Ib}y*C&ox&O7{jR_;?)3)N8YT(I=J zZ==G`U2?m5FF19u2EAHy?akswg}+-Ct`N+78@%1wdYS*D-3z1y*4V$8bGYWup5$ro zJ9cKtI_Z86Ty|fOdu1rc*0b(~e{cT&_4fDf%9T}@&Pi>kYjs*%bVTauo7XK{86(!+ zTT>}?WL=9A_ttEujE7uH6hxvm@cZ<~RTV literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/deconstructor-top.png b/core/assets-raw/sprites/blocks/payload/deconstructor-top.png new file mode 100644 index 0000000000000000000000000000000000000000..e75a3e44cfe94f51317a2b76f63a387672ac1da2 GIT binary patch literal 2030 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 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/factory-in-3.png b/core/assets-raw/sprites/blocks/payload/factory-in-3.png new file mode 100644 index 0000000000000000000000000000000000000000..9ea0ed0f19111354b1c316180b7d0a775af5ca67 GIT binary patch literal 655 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW6p}rHd>I(3)EF2VS{N99 zF)%PRykKA`)n{OM70T{@)i+jFYr?Xo;B2pybkFBJI$^5h%voA)t6Di$PGSVl|t9V$JT^>Rn8!&6cNNKpx89^)7PI1|0OELtn26yP&{8L?$RRD{GB1UW%~2S z2cO)0bK&8;e~F*2SJxKb{=;o@@!tJ7&Tr}8G**N} zNwe13EoHx8=iV0noJ-kpQ{oT1PkT9+IcAgfL(v7{E)sRi4t*$Ixt`UqMtT(k&wamg zhPM3G+6;%cUzcfk_&bB^z=yvf%my{_s~IKs?LW%EV}C4%p-nzrsNvypDVBsEAI~vL j)Cq|NGcX*``^)gg@vYzce#Sct3=9mOu6{1-oD!M<0G|_e literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/units/factory-in-5.png b/core/assets-raw/sprites/blocks/payload/factory-in-5.png similarity index 100% rename from core/assets-raw/sprites/blocks/units/factory-in-5.png rename to core/assets-raw/sprites/blocks/payload/factory-in-5.png diff --git a/core/assets-raw/sprites/blocks/units/factory-in-7.png b/core/assets-raw/sprites/blocks/payload/factory-in-7.png similarity index 100% rename from core/assets-raw/sprites/blocks/units/factory-in-7.png rename to core/assets-raw/sprites/blocks/payload/factory-in-7.png diff --git a/core/assets-raw/sprites/blocks/units/factory-in-9.png b/core/assets-raw/sprites/blocks/payload/factory-in-9.png similarity index 100% rename from core/assets-raw/sprites/blocks/units/factory-in-9.png rename to core/assets-raw/sprites/blocks/payload/factory-in-9.png diff --git a/core/assets-raw/sprites/blocks/payload/factory-out-3.png b/core/assets-raw/sprites/blocks/payload/factory-out-3.png new file mode 100644 index 0000000000000000000000000000000000000000..84caaa1c31d85b3cd47eb6483a66d13d070c7c37 GIT binary patch literal 609 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 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/large-constructor.png b/core/assets-raw/sprites/blocks/payload/large-constructor.png new file mode 100644 index 0000000000000000000000000000000000000000..f82c56b17d2d1439df097ece8e9eedb1d7446a62 GIT binary patch literal 4197 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 A2><{9 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/payload-loader-top.png b/core/assets-raw/sprites/blocks/payload/payload-loader-top.png new file mode 100644 index 0000000000000000000000000000000000000000..2207273e024adb89929c78a5dd111eb6f47be8fd GIT binary patch literal 1032 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&zB+( zH7kUeO;E^aa=7xqOQ1W$Uvk;!!e@{3(rvu{Sk1vuFTZIYU8AK+xU zQnL4Le1Mak<;@rYh5#qNoii<18Kz8d@)T2MxKXk7<==*>Q&m6M9rXA7@j83;;?Ey6 z6z5+0(72gPK*Q0$_XDFhg9E2jRx7^+3&RwFMQJ}8+!-7=^|IReSy&jRBpA8;aCl(H zz_2YYzly)-uKJ$)ziLz83487H&QV&GmYJn`=hvqvf4*++ogEwAB>8VWyZq$7t818E z6>JeY^Px3Pbw}F1u%z}IpamI@7T23jQOnBg_irL-iRlK zGW?0>+^V;Fb(_K5H|MH&;xBlusPqrs!!q;7H08-*huKS#SJygQdPwY>d8O!w+Gm;n z35_@67ft`rdha;@gD>70*RF=Wto_p>;KZSbn<(*n-~Z!G@}C-y^N05wJAXVxw&d@O ztTido(+%E#46hBcyJG2@@pH}s$>_J|;&ZMQx?MkO6=S?#opa?wZc`NzeaC&N&lCRp zzjQZmUY{7;`*ekeL(W-#&y3V>%nvv6+~qBru&mbXThW!EA8Ox%Qv4L+%};n|-V)D> ze(9{TMQWddP|VeA?;TUNP2KlyrxfR`Hb313FWFwco@br$_*%g0Bn6#?(?7IEn4gGR z`+K#?d$U5%7rHeERTCe66KtsZtuC^CeR-nS3)7l|Qxrh%arv)cTJ!Kp#m3(sT3@D= zr?u-(@#470{-llni~VO`r~6Awjvrj(Ta(ms>(2Kx0@bfn@{>g$uWHl3GHt?^iP1f) z<@N;xH&tmLmbk83vv3;A)wFqr>!N=+@p4{#o0A-K{X=UIr*d1oUuVmk|Lg{T>ZiQ1 zn4#M6FkU6xX%j=+dv$lGt-W_nXs#5x!X&Y8o(QKFBagl8+LjdzZS(ux4#g`kN=sgm TUVn*!fq}u()z4*}Q$iB}`zPI5 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/payload-loader.png b/core/assets-raw/sprites/blocks/payload/payload-loader.png new file mode 100644 index 0000000000000000000000000000000000000000..19762a9ef46e49d5e71e36ca20ef2fa6b60cd640 GIT binary patch literal 1412 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 Fng9aOJyZYy delta 1254 zcmca*_nvctWIZzj1H&G{>GBK=42;D=?oJHr&dI!FU|`@0@Ck99F>A?&T?g_C8oa!N z_rCmW$H2fc(bL5-q+-t7JFlx2J8-Zi7~T7C`+l2e2_H|50efa}-Pca0_v@_Ysa74~ z^E%i%rHT7Lb29h-hJ~&751crp9v~{Weu43hs2fMhb%g^2<=E>LRGQS{w)!{(R>(9= z@DM1n^QmBBeOSogq~v(wx57i#H(dS90@wDPJF;sU=MBMYrjd!SlhwCv^>GNhaV=$K zP=~6IgP%rNdrJ|k^ar7N-5z@U98;M>t$BZl#c59riWklL8EP&1Lu6lYmMN@dpZ0cwYfh|3a^O~GGojyECKs=B%}zYF zd6EB@t;Zc#^`r#rmjA>v1ZZn z=R#)tuQ^2f-ZWnx#W^Q%qAWjKlGz3sFLjYW%zek&nQD_2rM(-D$~SLhiui9T!g`_R z#36xo^N)091aN+l6Upd3fAfv?0j><8d9PcqS~5R+^RxW*x*bf5+D)$2rQM$x@2ZubvHoRx+@XHG z3p-?!?Xvki9YSAxyt&DPEz4>8`AKe40%B1$6;dw`JpFDSdOzsFgP(Vb7S!H+&sufO zkGcLr>DBFDdpTAL20i}FT&pa=p7Q;u3D=qMX@LsGCeB6y`E!IcraW8V=F!{F`l>gW zsr8Y}6^@l33mx|6zjb~#rSXnuF#n5>KU?e;oPRE~t08W_`wPZjwR{iPv$Vw@`Di}r z$6=qAMyo&T^!^=m`g3^g5B4=5`F}p1Z&G_8GyRY7_xg(t4PKnjr*$oR>9FNu?DYFQ zbMtmKSZ<%L@6L4Y{j-xV;%%ZXpZ}--D$d5}_qKZ{7~Ky{$&p?A*}idZw>#hcZL>Qc z*e!Xx{biZXuF1DK{w(chIa0qu>0h#Dx!sNC)cfBvL}t}qKb%_e&9?t}dcS=Rs+#`o?}7@vkFQd%4Row6I?1?f8gQ|iw9G* z*Hlhv|6{DR?!(_X*RNHTpS5vn=dbvR!??KRoZ^xC3BqJ*(~9mt$Fev ze!Y`)ncWh)m|fWZqv$6!-5cINe4qTBUJhZ&LC+)sEr**upHUIQPr)d|stc4Zh^;{Y_KU_WZ&gI1P`i)|Y zY6rr(T01Aa7fL$7@}sE5Lh#dECxHt0fRYEVSMM*^Ub5iDf%or)?&nTuUc@Z?mw92) XoqPBGCLd&AU|{fc^>bP0l+XkK5rjzN diff --git a/core/assets-raw/sprites/blocks/payload/payload-unloader-top.png b/core/assets-raw/sprites/blocks/payload/payload-unloader-top.png new file mode 100644 index 0000000000000000000000000000000000000000..34ec963e54056d923cfcd0219b668bdd4ad21f82 GIT binary patch literal 1033 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&zK-mVmCVhsK75Q@J;|2@5@zJ#eql(9khU&mme` z*JlR)z^Qi2%-*N0s#la%S8x2utU6`# ztcy4Pv8q0qJo#)7LxWJ!iL*yE85XpNcx{qkJa9VP`W}Bo+&=z$>OV9&>yvA9OXTEK zmjp}M6__!xcwGoqt~nsY5YW^X^^lp5kwLX(g{0jBL56^)?pqI;85tQ=XYdHx3GCo! zXfR)2zK8wTJK<&V+t;6|bvn88h}EK1)0g$NEcc$Tx9`ElC9fwYwEchh!}HR{S&kb^H!lP!39AEhMeca_x;?z!kBYmrn;J9>R;}4jAjc>6TkjoP`CeI z)&Dlrw`1#7(^IWiy8G9}W%jq6ZJqr2`gXbdM;_gkHa@<`x`KhBV&l&>FDGu;z1^Nw zK;~?dtx(arKbsBamF=rMHXIg@nw-$CpP@2k zP5x2E7L}UFC{DTCT`ciG4owgj_w3%#-F`cNE^kNcec^q9F^^BJi{dwNZ&LdcR<&P1 zn)Sec<%QQ$ORoy>sYhRyJ1o9MDC^#35r0RueJ@Wex%z=I_IB3&06w9%_3B$?_MNo3 zZ6BfV&GU=Uz6;gA8RyJvNsG&!dhfPBhhmF>6Hdb8`Imnk=WTACZ~tO*X7%41pR117 z`&S-o%XuyI?C!r+{-@ftmUHN?=bW|e>bgHilVsCNV%HqL`nh`21gU)i&Q2dip3E)_ z>*x2f-z8bYIy=k9-IXb+xX!~#d#^ml_S6?y6PiWdZr-?j@w7wc8<#6>X8yU#G0yAa zzJQxv@=6P@eqf#WKx*%{7H`eym#gQkp145s#TBMmSrhiX+Sx3Y$2(Pz?T}CvXM9I! z^P13I`@KHq2~TgicSt-&=-WRXrWM=koeFf3dGL3`oez!ws`q)mi18|yIdkiXy4#8~wdZ*@F8BLn7G}6E>xWY=r>FGJ zqdQ(b{uypyv9_6q#sYGc%1lG&v1DE zf|TP2br^Z-r^RrdWRlo7UxxFlS&{PcC0+sC2|ucWR93JU{3+QYw1`Qf&Q6N+KSKah X%5#=qvtt<;7#KWV{an^LB{Ts56eZMX literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/payload/payload-unloader.png b/core/assets-raw/sprites/blocks/payload/payload-unloader.png new file mode 100644 index 0000000000000000000000000000000000000000..2511a0db65fb117fb5188631e06109a6e755ef18 GIT binary patch literal 1417 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 diff --git a/core/assets/bundles/bundle.properties b/core/assets/bundles/bundle.properties index 6b6df7cfd7..1a85f5e625 100644 --- a/core/assets/bundles/bundle.properties +++ b/core/assets/bundles/bundle.properties @@ -1341,10 +1341,16 @@ block.disassembler.name = Disassembler block.silicon-crucible.name = Silicon Crucible block.overdrive-dome.name = Overdrive Dome block.interplanetary-accelerator.name = Interplanetary Accelerator -#experimental, may be removed -block.block-forge.name = Block Forge -block.block-loader.name = Block Loader -block.block-unloader.name = Block Unloader +block.constructor.name = Constructor +block.constructor.description = Fabricates structures up to 2x2 tiles in size. +block.large-constructor.name = Large Constructor +block.large-constructor.description = Fabricates structures up to 4x4 tiles in size. +block.deconstructor.name = Deconstructor +block.deconstructor.description = Deconstructs structures and units. Returns 100% of build cost. +block.payload-loader.name = Payload Loader +block.payload-loader.description = Load liquids and items into blocks. +block.payload-unloader.name = Payload Unloader +block.payload-unloader.description = Unloads liquids and items from blocks. block.switch.name = Switch block.micro-processor.name = Micro Processor diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index c2697d7487..e4ec21e238 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -64,7 +64,7 @@ public class Blocks implements ContentList{ duct, ductRouter, ductBridge, //liquid - mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidTank, liquidJunction, bridgeConduit, phaseConduit, + mechanicalPump, rotaryPump, thermalPump, conduit, pulseConduit, platedConduit, liquidRouter, liquidContainer, liquidTank, liquidJunction, bridgeConduit, phaseConduit, //power combustionGenerator, thermalGenerator, steamGenerator, differentialGenerator, rtgGenerator, solarPanel, largeSolarPanel, thoriumReactor, @@ -86,18 +86,19 @@ public class Blocks implements ContentList{ repairPoint, repairTurret, //payloads - payloadConveyor, payloadRouter, payloadPropulsionTower, + payloadConveyor, payloadRouter, payloadPropulsionTower, deconstructor, constructor, largeConstructor, payloadLoader, payloadUnloader, //logic message, switchBlock, microProcessor, logicProcessor, hyperProcessor, largeLogicDisplay, logicDisplay, memoryCell, memoryBank, //campaign - launchPad, interplanetaryAccelerator, - - //misc experimental - blockForge, blockLoader, blockUnloader + launchPad, interplanetaryAccelerator ; + /** @deprecated use the blocks with proper names, */ + @Deprecated + public static Block blockForge, blockLoader, blockUnloader; + @Override public void load(){ //region environment @@ -125,7 +126,7 @@ public class Blocks implements ContentList{ isLiquid = true; status = StatusEffects.wet; statusDuration = 120f; - drownTime = 140f; + drownTime = 200f; cacheLayer = CacheLayer.water; albedo = 0.9f; }}; @@ -158,7 +159,7 @@ public class Blocks implements ContentList{ variants = 0; status = StatusEffects.wet; statusDuration = 140f; - drownTime = 120f; + drownTime = 200f; liquidDrop = Liquids.water; isLiquid = true; cacheLayer = CacheLayer.water; @@ -186,7 +187,7 @@ public class Blocks implements ContentList{ }}; tar = new Floor("tar"){{ - drownTime = 150f; + drownTime = 230f; status = StatusEffects.tarred; statusDuration = 240f; speedMultiplier = 0.19f; @@ -213,7 +214,7 @@ public class Blocks implements ContentList{ }}; slag = new Floor("molten-slag"){{ - drownTime = 150f; + drownTime = 230f; status = StatusEffects.melting; statusDuration = 240f; speedMultiplier = 0.19f; @@ -233,6 +234,12 @@ public class Blocks implements ContentList{ placeableOn = false; solid = true; variants = 0; + canShadow = false; + }}; + + empty = new EmptyFloor("empty"){{ + placeableOn = false; + solid = true; }}; stone = new Floor("stone"); @@ -337,34 +344,36 @@ public class Blocks implements ContentList{ attributes.set(Attribute.oil, 1.6f); }}; + moss = new Floor("moss"){{ + variants = 3; + attributes.set(Attribute.spores, 0.15f); + }}; + + sporeMoss = new Floor("spore-moss"){{ + variants = 3; + attributes.set(Attribute.spores, 0.3f); + }}; + stoneWall = new StaticWall("stone-wall"){{ variants = 2; }}; sporeWall = new StaticWall("spore-wall"){{ - variants = 2; - taintedWater.asFloor().wall = deepTaintedWater.asFloor().wall = this; + taintedWater.asFloor().wall = deepTaintedWater.asFloor().wall = sporeMoss.asFloor().wall = this; }}; - dirtWall = new StaticWall("dirt-wall"){{ - variants = 2; - }}; + dirtWall = new StaticWall("dirt-wall"); - daciteWall = new StaticWall("dacite-wall"){{ - variants = 2; - }}; + daciteWall = new StaticWall("dacite-wall"); iceWall = new StaticWall("ice-wall"){{ - variants = 2; iceSnow.asFloor().wall = this; + albedo = 0.6f; }}; - snowWall = new StaticWall("snow-wall"){{ - variants = 2; - }}; + snowWall = new StaticWall("snow-wall"); duneWall = new StaticWall("dune-wall"){{ - variants = 2; basalt.asFloor().wall = darksandWater.asFloor().wall = darksandTaintedWater.asFloor().wall = this; }}; @@ -377,21 +386,15 @@ public class Blocks implements ContentList{ shrubs = new StaticWall("shrubs"); - shaleWall = new StaticWall("shale-wall"){{ - variants = 2; - }}; + shaleWall = new StaticWall("shale-wall"); sporePine = new StaticTree("spore-pine"){{ - variants = 0; + moss.asFloor().wall = this; }}; - snowPine = new StaticTree("snow-pine"){{ - variants = 0; - }}; + snowPine = new StaticTree("snow-pine"); - pine = new StaticTree("pine"){{ - variants = 0; - }}; + pine = new StaticTree("pine"); whiteTreeDead = new TreeBlock("white-tree-dead"); @@ -404,7 +407,7 @@ public class Blocks implements ContentList{ boulder = new Prop("boulder"){{ variants = 2; - stone.asFloor().decoration = this; + stone.asFloor().decoration = craters.asFloor().decoration = charr.asFloor().decoration = this; }}; snowBoulder = new Prop("snow-boulder"){{ @@ -414,18 +417,22 @@ public class Blocks implements ContentList{ shaleBoulder = new Prop("shale-boulder"){{ variants = 2; + shale.asFloor().decoration = this; }}; sandBoulder = new Prop("sand-boulder"){{ variants = 2; + sand.asFloor().decoration = this; }}; daciteBoulder = new Prop("dacite-boulder"){{ variants = 2; + dacite.asFloor().decoration = this; }}; basaltBoulder = new Prop("basalt-boulder"){{ variants = 2; + basalt.asFloor().decoration = hotrock.asFloor().decoration = darksand.asFloor().decoration = magmarock.asFloor().decoration = this; }}; moss = new Floor("moss"){{ @@ -1130,10 +1137,16 @@ public class Blocks implements ContentList{ liquidCapacity = 20f; }}; + liquidContainer = new LiquidRouter("liquid-container"){{ + requirements(Category.liquid, with(Items.titanium, 10, Items.metaglass, 15)); + liquidCapacity = 700f; + size = 2; + }}; + liquidTank = new LiquidRouter("liquid-tank"){{ - requirements(Category.liquid, with(Items.titanium, 25, Items.metaglass, 25)); + requirements(Category.liquid, with(Items.titanium, 30, Items.metaglass, 40)); size = 3; - liquidCapacity = 1500f; + liquidCapacity = 1800f; health = 500; }}; @@ -1899,6 +1912,7 @@ public class Blocks implements ContentList{ length = 200f; hitEffect = Fx.hitMeltdown; hitColor = Pal.meltdownHit; + status = StatusEffects.melting; drawSize = 420f; incendChance = 0.4f; @@ -2082,6 +2096,49 @@ public class Blocks implements ContentList{ consumes.power(6f); }}; + deconstructor = new PayloadDeconstructor("deconstructor"){{ + requirements(Category.units, with(Items.thorium, 250, Items.silicon, 200, Items.graphite, 250)); + itemCapacity = 250; + consumes.power(3f); + size = 5; + deconstructSpeed = 2f; + }}; + + constructor = new Constructor("constructor"){{ + requirements(Category.units, with(Items.thorium, 100)); + hasPower = true; + consumes.power(2f); + size = 3; + }}; + + largeConstructor = new Constructor("large-constructor"){{ + requirements(Category.units, with(Items.thorium, 100)); + hasPower = true; + consumes.power(2f); + maxBlockSize = 4; + minBlockSize = 3; + size = 5; + }}; + + payloadLoader = new PayloadLoader("payload-loader"){{ + requirements(Category.units, with(Items.thorium, 100)); + hasPower = true; + consumes.power(2f); + size = 3; + }}; + + payloadUnloader = new PayloadUnloader("payload-unloader"){{ + requirements(Category.units, with(Items.thorium, 100)); + hasPower = true; + consumes.power(2f); + size = 3; + }}; + + //TODO deprecated + blockForge = constructor; + blockLoader = payloadLoader; + blockUnloader = payloadUnloader; + //endregion //region sandbox @@ -2244,30 +2301,6 @@ public class Blocks implements ContentList{ size = 6; }}; - //endregion - //region experimental - - blockForge = new BlockForge("block-forge"){{ - requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100)); - hasPower = true; - consumes.power(2f); - size = 3; - }}; - - blockLoader = new BlockLoader("block-loader"){{ - requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100)); - hasPower = true; - consumes.power(2f); - size = 3; - }}; - - blockUnloader = new BlockUnloader("block-unloader"){{ - requirements(Category.units, BuildVisibility.debugOnly, with(Items.thorium, 100)); - hasPower = true; - consumes.power(2f); - size = 3; - }}; - //endregion } } diff --git a/core/src/mindustry/world/blocks/payloads/BlockProducer.java b/core/src/mindustry/world/blocks/payloads/BlockProducer.java index ec8642cc90..a3186a0dc3 100644 --- a/core/src/mindustry/world/blocks/payloads/BlockProducer.java +++ b/core/src/mindustry/world/blocks/payloads/BlockProducer.java @@ -5,6 +5,7 @@ import arc.math.*; import arc.util.*; import arc.util.io.*; import mindustry.*; +import mindustry.content.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.graphics.*; @@ -34,7 +35,14 @@ public abstract class BlockProducer extends PayloadBlock{ @Override public TextureRegion[] icons(){ - return new TextureRegion[]{region, outRegion}; + return new TextureRegion[]{region, outRegion, topRegion}; + } + + @Override + public void drawRequestRegion(BuildPlan req, Eachable list){ + Draw.rect(region, req.drawx(), req.drawy()); + Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90); + Draw.rect(topRegion, req.drawx(), req.drawy()); } @Override @@ -43,12 +51,6 @@ public abstract class BlockProducer extends PayloadBlock{ bars.add("progress", (BlockProducerBuild entity) -> new Bar("bar.progress", Pal.ammo, () -> entity.recipe() == null ? 0f : (entity.progress / entity.recipe().buildCost))); } - - @Override - public void drawRequestRegion(BuildPlan req, Eachable list){ - Draw.rect(region, req.drawx(), req.drawy()); - Draw.rect(outRegion, req.drawx(), req.drawy(), req.rotation * 90); - } public abstract class BlockProducerBuild extends PayloadBlockBuild{ public float progress, time, heat; @@ -85,6 +87,7 @@ public abstract class BlockProducer extends PayloadBlock{ if(progress >= recipe.buildCost){ consume(); payload = new BuildPayload(recipe, team); + Fx.placeBlock.at(x, y, payload.size() / tilesize); payVector.setZero(); progress %= 1f; } @@ -109,6 +112,7 @@ public abstract class BlockProducer extends PayloadBlock{ for(TextureRegion region : recipe.getGeneratedIcons()){ Shaders.blockbuild.region = region; + Shaders.blockbuild.time = time; Shaders.blockbuild.progress = progress / recipe.buildCost; Draw.rect(region, x, y, recipe.rotate ? rotdeg() : 0); @@ -126,6 +130,9 @@ public abstract class BlockProducer extends PayloadBlock{ } drawPayload(); + + Draw.z(Layer.blockBuilding + 1.1f); + Draw.rect(topRegion, x, y); } @Override diff --git a/core/src/mindustry/world/blocks/payloads/BlockUnloader.java b/core/src/mindustry/world/blocks/payloads/BlockUnloader.java deleted file mode 100644 index 8f4a644fce..0000000000 --- a/core/src/mindustry/world/blocks/payloads/BlockUnloader.java +++ /dev/null @@ -1,67 +0,0 @@ -package mindustry.world.blocks.payloads; - -import mindustry.gen.*; -import mindustry.type.*; - -import static mindustry.Vars.*; - -public class BlockUnloader extends BlockLoader{ - - public BlockUnloader(String name){ - super(name); - } - - @Override - public boolean outputsItems(){ - return true; - } - - @Override - public boolean rotatedOutput(int x, int y){ - return false; - } - - public class BlockUnloaderBuild extends BlockLoaderBuild{ - - @Override - public boolean acceptItem(Building source, Item item){ - return false; - } - - @Override - public void updateTile(){ - if(shouldExport()){ - moveOutPayload(); - }else if(moveInPayload()){ - - //load up items - if(payload.block().hasItems && !full()){ - if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ - //load up items a set amount of times - for(int j = 0; j < itemsLoaded && !full(); j++){ - for(int i = 0; i < items.length(); i++){ - if(payload.build.items.get(i) > 0){ - Item item = content.item(i); - payload.build.items.remove(item, 1); - items.add(item, 1); - break; - } - } - } - } - } - } - - dump(); - } - - public boolean full(){ - return items.total() >= itemCapacity; - } - - @Override - public boolean shouldExport(){ - return payload != null && (payload.block().hasItems && payload.build.items.empty()); - } - } -} diff --git a/core/src/mindustry/world/blocks/payloads/BuildPayload.java b/core/src/mindustry/world/blocks/payloads/BuildPayload.java index d9e6c08fa0..ad6a6a0fc9 100644 --- a/core/src/mindustry/world/blocks/payloads/BuildPayload.java +++ b/core/src/mindustry/world/blocks/payloads/BuildPayload.java @@ -5,6 +5,7 @@ import arc.util.io.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.type.*; import mindustry.world.*; import static mindustry.Vars.*; @@ -33,6 +34,16 @@ public class BuildPayload implements Payload{ build.dropped(); } + @Override + public ItemStack[] requirements(){ + return build.block.requirements; + } + + @Override + public float buildTime(){ + return build.block.buildCost; + } + @Override public float x(){ return build.x; @@ -61,9 +72,14 @@ public class BuildPayload implements Payload{ build.set(x, y); } + @Override + public void drawShadow(float alpha){ + Drawf.shadow(build.x, build.y, build.block.size * tilesize * 2f, alpha); + } + @Override public void draw(){ - Drawf.shadow(build.x, build.y, build.block.size * tilesize * 2f); + drawShadow(1f); Draw.rect(build.block.fullIcon, build.x, build.y); } diff --git a/core/src/mindustry/world/blocks/payloads/BlockForge.java b/core/src/mindustry/world/blocks/payloads/Constructor.java similarity index 88% rename from core/src/mindustry/world/blocks/payloads/BlockForge.java rename to core/src/mindustry/world/blocks/payloads/Constructor.java index 52827780af..4359fd581d 100644 --- a/core/src/mindustry/world/blocks/payloads/BlockForge.java +++ b/core/src/mindustry/world/blocks/payloads/Constructor.java @@ -6,7 +6,6 @@ import arc.scene.ui.layout.*; import arc.util.*; import arc.util.io.*; import mindustry.*; -import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.*; import mindustry.world.meta.*; @@ -14,17 +13,17 @@ import mindustry.world.meta.*; import static mindustry.Vars.*; /** Configurable BlockProducer variant. */ -public class BlockForge extends BlockProducer{ +public class Constructor extends BlockProducer{ public float buildSpeed = 0.4f; public int minBlockSize = 1, maxBlockSize = 2; - public BlockForge(String name){ + public Constructor(String name){ super(name); size = 3; configurable = true; - config(Block.class, (BlockForgeBuild tile, Block block) -> { + config(Block.class, (ConstructorBuild tile, Block block) -> { if(tile.recipe != block) tile.progress = 0f; if(canProduce(block)){ tile.recipe = block; @@ -43,7 +42,7 @@ public class BlockForge extends BlockProducer{ return b.isVisible() && b.size >= minBlockSize && b.size <= maxBlockSize; } - public class BlockForgeBuild extends BlockProducerBuild{ + public class ConstructorBuild extends BlockProducerBuild{ public @Nullable Block recipe; @Override @@ -53,7 +52,7 @@ public class BlockForge extends BlockProducer{ @Override public void buildConfiguration(Table table){ - ItemSelection.buildTable(table, content.blocks().select(BlockForge.this::canProduce), () -> recipe, this::configure); + ItemSelection.buildTable(table, content.blocks().select(Constructor.this::canProduce), () -> recipe, this::configure); } @Override diff --git a/core/src/mindustry/world/blocks/payloads/Payload.java b/core/src/mindustry/world/blocks/payloads/Payload.java index 9563db51d3..4be19f66bf 100644 --- a/core/src/mindustry/world/blocks/payloads/Payload.java +++ b/core/src/mindustry/world/blocks/payloads/Payload.java @@ -7,6 +7,7 @@ import arc.util.io.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.ui.*; +import mindustry.type.*; import mindustry.world.*; import static mindustry.Vars.*; @@ -20,6 +21,8 @@ public interface Payload extends Position{ /** draws this payload at a position. */ void draw(); + void drawShadow(float alpha); + /** @return hitbox size of the payload. */ float size(); @@ -27,6 +30,12 @@ public interface Payload extends Position{ float y(); + /** @return the items needed to make this payload; may be empty. */ + ItemStack[] requirements(); + + /** @return the time taken to build this payload. */ + float buildTime(); + /** @return whether this payload was dumped. */ default boolean dump(){ return false; diff --git a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java index 45bb21eee0..a7885e76c7 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java @@ -9,6 +9,7 @@ import mindustry.annotations.Annotations.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.world.*; +import mindustry.world.meta.*; import static mindustry.Vars.*; @@ -24,6 +25,7 @@ public class PayloadBlock extends Block{ update = true; sync = true; + envEnabled |= Env.space; } public static boolean blends(Building build, int direction){ @@ -78,16 +80,16 @@ public class PayloadBlock extends Block{ } @Override - public boolean canControlSelect(Unit player){ - return !player.spawnedByCore && this.payload == null && acceptUnitPayload(player) && player.tileOn() != null && player.tileOn().build == this; + public boolean canControlSelect(Player player){ + return !player.unit().spawnedByCore && this.payload == null && acceptUnitPayload(player.unit()) && player.tileOn() != null && player.tileOn().build == this; } @Override - public void onControlSelect(Unit player){ + public void onControlSelect(Player player){ float x = player.x, y = player.y; - handleUnitPayload(player, p -> payload = (T)p); + acceptPlayerPayload(player, p -> payload = (T)p); this.payVector.set(x, y).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f); - this.payRotation = player.rotation; + this.payRotation = player.unit().rotation; } @Override diff --git a/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java b/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java new file mode 100644 index 0000000000..328549f0a4 --- /dev/null +++ b/core/src/mindustry/world/blocks/payloads/PayloadDeconstructor.java @@ -0,0 +1,205 @@ +package mindustry.world.blocks.payloads; + +import arc.graphics.g2d.*; +import arc.math.*; +import arc.util.*; +import arc.util.io.*; +import mindustry.content.*; +import mindustry.gen.*; +import mindustry.graphics.*; +import mindustry.ui.*; + +import static mindustry.Vars.*; + +public class PayloadDeconstructor extends PayloadBlock{ + public float maxPayloadSize = 4; + public float deconstructSpeed = 2.5f; + public int dumpRate = 4; + + public PayloadDeconstructor(String name){ + super(name); + + outputsPayload = false; + acceptsPayload = true; + update = true; + rotate = false; + solid = true; + size = 5; + payloadSpeed = 1f; + //make sure to display large units. + clipSize = 120; + hasItems = true; + hasPower = true; + itemCapacity = 100; + } + + @Override + public TextureRegion[] icons(){ + return new TextureRegion[]{region, topRegion}; + } + + @Override + public void setBars(){ + super.setBars(); + + bars.add("progress", (PayloadDeconstructorBuild e) -> new Bar("bar.progress", Pal.ammo, () -> e.progress)); + } + + public class PayloadDeconstructorBuild extends PayloadBlockBuild{ + public @Nullable Payload deconstructing; + public @Nullable float[] accum; + public float progress; + public float time, speedScl; + + @Override + public void draw(){ + Draw.rect(region, x, y); + + //draw input + for(int i = 0; i < 4; i++){ + if(blends(i)){ + Draw.rect(inRegion, x, y, (i * 90) - 180); + } + } + + Draw.z(Layer.blockOver); + drawPayload(); + if(deconstructing != null){ + deconstructing.set(x + payVector.x, y + payVector.y, payRotation); + + Draw.z(Layer.blockOver); + deconstructing.drawShadow(1f - progress); + + //TODO looks really bad + Draw.draw(Layer.blockOver, () -> { + Drawf.construct(x, y, deconstructing.icon(), Pal.remove, deconstructing instanceof BuildPayload ? 0f : payRotation - 90f, 1f - progress, 1f - progress, time); + Draw.color(Pal.remove); + Draw.alpha(1f); + + Lines.lineAngleCenter(x + Mathf.sin(time, 20f, tilesize / 2f * block.size - 3f), y, 90f, block.size * tilesize - 6f); + + Draw.reset(); + }); + } + + Draw.rect(topRegion, x, y); + } + + @Override + public void handlePayload(Building source, Payload payload){ + super.handlePayload(source, payload); + accum = null; + } + + @Override + public boolean acceptPayload(Building source, Payload payload){ + return deconstructing == null && super.acceptPayload(source, payload) && payload.requirements().length > 0 && payload.fits(maxPayloadSize); + } + + @Override + public void updateTile(){ + if(items.total() > 0){ + for(int i = 0; i < dumpRate; i++){ + dumpAccumulate(); + } + } + + if(deconstructing == null){ + progress = 0f; + } + + payRotation = Angles.moveToward(payRotation, 90f, payloadRotateSpeed * edelta()); + + if(deconstructing != null){ + var reqs = deconstructing.requirements(); + if(accum == null || reqs.length != accum.length){ + accum = new float[reqs.length]; + } + + //check if there is enough space to get the items for deconstruction + boolean canProgress = items.total() <= itemCapacity; + if(canProgress){ + for(var ac : accum){ + if(ac >= 1f){ + canProgress = false; + break; + } + } + } + + //move progress forward if possible + if(canProgress){ + float shift = edelta() * deconstructSpeed / deconstructing.buildTime(); + float realShift = Math.min(shift, 1f - progress); + + progress += shift; + time += edelta(); + + for(int i = 0; i < reqs.length; i++){ + accum[i] += reqs[i].amount * realShift; + } + } + + speedScl = Mathf.lerpDelta(speedScl, canProgress ? 1f : 0f, 0.1f); + + //transfer items from accumulation buffer into block inventory when they reach integers + for(int i = 0; i < reqs.length; i++){ + int taken = Math.min((int)accum[i], itemCapacity - items.total()); + if(taken > 0){ + items.add(reqs[i].item, taken); + accum[i] -= taken; + } + } + + //finish deconstruction, prepare for next payload. + if(progress >= 1f){ + Fx.breakBlock.at(x, y, deconstructing.size() / tilesize); + + deconstructing = null; + accum = null; + } + }else if(moveInPayload(false) && payload != null){ + accum = new float[payload.requirements().length]; + deconstructing = payload; + payload = null; + progress = 0f; + } + } + + @Override + public boolean shouldConsume(){ + return deconstructing != null && enabled; + } + + @Override + public void write(Writes write){ + super.write(write); + + write.f(progress); + if(accum != null){ + write.s(accum.length); + for(float v : accum){ + write.f(v); + } + }else{ + write.s(0); + } + Payload.write(deconstructing, write); + } + + @Override + public void read(Reads read, byte revision){ + super.read(read, revision); + + progress = read.f(); + short accums = read.s(); + if(accums > 0){ + accum = new float[accums]; + for(int i = 0; i < accums; i++){ + accum[i] = read.f(); + } + } + deconstructing = Payload.read(read); + } + } +} diff --git a/core/src/mindustry/world/blocks/payloads/BlockLoader.java b/core/src/mindustry/world/blocks/payloads/PayloadLoader.java similarity index 74% rename from core/src/mindustry/world/blocks/payloads/BlockLoader.java rename to core/src/mindustry/world/blocks/payloads/PayloadLoader.java index 001f93712a..04c2d7c60b 100644 --- a/core/src/mindustry/world/blocks/payloads/BlockLoader.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadLoader.java @@ -11,20 +11,21 @@ import mindustry.ui.*; import static mindustry.Vars.*; -public class BlockLoader extends PayloadBlock{ +public class PayloadLoader extends PayloadBlock{ public final int timerLoad = timers++; public float loadTime = 2f; - public int itemsLoaded = 5; - public float liquidsLoaded = 5f; + public int itemsLoaded = 8; + public float liquidsLoaded = 40f; public int maxBlockSize = 2; - public BlockLoader(String name){ + public PayloadLoader(String name){ super(name); hasItems = true; - itemCapacity = 25; - //liquidCapacity = 25; + hasLiquids = true; + itemCapacity = 100; + liquidCapacity = 100f; update = true; outputsPayload = true; size = 3; @@ -45,7 +46,7 @@ public class BlockLoader extends PayloadBlock{ public void setBars(){ super.setBars(); - bars.add("progress", (BlockLoaderBuild entity) -> new Bar(() -> Core.bundle.format("bar.items", entity.payload == null ? 0 : entity.payload.build.items.total()), () -> Pal.items, entity::fraction)); + bars.add("progress", (PayloadLoaderBuild entity) -> new Bar(() -> Core.bundle.format("bar.items", entity.payload == null ? 0 : entity.payload.build.items.total()), () -> Pal.items, entity::fraction)); } @Override @@ -56,14 +57,14 @@ public class BlockLoader extends PayloadBlock{ Draw.rect(topRegion, req.drawx(), req.drawy()); } - public class BlockLoaderBuild extends PayloadBlockBuild{ + public class PayloadLoaderBuild extends PayloadBlockBuild{ @Override public boolean acceptPayload(Building source, Payload payload){ return super.acceptPayload(source, payload) && - (payload instanceof BuildPayload build) && - ((build.build.block.hasItems && build.block().unloadable && build.block().itemCapacity >= 10 && build.block().size <= maxBlockSize)/* || - ((BlockPayload)payload).entity.block().hasLiquids && ((BlockPayload)payload).block().liquidCapacity >= 10f)*/); + payload instanceof BuildPayload build && + ((build.build.block.hasItems && build.block().unloadable && build.block().itemCapacity >= 10 && build.block().size <= maxBlockSize) || + build.build.block().hasLiquids && build.block().liquidCapacity >= 10f); } @Override @@ -71,6 +72,11 @@ public class BlockLoader extends PayloadBlock{ return items.total() < itemCapacity; } + @Override + public boolean acceptLiquid(Building source, Liquid liquid){ + return liquids.current() == liquid || liquids.currentAmount() < 0.2f; + } + @Override public void draw(){ Draw.rect(region, x, y); @@ -120,17 +126,17 @@ public class BlockLoader extends PayloadBlock{ } } - //load up liquids (disabled) - /* + //load up liquids if(payload.block().hasLiquids && liquids.total() >= 0.001f){ Liquid liq = liquids.current(); float total = liquids.currentAmount(); - float flow = Math.min(Math.min(liquidsLoaded * delta(), payload.block().liquidCapacity - payload.entity.liquids.get(liq) - 0.0001f), total); - if(payload.entity.acceptLiquid(payload.entity, liq, flow)){ - payload.entity.liquids.add(liq, flow); + float flow = Math.min(Math.min(liquidsLoaded * edelta(), payload.block().liquidCapacity - payload.build.liquids.get(liq)), total); + //TODO potential crash here + if(payload.build.acceptLiquid(payload.build, liq)){ + payload.build.liquids.add(liq, flow); liquids.remove(liq, flow); } - }*/ + } } } @@ -139,8 +145,8 @@ public class BlockLoader extends PayloadBlock{ } public boolean shouldExport(){ - return payload != null && - ((payload.block().hasLiquids && payload.build.liquids.total() >= payload.block().liquidCapacity - 0.001f) || + return payload != null && ( + (payload.block().hasLiquids && payload.build.liquids.total() >= payload.block().liquidCapacity - 0.001f) || (payload.block().hasItems && payload.build.items.total() >= payload.block().itemCapacity)); } } diff --git a/core/src/mindustry/world/blocks/payloads/PayloadUnloader.java b/core/src/mindustry/world/blocks/payloads/PayloadUnloader.java new file mode 100644 index 0000000000..7d63380528 --- /dev/null +++ b/core/src/mindustry/world/blocks/payloads/PayloadUnloader.java @@ -0,0 +1,97 @@ +package mindustry.world.blocks.payloads; + +import mindustry.gen.*; +import mindustry.type.*; + +import static mindustry.Vars.*; + +public class PayloadUnloader extends PayloadLoader{ + public int offloadSpeed = 4; + + public PayloadUnloader(String name){ + super(name); + } + + @Override + public boolean outputsItems(){ + return true; + } + + @Override + public boolean rotatedOutput(int x, int y){ + return false; + } + + public class PayloadUnloaderBuild extends PayloadLoaderBuild{ + + @Override + public boolean acceptItem(Building source, Item item){ + return false; + } + + @Override + public boolean acceptLiquid(Building source, Liquid liquid){ + return false; + } + + @Override + public void updateTile(){ + if(shouldExport()){ + //one-use, disposable block + if(payload.block().instantDeconstruct){ + payload.block().breakEffect.at(this, payload.block().size); + payload = null; + }else{ + moveOutPayload(); + } + }else if(moveInPayload()){ + + //unload items + if(payload.block().hasItems && !full()){ + if(efficiency() > 0.01f && timer(timerLoad, loadTime / efficiency())){ + //load up items a set amount of times + for(int j = 0; j < itemsLoaded && !full(); j++){ + for(int i = 0; i < items.length(); i++){ + if(payload.build.items.get(i) > 0){ + Item item = content.item(i); + payload.build.items.remove(item, 1); + items.add(item, 1); + break; + } + } + } + } + } + + //unload liquids + //TODO tile is null may crash + if(payload.block().hasLiquids && payload.build.liquids.currentAmount() >= 0.01f && + (liquids.current() == payload.build.liquids.current() || liquids.currentAmount() <= 0.2f)){ + var liq = payload.build.liquids.current(); + float remaining = liquidCapacity - liquids.currentAmount(); + float flow = Math.min(Math.min(liquidsLoaded * delta(), remaining), payload.build.liquids.currentAmount()); + + liquids.add(liq, flow); + payload.build.liquids.remove(liq, flow); + } + } + + dumpLiquid(liquids.current()); + for(int i = 0; i < offloadSpeed; i++){ + dumpAccumulate(); + } + } + + public boolean full(){ + return items.total() >= itemCapacity; + } + + @Override + public boolean shouldExport(){ + return payload != null && ( + (!payload.block().hasItems || payload.build.items.empty()) && + (!payload.block().hasLiquids || payload.build.liquids.currentAmount() <= 0.001f) + ); + } + } +} diff --git a/core/src/mindustry/world/blocks/payloads/UnitPayload.java b/core/src/mindustry/world/blocks/payloads/UnitPayload.java index 99558971da..838f987477 100644 --- a/core/src/mindustry/world/blocks/payloads/UnitPayload.java +++ b/core/src/mindustry/world/blocks/payloads/UnitPayload.java @@ -13,6 +13,7 @@ import mindustry.entities.EntityCollisions.*; import mindustry.entities.*; import mindustry.game.EventType.*; import mindustry.gen.*; +import mindustry.type.*; import static mindustry.Vars.*; @@ -39,6 +40,16 @@ public class UnitPayload implements Payload{ showOverlay(icon.getRegion()); } + @Override + public ItemStack[] requirements(){ + return unit.type.getTotalRequirements(); + } + + @Override + public float buildTime(){ + return unit.type.getBuildTime(); + } + @Override public void write(Writes write){ write.b(payloadUnit); @@ -105,11 +116,20 @@ public class UnitPayload implements Payload{ //prevents stacking unit.vel.add(Mathf.range(0.5f), Mathf.range(0.5f)); unit.add(); + unit.unloaded(); Events.fire(new UnitUnloadEvent(unit)); return true; } + @Override + public void drawShadow(float alpha){ + //TODO should not happen + if(unit.type == null) return; + + unit.type.drawSoftShadow(unit, alpha); + } + @Override public void draw(){ //TODO should not happen From 6b59c1cd83043a0e56ac31c860abdb42ea47b613 Mon Sep 17 00:00:00 2001 From: Anuken Date: Thu, 14 Oct 2021 20:58:03 -0400 Subject: [PATCH 2/2] More branch merging --- .../blocks/liquid/liquid-container-bottom.png | Bin 0 -> 626 bytes .../blocks/liquid/liquid-container-top.png | Bin 0 -> 298 bytes .../sprites/blocks/liquid/rotary-pump.png | Bin 712 -> 1193 bytes core/assets/bundles/bundle.properties | 2 + core/assets/icons/icons.properties | 7 + core/assets/logicids.dat | Bin 2663 -> 2719 bytes core/assets/shaders/clouds.vert | 20 +++ core/src/mindustry/Vars.java | 2 +- core/src/mindustry/ai/Pathfinder.java | 1 + core/src/mindustry/ai/WaveSpawner.java | 1 + core/src/mindustry/content/Blocks.java | 25 +-- core/src/mindustry/content/Planets.java | 8 +- core/src/mindustry/content/UnitTypes.java | 35 ++--- core/src/mindustry/core/ContentLoader.java | 5 - core/src/mindustry/core/Renderer.java | 92 ++++++++++- core/src/mindustry/core/UI.java | 2 +- core/src/mindustry/core/World.java | 19 ++- core/src/mindustry/ctype/Content.java | 7 +- core/src/mindustry/entities/Units.java | 2 +- .../mindustry/entities/abilities/Ability.java | 1 + .../mindustry/entities/comp/EntityComp.java | 1 - .../mindustry/entities/comp/FlyingComp.java | 28 +++- .../entities/comp/LaunchCoreComp.java | 1 - .../src/mindustry/entities/comp/LegsComp.java | 40 ++++- .../src/mindustry/entities/comp/MechComp.java | 16 ++ .../mindustry/entities/comp/PayloadComp.java | 2 +- .../src/mindustry/entities/comp/UnitComp.java | 12 ++ core/src/mindustry/game/Rules.java | 19 +++ core/src/mindustry/game/Universe.java | 5 +- .../mindustry/graphics/IndexedRenderer.java | 42 ++--- .../src/mindustry/graphics/LightRenderer.java | 4 + core/src/mindustry/graphics/Shaders.java | 29 +++- core/src/mindustry/graphics/Voronoi.java | 2 +- .../mindustry/graphics/g3d/GenericMesh.java | 7 + core/src/mindustry/graphics/g3d/HexMesh.java | 6 +- .../mindustry/graphics/g3d/HexSkyMesh.java | 60 +++++++ core/src/mindustry/graphics/g3d/MatMesh.java | 22 +++ .../src/mindustry/graphics/g3d/MultiMesh.java | 18 +++ .../src/mindustry/graphics/g3d/NoiseMesh.java | 43 ++++++ .../mindustry/graphics/g3d/PlanetGrid.java | 2 + .../mindustry/graphics/g3d/PlanetMesh.java | 28 ++-- .../mindustry/graphics/g3d/PlanetParams.java | 40 +++++ .../graphics/g3d/PlanetRenderer.java | 126 ++++++++------- .../graphics/g3d/ShaderSphereMesh.java | 5 - core/src/mindustry/graphics/g3d/SunMesh.java | 5 - core/src/mindustry/io/JsonIO.java | 13 ++ core/src/mindustry/io/SaveFileReader.java | 5 +- .../maps/generators/PlanetGenerator.java | 5 + core/src/mindustry/mod/ClassMap.java | 6 - core/src/mindustry/type/ItemSeq.java | 21 +++ core/src/mindustry/type/Planet.java | 87 ++++++----- core/src/mindustry/type/Sector.java | 11 +- core/src/mindustry/type/UnitType.java | 146 ++++++++++++++---- core/src/mindustry/type/Weapon.java | 2 +- core/src/mindustry/type/Weather.java | 2 + core/src/mindustry/ui/Menus.java | 9 +- core/src/mindustry/ui/dialogs/JoinDialog.java | 2 +- .../mindustry/ui/dialogs/MapPlayDialog.java | 2 +- .../mindustry/ui/dialogs/PlanetDialog.java | 137 +++++++++------- core/src/mindustry/world/Block.java | 9 ++ core/src/mindustry/world/Build.java | 2 +- .../world/blocks/ConstructBlock.java | 1 + .../world/blocks/defense/ForceProjector.java | 1 + .../world/blocks/defense/MendProjector.java | 1 + .../blocks/defense/OverdriveProjector.java | 1 + .../mindustry/world/blocks/defense/Wall.java | 3 + .../blocks/defense/turrets/PowerTurret.java | 1 + .../world/blocks/distribution/ItemBridge.java | 2 +- .../world/blocks/distribution/MassDriver.java | 1 + .../blocks/distribution/PayloadConveyor.java | 1 + .../world/blocks/environment/AirBlock.java | 1 + .../world/blocks/environment/EmptyFloor.java | 24 +++ .../world/blocks/environment/Floor.java | 9 +- .../world/blocks/experimental/BlockForge.java | 7 +- .../blocks/experimental/BlockLoader.java | 6 +- .../blocks/experimental/BlockUnloader.java | 6 +- .../world/blocks/liquid/ArmoredConduit.java | 2 +- .../world/blocks/liquid/Conduit.java | 2 +- .../world/blocks/liquid/LiquidBlock.java | 1 + .../world/blocks/liquid/LiquidBridge.java | 1 + .../world/blocks/logic/LogicBlock.java | 3 + .../world/blocks/logic/LogicDisplay.java | 1 + .../world/blocks/logic/MemoryBlock.java | 1 + .../world/blocks/logic/MessageBlock.java | 1 + .../world/blocks/logic/SwitchBlock.java | 1 + .../world/blocks/payloads/PayloadBlock.java | 10 +- .../mindustry/world/blocks/power/Battery.java | 2 + .../world/blocks/power/DecayGenerator.java | 2 + .../world/blocks/power/ImpactReactor.java | 1 + .../world/blocks/power/LightBlock.java | 2 + .../world/blocks/power/NuclearReactor.java | 1 + .../world/blocks/power/PowerDiode.java | 1 + .../world/blocks/power/PowerNode.java | 1 + .../world/blocks/power/SolarGenerator.java | 1 + .../world/blocks/power/ThermalGenerator.java | 2 +- .../world/blocks/production/Drill.java | 8 +- .../world/blocks/production/Incinerator.java | 6 +- .../world/blocks/production/Pump.java | 3 +- .../world/blocks/production/SolidPump.java | 4 +- .../world/blocks/sandbox/LiquidSource.java | 1 + .../world/blocks/sandbox/LiquidVoid.java | 1 + .../world/blocks/storage/CoreBlock.java | 4 +- .../world/blocks/storage/StorageBlock.java | 1 + .../world/blocks/storage/Unloader.java | 1 + .../world/blocks/units/CommandCenter.java | 1 + .../world/blocks/units/RepairPoint.java | 2 + core/src/mindustry/world/meta/StatValues.java | 12 +- core/src/mindustry/world/meta/Stats.java | 2 +- 108 files changed, 1018 insertions(+), 380 deletions(-) create mode 100644 core/assets-raw/sprites/blocks/liquid/liquid-container-bottom.png create mode 100644 core/assets-raw/sprites/blocks/liquid/liquid-container-top.png create mode 100755 core/assets/shaders/clouds.vert create mode 100644 core/src/mindustry/graphics/g3d/GenericMesh.java create mode 100644 core/src/mindustry/graphics/g3d/HexSkyMesh.java create mode 100644 core/src/mindustry/graphics/g3d/MatMesh.java create mode 100644 core/src/mindustry/graphics/g3d/MultiMesh.java create mode 100644 core/src/mindustry/graphics/g3d/NoiseMesh.java create mode 100644 core/src/mindustry/graphics/g3d/PlanetParams.java create mode 100644 core/src/mindustry/world/blocks/environment/EmptyFloor.java diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-container-bottom.png b/core/assets-raw/sprites/blocks/liquid/liquid-container-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..a256cae0b7079406bbecc2780aeb1ce522b9a6b2 GIT binary patch literal 626 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE+`>V{rlK> zP29WHtCgy?x7yg2D$3XP?mpM0e`7QnL>zCY zs5G!Qvb<1?km_OEptONOrfKU=9tLhF;RS9VOs26eaDBm)!_nb8aXAAQBU1y<{9c7$ z!YBCVcV4LFkQ24zURcSa^zYcFxVANaH#G;WRyllW+q(KL3{aF8=MT7J=#>WWQ7jgF!ff z$)|xc;MJAIA6CY<`Te!3`Nci4Qq@e~b>sC%fgHcJzbCFcx>?{=z@!f=8P>L~xa<@h zvmvfRqk)}~SK%*Pf#n&7Fcy`DFOnBdJz(0Puz+F7@xt~0^%$fW85L|hw>*qw5OLDX z%DFnFMBe*{8UvRi^8u-S+yy31B^81P0y!if9%Z=6(NXt+;gWCk)UWS9{jOWc5ZA)P zuqs}ofn8B*!Bu^c16=}~3NOXE5*$02E_~r;y&zWgYxBQLe2g7Zje9vX87{tOlxrzy gSTkKw`w;&xqucvEg>qvU7#J8lUHx3vIVCg!07E|ntpET3 literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/liquid-container-top.png b/core/assets-raw/sprites/blocks/liquid/liquid-container-top.png new file mode 100644 index 0000000000000000000000000000000000000000..2c68c6dd5e6926aefa237c193c71ae21f1df30b5 GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEk44ofy`glX=O&!0^P= z#WAE}&f6Q_e9ZJkuW~K;$raM|z>Im7OT<5?Cnp!~yRPxw t&0|w{z_0rYTv_a2k;A-Z0aE5uX?mcCYfTydU%Q~loCIHJMafAQ> literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/liquid/rotary-pump.png b/core/assets-raw/sprites/blocks/liquid/rotary-pump.png index 29a234b331c6fb7bf012c94a7ee57e344e1775aa..ad88b067530fc5db069a004de3efe2b8b5130757 100644 GIT binary patch delta 1174 zcmX@Xx{`B(ayoTTlB@kxz{O=!n3Jee%;ANz9(?VyO|H&4 zXq~%f_v6a^<8kNLTxMjFx2uZ_e%rBnw)6c=b-prvm+MbA)mPOQUDyRk1ha{J3HyV2HfAALIli_1c4P~l ztM7U5$jg&~%abR$PZ3cRTlqvVcGbpp%LB43jyP=A4`OJj4`+F6x2;C~brlzbf*jxJ zn=<>ZufM$a(*kiro7w`mdIl#c&eJz*lq39(xGdve!!XIud*bw8!b}qx_W5~Fe*TBE zwVZE(73&Xc^@1m>v!*M1>RGtg-Qi-`MbC16O^N%Zw){Tr?_A z+Af^!7!qaTNe~EQ@W4iaWW3R`D)K&LdFJu|&Io-SZY;)rSrAF_|N3|WL zXFb_jtf^GcA=IF3|MLrv*yYgGv6D_s6RTH@VBoA|Xn6Xx)S0DAVx30r6cNQLjsS;G z93lr@=UKTO=@Q^FWz6~1@%8R*F~{(*%*8DnT1-1mJZ?HwqhDOgD!tS7h1a$Jb_Gua z6{o67?{u-av3Ey*LxkXxCya;GRtPCA*n3vMqnBm=+!;45U0MQWlt~pktm24zB53Kd z;eCF+Z&TDnh6TYA7uPTZKU%qt`Jek1mJ2%lnIYY#_DvhUHiUe-@>f&IqgzUG!~6Rj z3%)+t>T<$y-loYRqH^-}%D01EITX)}`ouU?AH2_+lhG{@bgoM^|5j)vcSdK!b05K? ziS8@*vIVVzQvGV9|v2s|07;TJ7hV z8JYO%x?=dl7H6%>%jwr&Pw8p6P!yJ@6WpLC;$v3VsVaWwFmr;ZWGaGfj%q@WQsvi60*P z`1!%+*nzii`76E@aBr)&e)99}J-$D?eoMYz$f$SgRn@6q&JSNsUZ41zCAUC&a*<0D zXZ7m1Gy4ucN%5$DzfNRbZP&toD^i-5x;`+=Z2Pfz8sBsw$6e-;ul(yT$vBis?S3phM8HdCz*?cK%Y^rT?wC1=O0@K!&w?DU|CuoeHGNTk!7Jam;r(mQ2ON%8yo(MQ3wr$( zVf@B1Nz2NH`QeIw=LX3}(fYV*{ziutis6h!-5lp#CiEE@Gg#ShH!>;ks^uS_A-YZB z0Hevn=W4C9ua<(7hbFe5d3kr7b?`bDxz(bYC!QzTCWo+H8d-jt$2m6cG1WVpa42uL!p6FKFO3@o{c P$*Bw+X`AP8oMr?7gQPM> delta 67 zcmbO)`doyGVdh4rU=Abhq@4WZY~8f{qV!Y-9tbBVKQSe>h=C8nE6s!QIZ85$N{dT2 J@8@{H2mtK27fApB diff --git a/core/assets/shaders/clouds.vert b/core/assets/shaders/clouds.vert new file mode 100755 index 0000000000..869b40b964 --- /dev/null +++ b/core/assets/shaders/clouds.vert @@ -0,0 +1,20 @@ +attribute vec4 a_position; +attribute vec3 a_normal; +attribute vec4 a_color; + +uniform mat4 u_proj; +uniform mat4 u_trans; +uniform vec3 u_lightdir; +uniform vec3 u_ambientColor; +uniform float u_alpha; + +varying vec4 v_col; + +const vec3 diffuse = vec3(0.01); + +void main(){ + vec3 norc = u_ambientColor * (diffuse + vec3(clamp((dot(a_normal, u_lightdir) + 1.0) / 2.0, 0.0, 1.0))); + + v_col = a_color * vec4(norc, u_alpha); + gl_Position = u_proj * u_trans * a_position; +} diff --git a/core/src/mindustry/Vars.java b/core/src/mindustry/Vars.java index e890156fc6..3034d4e228 100644 --- a/core/src/mindustry/Vars.java +++ b/core/src/mindustry/Vars.java @@ -380,7 +380,7 @@ public class Vars implements Loadable{ log.log(level, text); try{ - writer.write("[" + Character.toUpperCase(level.name().charAt(0)) +"] " + Log.removeColors(text) + "\n"); + writer.write("[" + Character.toUpperCase(level.name().charAt(0)) + "] " + Log.removeColors(text) + "\n"); writer.flush(); }catch(IOException e){ e.printStackTrace(); diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index beae777c76..de447c3a59 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -50,6 +50,7 @@ public class Pathfinder implements Runnable{ //legs (team, tile) -> PathTile.legSolid(tile) ? impassable : 1 + + (PathTile.deep(tile) ? 6000 : 0) + //leg units can now drown (PathTile.solid(tile) ? 5 : 0), //water diff --git a/core/src/mindustry/ai/WaveSpawner.java b/core/src/mindustry/ai/WaveSpawner.java index 6800c6f618..d2d780618b 100644 --- a/core/src/mindustry/ai/WaveSpawner.java +++ b/core/src/mindustry/ai/WaveSpawner.java @@ -203,6 +203,7 @@ public class WaveSpawner{ unit.apply(StatusEffects.unmoving, 30f); unit.apply(StatusEffects.invincible, 60f); unit.add(); + unit.unloaded(); Events.fire(new UnitSpawnEvent(unit)); Call.spawnEffect(unit.x, unit.y, unit.rotation, unit.type); diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index e4ec21e238..b9204490a9 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -35,7 +35,7 @@ public class Blocks implements ContentList{ public static Block //environment - air, spawn, cliff, deepwater, water, taintedWater, deepTaintedWater, tar, slag, cryofluid, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space, + air, spawn, cliff, deepwater, water, taintedWater, deepTaintedWater, tar, slag, cryofluid, stone, craters, charr, sand, darksand, dirt, mud, ice, snow, darksandTaintedWater, space, empty, dacite, stoneWall, dirtWall, sporeWall, iceWall, daciteWall, sporePine, snowPine, pine, shrubs, whiteTree, whiteTreeDead, sporeCluster, iceSnow, sandWater, darksandWater, duneWall, sandWall, moss, sporeMoss, shale, shaleWall, shaleBoulder, sandBoulder, daciteBoulder, boulder, snowBoulder, basaltBoulder, grass, salt, @@ -435,18 +435,6 @@ public class Blocks implements ContentList{ basalt.asFloor().decoration = hotrock.asFloor().decoration = darksand.asFloor().decoration = magmarock.asFloor().decoration = this; }}; - moss = new Floor("moss"){{ - variants = 3; - attributes.set(Attribute.spores, 0.15f); - wall = sporePine; - }}; - - sporeMoss = new Floor("spore-moss"){{ - variants = 3; - attributes.set(Attribute.spores, 0.3f); - wall = sporeWall; - }}; - metalFloor = new Floor("metal-floor", 0); metalFloorDamaged = new Floor("metal-floor-damaged", 3); @@ -2105,14 +2093,15 @@ public class Blocks implements ContentList{ }}; constructor = new Constructor("constructor"){{ - requirements(Category.units, with(Items.thorium, 100)); + requirements(Category.units, with(Items.silicon, 50, Items.thorium, 70, Items.graphite, 50)); hasPower = true; consumes.power(2f); size = 3; }}; + //yes this block is pretty much useless largeConstructor = new Constructor("large-constructor"){{ - requirements(Category.units, with(Items.thorium, 100)); + requirements(Category.units, with(Items.silicon, 100, Items.thorium, 150, Items.graphite, 50, Items.phaseFabric, 40)); hasPower = true; consumes.power(2f); maxBlockSize = 4; @@ -2121,20 +2110,20 @@ public class Blocks implements ContentList{ }}; payloadLoader = new PayloadLoader("payload-loader"){{ - requirements(Category.units, with(Items.thorium, 100)); + requirements(Category.units, with(Items.graphite, 50, Items.silicon, 50, Items.copper, 100)); hasPower = true; consumes.power(2f); size = 3; }}; payloadUnloader = new PayloadUnloader("payload-unloader"){{ - requirements(Category.units, with(Items.thorium, 100)); + requirements(Category.units, with(Items.graphite, 50, Items.silicon, 50, Items.copper, 100)); hasPower = true; consumes.power(2f); size = 3; }}; - //TODO deprecated + //deprecated, will be removed. blockForge = constructor; blockLoader = payloadLoader; blockUnloader = payloadUnloader; diff --git a/core/src/mindustry/content/Planets.java b/core/src/mindustry/content/Planets.java index 6e81501d15..e7778e4f61 100644 --- a/core/src/mindustry/content/Planets.java +++ b/core/src/mindustry/content/Planets.java @@ -14,7 +14,7 @@ public class Planets implements ContentList{ @Override public void load(){ - sun = new Planet("sun", null, 0, 2){{ + sun = new Planet("sun", null, 4){{ bloom = true; accessible = false; @@ -31,9 +31,13 @@ public class Planets implements ContentList{ ); }}; - serpulo = new Planet("serpulo", sun, 3, 1){{ + serpulo = new Planet("serpulo", sun, 1, 3){{ generator = new SerpuloPlanetGenerator(); meshLoader = () -> new HexMesh(this, 6); + cloudMeshLoader = () -> new MultiMesh( + new HexSkyMesh(this, 11, 0.15f, 0.13f, 5, new Color().set(Pal.spore).mul(0.9f).a(0.75f), 2, 0.45f, 0.9f, 0.38f), + new HexSkyMesh(this, 1, 0.6f, 0.16f, 5, Color.white.cpy().lerp(Pal.spore, 0.55f).a(0.75f), 2, 0.45f, 1f, 0.41f) + ); atmosphereColor = Color.valueOf("3c1b8f"); atmosphereRadIn = 0.02f; atmosphereRadOut = 0.3f; diff --git a/core/src/mindustry/content/UnitTypes.java b/core/src/mindustry/content/UnitTypes.java index 9874f66516..d9afc9eb0d 100644 --- a/core/src/mindustry/content/UnitTypes.java +++ b/core/src/mindustry/content/UnitTypes.java @@ -157,13 +157,13 @@ public class UnitTypes implements ContentList{ rotateSpeed = 2.1f; health = 9000; armor = 10f; - canDrown = false; mechFrontSway = 1f; ammoType = new ItemAmmoType(Items.thorium); mechStepParticles = true; mechStepShake = 0.15f; singleTarget = true; + drownTimeMultiplier = 4f; weapons.add( new Weapon("scepter-weapon"){{ @@ -221,7 +221,7 @@ public class UnitTypes implements ContentList{ armor = 14f; mechStepParticles = true; mechStepShake = 0.75f; - canDrown = false; + drownTimeMultiplier = 6f; mechFrontSway = 1.9f; mechSideSway = 0.6f; ammoType = new ItemAmmoType(Items.thorium); @@ -414,13 +414,13 @@ public class UnitTypes implements ContentList{ hitSize = 24f; rotateSpeed = 1.8f; - canDrown = false; mechFrontSway = 1f; buildSpeed = 3f; mechStepParticles = true; mechStepShake = 0.15f; ammoType = new PowerAmmoType(2500); + drownTimeMultiplier = 4f; speed = 0.44f; boostMultiplier = 2.4f; @@ -501,6 +501,7 @@ public class UnitTypes implements ContentList{ armor = 9f; landShake = 1.5f; rotateSpeed = 1.5f; + drownTimeMultiplier = 6f; commandLimit = 8; @@ -511,7 +512,6 @@ public class UnitTypes implements ContentList{ legTrns = 0.58f; hovering = true; visualElevation = 0.2f; - allowLegStep = true; ammoType = new PowerAmmoType(4000); groundLayer = Layer.legUnit; @@ -602,7 +602,7 @@ public class UnitTypes implements ContentList{ }}; atrax = new UnitType("atrax"){{ - speed = 0.54f; + speed = 0.57f; drag = 0.4f; hitSize = 13f; rotateSpeed = 3f; @@ -618,7 +618,6 @@ public class UnitTypes implements ContentList{ armor = 3f; ammoType = new ItemAmmoType(Items.coal); - allowLegStep = true; visualElevation = 0.2f; groundLayer = Layer.legUnit - 1f; @@ -632,8 +631,8 @@ public class UnitTypes implements ContentList{ shootSound = Sounds.flame; bullet = new LiquidBulletType(Liquids.slag){{ - damage = 11; - speed = 2.4f; + damage = 13; + speed = 2.5f; drag = 0.009f; shootEffect = Fx.shootSmall; lifetime = 57f; @@ -643,11 +642,11 @@ public class UnitTypes implements ContentList{ }}; spiroct = new UnitType("spiroct"){{ - speed = 0.48f; + speed = 0.52f; drag = 0.4f; hitSize = 15f; rotateSpeed = 3f; - health = 910; + health = 940; immunities = ObjectSet.with(StatusEffects.burning, StatusEffects.melting); legCount = 6; legLength = 13f; @@ -660,7 +659,6 @@ public class UnitTypes implements ContentList{ buildSpeed = 0.75f; - allowLegStep = true; visualElevation = 0.3f; groundLayer = Layer.legUnit; @@ -678,7 +676,7 @@ public class UnitTypes implements ContentList{ bullet = new SapBulletType(){{ sapStrength = 0.5f; length = 75f; - damage = 20; + damage = 23; shootEffect = Fx.shootSmall; hitColor = color = Color.valueOf("bf92f9"); despawnEffect = Fx.none; @@ -698,7 +696,7 @@ public class UnitTypes implements ContentList{ bullet = new SapBulletType(){{ sapStrength = 0.8f; length = 40f; - damage = 16; + damage = 18; shootEffect = Fx.shootSmall; hitColor = color = Color.valueOf("bf92f9"); despawnEffect = Fx.none; @@ -711,7 +709,7 @@ public class UnitTypes implements ContentList{ arkyid = new UnitType("arkyid"){{ drag = 0.1f; - speed = 0.6f; + speed = 0.62f; hitSize = 23f; health = 8000; armor = 6f; @@ -733,16 +731,16 @@ public class UnitTypes implements ContentList{ legSplashDamage = 32; legSplashRange = 30; + drownTimeMultiplier = 2f; hovering = true; - allowLegStep = true; visualElevation = 0.65f; groundLayer = Layer.legUnit; BulletType sapper = new SapBulletType(){{ sapStrength = 0.85f; length = 55f; - damage = 37; + damage = 40; shootEffect = Fx.shootSmall; hitColor = color = Color.valueOf("bf92f9"); despawnEffect = Fx.none; @@ -820,6 +818,7 @@ public class UnitTypes implements ContentList{ lightRadius = 140f; rotateSpeed = 1.9f; + drownTimeMultiplier = 3f; legCount = 8; legMoveSpace = 0.8f; @@ -828,7 +827,6 @@ public class UnitTypes implements ContentList{ legExtension = -20; legBaseOffset = 8f; landShake = 1f; - legSpeed = 0.1f; legLengthScl = 0.93f; rippleScale = 3f; legSpeed = 0.19f; @@ -839,7 +837,6 @@ public class UnitTypes implements ContentList{ legSplashRange = 60; hovering = true; - allowLegStep = true; visualElevation = 0.95f; groundLayer = Layer.legUnit; @@ -2287,6 +2284,7 @@ public class UnitTypes implements ContentList{ defaultController = BuilderAI::new; isCounted = false; + lowAltitude = true; flying = true; mineSpeed = 6.5f; mineTier = 1; @@ -2366,6 +2364,7 @@ public class UnitTypes implements ContentList{ defaultController = BuilderAI::new; isCounted = false; + lowAltitude = true; flying = true; mineSpeed = 8f; mineTier = 2; diff --git a/core/src/mindustry/core/ContentLoader.java b/core/src/mindustry/core/ContentLoader.java index 6a836dceb5..715c22a89b 100644 --- a/core/src/mindustry/core/ContentLoader.java +++ b/core/src/mindustry/core/ContentLoader.java @@ -151,11 +151,6 @@ public class ContentLoader{ ColorMapper.load(); } - public void dispose(){ - initialize(Content::dispose); - clear(); - } - /** Get last piece of content created for error-handling purposes. */ public @Nullable Content getLastAdded(){ return lastAdded; diff --git a/core/src/mindustry/core/Renderer.java b/core/src/mindustry/core/Renderer.java index 61e44b011e..79f5ddfc4a 100644 --- a/core/src/mindustry/core/Renderer.java +++ b/core/src/mindustry/core/Renderer.java @@ -1,6 +1,7 @@ package mindustry.core; import arc.*; +import arc.assets.loaders.TextureLoader.*; import arc.files.*; import arc.graphics.*; import arc.graphics.Texture.*; @@ -42,12 +43,14 @@ public class Renderer implements ApplicationListener{ public PlanetRenderer planets; public @Nullable Bloom bloom; + public @Nullable FrameBuffer backgroundBuffer; public FrameBuffer effectBuffer = new FrameBuffer(); public boolean animateShields, drawWeather = true, drawStatus; public float weatherAlpha; /** minZoom = zooming out, maxZoom = zooming in */ public float minZoom = 1.5f, maxZoom = 6f; public Seq envRenderers = new Seq<>(); + public ObjectMap customBackgrounds = new ObjectMap<>(); public TextureRegion[] bubbles = new TextureRegion[16], splashes = new TextureRegion[12]; private @Nullable CoreBuild landCore; @@ -88,6 +91,10 @@ public class Renderer implements ApplicationListener{ envRenderers.add(new EnvRenderer(mask, render)); } + public void addCustomBackground(String name, Runnable render){ + customBackgrounds.put(name, render); + } + @Override public void init(){ planets = new PlanetRenderer(); @@ -108,6 +115,14 @@ public class Renderer implements ApplicationListener{ t.setWrap(TextureWrap.repeat); t.setFilter(TextureFilter.linear); }; + + Events.on(WorldLoadEvent.class, e -> { + //reset background buffer on every world load, so it can be re-cached first render + if(backgroundBuffer != null){ + backgroundBuffer.dispose(); + backgroundBuffer = null; + } + }); } @Override @@ -311,8 +326,81 @@ public class Renderer implements ApplicationListener{ Events.fire(Trigger.postDraw); } - private void drawBackground(){ - //nothing to draw currently + protected void drawBackground(){ + //draw background only if there is no planet background with a skybox + if(state.rules.backgroundTexture != null && (state.rules.planetBackground == null || !state.rules.planetBackground.drawSkybox)){ + if(!assets.isLoaded(state.rules.backgroundTexture, Texture.class)){ + var file = assets.getFileHandleResolver().resolve(state.rules.backgroundTexture); + + //don't draw invalid/non-existent backgrounds. + if(!file.exists() || !file.extEquals("png")){ + return; + } + + var desc = assets.load(state.rules.backgroundTexture, Texture.class, new TextureParameter(){{ + wrapU = wrapV = TextureWrap.mirroredRepeat; + magFilter = minFilter = TextureFilter.linear; + }}); + + assets.finishLoadingAsset(desc); + } + + Texture tex = assets.get(state.rules.backgroundTexture, Texture.class); + Tmp.tr1.set(tex); + Tmp.tr1.u = 0f; + Tmp.tr1.v = 0f; + + float ratio = camera.width / camera.height; + float size = state.rules.backgroundScl; + + Tmp.tr1.u2 = size; + Tmp.tr1.v2 = size / ratio; + + float sx = 0f, sy = 0f; + + if(!Mathf.zero(state.rules.backgroundSpeed)){ + sx = (camera.position.x) / state.rules.backgroundSpeed; + sy = (camera.position.y) / state.rules.backgroundSpeed; + } + + Tmp.tr1.scroll(sx + state.rules.backgroundOffsetX, -sy + state.rules.backgroundOffsetY); + + Draw.rect(Tmp.tr1, camera.position.x, camera.position.y, camera.width, camera.height); + } + + if(state.rules.planetBackground != null){ + int size = Math.max(graphics.getWidth(), graphics.getHeight()); + + boolean resized = false; + if(backgroundBuffer == null){ + resized = true; + backgroundBuffer = new FrameBuffer(size, size); + } + + if(resized || backgroundBuffer.resizeCheck(size, size)){ + backgroundBuffer.begin(Color.clear); + + var params = state.rules.planetBackground; + + //override some values + params.viewW = size; + params.viewH = size; + params.alwaysDrawAtmosphere = true; + params.drawUi = false; + + planets.render(params); + + backgroundBuffer.end(); + } + + float drawSize = Math.max(camera.width, camera.height); + Draw.rect(Draw.wrap(backgroundBuffer.getTexture()), camera.position.x, camera.position.y, drawSize, -drawSize); + } + + if(state.rules.customBackgroundCallback != null && customBackgrounds.containsKey(state.rules.customBackgroundCallback)){ + customBackgrounds.get(state.rules.customBackgroundCallback).run(); + } + } void updateLandParticles(){ diff --git a/core/src/mindustry/core/UI.java b/core/src/mindustry/core/UI.java index b0180958a1..49b5afcdda 100644 --- a/core/src/mindustry/core/UI.java +++ b/core/src/mindustry/core/UI.java @@ -588,7 +588,7 @@ public class UI implements ApplicationListener, Loadable{ if(mag >= 1_000_000_000){ return sign + Strings.fixed(mag / 1_000_000_000f, 1) + "[gray]" + billions+ "[]"; }else if(mag >= 1_000_000){ - return sign + Strings.fixed(mag / 1_000_000f, 1) + "[gray]" +millions + "[]"; + return sign + Strings.fixed(mag / 1_000_000f, 1) + "[gray]" + millions + "[]"; }else if(mag >= 10_000){ return number / 1000 + "[gray]" + thousands + "[]"; }else if(mag >= 1000){ diff --git a/core/src/mindustry/core/World.java b/core/src/mindustry/core/World.java index 839b453867..47d4c05475 100644 --- a/core/src/mindustry/core/World.java +++ b/core/src/mindustry/core/World.java @@ -8,6 +8,7 @@ import arc.math.geom.Geometry.*; import arc.struct.*; import arc.util.*; import arc.util.noise.*; +import mindustry.*; import mindustry.content.*; import mindustry.core.GameState.*; import mindustry.ctype.*; @@ -246,15 +247,17 @@ public class World{ if(sector.preset != null){ sector.preset.generator.generate(tiles); sector.preset.rules.get(state.rules); //apply extra rules - }else{ + }else if(sector.planet.generator != null){ sector.planet.generator.generate(tiles, sector); + }else{ + throw new RuntimeException("Sector " + sector.id + " on planet " + sector.planet.name + " has no generator or preset defined. Provide a planet generator or preset map."); } //just in case state.rules.sector = sector; }); //postgenerate for bases - if(sector.preset == null){ + if(sector.preset == null && sector.planet.generator != null){ sector.planet.generator.postGenerate(tiles); } @@ -477,12 +480,14 @@ public class World{ //TODO optimize; this is very slow and called too often! public float getDarkness(int x, int y){ - int edgeBlend = 2; - float dark = 0; - int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (tiles.width - 1)), Math.abs(y - (tiles.height - 1))))); - if(edgeDst <= edgeBlend){ - dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark); + + if(Vars.state.rules.borderDarkness){ + int edgeBlend = 2; + int edgeDst = Math.min(x, Math.min(y, Math.min(Math.abs(x - (tiles.width - 1)), Math.abs(y - (tiles.height - 1))))); + if(edgeDst <= edgeBlend){ + dark = Math.max((edgeBlend - edgeDst) * (4f / edgeBlend), dark); + } } if(state.hasSector() && state.getSector().preset == null){ diff --git a/core/src/mindustry/ctype/Content.java b/core/src/mindustry/ctype/Content.java index 4103f4899e..fb0679bf9b 100644 --- a/core/src/mindustry/ctype/Content.java +++ b/core/src/mindustry/ctype/Content.java @@ -6,7 +6,7 @@ import mindustry.*; import mindustry.mod.Mods.*; /** Base class for a content type that is loaded in {@link mindustry.core.ContentLoader}. */ -public abstract class Content implements Comparable, Disposable{ +public abstract class Content implements Comparable{ public short id; /** Info on which mod this content was loaded from. */ public ModContentInfo minfo = new ModContentInfo(); @@ -39,11 +39,6 @@ public abstract class Content implements Comparable, Disposable{ return minfo.error != null; } - @Override - public void dispose(){ - //does nothing by default - } - @Override public int compareTo(Content c){ return Integer.compare(id, c.id); diff --git a/core/src/mindustry/entities/Units.java b/core/src/mindustry/entities/Units.java index ae4f8e00eb..532c74fbbf 100644 --- a/core/src/mindustry/entities/Units.java +++ b/core/src/mindustry/entities/Units.java @@ -129,7 +129,7 @@ public class Units{ nearby(x, y, width, height, unit -> { if(boolResult) return; - if((unit.isGrounded() && !unit.type.hovering) == ground){ + if((unit.isGrounded() && !unit.hovering) == ground){ unit.hitboxTile(hitrect); if(hitrect.overlaps(x, y, width, height)){ diff --git a/core/src/mindustry/entities/abilities/Ability.java b/core/src/mindustry/entities/abilities/Ability.java index c11c393602..433563d70b 100644 --- a/core/src/mindustry/entities/abilities/Ability.java +++ b/core/src/mindustry/entities/abilities/Ability.java @@ -7,6 +7,7 @@ import mindustry.gen.*; public abstract class Ability implements Cloneable{ public void update(Unit unit){} public void draw(Unit unit){} + public void death(Unit unit){} public Ability copy(){ try{ diff --git a/core/src/mindustry/entities/comp/EntityComp.java b/core/src/mindustry/entities/comp/EntityComp.java index d44e5481b4..aaacdc521b 100644 --- a/core/src/mindustry/entities/comp/EntityComp.java +++ b/core/src/mindustry/entities/comp/EntityComp.java @@ -1,6 +1,5 @@ package mindustry.entities.comp; -import arc.func.*; import arc.util.io.*; import mindustry.annotations.Annotations.*; import mindustry.entities.*; diff --git a/core/src/mindustry/entities/comp/FlyingComp.java b/core/src/mindustry/entities/comp/FlyingComp.java index 1242593c49..c2d6dacea4 100644 --- a/core/src/mindustry/entities/comp/FlyingComp.java +++ b/core/src/mindustry/entities/comp/FlyingComp.java @@ -8,6 +8,7 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.game.EventType.*; import mindustry.gen.*; +import mindustry.type.*; import mindustry.world.blocks.environment.*; import static mindustry.Vars.*; @@ -16,14 +17,16 @@ import static mindustry.Vars.*; abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{ private static final Vec2 tmp1 = new Vec2(), tmp2 = new Vec2(); - @Import float x, y, speedMultiplier; + @Import float x, y, speedMultiplier, hitSize; @Import Vec2 vel; + @Import UnitType type; @SyncLocal float elevation; private transient boolean wasFlying; transient boolean hovering; transient float drownTime; transient float splashTimer; + transient @Nullable Floor lastDrownFloor; boolean checkTarget(boolean targetAir, boolean targetGround){ return (isGrounded() && targetGround) || (isFlying() && targetAir); @@ -41,6 +44,10 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{ return isGrounded() && !hovering; } + @Nullable Floor drownFloor(){ + return canDrown() ? floorOn() : null; + } + boolean emitWalkSound(){ return true; } @@ -90,20 +97,27 @@ abstract class FlyingComp implements Posc, Velc, Healthc, Hitboxc{ } } - if(canDrown() && floor.isLiquid && floor.drownTime > 0){ - drownTime += Time.delta / floor.drownTime; - drownTime = Mathf.clamp(drownTime); + updateDrowning(); + } + + public void updateDrowning(){ + Floor floor = drownFloor(); + + if(floor != null && floor.isLiquid && floor.drownTime > 0){ + lastDrownFloor = floor; + drownTime += Time.delta / floor.drownTime / type.drownTimeMultiplier; if(Mathf.chanceDelta(0.05f)){ - floor.drownUpdateEffect.at(x, y, 1f, floor.mapColor); + floor.drownUpdateEffect.at(x, y, hitSize, floor.mapColor); } - //TODO is the netClient check necessary? if(drownTime >= 0.999f && !net.client()){ kill(); Events.fire(new UnitDrownEvent(self())); } }else{ - drownTime = Mathf.lerpDelta(drownTime, 0f, 0.03f); + drownTime -= Time.delta / 50f; } + + drownTime = Mathf.clamp(drownTime); } } diff --git a/core/src/mindustry/entities/comp/LaunchCoreComp.java b/core/src/mindustry/entities/comp/LaunchCoreComp.java index e91e3749fc..b28e3657b8 100644 --- a/core/src/mindustry/entities/comp/LaunchCoreComp.java +++ b/core/src/mindustry/entities/comp/LaunchCoreComp.java @@ -7,7 +7,6 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.gen.*; import mindustry.graphics.*; -import mindustry.ui.*; import mindustry.world.*; @EntityDef(value = LaunchCorec.class, serialize = false) diff --git a/core/src/mindustry/entities/comp/LegsComp.java b/core/src/mindustry/entities/comp/LegsComp.java index 8ddb4793e4..81073abc1f 100644 --- a/core/src/mindustry/entities/comp/LegsComp.java +++ b/core/src/mindustry/entities/comp/LegsComp.java @@ -9,6 +9,7 @@ import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.entities.*; import mindustry.entities.EntityCollisions.*; +import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; import mindustry.type.*; @@ -18,22 +19,30 @@ import mindustry.world.blocks.environment.*; abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ @Import float x, y; @Import UnitType type; + @Import Team team; transient Leg[] legs = {}; transient float totalLength; transient float moveSpace; transient float baseRotation; + transient Floor lastDeepFloor; @Replace @Override public SolidPred solidity(){ - return !type.allowLegStep ? EntityCollisions::solid : EntityCollisions::legsSolid; + return type.allowLegStep ? EntityCollisions::legsSolid : EntityCollisions::solid; } @Override @Replace public int pathType(){ - return Pathfinder.costLegs; + return type.allowLegStep ? Pathfinder.costGround : Pathfinder.costLegs; + } + + @Override + @Replace + public Floor drownFloor(){ + return lastDeepFloor; } @Override @@ -41,10 +50,18 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ resetLegs(); } + @Override + public void unloaded(){ + resetLegs(1f); + } + public void resetLegs(){ + resetLegs(type.legLength); + } + + public void resetLegs(float legLength){ float rot = baseRotation; int count = type.legCount; - float legLength = type.legLength; this.legs = new Leg[count]; @@ -85,6 +102,9 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ Vec2 moveOffset = Tmp.v4.trns(rot, trns); boolean moving = moving(); + lastDeepFloor = null; + int deeps = 0; + for(int i = 0; i < legs.length; i++){ float dstRot = legAngle(rot, i); Vec2 baseOffset = Tmp.v5.trns(dstRot, type.legBaseOffset).add(x, y); @@ -105,11 +125,16 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ l.moving = move; l.stage = moving ? stageF % 1f : Mathf.lerpDelta(l.stage, 0f, 0.1f); + Floor floor = Vars.world.floorWorld(l.base.x, l.base.y); + if(floor.isDeep()){ + deeps ++; + lastDeepFloor = floor; + } + if(l.group != group){ //create effect when transitioning to a group it can't move in if(!move && i % div == l.group){ - Floor floor = Vars.world.floorWorld(l.base.x, l.base.y); if(floor.isLiquid){ floor.walkEffect.at(l.base.x, l.base.y, type.rippleScale, floor.mapColor); floor.walkSound.at(x, y, 1f, floor.walkSoundVolume); @@ -123,7 +148,7 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ } if(type.legSplashDamage > 0){ - Damage.damage(team(), l.base.x, l.base.y, type.legSplashRange, type.legSplashDamage, false, true); + Damage.damage(team, l.base.x, l.base.y, type.legSplashRange, type.legSplashDamage, false, true); } } @@ -148,6 +173,11 @@ abstract class LegsComp implements Posc, Rotc, Hitboxc, Flyingc, Unitc{ l.joint.lerpDelta(jointDest, moveSpeed / 4f); } + + //when at least 1 leg is touching land, it can't drown + if(deeps != legs.length){ + lastDeepFloor = null; + } } /** @return outwards facing angle of leg at the specified index. */ diff --git a/core/src/mindustry/entities/comp/MechComp.java b/core/src/mindustry/entities/comp/MechComp.java index bd822d523b..18ffc784a7 100644 --- a/core/src/mindustry/entities/comp/MechComp.java +++ b/core/src/mindustry/entities/comp/MechComp.java @@ -10,6 +10,7 @@ import mindustry.entities.*; import mindustry.gen.*; import mindustry.type.*; import mindustry.world.*; +import mindustry.world.blocks.environment.*; import static mindustry.Vars.*; @@ -62,6 +63,21 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati walkExtension = extendScl; } + @Replace + @Override + public @Nullable Floor drownFloor(){ + //large mechs can only drown when all the nearby floors are deep + if(hitSize >= 12 && canDrown()){ + for(Point2 p : Geometry.d8){ + Floor f = world.floorWorld(x + p.x * tilesize, y + p.y * tilesize); + if(!f.isDeep()){ + return null; + } + } + } + return canDrown() ? floorOn() : null; + } + public float walkExtend(boolean scaled){ //now ranges from -maxExtension to maxExtension*3 diff --git a/core/src/mindustry/entities/comp/PayloadComp.java b/core/src/mindustry/entities/comp/PayloadComp.java index 0f02434a92..3cc63c835d 100644 --- a/core/src/mindustry/entities/comp/PayloadComp.java +++ b/core/src/mindustry/entities/comp/PayloadComp.java @@ -13,7 +13,6 @@ import mindustry.entities.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.type.*; -import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.payloads.*; @@ -123,6 +122,7 @@ abstract class PayloadComp implements Posc, Rotc, Hitboxc, Unitc{ //decrement count to prevent double increment if(!u.isAdded()) u.team.data().updateCount(u.type, -1); u.add(); + u.unloaded(); return true; } diff --git a/core/src/mindustry/entities/comp/UnitComp.java b/core/src/mindustry/entities/comp/UnitComp.java index ab36682972..7236f5618b 100644 --- a/core/src/mindustry/entities/comp/UnitComp.java +++ b/core/src/mindustry/entities/comp/UnitComp.java @@ -45,12 +45,18 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I boolean spawnedByCore; double flag; + transient float shadowAlpha = -1f; transient Seq abilities = new Seq<>(0); transient float healTime; private transient float resupplyTime = Mathf.random(10f); private transient boolean wasPlayer; private transient boolean wasHealed; + /** Called when this unit was unloaded from a factory or spawn point. */ + public void unloaded(){ + + } + /** Move based on preferred unit movement type. */ public void movePref(Vec2 movement){ if(type.omniMovement){ @@ -520,6 +526,12 @@ abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, I } } + if(abilities.size > 0){ + for(Ability a : abilities){ + a.death(self()); + } + } + remove(); } diff --git a/core/src/mindustry/game/Rules.java b/core/src/mindustry/game/Rules.java index 3354f223f4..bed0a54cab 100644 --- a/core/src/mindustry/game/Rules.java +++ b/core/src/mindustry/game/Rules.java @@ -6,6 +6,7 @@ import arc.util.*; import arc.util.serialization.*; import arc.util.serialization.Json.*; import mindustry.content.*; +import mindustry.graphics.g3d.*; import mindustry.io.*; import mindustry.type.*; import mindustry.type.Weather.*; @@ -117,8 +118,22 @@ public class Rules{ public @Nullable String modeName; /** Whether cores incinerate items when full, just like in the campaign. */ public boolean coreIncinerates = false; + /** If false, borders fade out into darkness. Only use with custom backgrounds!*/ + public boolean borderDarkness = true; /** special tags for additional info. */ public StringMap tags = new StringMap(); + /** Name of callback to call for background rendering in mods; see Renderer#addCustomBackground. Runs last. */ + public @Nullable String customBackgroundCallback; + /** path to background texture with extension (e.g. "sprites/space.png")*/ + public @Nullable String backgroundTexture; + /** background texture move speed scaling - bigger numbers mean slower movement. 0 to disable. */ + public float backgroundSpeed = 27000f; + /** background texture scaling factor */ + public float backgroundScl = 1f; + /** background UV offsets */ + public float backgroundOffsetX = 0.1f, backgroundOffsetY = 0.1f; + /** Parameters for planet rendered in the background. Cannot be changed once a map is loaded. */ + public @Nullable PlanetParams planetBackground; /** Copies this ruleset exactly. Not efficient at all, do not use often. */ public Rules copy(){ @@ -140,6 +155,10 @@ public class Rules{ } } + public boolean hasEnv(int env){ + return (environment & env) != 0; + } + public float unitBuildSpeed(Team team){ return unitBuildSpeedMultiplier * teams.get(team).unitBuildSpeedMultiplier; } diff --git a/core/src/mindustry/game/Universe.java b/core/src/mindustry/game/Universe.java index 8d563f9957..f12250980c 100644 --- a/core/src/mindustry/game/Universe.java +++ b/core/src/mindustry/game/Universe.java @@ -82,10 +82,11 @@ public class Universe{ } } - if(state.hasSector()){ + if(state.hasSector() && state.getSector().planet.updateLighting){ + var planet = state.getSector().planet; //update sector light float light = state.getSector().getLight(); - float alpha = Mathf.clamp(Mathf.map(light, 0f, 0.8f, 0.3f, 1f)); + float alpha = Mathf.clamp(Mathf.map(light, planet.lightSrcFrom, planet.lightSrcTo, planet.lightDstFrom, planet.lightDstTo)); //assign and map so darkness is not 100% dark state.rules.ambientLight.a = 1f - alpha; diff --git a/core/src/mindustry/graphics/IndexedRenderer.java b/core/src/mindustry/graphics/IndexedRenderer.java index 9c02615fbd..4353279355 100644 --- a/core/src/mindustry/graphics/IndexedRenderer.java +++ b/core/src/mindustry/graphics/IndexedRenderer.java @@ -10,27 +10,29 @@ public class IndexedRenderer implements Disposable{ private static final int vsize = 5; private final Shader program = new Shader( - "attribute vec4 a_position;\n" + - "attribute vec4 a_color;\n" + - "attribute vec2 a_texCoord0;\n" + - "uniform mat4 u_projTrans;\n" + - "varying vec4 v_color;\n" + - "varying vec2 v_texCoords;\n" + + """ + attribute vec4 a_position; + attribute vec4 a_color; + attribute vec2 a_texCoord0; + uniform mat4 u_projTrans; + varying vec4 v_color; + varying vec2 v_texCoords; + void main(){ + v_color = a_color; + v_color.a = v_color.a * (255.0/254.0); + v_texCoords = a_texCoord0; + gl_Position = u_projTrans * a_position; + } + """, - "void main(){\n" + - " v_color = a_color;\n" + - " v_color.a = v_color.a * (255.0/254.0);\n" + - " v_texCoords = a_texCoord0;\n" + - " gl_Position = u_projTrans * a_position;\n" + - "}", - - "varying lowp vec4 v_color;\n" + - "varying vec2 v_texCoords;\n" + - "uniform sampler2D u_texture;\n" + - - "void main(){\n" + - " gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" + - "}" + """ + varying lowp vec4 v_color; + varying vec2 v_texCoords; + uniform sampler2D u_texture; + void main(){ + gl_FragColor = v_color * texture2D(u_texture, v_texCoords); + } + """ ); private Mesh mesh; private float[] tmpVerts = new float[vsize * 6]; diff --git a/core/src/mindustry/graphics/LightRenderer.java b/core/src/mindustry/graphics/LightRenderer.java index acf0583e02..b117b984bc 100644 --- a/core/src/mindustry/graphics/LightRenderer.java +++ b/core/src/mindustry/graphics/LightRenderer.java @@ -191,7 +191,10 @@ public class LightRenderer{ Draw.color(); buffer.begin(Color.clear); + Draw.sort(false); Gl.blendEquationSeparate(Gl.funcAdd, Gl.max); + //apparently necessary + Blending.normal.apply(); for(Runnable run : lights){ run.run(); @@ -202,6 +205,7 @@ public class LightRenderer{ Draw.rect(circleRegion, cir.x, cir.y, cir.radius * 2, cir.radius * 2); } Draw.reset(); + Draw.sort(true); buffer.end(); Gl.blendEquationSeparate(Gl.funcAdd, Gl.funcAdd); diff --git a/core/src/mindustry/graphics/Shaders.java b/core/src/mindustry/graphics/Shaders.java index 33026d6fe1..f0054e95d5 100644 --- a/core/src/mindustry/graphics/Shaders.java +++ b/core/src/mindustry/graphics/Shaders.java @@ -23,6 +23,7 @@ public class Shaders{ public static LightShader light; public static SurfaceShader water, mud, tar, slag, cryofluid, space, caustics; public static PlanetShader planet; + public static CloudShader clouds; public static PlanetGridShader planetGrid; public static AtmosphereShader atmosphere; public static MeshShader mesh; @@ -56,6 +57,7 @@ public class Shaders{ // } //}; planet = new PlanetShader(); + clouds = new CloudShader(); planetGrid = new PlanetGridShader(); atmosphere = new AtmosphereShader(); unlit = new LoadShader("planet", "unlit"); @@ -94,6 +96,7 @@ public class Shaders{ public Vec3 lightDir = new Vec3(1, 1, 1).nor(); public Color ambientColor = Color.white.cpy(); public Vec3 camDir = new Vec3(); + public Planet planet; public PlanetShader(){ super("planet", "planet"); @@ -101,7 +104,7 @@ public class Shaders{ @Override public void apply(){ - camDir.set(renderer.planets.cam.direction).rotate(Vec3.Y, renderer.planets.planet.getRotation()); + camDir.set(renderer.planets.cam.direction).rotate(Vec3.Y, planet.getRotation()); setUniformf("u_lightdir", lightDir); setUniformf("u_ambientColor", ambientColor.r, ambientColor.g, ambientColor.b); @@ -109,6 +112,27 @@ public class Shaders{ } } + public static class CloudShader extends LoadShader{ + public Vec3 lightDir = new Vec3(1, 1, 1).nor(); + public Color ambientColor = Color.white.cpy(); + public Vec3 camDir = new Vec3(); + public float alpha = 1f; + public Planet planet; + + public CloudShader(){ + super("planet", "clouds"); + } + + @Override + public void apply(){ + camDir.set(renderer.planets.cam.direction).rotate(Vec3.Y, planet.getRotation()); + + setUniformf("u_alpha", alpha); + setUniformf("u_lightdir", lightDir); + setUniformf("u_ambientColor", ambientColor.r, ambientColor.g, ambientColor.b); + } + } + public static class MeshShader extends LoadShader{ public MeshShader(){ @@ -176,6 +200,7 @@ public class Shaders{ public static class BlockBuildShader extends LoadShader{ public float progress; public TextureRegion region = new TextureRegion(); + public float time; public BlockBuildShader(){ super("blockbuild", "default"); @@ -186,7 +211,7 @@ public class Shaders{ setUniformf("u_progress", progress); setUniformf("u_uv", region.u, region.v); setUniformf("u_uv2", region.u2, region.v2); - setUniformf("u_time", Time.time); + setUniformf("u_time", time); setUniformf("u_texsize", region.texture.width, region.texture.height); } } diff --git a/core/src/mindustry/graphics/Voronoi.java b/core/src/mindustry/graphics/Voronoi.java index 7b10e497e2..84065f53b2 100755 --- a/core/src/mindustry/graphics/Voronoi.java +++ b/core/src/mindustry/graphics/Voronoi.java @@ -396,7 +396,7 @@ public class Voronoi{ private void clipLine(Edge e){ float pxmin, pxmax, pymin, pymax; Site s1, s2; - float x1 = 0, x2 = 0, y1 = 0, y2 = 0; + float x1, x2, y1, y2; x1 = e.reg[0].coord.x; x2 = e.reg[1].coord.x; diff --git a/core/src/mindustry/graphics/g3d/GenericMesh.java b/core/src/mindustry/graphics/g3d/GenericMesh.java new file mode 100644 index 0000000000..d4198f5d9a --- /dev/null +++ b/core/src/mindustry/graphics/g3d/GenericMesh.java @@ -0,0 +1,7 @@ +package mindustry.graphics.g3d; + +import arc.math.geom.*; + +public interface GenericMesh{ + void render(PlanetParams params, Mat3D projection, Mat3D transform); +} diff --git a/core/src/mindustry/graphics/g3d/HexMesh.java b/core/src/mindustry/graphics/g3d/HexMesh.java index e3ba7b9f27..bf5af1366e 100644 --- a/core/src/mindustry/graphics/g3d/HexMesh.java +++ b/core/src/mindustry/graphics/g3d/HexMesh.java @@ -15,8 +15,12 @@ public class HexMesh extends PlanetMesh{ super(planet, MeshBuilder.buildHex(mesher, divisions, false, planet.radius, 0.2f), shader); } + public HexMesh(){ + } + @Override - public void preRender(){ + public void preRender(PlanetParams params){ + Shaders.planet.planet = planet; Shaders.planet.lightDir.set(planet.solarSystem.position).sub(planet.position).rotate(Vec3.Y, planet.getRotation()).nor(); Shaders.planet.ambientColor.set(planet.solarSystem.lightColor); } diff --git a/core/src/mindustry/graphics/g3d/HexSkyMesh.java b/core/src/mindustry/graphics/g3d/HexSkyMesh.java new file mode 100644 index 0000000000..9cb08db46e --- /dev/null +++ b/core/src/mindustry/graphics/g3d/HexSkyMesh.java @@ -0,0 +1,60 @@ +package mindustry.graphics.g3d; + +import arc.graphics.*; +import arc.math.geom.*; +import arc.util.*; +import arc.util.noise.*; +import mindustry.graphics.*; +import mindustry.type.*; + +public class HexSkyMesh extends PlanetMesh{ + static Mat3D mat = new Mat3D(); + + public float speed = 0f; + + public HexSkyMesh(Planet planet, int seed, float speed, float radius, int divisions, Color color, int octaves, float persistence, float scl, float thresh){ + super(planet, MeshBuilder.buildHex(new HexMesher(){ + @Override + public float getHeight(Vec3 position){ + return 1f; + } + + @Override + public Color getColor(Vec3 position){ + return color; + } + + @Override + public boolean skip(Vec3 position){ + return Simplex.noise3d(planet.id + seed, octaves, persistence, scl, position.x, position.y * 3f, position.z) >= thresh; + } + }, divisions, false, planet.radius, radius), Shaders.clouds); + + this.speed = speed; + } + + public HexSkyMesh(){ + } + + public float relRot(){ + return Time.globalTime * speed / 40f; + } + + @Override + public void render(PlanetParams params, Mat3D projection, Mat3D transform){ + preRender(params); + shader.bind(); + shader.setUniformMatrix4("u_proj", projection.val); + shader.setUniformMatrix4("u_trans", mat.setToTranslation(planet.position).rotate(Vec3.Y, planet.getRotation() + relRot()).val); + shader.apply(); + mesh.render(shader, Gl.triangles); + } + + @Override + public void preRender(PlanetParams params){ + Shaders.clouds.planet = planet; + Shaders.clouds.lightDir.set(planet.solarSystem.position).sub(planet.position).rotate(Vec3.Y, planet.getRotation() + relRot()).nor(); + Shaders.clouds.ambientColor.set(planet.solarSystem.lightColor); + Shaders.clouds.alpha = 1f - params.uiAlpha; + } +} diff --git a/core/src/mindustry/graphics/g3d/MatMesh.java b/core/src/mindustry/graphics/g3d/MatMesh.java new file mode 100644 index 0000000000..5fd064dfda --- /dev/null +++ b/core/src/mindustry/graphics/g3d/MatMesh.java @@ -0,0 +1,22 @@ +package mindustry.graphics.g3d; + +import arc.math.geom.*; + +//TODO maybe this is a bad idea +/** A GenericMesh that wraps and applies an additional transform to a generic mesh. */ +public class MatMesh implements GenericMesh{ + private static final Mat3D tmp = new Mat3D(); + + GenericMesh mesh; + Mat3D mat; + + public MatMesh(GenericMesh mesh, Mat3D mat){ + this.mesh = mesh; + this.mat = mat; + } + + @Override + public void render(PlanetParams params, Mat3D projection, Mat3D transform){ + mesh.render(params, projection, tmp.set(transform).mul(mat)); + } +} diff --git a/core/src/mindustry/graphics/g3d/MultiMesh.java b/core/src/mindustry/graphics/g3d/MultiMesh.java new file mode 100644 index 0000000000..2512a5f172 --- /dev/null +++ b/core/src/mindustry/graphics/g3d/MultiMesh.java @@ -0,0 +1,18 @@ +package mindustry.graphics.g3d; + +import arc.math.geom.*; + +public class MultiMesh implements GenericMesh{ + GenericMesh[] meshes; + + public MultiMesh(GenericMesh... meshes){ + this.meshes = meshes; + } + + @Override + public void render(PlanetParams params, Mat3D projection, Mat3D transform){ + for(var v : meshes){ + v.render(params, projection, transform); + } + } +} diff --git a/core/src/mindustry/graphics/g3d/NoiseMesh.java b/core/src/mindustry/graphics/g3d/NoiseMesh.java new file mode 100644 index 0000000000..f6b8b7328e --- /dev/null +++ b/core/src/mindustry/graphics/g3d/NoiseMesh.java @@ -0,0 +1,43 @@ +package mindustry.graphics.g3d; + +import arc.graphics.*; +import arc.math.geom.*; +import arc.util.noise.*; +import mindustry.graphics.*; +import mindustry.type.*; + +public class NoiseMesh extends HexMesh{ + + public NoiseMesh(Planet planet, int seed, int divisions, Color color, float radius, int octaves, float persistence, float scale, float mag){ + this.planet = planet; + this.shader = Shaders.planet; + this.mesh = MeshBuilder.buildHex(new HexMesher(){ + @Override + public float getHeight(Vec3 position){ + return Simplex.noise3d(planet.id + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag; + } + + @Override + public Color getColor(Vec3 position){ + return color; + } + }, divisions, false, radius, 0.2f); + } + + /** Two-color variant. */ + public NoiseMesh(Planet planet, int seed, int divisions, float radius, int octaves, float persistence, float scale, float mag, Color color1, Color color2, int coct, float cper, float cscl, float cthresh){ + this.planet = planet; + this.shader = Shaders.planet; + this.mesh = MeshBuilder.buildHex(new HexMesher(){ + @Override + public float getHeight(Vec3 position){ + return Simplex.noise3d(planet.id + seed, octaves, persistence, scale, 5f + position.x, 5f + position.y, 5f + position.z) * mag; + } + + @Override + public Color getColor(Vec3 position){ + return Simplex.noise3d(planet.id + seed + 1, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1; + } + }, divisions, false, radius, 0.2f); + } +} diff --git a/core/src/mindustry/graphics/g3d/PlanetGrid.java b/core/src/mindustry/graphics/g3d/PlanetGrid.java index 9b72970087..993ca34c25 100644 --- a/core/src/mindustry/graphics/g3d/PlanetGrid.java +++ b/core/src/mindustry/graphics/g3d/PlanetGrid.java @@ -221,6 +221,8 @@ public class PlanetGrid{ } public static class Ptile{ + public static final Ptile empty = new Ptile(0, 0); + public int id; public int edgeCount; diff --git a/core/src/mindustry/graphics/g3d/PlanetMesh.java b/core/src/mindustry/graphics/g3d/PlanetMesh.java index 23e7966d33..0eaa18adbb 100644 --- a/core/src/mindustry/graphics/g3d/PlanetMesh.java +++ b/core/src/mindustry/graphics/g3d/PlanetMesh.java @@ -3,14 +3,13 @@ package mindustry.graphics.g3d; import arc.graphics.*; import arc.graphics.gl.*; import arc.math.geom.*; -import arc.util.*; import mindustry.type.*; /** Defines a mesh that is rendered for a planet. Subclasses provide a mesh and a shader. */ -public abstract class PlanetMesh implements Disposable{ - protected final Mesh mesh; - protected final Planet planet; - protected final Shader shader; +public abstract class PlanetMesh implements GenericMesh{ + protected Mesh mesh; + protected Planet planet; + protected Shader shader; public PlanetMesh(Planet planet, Mesh mesh, Shader shader){ this.planet = planet; @@ -18,20 +17,21 @@ public abstract class PlanetMesh implements Disposable{ this.shader = shader; } - /** Should be overridden to set up any shader parameters such as planet position, normals, etc. */ - public abstract void preRender(); + public PlanetMesh(){} - public void render(Mat3D projection, Mat3D transform){ - preRender(); + /** Should be overridden to set up any shader parameters such as planet position, normals, etc. + * @param params*/ + public void preRender(PlanetParams params){ + + } + + @Override + public void render(PlanetParams params, Mat3D projection, Mat3D transform){ + preRender(params); shader.bind(); shader.setUniformMatrix4("u_proj", projection.val); shader.setUniformMatrix4("u_trans", transform.val); shader.apply(); mesh.render(shader, Gl.triangles); } - - @Override - public void dispose(){ - mesh.dispose(); - } } diff --git a/core/src/mindustry/graphics/g3d/PlanetParams.java b/core/src/mindustry/graphics/g3d/PlanetParams.java new file mode 100644 index 0000000000..9116592fae --- /dev/null +++ b/core/src/mindustry/graphics/g3d/PlanetParams.java @@ -0,0 +1,40 @@ +package mindustry.graphics.g3d; + +import arc.math.geom.*; +import arc.util.*; +import mindustry.content.*; +import mindustry.graphics.g3d.PlanetRenderer.*; +import mindustry.type.*; + +/** Parameters for rendering a solar system. */ +public class PlanetParams{ + /** Camera direction relative to the planet. Length is determined by zoom. */ + public Vec3 camPos = new Vec3(0f, 0f, 4f); + /** Camera up vector. */ + public Vec3 camUp = new Vec3(0f, 1f, 0f); + /** the unit length direction vector of the camera **/ + public Vec3 camDir = new Vec3(0, 0, -1); + /** The sun/main planet of the solar system from which everything is rendered. */ + public Planet solarSystem = Planets.sun; + /** Planet being looked at. */ + public Planet planet = Planets.serpulo; + /** Zoom relative to planet. */ + public float zoom = 1f; + /** Alpha of orbit rings and other UI elements. */ + public float uiAlpha = 1f; + /** If false, orbit and sector grid are not drawn. */ + public boolean drawUi = false; + /** If true, a space skybox is drawn. */ + public boolean drawSkybox = true; + + /** Handles drawing details. */ + public @Nullable transient PlanetInterfaceRenderer renderer; + /** Viewport size. <=0 to use screen size. Do not change in rules. */ + public transient int viewW = -1, viewH = -1; + /** If true, atmosphere will be drawn regardless of player options. */ + public transient boolean alwaysDrawAtmosphere = false; + + //TODO: + //- blur + //- darken +} diff --git a/core/src/mindustry/graphics/g3d/PlanetRenderer.java b/core/src/mindustry/graphics/g3d/PlanetRenderer.java index 2aef57b29a..dbdac3100b 100644 --- a/core/src/mindustry/graphics/g3d/PlanetRenderer.java +++ b/core/src/mindustry/graphics/g3d/PlanetRenderer.java @@ -9,7 +9,6 @@ import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; -import mindustry.content.*; import mindustry.game.EventType.*; import mindustry.graphics.*; import mindustry.graphics.g3d.PlanetGrid.*; @@ -25,25 +24,15 @@ public class PlanetRenderer implements Disposable{ private static final Seq points = new Seq<>(); - /** Camera direction relative to the planet. Length is determined by zoom. */ - public final Vec3 camPos = new Vec3(); - /** The sun/main planet of the solar system from which everything is rendered. */ - public final Planet solarSystem = Planets.sun; - /** Planet being looked at. */ - public Planet planet = Planets.serpulo; /** Camera used for rendering. */ - public Camera3D cam = new Camera3D(); + public final Camera3D cam = new Camera3D(); /** Raw vertex batch. */ public final VertexBatch3D batch = new VertexBatch3D(20000, false, true, 0); - public float zoom = 1f; - public float orbitAlpha = 1f; - private final Mesh[] outlines = new Mesh[10]; public final PlaneBatch3D projector = new PlaneBatch3D(); public final Mat3D mat = new Mat3D(); public final FrameBuffer buffer = new FrameBuffer(2, 2, true); - public PlanetInterfaceRenderer irenderer; public final Bloom bloom = new Bloom(Core.graphics.getWidth()/4, Core.graphics.getHeight()/4, true, false){{ setThreshold(0.8f); @@ -55,16 +44,13 @@ public class PlanetRenderer implements Disposable{ public final CubemapMesh skybox = new CubemapMesh(new Cubemap("cubemaps/stars/")); public PlanetRenderer(){ - camPos.set(0, 0f, camLength); projector.setScaling(1f / 150f); cam.fov = 60f; cam.far = 150f; } /** Render the entire planet scene to the screen. */ - public void render(PlanetInterfaceRenderer irenderer){ - this.irenderer = irenderer; - + public void render(PlanetParams params){ Draw.flush(); Gl.clear(Gl.depthBufferBit); Gl.enable(Gl.depthTest); @@ -73,49 +59,63 @@ public class PlanetRenderer implements Disposable{ Gl.enable(Gl.cullFace); Gl.cullFace(Gl.back); + int w = params.viewW <= 0 ? Core.graphics.getWidth() : params.viewW; + int h = params.viewH <= 0 ? Core.graphics.getHeight() : params.viewH; + + bloom.blending = !params.drawSkybox; + //lock to up vector so it doesn't get confusing cam.up.set(Vec3.Y); - cam.resize(Core.graphics.getWidth(), Core.graphics.getHeight()); - camPos.setLength(planet.radius * camLength + (zoom-1f) * planet.radius * 2); - cam.position.set(planet.position).add(camPos); - cam.lookAt(planet.position); + cam.resize(w, h); + params.camPos.setLength((params.planet.radius + params.planet.camRadius) * camLength + (params.zoom-1f) * (params.planet.radius + params.planet.camRadius) * 2); + cam.position.set(params.planet.position).add(params.camPos); + //cam.up.set(params.camUp); //TODO broken + cam.lookAt(params.planet.position); cam.update(); + //write back once it changes. + params.camUp.set(cam.up); + params.camDir.set(cam.direction); projector.proj(cam.combined); batch.proj(cam.combined); Events.fire(Trigger.universeDrawBegin); - beginBloom(); + //begin bloom + bloom.resize(w, h); + bloom.capture(); - //render skybox at 0,0,0 - Vec3 lastPos = Tmp.v31.set(cam.position); - cam.position.setZero(); - cam.update(); + if(params.drawSkybox){ + //render skybox at 0,0,0 + Vec3 lastPos = Tmp.v31.set(cam.position); + cam.position.setZero(); + cam.update(); - Gl.depthMask(false); + Gl.depthMask(false); - skybox.render(cam.combined); + skybox.render(cam.combined); - Gl.depthMask(true); + Gl.depthMask(true); - cam.position.set(lastPos); - cam.update(); + cam.position.set(lastPos); + cam.update(); + } Events.fire(Trigger.universeDraw); - renderPlanet(solarSystem); + renderPlanet(params.solarSystem, params); + renderTransparent(params.solarSystem, params); - renderTransparent(solarSystem); - - endBloom(); + bloom.render(); Events.fire(Trigger.universeDrawEnd); Gl.enable(Gl.blend); - irenderer.renderProjections(planet); + if(params.renderer != null){ + params.renderer.renderProjections(params.planet); + } Gl.disable(Gl.cullFace); Gl.disable(Gl.depthTest); @@ -123,68 +123,64 @@ public class PlanetRenderer implements Disposable{ cam.update(); } - public void beginBloom(){ - bloom.resize(Core.graphics.getWidth(), Core.graphics.getHeight()); - bloom.capture(); - } - - public void endBloom(){ - bloom.render(); - } - - - public void renderPlanet(Planet planet){ + public void renderPlanet(Planet planet, PlanetParams params){ if(!planet.visible()) return; cam.update(); if(cam.frustum.containsSphere(planet.position, planet.clipRadius)){ //render planet at offsetted position in the world - planet.draw(cam.combined, planet.getTransform(mat)); + planet.draw(params, cam.combined, planet.getTransform(mat)); } - renderOrbit(planet); - for(Planet child : planet.children){ - renderPlanet(child); + renderPlanet(child, params); } } - public void renderTransparent(Planet planet){ + public void renderTransparent(Planet planet, PlanetParams params){ if(!planet.visible()) return; - if(planet.hasGrid() && planet == this.planet){ - renderSectors(planet); + planet.drawClouds(params, cam.combined, planet.getTransform(mat)); + + if(planet.hasGrid() && planet == params.planet && params.drawUi){ + renderSectors(planet, params); } - if(cam.frustum.containsSphere(planet.position, planet.clipRadius) && planet.parent != null && planet.hasAtmosphere && Core.settings.getBool("atmosphere")){ + if(cam.frustum.containsSphere(planet.position, planet.clipRadius) && planet.parent != null && planet.hasAtmosphere && (params.alwaysDrawAtmosphere || Core.settings.getBool("atmosphere"))){ planet.drawAtmosphere(atmosphere, cam); } - planet.drawClouds(cam.combined, planet.getTransform(mat)); - for(Planet child : planet.children){ - renderTransparent(child); + renderTransparent(child, params); + } + + batch.proj(cam.combined); + + if(params.drawUi){ + renderOrbit(planet, params); } } - public void renderOrbit(Planet planet){ - if(planet.parent == null || !planet.visible() || orbitAlpha <= 0.02f) return; + public void renderOrbit(Planet planet, PlanetParams params){ + if(planet.parent == null || !planet.visible() || params.uiAlpha <= 0.02f || !planet.drawOrbit) return; Vec3 center = planet.parent.position; float radius = planet.orbitRadius; int points = (int)(radius * 10); - Angles.circleVectors(points, radius, (cx, cy) -> batch.vertex(Tmp.v32.set(center).add(cx, 0, cy), Pal.gray.write(Tmp.c1).a(orbitAlpha))); + Angles.circleVectors(points, radius, (cx, cy) -> batch.vertex(Tmp.v32.set(center).add(cx, 0, cy), Pal.gray.write(Tmp.c1).a(params.uiAlpha))); batch.flush(Gl.lineLoop); } - public void renderSectors(Planet planet){ - if(orbitAlpha <= 0.02f) return; + public void renderSectors(Planet planet, PlanetParams params){ + if(params.uiAlpha <= 0.02f) return; //apply transformed position batch.proj().mul(planet.getTransform(mat)); - irenderer.renderSectors(planet); + if(params.renderer != null){ + params.renderer.renderSectors(planet); + } //render sector grid Mesh mesh = outline(planet.grid.size); @@ -262,12 +258,12 @@ public class PlanetRenderer implements Disposable{ } public void setPlane(Sector sector){ - float rotation = -planet.getRotation(); + float rotation = -sector.planet.getRotation(); float length = 0.01f; projector.setPlane( //origin on sector position - Tmp.v33.set(sector.tile.v).setLength(outlineRad + length).rotate(Vec3.Y, rotation).add(planet.position), + Tmp.v33.set(sector.tile.v).setLength(outlineRad + length).rotate(Vec3.Y, rotation).add(sector.planet.position), //face up sector.plane.project(Tmp.v32.set(sector.tile.v).add(Vec3.Y)).sub(sector.tile.v).rotate(Vec3.Y, rotation).nor(), //right vector diff --git a/core/src/mindustry/graphics/g3d/ShaderSphereMesh.java b/core/src/mindustry/graphics/g3d/ShaderSphereMesh.java index e70dd07c29..b7529229f6 100644 --- a/core/src/mindustry/graphics/g3d/ShaderSphereMesh.java +++ b/core/src/mindustry/graphics/g3d/ShaderSphereMesh.java @@ -8,9 +8,4 @@ public class ShaderSphereMesh extends PlanetMesh{ public ShaderSphereMesh(Planet planet, Shader shader, int divisions){ super(planet, MeshBuilder.buildIcosphere(divisions, planet.radius), shader); } - - @Override - public void preRender(){ - - } } diff --git a/core/src/mindustry/graphics/g3d/SunMesh.java b/core/src/mindustry/graphics/g3d/SunMesh.java index 6b00aa3885..b858303e39 100644 --- a/core/src/mindustry/graphics/g3d/SunMesh.java +++ b/core/src/mindustry/graphics/g3d/SunMesh.java @@ -25,9 +25,4 @@ public class SunMesh extends HexMesh{ } }, divisions, Shaders.unlit); } - - @Override - public void preRender(){ - //do absolutely nothing - } } diff --git a/core/src/mindustry/io/JsonIO.java b/core/src/mindustry/io/JsonIO.java index 3e6beb26e8..e92f59bdb1 100644 --- a/core/src/mindustry/io/JsonIO.java +++ b/core/src/mindustry/io/JsonIO.java @@ -163,6 +163,19 @@ public class JsonIO{ } }); + json.setSerializer(Planet.class, new Serializer<>(){ + @Override + public void write(Json json, Planet object, Class knownType){ + json.writeValue(object.name); + } + + @Override + public Planet read(Json json, JsonValue jsonData, Class type){ + Planet block = Vars.content.getByName(ContentType.planet, jsonData.asString()); + return block == null ? Planets.serpulo : block; + } + }); + json.setSerializer(Weather.class, new Serializer<>(){ @Override public void write(Json json, Weather object, Class knownType){ diff --git a/core/src/mindustry/io/SaveFileReader.java b/core/src/mindustry/io/SaveFileReader.java index 0ea4f3af9d..37f07b3e86 100644 --- a/core/src/mindustry/io/SaveFileReader.java +++ b/core/src/mindustry/io/SaveFileReader.java @@ -56,7 +56,10 @@ public abstract class SaveFileReader{ "water", "shallow-water", "slag", "molten-slag", - "cryofluidmixer", "cryofluid-mixer" + "cryofluidmixer", "cryofluid-mixer", + "block-forge", "constructor", + "block-unloader", "payload-unloader", + "block-loader", "payload-loader" ); public static final ObjectMap modContentNameMap = ObjectMap.of( diff --git a/core/src/mindustry/maps/generators/PlanetGenerator.java b/core/src/mindustry/maps/generators/PlanetGenerator.java index 3533943b19..806c04d70c 100644 --- a/core/src/mindustry/maps/generators/PlanetGenerator.java +++ b/core/src/mindustry/maps/generators/PlanetGenerator.java @@ -123,6 +123,11 @@ public abstract class PlanetGenerator extends BasicGenerator implements HexMeshe return 3200; } + public int getSectorSize(Sector sector){ + int res = (int)(sector.rect.radius * getSizeScl()); + return res % 2 == 0 ? res : res + 1; + } + public void generate(Tiles tiles, Sector sec){ this.tiles = tiles; this.sector = sec; diff --git a/core/src/mindustry/mod/ClassMap.java b/core/src/mindustry/mod/ClassMap.java index 7599ca0713..54b5891ab1 100644 --- a/core/src/mindustry/mod/ClassMap.java +++ b/core/src/mindustry/mod/ClassMap.java @@ -218,14 +218,8 @@ public class ClassMap{ classes.put("SwitchBuild", mindustry.world.blocks.logic.SwitchBlock.SwitchBuild.class); classes.put("BallisticSilo", mindustry.world.blocks.payloads.BallisticSilo.class); classes.put("BallisticSiloBuild", mindustry.world.blocks.payloads.BallisticSilo.BallisticSiloBuild.class); - classes.put("BlockForge", mindustry.world.blocks.payloads.BlockForge.class); - classes.put("BlockForgeBuild", mindustry.world.blocks.payloads.BlockForge.BlockForgeBuild.class); - classes.put("BlockLoader", mindustry.world.blocks.payloads.BlockLoader.class); - classes.put("BlockLoaderBuild", mindustry.world.blocks.payloads.BlockLoader.BlockLoaderBuild.class); classes.put("BlockProducer", mindustry.world.blocks.payloads.BlockProducer.class); classes.put("BlockProducerBuild", mindustry.world.blocks.payloads.BlockProducer.BlockProducerBuild.class); - classes.put("BlockUnloader", mindustry.world.blocks.payloads.BlockUnloader.class); - classes.put("BlockUnloaderBuild", mindustry.world.blocks.payloads.BlockUnloader.BlockUnloaderBuild.class); classes.put("BuildPayload", mindustry.world.blocks.payloads.BuildPayload.class); classes.put("NuclearWarhead", mindustry.world.blocks.payloads.NuclearWarhead.class); classes.put("NuclearWarheadBuild", mindustry.world.blocks.payloads.NuclearWarhead.NuclearWarheadBuild.class); diff --git a/core/src/mindustry/type/ItemSeq.java b/core/src/mindustry/type/ItemSeq.java index a02f29bdb6..61bcc37e3a 100644 --- a/core/src/mindustry/type/ItemSeq.java +++ b/core/src/mindustry/type/ItemSeq.java @@ -55,6 +55,21 @@ public class ItemSeq implements Iterable, JsonSerializable{ return out; } + public ItemStack[] toArray(){ + int count = 0; + for(int value : values){ + if(value != 0) count++; + } + ItemStack[] result = new ItemStack[count]; + int index = 0; + for(int i = 0; i < values.length; i++){ + if(values[i] != 0){ + result[index ++] = new ItemStack(Vars.content.item(i), values[i]); + } + } + return result; + } + public void min(int number){ for(Item item : Vars.content.items()){ set(item, Math.min(get(item), number)); @@ -90,6 +105,12 @@ public class ItemSeq implements Iterable, JsonSerializable{ itemModule.each(this::add); } + public void add(ItemStack[] stacks){ + for(var s : stacks){ + add(s); + } + } + public void add(ItemSeq seq){ seq.each(this::add); } diff --git a/core/src/mindustry/type/Planet.java b/core/src/mindustry/type/Planet.java index ac7c2761de..adfc406768 100644 --- a/core/src/mindustry/type/Planet.java +++ b/core/src/mindustry/type/Planet.java @@ -18,14 +18,12 @@ import mindustry.maps.generators.*; import static mindustry.Vars.*; public class Planet extends UnlockableContent{ - /** Default spacing between planet orbits in world units. */ - private static final float orbitSpacing = 11f; /** intersect() temp var. */ private static final Vec3 intersectResult = new Vec3(); /** Mesh used for rendering. Created on load() - will be null on the server! */ - public @Nullable PlanetMesh mesh; + public @Nullable GenericMesh mesh; /** Mesh used for rendering planet clouds. Null if no clouds are present. */ - public @Nullable PlanetMesh cloudMesh; + public @Nullable GenericMesh cloudMesh; /** Position in global coordinates. Will be 0,0,0 until the Universe updates it. */ public Vec3 position = new Vec3(); /** Grid used for the sectors on the planet. Null if this planet can't be landed on. */ @@ -33,9 +31,17 @@ public class Planet extends UnlockableContent{ /** Generator that will make the planet. Can be null for planets that don't need to be landed on. */ public @Nullable PlanetGenerator generator; /** Array of sectors; directly maps to tiles in the grid. */ - public Seq sectors; + public Seq sectors = new Seq<>(); + /** Default spacing between planet orbits in world units. This is defined per-parent! */ + public float orbitSpacing = 12f; /** Radius of this planet's sphere. Does not take into account satellites. */ public float radius; + /** Camera radius offset. */ + public float camRadius; + /** Minimum camera zoom value. */ + public float minZoom = 0.5f; + /** Whether to draw the orbital circle. */ + public boolean drawOrbit = true; /** Atmosphere radius adjustment parameters. */ public float atmosphereRadIn = 0, atmosphereRadOut = 0.3f; /** Frustrum sphere clip radius. */ @@ -48,12 +54,18 @@ public class Planet extends UnlockableContent{ public float orbitTime; /** Time for the planet to perform a full revolution, in seconds. One day. */ public float rotateTime = 24f * 60f; + /** Random orbit angle offset to prevent planets from starting out in a line. */ + public float orbitOffset; /** Approx. radius of one sector. */ public float sectorApproxRadius; /** Whether this planet is tidally locked relative to its parent - see https://en.wikipedia.org/wiki/Tidal_locking */ public boolean tidalLock = false; /** Whether this planet is listed in the planet access UI. **/ public boolean accessible = true; + /** If true, a day/night cycle is simulated. */ + public boolean updateLighting = true; + /** Day/night cycle parameters. */ + public float lightSrcFrom = 0f, lightSrcTo = 0.8f, lightDstFrom = 0.2f, lightDstTo = 1f; /** The default starting sector displayed to the map dialog. */ public int startSector = 0; /** Whether the bloom render effect is enabled. */ @@ -77,32 +89,20 @@ public class Planet extends UnlockableContent{ /** Satellites orbiting this planet. */ public Seq satellites = new Seq<>(); /** Loads the mesh. Clientside only. Defaults to a boring sphere mesh. */ - protected Prov meshLoader = () -> new ShaderSphereMesh(this, Shaders.unlit, 2), cloudMeshLoader = () -> null; + protected Prov meshLoader = () -> new ShaderSphereMesh(this, Shaders.unlit, 2), cloudMeshLoader = () -> null; - public Planet(String name, Planet parent, int sectorSize, float radius){ + public Planet(String name, Planet parent, float radius){ super(name); this.radius = radius; this.parent = parent; - - if(sectorSize > 0){ - grid = PlanetGrid.create(sectorSize); - - sectors = new Seq<>(grid.tiles.length); - for(int i = 0; i < grid.tiles.length; i++){ - sectors.add(new Sector(this, grid.tiles[i])); - } - - sectorApproxRadius = sectors.first().tile.v.dst(sectors.first().tile.corners[0].v); - }else{ - sectors = new Seq<>(); - } + this.orbitOffset = Mathf.randomSeed(id, 360); //total radius is initially just the radius - totalRadius += radius; + totalRadius = radius; //get orbit radius by extending past the parent's total radius - orbitRadius = parent == null ? 0f : (parent.totalRadius + orbitSpacing + totalRadius); + orbitRadius = parent == null ? 0f : (parent.totalRadius + parent.orbitSpacing + totalRadius); //orbit time is based on radius [kepler's third law] orbitTime = Mathf.pow(orbitRadius, 1.5f) * 1000; @@ -117,6 +117,21 @@ public class Planet extends UnlockableContent{ for(solarSystem = this; solarSystem.parent != null; solarSystem = solarSystem.parent); } + public Planet(String name, Planet parent, float radius, int sectorSize){ + this(name, parent, radius); + + if(sectorSize > 0){ + grid = PlanetGrid.create(sectorSize); + + sectors.ensureCapacity(grid.tiles.length); + for(int i = 0; i < grid.tiles.length; i++){ + sectors.add(new Sector(this, grid.tiles[i])); + } + + sectorApproxRadius = sectors.first().tile.v.dst(sectors.first().tile.corners[0].v); + } + } + public @Nullable Sector getLastSector(){ if(sectors.isEmpty()){ return null; @@ -137,6 +152,11 @@ public class Planet extends UnlockableContent{ return grid != null && generator != null && sectors.size > 0; } + /** @return whether this planet has any sectors to land on. */ + public boolean isLandable(){ + return sectors.size > 0; + } + public void updateTotalRadius(){ totalRadius = radius; for(Planet planet : children){ @@ -151,9 +171,7 @@ public class Planet extends UnlockableContent{ /** Calculates orbital rotation based on universe time.*/ public float getOrbitAngle(){ - //applies random offset to prevent planets from starting out in a line - float offset = Mathf.randomSeed(id, 360); - return (offset + universe.secondsf() / (orbitTime / 360f)) % 360f; + return (orbitOffset + universe.secondsf() / (orbitTime / 360f)) % 360f; } /** Calulates rotation on own axis based on universe time.*/ @@ -212,9 +230,6 @@ public class Planet extends UnlockableContent{ /** Regenerates the planet mesh. For debugging only. */ public void reloadMesh(){ - if(mesh != null){ - mesh.dispose(); - } mesh = meshLoader.get(); } @@ -246,14 +261,6 @@ public class Planet extends UnlockableContent{ clipRadius = Math.max(clipRadius, radius + atmosphereRadOut + 0.5f); } - @Override - public void dispose(){ - if(mesh != null){ - mesh.dispose(); - mesh = null; - } - } - /** Gets a sector a tile position. */ public Sector getSector(Ptile tile){ return sectors.get(tile.id); @@ -294,8 +301,8 @@ public class Planet extends UnlockableContent{ return visible; } - public void draw(Mat3D projection, Mat3D transform){ - mesh.render(projection, transform); + public void draw(PlanetParams params, Mat3D projection, Mat3D transform){ + mesh.render(params, projection, transform); } public void drawAtmosphere(Mesh atmosphere, Camera3D cam){ @@ -316,9 +323,9 @@ public class Planet extends UnlockableContent{ Gl.depthMask(true); } - public void drawClouds(Mat3D projection, Mat3D transform){ + public void drawClouds(PlanetParams params, Mat3D projection, Mat3D transform){ if(cloudMesh != null){ - cloudMesh.render(projection, transform); + cloudMesh.render(params, projection, transform); } } } diff --git a/core/src/mindustry/type/Sector.java b/core/src/mindustry/type/Sector.java index 39f31e75ae..0637ed8c2b 100644 --- a/core/src/mindustry/type/Sector.java +++ b/core/src/mindustry/type/Sector.java @@ -40,7 +40,12 @@ public class Sector{ this.planet = planet; this.tile = tile; this.plane = new Plane(); - this.rect = makeRect(); + //empty sector tile needs a special rect + if(tile.corners.length == 0){ + rect = new SectorRect(1f, Vec3.Zero.cpy(), Vec3.Y.cpy(), Vec3.X.cpy(), 0f); + }else{ + this.rect = makeRect(); + } this.id = tile.id; } @@ -156,9 +161,7 @@ public class Sector{ /** @return the sector size, in tiles */ public int getSize(){ - if(planet.generator == null) return 1; - int res = (int)(rect.radius * planet.generator.getSizeScl()); - return res % 2 == 0 ? res : res + 1; + return planet.generator == null ? 1 : planet.generator.getSectorSize(this); } public void removeItems(ItemSeq items){ diff --git a/core/src/mindustry/type/UnitType.java b/core/src/mindustry/type/UnitType.java index 895fcc1f15..d934824d3b 100644 --- a/core/src/mindustry/type/UnitType.java +++ b/core/src/mindustry/type/UnitType.java @@ -71,9 +71,13 @@ public class UnitType extends UnlockableContent{ public int commandLimit = 8; public float commandRadius = 150f; public float visualElevation = -1f; + /** If true and this is a legged unit, this unit can walk over blocks. */ public boolean allowLegStep = false; + /** If true, this unit cannot drown, and will not be affected by the floor under it. */ public boolean hovering = false; public boolean omniMovement = true; + public boolean showHeal = true; + public Color healColor = Pal.heal; public Effect fallEffect = Fx.fallSmoke; public Effect fallThrusterEffect = Fx.fallSmoke; public Effect deathExplosionEffect = Fx.dynamicExplosion; @@ -112,13 +116,14 @@ public class UnitType extends UnlockableContent{ public float dpsEstimate = -1; public float clipSize = -1; public boolean canDrown = true, naval = false; + public float drownTimeMultiplier = 1f; public float engineOffset = 5f, engineSize = 2.5f; public float strafePenalty = 0.5f; public float hitSize = 6f; public float itemOffsetY = 3f; public float lightRadius = -1f, lightOpacity = 0.6f; public Color lightColor = Pal.powerLight; - public boolean drawCell = true, drawItems = true, drawShields = true; + public boolean drawCell = true, drawItems = true, drawShields = true, drawBody = true; public int trailLength = 3; public float trailX = 4f, trailY = -3f, trailScl = 1f; /** Whether the unit can heal blocks. Initialized in init() */ @@ -135,7 +140,9 @@ public class UnitType extends UnlockableContent{ softShadowRegion, jointRegion, footRegion, legBaseRegion, baseJointRegion, outlineRegion; public TextureRegion[] wreckRegions; + protected float buildTime = -1f; protected @Nullable ItemStack[] cachedRequirements; + protected @Nullable ItemStack[] totalRequirements; public UnitType(String name){ super(name); @@ -312,6 +319,8 @@ public class UnitType extends UnlockableContent{ Unit example = constructor.get(); + allowLegStep = example instanceof Legsc; + //water preset if(example instanceof WaterMovec){ naval = true; @@ -323,6 +332,10 @@ public class UnitType extends UnlockableContent{ } } + if(flying){ + envEnabled |= Env.space; + } + if(lightRadius == -1){ lightRadius = Math.max(60f, hitSize * 2.3f); } @@ -475,25 +488,72 @@ public class UnitType extends UnlockableContent{ } } + /** @return the time required to build this unit, as a value that takes into account reconstructors */ + public float getBuildTime(){ + getTotalRequirements(); + return buildTime; + } + + /** @return all items needed to build this unit, including reconstructor steps. */ + public ItemStack[] getTotalRequirements(){ + if(totalRequirements == null){ + UnitType[] ret = {null}; + float[] timeret = {0f}; + ItemStack[] result = getRequirements(ret, timeret); + + //prevents stack overflow if requirements are circular and result != null + totalRequirements = ItemStack.empty; + + if(result != null){ + ItemSeq total = new ItemSeq(); + + total.add(result); + if(ret[0] != null){ + total.add(ret[0].getTotalRequirements()); + } + totalRequirements = total.toArray(); + } + + for(var stack : totalRequirements){ + buildTime += stack.item.cost * stack.amount; + } + } + return totalRequirements; + } + + /** @return item requirements based on reconstructors or factories found; returns previous unit in array if provided */ + public @Nullable ItemStack[] getRequirements(@Nullable UnitType[] prevReturn, @Nullable float[] timeReturn){ + var rec = (Reconstructor)content.blocks().find(b -> b instanceof Reconstructor re && re.upgrades.contains(u -> u[1] == this)); + + if(rec != null && rec.consumes.has(ConsumeType.item) && rec.consumes.get(ConsumeType.item) instanceof ConsumeItems ci){ + if(prevReturn != null){ + prevReturn[0] = rec.upgrades.find(u -> u[1] == this)[0]; + } + if(timeReturn != null){ + timeReturn[0] = rec.constructTime; + } + return ci.items; + }else{ + var factory = (UnitFactory)content.blocks().find(u -> u instanceof UnitFactory uf && uf.plans.contains(p -> p.unit == this)); + if(factory != null){ + + var plan = factory.plans.find(p -> p.unit == this); + if(timeReturn != null){ + timeReturn[0] = plan.time; + } + return plan.requirements; + } + } + return null; + } + @Override public ItemStack[] researchRequirements(){ if(cachedRequirements != null){ return cachedRequirements; } - ItemStack[] stacks = null; - - //calculate costs based on reconstructors or factories found - Block rec = content.blocks().find(b -> b instanceof Reconstructor re && re.upgrades.contains(u -> u[1] == this)); - - if(rec != null && rec.consumes.has(ConsumeType.item) && rec.consumes.get(ConsumeType.item) instanceof ConsumeItems ci){ - stacks = ci.items; - }else{ - UnitFactory factory = (UnitFactory)content.blocks().find(u -> u instanceof UnitFactory uf && uf.plans.contains(p -> p.unit == this)); - if(factory != null){ - stacks = factory.plans.find(p -> p.unit == this).requirements; - } - } + ItemStack[] stacks = getRequirements(null, null); if(stacks != null){ ItemStack[] out = new ItemStack[stacks.length]; @@ -557,10 +617,10 @@ public class UnitType extends UnlockableContent{ Draw.z(z); - drawOutline(unit); + if(drawBody) drawOutline(unit); drawWeaponOutlines(unit); if(engineSize > 0) drawEngine(unit); - drawBody(unit); + if(drawBody) drawBody(unit); if(drawCell) drawCell(unit); drawWeapons(unit); if(drawItems) drawItems(unit); @@ -623,14 +683,25 @@ public class UnitType extends UnlockableContent{ } public void drawShadow(Unit unit){ - Draw.color(Pal.shadow); - float e = Math.max(unit.elevation, visualElevation); + float e = Math.max(unit.elevation, visualElevation) * (1f - unit.drownTime); + float x = unit.x + shadowTX * e, y = unit.y + shadowTY * e; + Floor floor = world.floorWorld(x, y); + + float dest = floor.canShadow ? 1f : 0f; + //yes, this updates state in draw()... which isn't a problem, because I don't want it to be obvious anyway + unit.shadowAlpha = unit.shadowAlpha < 0 ? dest : Mathf.approachDelta(unit.shadowAlpha, dest, 0.11f); + Draw.color(Pal.shadow, Pal.shadow.a * unit.shadowAlpha); + Draw.rect(shadowRegion, unit.x + shadowTX * e, unit.y + shadowTY * e, unit.rotation - 90); Draw.color(); } public void drawSoftShadow(Unit unit){ - Draw.color(0, 0, 0, 0.4f); + drawSoftShadow(unit, 1f); + } + + public void drawSoftShadow(Unit unit, float alpha){ + Draw.color(0, 0, 0, 0.4f * alpha); float rad = 1.6f; float size = Math.max(region.width, region.height) * Draw.scl; Draw.rect(softShadowRegion, unit, size * rad * Draw.xscl, size * rad * Draw.yscl, unit.rotation - 90); @@ -719,16 +790,11 @@ public class UnitType extends UnlockableContent{ Draw.reset(); } - public void applyOutlineColor(Unit unit){ - if(unit.isBoss()){ - Draw.mixcol(unit.team.color, Mathf.absin(7f, 1f)); - } - } - public void drawOutline(Unit unit){ Draw.reset(); if(Core.atlas.isFound(outlineRegion)){ + applyColor(unit); applyOutlineColor(unit); Draw.rect(outlineRegion, unit.x, unit.y, unit.rotation - 90); Draw.reset(); @@ -764,14 +830,16 @@ public class UnitType extends UnlockableContent{ public void drawLegs(T unit){ applyColor(unit); + Tmp.c3.set(Draw.getMixColor()); Leg[] legs = unit.legs(); float ssize = footRegion.width * Draw.scl * 1.5f; float rotation = unit.baseRotation(); + float invDrown = 1f - unit.drownTime; for(Leg leg : legs){ - Drawf.shadow(leg.base.x, leg.base.y, ssize); + Drawf.shadow(leg.base.x, leg.base.y, ssize, invDrown); } //legs are drawn front first @@ -787,13 +855,15 @@ public class UnitType extends UnlockableContent{ Tmp.v1.set(leg.base).sub(leg.joint).inv().setLength(legExtension); if(leg.moving && visualElevation > 0){ - float scl = visualElevation; + float scl = visualElevation * invDrown; float elev = Mathf.slope(1f - leg.stage) * scl; Draw.color(Pal.shadow); Draw.rect(footRegion, leg.base.x + shadowTX * elev, leg.base.y + shadowTY * elev, position.angleTo(leg.base)); Draw.color(); } + Draw.mixcol(Tmp.c3, Tmp.c3.a); + Draw.rect(footRegion, leg.base.x, leg.base.y, position.angleTo(leg.base)); Lines.stroke(legRegion.height * Draw.scl * flips); @@ -848,8 +918,8 @@ public class UnitType extends UnlockableContent{ Draw.mixcol(Color.white, unit.hitTime); - if(floor.isLiquid){ - Draw.color(Color.white, floor.mapColor, unit.drownTime() * 0.4f); + if(unit.lastDrownFloor != null){ + Draw.color(Color.white, Tmp.c1.set(unit.lastDrownFloor.mapColor).mul(0.83f), unit.drownTime * 0.9f); }else{ Draw.color(Color.white); } @@ -859,13 +929,25 @@ public class UnitType extends UnlockableContent{ Draw.mixcol(); } + public void applyOutlineColor(Unit unit){ + if(unit.isBoss()){ + Draw.mixcol(unit.team.color, Mathf.absin(7f, 1f)); + } + + if(unit.drownTime > 0 && unit.lastDrownFloor != null){ + Draw.color(Color.white, Tmp.c1.set(unit.lastDrownFloor.mapColor).mul(0.8f), unit.drownTime * 0.9f); + } + } + public void applyColor(Unit unit){ Draw.color(); - Tmp.c1.set(Color.white).lerp(Pal.heal, Mathf.clamp(unit.healTime - unit.hitTime)); + if(showHeal){ + Tmp.c1.set(Color.white).lerp(healColor, Mathf.clamp(unit.healTime - unit.hitTime)); + } Draw.mixcol(Tmp.c1, Math.max(unit.hitTime, Mathf.clamp(unit.healTime))); - if(unit.drownTime > 0 && unit.floorOn().isDeep()){ - Draw.mixcol(unit.floorOn().mapColor, unit.drownTime * 0.8f); + if(unit.drownTime > 0 && unit.lastDrownFloor != null){ + Draw.mixcol(Tmp.c1.set(unit.lastDrownFloor.mapColor).mul(0.83f), unit.drownTime * 0.9f); } } diff --git a/core/src/mindustry/type/Weapon.java b/core/src/mindustry/type/Weapon.java index db3c924825..dc83c9282f 100644 --- a/core/src/mindustry/type/Weapon.java +++ b/core/src/mindustry/type/Weapon.java @@ -26,7 +26,7 @@ public class Weapon implements Cloneable{ static int sequenceNum = 0; /** displayed weapon region */ - public String name = ""; + public String name; /** bullet shot */ public BulletType bullet = Bullets.standardCopper; /** shell ejection effect */ diff --git a/core/src/mindustry/type/Weather.java b/core/src/mindustry/type/Weather.java index 0f62e423a4..93790fbeee 100644 --- a/core/src/mindustry/type/Weather.java +++ b/core/src/mindustry/type/Weather.java @@ -148,6 +148,8 @@ public class Weather extends UnlockableContent{ Draw.rect(region, x, y, size, size, rotation); } } + + Draw.reset(); } public static void drawRain(float sizeMin, float sizeMax, float xspeed, float yspeed, float density, float intensity, float stroke, Color color){ diff --git a/core/src/mindustry/ui/Menus.java b/core/src/mindustry/ui/Menus.java index 511bd7b55b..016b3416c0 100644 --- a/core/src/mindustry/ui/Menus.java +++ b/core/src/mindustry/ui/Menus.java @@ -11,11 +11,12 @@ import static mindustry.Vars.*; /** Class for handling menus and notifications across the network. Unstable API! */ public class Menus{ - private static IntMap menuListeners = new IntMap<>(); + private static final Seq menuListeners = new Seq<>(); /** Register a *global* menu listener. If no option is chosen, the option is returned as -1. */ - public static void registerMenu(int id, MenuListener listener){ - menuListeners.put(id, listener); + public static int registerMenu(MenuListener listener){ + menuListeners.add(listener); + return menuListeners.size - 1; } //do not invoke any of the methods below directly, use Call @@ -30,7 +31,7 @@ public class Menus{ @Remote(targets = Loc.both, called = Loc.both) public static void menuChoose(@Nullable Player player, int menuId, int option){ - if(player != null && menuListeners.containsKey(menuId)){ + if(player != null && menuId >= 0 && menuId < menuListeners.size){ Events.fire(new MenuOptionChooseEvent(player, menuId, option)); menuListeners.get(menuId).get(player, option); } diff --git a/core/src/mindustry/ui/dialogs/JoinDialog.java b/core/src/mindustry/ui/dialogs/JoinDialog.java index a859620f61..501804f32e 100644 --- a/core/src/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/mindustry/ui/dialogs/JoinDialog.java @@ -512,7 +512,7 @@ public class JoinDialog extends BaseDialog{ void safeConnect(String ip, int port, int version){ if(version != Version.build && Version.build != -1 && version != -1){ - ui.showInfo("[scarlet]" + (version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated).toString() + "\n[]" + + ui.showInfo("[scarlet]" + (version > Version.build ? KickReason.clientOutdated : KickReason.serverOutdated) + "\n[]" + Core.bundle.format("server.versions", Version.build, version)); }else{ connect(ip, port); diff --git a/core/src/mindustry/ui/dialogs/MapPlayDialog.java b/core/src/mindustry/ui/dialogs/MapPlayDialog.java index 004c990f00..730762932c 100644 --- a/core/src/mindustry/ui/dialogs/MapPlayDialog.java +++ b/core/src/mindustry/ui/dialogs/MapPlayDialog.java @@ -97,7 +97,7 @@ public class MapPlayDialog extends BaseDialog{ table.row(); for(Gamemode mode : Gamemode.values()){ if(mode.hidden) continue; - table.labelWrap("[accent]" + mode.toString() + ":[] [lightgray]" + mode.description()).width(400f); + table.labelWrap("[accent]" + mode + ":[] [lightgray]" + mode.description()).width(400f); table.row(); } diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 0ddd26830d..eeab52ae59 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -15,6 +15,7 @@ import arc.scene.ui.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; +import mindustry.*; import mindustry.content.*; import mindustry.content.TechTree.*; import mindustry.core.*; @@ -24,6 +25,7 @@ import mindustry.game.SectorInfo.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; +import mindustry.graphics.g3d.PlanetGrid.*; import mindustry.graphics.g3d.*; import mindustry.input.*; import mindustry.maps.*; @@ -49,10 +51,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ public static float sectorShowDuration = 60f * 2.4f; public final FrameBuffer buffer = new FrameBuffer(2, 2, true); - public final PlanetRenderer planets = renderer.planets; public final LaunchLoadoutDialog loadouts = new LaunchLoadoutDialog(); + public final PlanetRenderer planets = renderer.planets; - public float zoom = 1f, selectAlpha = 1f; + public PlanetParams state = new PlanetParams(); + public float zoom = 1f; public @Nullable Sector selected, hovered, launchSector; public Mode mode = look; public boolean launching; @@ -68,10 +71,13 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ public PlanetDialog(){ super("", Styles.fullDialog); + + state.renderer = this; + state.drawUi = true; shouldPause = true; - planets.planet = content.getByName(ContentType.planet, Core.settings.getString("lastplanet", "serpulo")); - if(planets.planet == null) planets.planet = Planets.serpulo; + state.planet = content.getByName(ContentType.planet, Core.settings.getString("lastplanet", "serpulo")); + if(state.planet == null) state.planet = Planets.serpulo; addListener(new InputListener(){ @Override @@ -107,7 +113,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ newPresets.clear(); } - Vec3 pos = planets.camPos; + Vec3 pos = state.camPos; float upV = pos.angle(Vec3.Y); float xscale = 9f, yscale = 10f; @@ -116,20 +122,20 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //scale X speed depending on polar coordinate float speed = 1f - Math.abs(upV - 90) / 90f; - pos.rotate(planets.cam.up, cx / xscale * speed); + pos.rotate(state.camUp, cx / xscale * speed); //prevent user from scrolling all the way up and glitching it out float amount = cy / yscale; amount = Mathf.clamp(upV + amount, margin, 180f - margin) - upV; - pos.rotate(Tmp.v31.set(planets.cam.up).rotate(planets.cam.direction, 90), amount); + pos.rotate(Tmp.v31.set(state.camUp).rotate(state.camDir, 90), amount); }); addListener(new InputListener(){ @Override public boolean scrolled(InputEvent event, float x, float y, float amountX, float amountY){ if(event.targetActor == PlanetDialog.this){ - zoom = Mathf.clamp(zoom + amountY / 10f, 0.5f, 2f); + zoom = Mathf.clamp(zoom + amountY / 10f, state.planet.minZoom, 2f); } return true; } @@ -144,7 +150,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ lastZoom = zoom; } - zoom = (Mathf.clamp(initialDistance / distance * lastZoom, 0.5f, 2f)); + zoom = (Mathf.clamp(initialDistance / distance * lastZoom, state.planet.minZoom, 2f)); } @Override @@ -170,9 +176,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ launching = false; zoom = 1f; - planets.zoom = 1f; - selectAlpha = 0f; - launchSector = state.getSector(); + state.zoom = 1f; + state.uiAlpha = 0f; + launchSector = Vars.state.getSector(); presetShow = 0f; showed = false; listener = s -> {}; @@ -181,7 +187,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //announce new presets for(SectorPreset preset : content.sectors()){ - if(preset.unlocked() && !preset.alwaysUnlocked && !preset.sector.info.shown && !preset.sector.hasBase() && preset.planet == planets.planet){ + if(preset.unlocked() && !preset.alwaysUnlocked && !preset.sector.info.shown && !preset.sector.hasBase() && preset.planet == state.planet){ newPresets.add(preset.sector); preset.sector.info.shown = true; preset.sector.saveInfo(); @@ -189,14 +195,14 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } if(newPresets.any()){ - newPresets.add(planets.planet.getLastSector()); + newPresets.add(state.planet.getLastSector()); } newPresets.reverse(); updateSelected(); - if(planets.planet.getLastSector() != null){ - lookAt(planets.planet.getLastSector()); + if(state.planet.getLastSector() != null){ + lookAt(state.planet.getLastSector()); } return super.show(); @@ -252,8 +258,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //update view to sector zoom = 1f; - planets.zoom = 1f; - selectAlpha = 0f; + state.zoom = 1f; + state.uiAlpha = 0f; mode = planetLaunch; @@ -269,8 +275,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //update view to sector lookAt(sector); zoom = 1f; - planets.zoom = 1f; - selectAlpha = 0f; + state.zoom = 1f; + state.uiAlpha = 0f; launchSector = sector; mode = select; @@ -279,7 +285,9 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } void lookAt(Sector sector){ - planets.camPos.set(Tmp.v33.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation())); + if(sector.tile == Ptile.empty) return; + + state.camPos.set(Tmp.v33.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation())); } boolean canSelect(Sector sector){ @@ -307,7 +315,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ launchFrom = to.near().find(Sector::hasBase); if(launchFrom == null && to.preset != null){ if(launchSector != null) return launchSector; - launchFrom = planets.planet.sectors.min(s -> !s.hasBase() ? Float.MAX_VALUE : s.tile.v.dst2(to.tile.v)); + launchFrom = state.planet.sectors.min(s -> !s.hasBase() ? Float.MAX_VALUE : s.tile.v.dst2(to.tile.v)); if(!launchFrom.hasBase()) launchFrom = null; } } @@ -323,7 +331,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ public void renderSectors(Planet planet){ //draw all sector stuff - if(selectAlpha > 0.01f){ + if(state.uiAlpha > 0.01f){ for(Sector sec : planet.sectors){ if(canSelect(sec) || sec.unlocked() || debugSelect){ @@ -336,15 +344,15 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ null; if(color != null){ - planets.drawSelection(sec, Tmp.c1.set(color).mul(0.8f).a(selectAlpha), 0.026f, -0.001f); + planets.drawSelection(sec, Tmp.c1.set(color).mul(0.8f).a(state.uiAlpha), 0.026f, -0.001f); } }else{ - planets.fill(sec, Tmp.c1.set(shadowColor).mul(1, 1, 1, selectAlpha), -0.001f); + planets.fill(sec, Tmp.c1.set(shadowColor).mul(1, 1, 1, state.uiAlpha), -0.001f); } } } - Sector current = state.getSector() != null && state.getSector().isBeingPlayed() && state.getSector().planet == planets.planet ? state.getSector() : null; + Sector current = Vars.state.getSector() != null && Vars.state.getSector().isBeingPlayed() && Vars.state.getSector().planet == state.planet ? Vars.state.getSector() : null; if(current != null){ planets.fill(current, hoverColor, -0.001f); @@ -371,12 +379,12 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } } - if(selectAlpha > 0.001f){ + if(state.uiAlpha > 0.001f){ for(Sector sec : planet.sectors){ if(sec.hasBase()){ for(Sector enemy : sec.near()){ if(enemy.hasEnemyBase()){ - planets.drawArc(planet, enemy.tile.v, sec.tile.v, Team.crux.color.write(Tmp.c2).a(selectAlpha), Color.clear, 0.24f, 110f, 25); + planets.drawArc(planet, enemy.tile.v, sec.tile.v, Team.crux.color.write(Tmp.c2).a(state.uiAlpha), Color.clear, 0.24f, 110f, 25); } } @@ -384,11 +392,11 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(selected != null && selected != sec && selected.hasBase()){ //imports if(sec.info.getRealDestination() == selected && sec.info.anyExports()){ - planets.drawArc(planet, sec.tile.v, selected.tile.v, Color.gray.write(Tmp.c2).a(selectAlpha), Pal.accent.write(Tmp.c3).a(selectAlpha), 0.4f, 90f, 25); + planets.drawArc(planet, sec.tile.v, selected.tile.v, Color.gray.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25); } //exports if(selected.info.getRealDestination() == sec && selected.info.anyExports()){ - planets.drawArc(planet, selected.tile.v, sec.tile.v, Pal.place.write(Tmp.c2).a(selectAlpha), Pal.accent.write(Tmp.c3).a(selectAlpha), 0.4f, 90f, 25); + planets.drawArc(planet, selected.tile.v, sec.tile.v, Pal.place.write(Tmp.c2).a(state.uiAlpha), Pal.accent.write(Tmp.c3).a(state.uiAlpha), 0.4f, 90f, 25); } } } @@ -415,7 +423,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(icon != null){ planets.drawPlane(sec, () -> { //use white for content icons - Draw.color(preficon == icon && sec.info.contentIcon != null ? Color.white : color, selectAlpha); + Draw.color(preficon == icon && sec.info.contentIcon != null ? Color.white : color, state.uiAlpha); Draw.rect(icon, 0, 0, iw, iw * icon.height / icon.width); }); } @@ -444,13 +452,13 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ boolean selectable(Planet planet){ //TODO what if any sector is selectable? if(mode == planetLaunch) return launchSector != null && planet != launchSector.planet; - return planet == planets.planet || planet.alwaysUnlocked || planet.sectors.contains(Sector::hasBase); + return planet == state.planet || (planet.alwaysUnlocked && planet.isLandable()) || planet.sectors.contains(Sector::hasBase); } void setup(){ searchText = ""; - zoom = planets.zoom = 1f; - selectAlpha = 1f; + zoom = state.zoom = 1f; + state.uiAlpha = 1f; ui.minimapfrag.hide(); clearChildren(); @@ -492,8 +500,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ @Override public void draw(){ - planets.orbitAlpha = selectAlpha; - planets.render(PlanetDialog.this); + planets.render(state); } }, //info text @@ -518,12 +525,12 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ pt.button(planet.localizedName, Styles.clearTogglet, () -> { selected = null; launchSector = null; - if(renderer.planets.planet != planet){ - renderer.planets.planet = planet; + if(state.planet != planet){ + state.planet = planet; rebuildList(); } settings.put("lastplanet", planet.name); - }).width(200).height(40).growX().update(bb -> bb.setChecked(renderer.planets.planet == planet)); + }).width(200).height(40).growX().update(bb -> bb.setChecked(state.planet == planet)); pt.row(); } } @@ -533,8 +540,8 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ new Table(c -> { c.visible(() -> !(graphics.isPortrait() && mobile)); - if(planets.planet.sectors.contains(Sector::hasBase)){ - int attacked = planets.planet.sectors.count(Sector::isAttacked); + if(state.planet.sectors.contains(Sector::hasBase)){ + int attacked = state.planet.sectors.count(Sector::isAttacked); //sector notifications & search c.top().right(); @@ -564,7 +571,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ notifs.clear(); - var all = planets.planet.sectors.select(Sector::hasBase); + var all = state.planet.sectors.select(Sector::hasBase); all.sort(Structs.comps(Structs.comparingBool(s -> !s.isAttacked()), Structs.comparingInt(s -> s.save == null ? 0 : -(int)s.save.meta.timePlayed))); notifs.pane(p -> { @@ -646,20 +653,20 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } public void lookAt(Sector sector, float alpha){ - float len = planets.camPos.len(); - planets.camPos.slerp(Tmp.v31.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation()).setLength(len), alpha); + float len = state.camPos.len(); + state.camPos.slerp(Tmp.v31.set(sector.tile.v).rotate(Vec3.Y, -sector.planet.getRotation()).setLength(len), alpha); } @Override public void act(float delta){ super.act(delta); - if(hovered != null && !mobile){ + if(hovered != null && !mobile && state.planet.hasGrid()){ addChild(hoverLabel); hoverLabel.toFront(); hoverLabel.touchable = Touchable.disabled; - Vec3 pos = planets.cam.project(Tmp.v31.set(hovered.tile.v).setLength(PlanetRenderer.outlineRad).rotate(Vec3.Y, -planets.planet.getRotation()).add(planets.planet.position)); + Vec3 pos = planets.cam.project(Tmp.v31.set(hovered.tile.v).setLength(PlanetRenderer.outlineRad).rotate(Vec3.Y, -state.planet.getRotation()).add(state.planet.position)); hoverLabel.setPosition(pos.x - Core.scene.marginLeft, pos.y - Core.scene.marginBottom, Align.center); hoverLabel.getText().setLength(0); @@ -704,14 +711,24 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ } } - if(planets.planet.hasGrid()){ - hovered = planets.planet.getSector(planets.cam.getMouseRay(), PlanetRenderer.outlineRad); + if(state.planet.hasGrid()){ + hovered = state.planet.getSector(planets.cam.getMouseRay(), PlanetRenderer.outlineRad); + }else if(state.planet.isLandable()){ + boolean wasNull = selected == null; + //always have the first sector selected. + //TODO better support for multiple sectors in gridless planets? + hovered = selected = state.planet.sectors.first(); + + //autoshow + if(wasNull){ + updateSelected(); + } }else{ hovered = selected = null; } - planets.zoom = Mathf.lerpDelta(planets.zoom, zoom, 0.4f); - selectAlpha = Mathf.lerpDelta(selectAlpha, Mathf.num(planets.zoom < 1.9f), 0.1f); + state.zoom = Mathf.lerpDelta(state.zoom, zoom, 0.4f); + state.uiAlpha = Mathf.lerpDelta(state.uiAlpha, Mathf.num(state.zoom < 1.9f), 0.1f); } void displayItems(Table c, float scl, ObjectMap stats, String name){ @@ -996,13 +1013,17 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ if(launching){ stable.color.sub(0, 0, 0, 0.05f * Time.delta); }else{ - //fade out UI when not facing selected sector - Tmp.v31.set(selected.tile.v).rotate(Vec3.Y, -planets.planet.getRotation()).scl(-1f).nor(); - float dot = planets.cam.direction.dot(Tmp.v31); - stable.color.a = Math.max(dot, 0f)*2f; - if(dot*2f <= -0.1f){ - selected = null; - updateSelected(); + if(!state.planet.hasGrid()){ + stable.color.a = 1f; + }else{ + //fade out UI when not facing selected sector + Tmp.v31.set(selected.tile.v).rotate(Vec3.Y, -state.planet.getRotation()).scl(-1f).nor(); + float dot = planets.cam.direction.dot(Tmp.v31); + stable.color.a = Math.max(dot, 0f)*2f; + if(dot*2f <= -0.1f){ + selected = null; + updateSelected(); + } } } } @@ -1029,7 +1050,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ boolean shouldHide = true; //save before launch. - if(control.saves.getCurrent() != null && state.isGame() && mode != select){ + if(control.saves.getCurrent() != null && Vars.state.isGame() && mode != select){ try{ control.saves.getCurrent().save(); }catch(Throwable e){ diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index d0e900e479..feaf234ae9 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -25,6 +25,7 @@ import mindustry.graphics.*; import mindustry.graphics.MultiPacker.*; import mindustry.type.*; import mindustry.ui.*; +import mindustry.world.blocks.*; import mindustry.world.blocks.environment.*; import mindustry.world.blocks.power.*; import mindustry.world.consumers.*; @@ -122,6 +123,8 @@ public class Block extends UnlockableContent{ public boolean useColor = true; /** item that drops from this block, used for drills */ public @Nullable Item itemDrop = null; + /** Array of affinities to certain things. */ + public Attributes attributes = new Attributes(); /** tile entity health */ public int health = -1; /** base block explosiveness */ @@ -384,10 +387,16 @@ public class Block extends UnlockableContent{ } /** Returns whether or not this block can be place on the specified */ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ + return canPlaceOn(tile, team); + } + + /** Legacy canPlaceOn implementation, override {@link #canPlaceOn(Tile, Team, int)} instead.*/ public boolean canPlaceOn(Tile tile, Team team){ return true; } + public boolean canBreak(Tile tile){ return true; } diff --git a/core/src/mindustry/world/Build.java b/core/src/mindustry/world/Build.java index 0f6c13d9be..c4317a542b 100644 --- a/core/src/mindustry/world/Build.java +++ b/core/src/mindustry/world/Build.java @@ -169,7 +169,7 @@ public class Build{ return false; } - if(!type.canPlaceOn(tile, team)){ + if(!type.canPlaceOn(tile, team, rotation)){ return false; } diff --git a/core/src/mindustry/world/blocks/ConstructBlock.java b/core/src/mindustry/world/blocks/ConstructBlock.java index ee42782485..294a3a5964 100644 --- a/core/src/mindustry/world/blocks/ConstructBlock.java +++ b/core/src/mindustry/world/blocks/ConstructBlock.java @@ -227,6 +227,7 @@ public class ConstructBlock extends Block{ for(TextureRegion region : current.getGeneratedIcons()){ Shaders.blockbuild.region = region; + Shaders.blockbuild.time = Time.time; Shaders.blockbuild.progress = progress; Draw.rect(region, x, y, current.rotate ? rotdeg() : 0); diff --git a/core/src/mindustry/world/blocks/defense/ForceProjector.java b/core/src/mindustry/world/blocks/defense/ForceProjector.java index 1c712abf89..c4cf8812db 100644 --- a/core/src/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/mindustry/world/blocks/defense/ForceProjector.java @@ -58,6 +58,7 @@ public class ForceProjector extends Block{ ambientSound = Sounds.shield; ambientSoundVolume = 0.08f; consumes.add(new ConsumeCoolant(0.1f)).boost().update(false); + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/blocks/defense/MendProjector.java b/core/src/mindustry/world/blocks/defense/MendProjector.java index f61f3c0355..90d3e30c95 100644 --- a/core/src/mindustry/world/blocks/defense/MendProjector.java +++ b/core/src/mindustry/world/blocks/defense/MendProjector.java @@ -36,6 +36,7 @@ public class MendProjector extends Block{ hasItems = true; emitLight = true; lightRadius = 50f; + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java index b2c43ab490..95daea2e29 100644 --- a/core/src/mindustry/world/blocks/defense/OverdriveProjector.java +++ b/core/src/mindustry/world/blocks/defense/OverdriveProjector.java @@ -41,6 +41,7 @@ public class OverdriveProjector extends Block{ canOverdrive = false; emitLight = true; lightRadius = 50f; + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/blocks/defense/Wall.java b/core/src/mindustry/world/blocks/defense/Wall.java index 12ae157afd..9a22520775 100644 --- a/core/src/mindustry/world/blocks/defense/Wall.java +++ b/core/src/mindustry/world/blocks/defense/Wall.java @@ -36,6 +36,9 @@ public class Wall extends Block{ buildCostMultiplier = 6f; canOverdrive = false; drawDisabled = false; + + //it's a wall of course it's supported everywhere + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java index be1d3c25b8..7dc4eed3f6 100644 --- a/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java +++ b/core/src/mindustry/world/blocks/defense/turrets/PowerTurret.java @@ -12,6 +12,7 @@ public class PowerTurret extends Turret{ public PowerTurret(String name){ super(name); hasPower = true; + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/blocks/distribution/ItemBridge.java b/core/src/mindustry/world/blocks/distribution/ItemBridge.java index 59d90b1817..746ee0cfc7 100644 --- a/core/src/mindustry/world/blocks/distribution/ItemBridge.java +++ b/core/src/mindustry/world/blocks/distribution/ItemBridge.java @@ -410,7 +410,7 @@ public class ItemBridge extends Block{ } protected boolean checkAccept(Building source, Tile other){ - if(linked(source)) return true; + if(tile == null || linked(source)) return true; if(linkValid(tile, other)){ int rel = relativeTo(other); diff --git a/core/src/mindustry/world/blocks/distribution/MassDriver.java b/core/src/mindustry/world/blocks/distribution/MassDriver.java index 4b0f40d87b..c0bd7d73b2 100644 --- a/core/src/mindustry/world/blocks/distribution/MassDriver.java +++ b/core/src/mindustry/world/blocks/distribution/MassDriver.java @@ -48,6 +48,7 @@ public class MassDriver extends Block{ hasPower = true; outlineIcon = true; sync = true; + envEnabled |= Env.space; //point2 is relative config(Point2.class, (MassDriverBuild tile, Point2 point) -> tile.link = Point2.pack(point.x + tile.tileX(), point.y + tile.tileY())); diff --git a/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java b/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java index a2d470c528..2681376e35 100644 --- a/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java +++ b/core/src/mindustry/world/blocks/distribution/PayloadConveyor.java @@ -30,6 +30,7 @@ public class PayloadConveyor extends Block{ update = true; outputsPayload = true; noUpdateDisabled = true; + envEnabled |= Env.space; sync = true; } diff --git a/core/src/mindustry/world/blocks/environment/AirBlock.java b/core/src/mindustry/world/blocks/environment/AirBlock.java index b100bbc4ef..e0eef67635 100644 --- a/core/src/mindustry/world/blocks/environment/AirBlock.java +++ b/core/src/mindustry/world/blocks/environment/AirBlock.java @@ -14,6 +14,7 @@ public class AirBlock extends Floor{ useColor = false; wall = this; needsSurface = false; + canShadow = false; } @Override diff --git a/core/src/mindustry/world/blocks/environment/EmptyFloor.java b/core/src/mindustry/world/blocks/environment/EmptyFloor.java new file mode 100644 index 0000000000..3b1aba9e82 --- /dev/null +++ b/core/src/mindustry/world/blocks/environment/EmptyFloor.java @@ -0,0 +1,24 @@ +package mindustry.world.blocks.environment; + +import mindustry.content.*; +import mindustry.world.*; + +public class EmptyFloor extends Floor{ + + public EmptyFloor(String name){ + super(name); + variants = 0; + canShadow = false; + } + + @Override + public void drawBase(Tile tile){ + //draws only edges, never itself + drawEdges(tile); + + Floor floor = tile.overlay(); + if(floor != Blocks.air && floor != this){ + floor.drawBase(tile); + } + } +} diff --git a/core/src/mindustry/world/blocks/environment/Floor.java b/core/src/mindustry/world/blocks/environment/Floor.java index ddad7804b3..91d677825b 100644 --- a/core/src/mindustry/world/blocks/environment/Floor.java +++ b/core/src/mindustry/world/blocks/environment/Floor.java @@ -15,7 +15,6 @@ import mindustry.graphics.*; import mindustry.graphics.MultiPacker.*; import mindustry.type.*; import mindustry.world.*; -import mindustry.world.blocks.*; import static mindustry.Vars.*; @@ -54,8 +53,6 @@ public class Floor extends Block{ public boolean playerUnmineable = false; /** Group of blocks that this block does not draw edges on. */ public Block blendGroup = this; - /** Array of affinities to certain things. */ - public Attributes attributes = new Attributes(); /** Whether this ore generates in maps by default. */ public boolean oreDefault = false; /** Ore generation params. */ @@ -64,6 +61,8 @@ public class Floor extends Block{ public Block wall = Blocks.air; /** Decoration block. Usually a rock. May be air. */ public Block decoration = Blocks.air; + /** Whether units can draw shadows over this. */ + public boolean canShadow = true; /** Whether this overlay needs a surface to be on. False for floating blocks, like spawns. */ public boolean needsSurface = true; @@ -116,10 +115,6 @@ public class Floor extends Block{ //keep default value if not found... if(wall == null) wall = Blocks.air; - if(decoration == Blocks.air){ - decoration = content.blocks().min(b -> b instanceof Prop && b.minfo.mod == null && b.breakable ? mapColor.diff(b.mapColor) : Float.POSITIVE_INFINITY); - } - if(isLiquid && walkEffect == Fx.none){ walkEffect = Fx.ripple; } diff --git a/core/src/mindustry/world/blocks/experimental/BlockForge.java b/core/src/mindustry/world/blocks/experimental/BlockForge.java index 29adc64d00..6f9987f8dc 100644 --- a/core/src/mindustry/world/blocks/experimental/BlockForge.java +++ b/core/src/mindustry/world/blocks/experimental/BlockForge.java @@ -1,14 +1,17 @@ package mindustry.world.blocks.experimental; +import mindustry.world.blocks.payloads.*; + +/** @deprecated use Constructor instead. */ @Deprecated -public class BlockForge extends mindustry.world.blocks.payloads.BlockForge{ +public class BlockForge extends Constructor{ public BlockForge(String name){ super(name); } @Deprecated - public class BlockForgeBuild extends mindustry.world.blocks.payloads.BlockForge.BlockForgeBuild{ + public class BlockForgeBuild extends Constructor.ConstructorBuild{ } } diff --git a/core/src/mindustry/world/blocks/experimental/BlockLoader.java b/core/src/mindustry/world/blocks/experimental/BlockLoader.java index 91a044d4f8..6c0202a883 100644 --- a/core/src/mindustry/world/blocks/experimental/BlockLoader.java +++ b/core/src/mindustry/world/blocks/experimental/BlockLoader.java @@ -1,14 +1,16 @@ package mindustry.world.blocks.experimental; +import mindustry.world.blocks.payloads.*; + @Deprecated -public class BlockLoader extends mindustry.world.blocks.payloads.BlockLoader{ +public class BlockLoader extends PayloadLoader{ public BlockLoader(String name){ super(name); } @Deprecated - public class BlockLoaderBuild extends mindustry.world.blocks.payloads.BlockLoader.BlockLoaderBuild{ + public class BlockLoaderBuild extends PayloadLoaderBuild{ } } diff --git a/core/src/mindustry/world/blocks/experimental/BlockUnloader.java b/core/src/mindustry/world/blocks/experimental/BlockUnloader.java index 1c655318e3..45403c0f1a 100644 --- a/core/src/mindustry/world/blocks/experimental/BlockUnloader.java +++ b/core/src/mindustry/world/blocks/experimental/BlockUnloader.java @@ -1,14 +1,16 @@ package mindustry.world.blocks.experimental; +import mindustry.world.blocks.payloads.*; + @Deprecated -public class BlockUnloader extends mindustry.world.blocks.payloads.BlockUnloader{ +public class BlockUnloader extends PayloadUnloader{ public BlockUnloader(String name){ super(name); } @Deprecated - public class BlockUnloaderBuild extends mindustry.world.blocks.payloads.BlockUnloader.BlockUnloaderBuild{ + public class BlockUnloaderBuild extends PayloadUnloaderBuild{ } } diff --git a/core/src/mindustry/world/blocks/liquid/ArmoredConduit.java b/core/src/mindustry/world/blocks/liquid/ArmoredConduit.java index d8f480836c..c53e05ee21 100644 --- a/core/src/mindustry/world/blocks/liquid/ArmoredConduit.java +++ b/core/src/mindustry/world/blocks/liquid/ArmoredConduit.java @@ -20,7 +20,7 @@ public class ArmoredConduit extends Conduit{ public class ArmoredConduitBuild extends ConduitBuild{ @Override public boolean acceptLiquid(Building source, Liquid liquid){ - return super.acceptLiquid(source, liquid) && (source.block instanceof Conduit || + return super.acceptLiquid(source, liquid) && (tile == null || source.block instanceof Conduit || source.tile.absoluteRelativeTo(tile.x, tile.y) == rotation); } } diff --git a/core/src/mindustry/world/blocks/liquid/Conduit.java b/core/src/mindustry/world/blocks/liquid/Conduit.java index 0b4295f7dd..11ba66255b 100644 --- a/core/src/mindustry/world/blocks/liquid/Conduit.java +++ b/core/src/mindustry/world/blocks/liquid/Conduit.java @@ -151,7 +151,7 @@ public class Conduit extends LiquidBlock implements Autotiler{ public boolean acceptLiquid(Building source, Liquid liquid){ noSleep(); return (liquids.current() == liquid || liquids.currentAmount() < 0.2f) - && ((source.relativeTo(tile.x, tile.y) + 2) % 4 != rotation); + && (tile == null || (source.relativeTo(tile.x, tile.y) + 2) % 4 != rotation); } @Override diff --git a/core/src/mindustry/world/blocks/liquid/LiquidBlock.java b/core/src/mindustry/world/blocks/liquid/LiquidBlock.java index eb9a3f16d3..ec5d40f571 100644 --- a/core/src/mindustry/world/blocks/liquid/LiquidBlock.java +++ b/core/src/mindustry/world/blocks/liquid/LiquidBlock.java @@ -19,6 +19,7 @@ public class LiquidBlock extends Block{ hasLiquids = true; group = BlockGroup.liquids; outputsLiquid = true; + envEnabled |= Env.space | Env.underwater; } @Override diff --git a/core/src/mindustry/world/blocks/liquid/LiquidBridge.java b/core/src/mindustry/world/blocks/liquid/LiquidBridge.java index c58a60c36c..984688a468 100644 --- a/core/src/mindustry/world/blocks/liquid/LiquidBridge.java +++ b/core/src/mindustry/world/blocks/liquid/LiquidBridge.java @@ -13,6 +13,7 @@ public class LiquidBridge extends ItemBridge{ outputsLiquid = true; canOverdrive = false; group = BlockGroup.liquids; + envEnabled = Env.any; } public class LiquidBridgeBuild extends ItemBridgeBuild{ diff --git a/core/src/mindustry/world/blocks/logic/LogicBlock.java b/core/src/mindustry/world/blocks/logic/LogicBlock.java index 80385a14ac..e8cf14ef16 100644 --- a/core/src/mindustry/world/blocks/logic/LogicBlock.java +++ b/core/src/mindustry/world/blocks/logic/LogicBlock.java @@ -42,6 +42,9 @@ public class LogicBlock extends Block{ group = BlockGroup.logic; schematicPriority = 5; + //universal, no real requirements + envEnabled = Env.any; + config(byte[].class, (LogicBuild build, byte[] data) -> build.readCompressed(data, true)); config(Integer.class, (LogicBuild entity, Integer pos) -> { diff --git a/core/src/mindustry/world/blocks/logic/LogicDisplay.java b/core/src/mindustry/world/blocks/logic/LogicDisplay.java index 772e17fdba..7e6bb10e29 100644 --- a/core/src/mindustry/world/blocks/logic/LogicDisplay.java +++ b/core/src/mindustry/world/blocks/logic/LogicDisplay.java @@ -36,6 +36,7 @@ public class LogicDisplay extends Block{ solid = true; group = BlockGroup.logic; drawDisabled = false; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/logic/MemoryBlock.java b/core/src/mindustry/world/blocks/logic/MemoryBlock.java index cff63b98a3..3868b80935 100644 --- a/core/src/mindustry/world/blocks/logic/MemoryBlock.java +++ b/core/src/mindustry/world/blocks/logic/MemoryBlock.java @@ -14,6 +14,7 @@ public class MemoryBlock extends Block{ solid = true; group = BlockGroup.logic; drawDisabled = false; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/logic/MessageBlock.java b/core/src/mindustry/world/blocks/logic/MessageBlock.java index d6bffd83f2..3a982c37cb 100644 --- a/core/src/mindustry/world/blocks/logic/MessageBlock.java +++ b/core/src/mindustry/world/blocks/logic/MessageBlock.java @@ -30,6 +30,7 @@ public class MessageBlock extends Block{ destructible = true; group = BlockGroup.logic; drawDisabled = false; + envEnabled = Env.any; config(String.class, (MessageBuild tile, String text) -> { if(text.length() > maxTextLength){ diff --git a/core/src/mindustry/world/blocks/logic/SwitchBlock.java b/core/src/mindustry/world/blocks/logic/SwitchBlock.java index 60e5db6dc7..ac9780a16b 100644 --- a/core/src/mindustry/world/blocks/logic/SwitchBlock.java +++ b/core/src/mindustry/world/blocks/logic/SwitchBlock.java @@ -17,6 +17,7 @@ public class SwitchBlock extends Block{ drawDisabled = false; autoResetEnabled = false; group = BlockGroup.logic; + envEnabled = Env.any; config(Boolean.class, (SwitchBuild entity, Boolean b) -> entity.enabled = b); } diff --git a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java index a7885e76c7..17f74ac4ad 100644 --- a/core/src/mindustry/world/blocks/payloads/PayloadBlock.java +++ b/core/src/mindustry/world/blocks/payloads/PayloadBlock.java @@ -80,16 +80,16 @@ public class PayloadBlock extends Block{ } @Override - public boolean canControlSelect(Player player){ - return !player.unit().spawnedByCore && this.payload == null && acceptUnitPayload(player.unit()) && player.tileOn() != null && player.tileOn().build == this; + public boolean canControlSelect(Unit player){ + return !player.spawnedByCore && this.payload == null && acceptUnitPayload(player) && player.tileOn() != null && player.tileOn().build == this; } @Override - public void onControlSelect(Player player){ + public void onControlSelect(Unit player){ float x = player.x, y = player.y; - acceptPlayerPayload(player, p -> payload = (T)p); + handleUnitPayload(player, p -> payload = (T)p); this.payVector.set(x, y).sub(this).clamp(-size * tilesize / 2f, -size * tilesize / 2f, size * tilesize / 2f, size * tilesize / 2f); - this.payRotation = player.unit().rotation; + this.payRotation = player.rotation; } @Override diff --git a/core/src/mindustry/world/blocks/power/Battery.java b/core/src/mindustry/world/blocks/power/Battery.java index f0cff96156..b3d36969fc 100644 --- a/core/src/mindustry/world/blocks/power/Battery.java +++ b/core/src/mindustry/world/blocks/power/Battery.java @@ -21,6 +21,8 @@ public class Battery extends PowerDistributor{ outputsPower = true; consumesPower = true; flags = EnumSet.of(BlockFlag.battery); + //TODO could be supported everywhere... + envEnabled |= Env.space; } public class BatteryBuild extends Building{ diff --git a/core/src/mindustry/world/blocks/power/DecayGenerator.java b/core/src/mindustry/world/blocks/power/DecayGenerator.java index becce39642..0bb9e2eac9 100644 --- a/core/src/mindustry/world/blocks/power/DecayGenerator.java +++ b/core/src/mindustry/world/blocks/power/DecayGenerator.java @@ -1,6 +1,7 @@ package mindustry.world.blocks.power; import mindustry.type.*; +import mindustry.world.meta.*; public class DecayGenerator extends ItemLiquidGenerator{ @@ -8,6 +9,7 @@ public class DecayGenerator extends ItemLiquidGenerator{ super(true, false, name); hasItems = true; hasLiquids = false; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/power/ImpactReactor.java b/core/src/mindustry/world/blocks/power/ImpactReactor.java index 64e94d8c14..3679b2be80 100644 --- a/core/src/mindustry/world/blocks/power/ImpactReactor.java +++ b/core/src/mindustry/world/blocks/power/ImpactReactor.java @@ -43,6 +43,7 @@ public class ImpactReactor extends PowerGenerator{ flags = EnumSet.of(BlockFlag.reactor, BlockFlag.generator); lightRadius = 115f; emitLight = true; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/power/LightBlock.java b/core/src/mindustry/world/blocks/power/LightBlock.java index 2f1f05d606..36fc8c0f5f 100644 --- a/core/src/mindustry/world/blocks/power/LightBlock.java +++ b/core/src/mindustry/world/blocks/power/LightBlock.java @@ -14,6 +14,7 @@ import mindustry.graphics.*; import mindustry.input.*; import mindustry.logic.*; import mindustry.world.*; +import mindustry.world.meta.*; import static mindustry.Vars.*; @@ -28,6 +29,7 @@ public class LightBlock extends Block{ update = true; configurable = true; saveConfig = true; + envEnabled |= Env.space; swapDiagonalPlacement = true; config(Integer.class, (LightBuild tile, Integer value) -> tile.color = value); diff --git a/core/src/mindustry/world/blocks/power/NuclearReactor.java b/core/src/mindustry/world/blocks/power/NuclearReactor.java index 6ed9f11189..a26a99d5cd 100644 --- a/core/src/mindustry/world/blocks/power/NuclearReactor.java +++ b/core/src/mindustry/world/blocks/power/NuclearReactor.java @@ -56,6 +56,7 @@ public class NuclearReactor extends PowerGenerator{ rebuildable = false; flags = EnumSet.of(BlockFlag.reactor, BlockFlag.generator); schematicPriority = -5; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/power/PowerDiode.java b/core/src/mindustry/world/blocks/power/PowerDiode.java index 422c978aef..f31cf71cf6 100644 --- a/core/src/mindustry/world/blocks/power/PowerDiode.java +++ b/core/src/mindustry/world/blocks/power/PowerDiode.java @@ -23,6 +23,7 @@ public class PowerDiode extends Block{ group = BlockGroup.power; noUpdateDisabled = true; schematicPriority = 10; + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/blocks/power/PowerNode.java b/core/src/mindustry/world/blocks/power/PowerNode.java index 7d8c609b77..8ee013f6fe 100644 --- a/core/src/mindustry/world/blocks/power/PowerNode.java +++ b/core/src/mindustry/world/blocks/power/PowerNode.java @@ -43,6 +43,7 @@ public class PowerNode extends PowerBlock{ swapDiagonalPlacement = true; schematicPriority = -10; drawDisabled = false; + envEnabled |= Env.space; config(Integer.class, (entity, value) -> { PowerModule power = entity.power; diff --git a/core/src/mindustry/world/blocks/power/SolarGenerator.java b/core/src/mindustry/world/blocks/power/SolarGenerator.java index 84e0b0ba81..bac6345115 100644 --- a/core/src/mindustry/world/blocks/power/SolarGenerator.java +++ b/core/src/mindustry/world/blocks/power/SolarGenerator.java @@ -12,6 +12,7 @@ public class SolarGenerator extends PowerGenerator{ super(name); //remove the BlockFlag.generator flag to make this a lower priority target than other generators. flags = EnumSet.of(); + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/power/ThermalGenerator.java b/core/src/mindustry/world/blocks/power/ThermalGenerator.java index 8c97beca13..d0b979a29d 100644 --- a/core/src/mindustry/world/blocks/power/ThermalGenerator.java +++ b/core/src/mindustry/world/blocks/power/ThermalGenerator.java @@ -41,7 +41,7 @@ public class ThermalGenerator extends PowerGenerator{ } @Override - public boolean canPlaceOn(Tile tile, Team team){ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ //make sure there's heat at this location return tile.getLinkedTilesAs(this, tempTiles).sumf(other -> other.floor().attributes.get(attribute)) > 0.01f; } diff --git a/core/src/mindustry/world/blocks/production/Drill.java b/core/src/mindustry/world/blocks/production/Drill.java index 9ee6eb8e6d..fa3a274301 100644 --- a/core/src/mindustry/world/blocks/production/Drill.java +++ b/core/src/mindustry/world/blocks/production/Drill.java @@ -36,7 +36,7 @@ public class Drill extends Block{ /** How many times faster the drill will progress when boosted by liquid. */ public float liquidBoostIntensity = 1.6f; /** Speed at which the drill speeds up. */ - public float warmupSpeed = 0.02f; + public float warmupSpeed = 0.015f; //return variables for countOre protected @Nullable Item returnItem; @@ -108,7 +108,7 @@ public class Drill extends Block{ } @Override - public boolean canPlaceOn(Tile tile, Team team){ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ if(isMultiblock()){ for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){ if(canMine(other)){ @@ -275,14 +275,14 @@ public class Drill extends Block{ speed *= efficiency(); // Drill slower when not at full power lastDrillSpeed = (speed * dominantItems * warmup) / (drillTime + hardnessDrillMultiplier * dominantItem.hardness); - warmup = Mathf.lerpDelta(warmup, speed, warmupSpeed); + warmup = Mathf.approachDelta(warmup, speed, warmupSpeed); progress += delta() * dominantItems * speed * warmup; if(Mathf.chanceDelta(updateEffectChance * warmup)) updateEffect.at(x + Mathf.range(size * 2f), y + Mathf.range(size * 2f)); }else{ lastDrillSpeed = 0f; - warmup = Mathf.lerpDelta(warmup, 0f, warmupSpeed); + warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed); return; } diff --git a/core/src/mindustry/world/blocks/production/Incinerator.java b/core/src/mindustry/world/blocks/production/Incinerator.java index 906763d7e6..b5ceff931b 100644 --- a/core/src/mindustry/world/blocks/production/Incinerator.java +++ b/core/src/mindustry/world/blocks/production/Incinerator.java @@ -28,11 +28,7 @@ public class Incinerator extends Block{ @Override public void updateTile(){ - if(consValid() && efficiency() > 0.9f){ - heat = Mathf.lerpDelta(heat, 1f, 0.04f); - }else{ - heat = Mathf.lerpDelta(heat, 0f, 0.02f); - } + heat = Mathf.approachDelta(heat, consValid() && efficiency() > 0.9f ? 1f : 0f, 0.04f); } @Override diff --git a/core/src/mindustry/world/blocks/production/Pump.java b/core/src/mindustry/world/blocks/production/Pump.java index de524f72f6..9a9aca65c3 100644 --- a/core/src/mindustry/world/blocks/production/Pump.java +++ b/core/src/mindustry/world/blocks/production/Pump.java @@ -20,6 +20,7 @@ public class Pump extends LiquidBlock{ super(name); group = BlockGroup.liquids; floating = true; + envEnabled = Env.terrestrial; } @Override @@ -61,7 +62,7 @@ public class Pump extends LiquidBlock{ } @Override - public boolean canPlaceOn(Tile tile, Team team){ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ if(isMultiblock()){ Liquid last = null; for(Tile other : tile.getLinkedTilesAs(this, tempTiles)){ diff --git a/core/src/mindustry/world/blocks/production/SolidPump.java b/core/src/mindustry/world/blocks/production/SolidPump.java index 8098bebd59..912af46b18 100644 --- a/core/src/mindustry/world/blocks/production/SolidPump.java +++ b/core/src/mindustry/world/blocks/production/SolidPump.java @@ -31,6 +31,8 @@ public class SolidPump extends Pump{ public SolidPump(String name){ super(name); hasPower = true; + //only supports ground by default + envEnabled = Env.terrestrial; } @Override @@ -63,7 +65,7 @@ public class SolidPump extends Pump{ } @Override - public boolean canPlaceOn(Tile tile, Team team){ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ float sum = tile.getLinkedTilesAs(this, tempTiles).sumf(t -> canPump(t) ? baseEfficiency + (attribute != null ? t.floor().attributes.get(attribute) : 0f) : 0f); return sum > 0.00001f; } diff --git a/core/src/mindustry/world/blocks/sandbox/LiquidSource.java b/core/src/mindustry/world/blocks/sandbox/LiquidSource.java index 7f029299c3..621c2252df 100644 --- a/core/src/mindustry/world/blocks/sandbox/LiquidSource.java +++ b/core/src/mindustry/world/blocks/sandbox/LiquidSource.java @@ -27,6 +27,7 @@ public class LiquidSource extends Block{ noUpdateDisabled = true; displayFlow = false; group = BlockGroup.liquids; + envEnabled = Env.any; config(Liquid.class, (LiquidSourceBuild tile, Liquid l) -> tile.source = l); configClear((LiquidSourceBuild tile) -> tile.source = null); diff --git a/core/src/mindustry/world/blocks/sandbox/LiquidVoid.java b/core/src/mindustry/world/blocks/sandbox/LiquidVoid.java index bde9742467..713dace08f 100644 --- a/core/src/mindustry/world/blocks/sandbox/LiquidVoid.java +++ b/core/src/mindustry/world/blocks/sandbox/LiquidVoid.java @@ -13,6 +13,7 @@ public class LiquidVoid extends Block{ solid = true; update = true; group = BlockGroup.liquids; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/storage/CoreBlock.java b/core/src/mindustry/world/blocks/storage/CoreBlock.java index 013937eb48..7d76842a86 100644 --- a/core/src/mindustry/world/blocks/storage/CoreBlock.java +++ b/core/src/mindustry/world/blocks/storage/CoreBlock.java @@ -121,7 +121,7 @@ public class CoreBlock extends StorageBlock{ } @Override - public boolean canPlaceOn(Tile tile, Team team){ + public boolean canPlaceOn(Tile tile, Team team, int rotation){ if(tile == null) return false; CoreBuild core = team.core(); //must have all requirements @@ -166,7 +166,7 @@ public class CoreBlock extends StorageBlock{ public void drawPlace(int x, int y, int rotation, boolean valid){ if(world.tile(x, y) == null) return; - if(!canPlaceOn(world.tile(x, y), player.team())){ + if(!canPlaceOn(world.tile(x, y), player.team(), rotation)){ drawPlaceText(Core.bundle.get( (player.team().core() != null && player.team().core().items.has(requirements, state.rules.buildCostMultiplier)) || state.rules.infiniteResources ? diff --git a/core/src/mindustry/world/blocks/storage/StorageBlock.java b/core/src/mindustry/world/blocks/storage/StorageBlock.java index 260e3487bd..6135288c3c 100644 --- a/core/src/mindustry/world/blocks/storage/StorageBlock.java +++ b/core/src/mindustry/world/blocks/storage/StorageBlock.java @@ -23,6 +23,7 @@ public class StorageBlock extends Block{ group = BlockGroup.transportation; flags = EnumSet.of(BlockFlag.storage); allowResupply = true; + envEnabled = Env.any; } @Override diff --git a/core/src/mindustry/world/blocks/storage/Unloader.java b/core/src/mindustry/world/blocks/storage/Unloader.java index d43015b7cf..d535952e70 100644 --- a/core/src/mindustry/world/blocks/storage/Unloader.java +++ b/core/src/mindustry/world/blocks/storage/Unloader.java @@ -28,6 +28,7 @@ public class Unloader extends Block{ itemCapacity = 0; noUpdateDisabled = true; unloadable = false; + envEnabled = Env.any; config(Item.class, (UnloaderBuild tile, Item item) -> tile.sortItem = item); configClear((UnloaderBuild tile) -> tile.sortItem = null); diff --git a/core/src/mindustry/world/blocks/units/CommandCenter.java b/core/src/mindustry/world/blocks/units/CommandCenter.java index ad2b3e075a..3d8f9c024f 100644 --- a/core/src/mindustry/world/blocks/units/CommandCenter.java +++ b/core/src/mindustry/world/blocks/units/CommandCenter.java @@ -37,6 +37,7 @@ public class CommandCenter extends Block{ configurable = true; drawDisabled = false; logicConfigurable = true; + envEnabled = Env.any; config(UnitCommand.class, (CommandBuild build, UnitCommand command) -> { if(build.team.data().command != command){ diff --git a/core/src/mindustry/world/blocks/units/RepairPoint.java b/core/src/mindustry/world/blocks/units/RepairPoint.java index 1e8e9c9ddf..3ca61ec0d7 100644 --- a/core/src/mindustry/world/blocks/units/RepairPoint.java +++ b/core/src/mindustry/world/blocks/units/RepairPoint.java @@ -59,6 +59,8 @@ public class RepairPoint extends Block{ outlineIcon = true; //yeah, this isn't the same thing, but it's close enough group = BlockGroup.projectors; + + envEnabled |= Env.space; } @Override diff --git a/core/src/mindustry/world/meta/StatValues.java b/core/src/mindustry/world/meta/StatValues.java index b556e7809c..ba07d8e19d 100644 --- a/core/src/mindustry/world/meta/StatValues.java +++ b/core/src/mindustry/world/meta/StatValues.java @@ -108,14 +108,18 @@ public class StatValues{ }; } - public static StatValue floorEfficiency(Floor floor, float multiplier, boolean startZero){ + public static StatValue blockEfficiency(Block floor, float multiplier, boolean startZero){ return table -> table.stack( new Image(floor.uiIcon).setScaling(Scaling.fit), new Table(t -> t.top().right().add((multiplier < 0 ? "[scarlet]" : startZero ? "[accent]" : "[accent]+") + (int)((multiplier) * 100) + "%").style(Styles.outlineLabel)) ); } - public static StatValue floors(Attribute attr, boolean floating, float scale, boolean startZero){ + public static StatValue blocks(Attribute attr, boolean floating, float scale, boolean startZero){ + return blocks(attr, floating, scale, startZero, true); + } + + public static StatValue blocks(Attribute attr, boolean floating, float scale, boolean startZero, boolean checkFloors){ return table -> table.table(c -> { Runnable[] rebuild = {null}; Map[] lastMap = {null}; @@ -126,14 +130,14 @@ public class StatValues{ if(state.isGame()){ var blocks = Vars.content.blocks() - .select(block -> block instanceof Floor f && indexer.isBlockPresent(block) && f.attributes.get(attr) != 0 && !(f.isDeep() && !floating)) + .select(block -> (!checkFloors || block instanceof Floor) && indexer.isBlockPresent(block) && block.attributes.get(attr) != 0 && !((block instanceof Floor f && f.isDeep()) && !floating)) .as().with(s -> s.sort(f -> f.attributes.get(attr))); if(blocks.any()){ int i = 0; for(var block : blocks){ - floorEfficiency(block, block.attributes.get(attr) * scale, startZero).display(c); + blockEfficiency(block, block.attributes.get(attr) * scale, startZero).display(c); if(++i % 5 == 0){ c.row(); } diff --git a/core/src/mindustry/world/meta/Stats.java b/core/src/mindustry/world/meta/Stats.java index d11b56f395..742400989f 100644 --- a/core/src/mindustry/world/meta/Stats.java +++ b/core/src/mindustry/world/meta/Stats.java @@ -66,7 +66,7 @@ public class Stats{ } public void add(Stat stat, Attribute attr, boolean floating, float scale, boolean startZero){ - add(stat, StatValues.floors(attr, floating, scale, startZero)); + add(stat, StatValues.blocks(attr, floating, scale, startZero)); } /** Adds a single string value with this stat. */