From 7aee34bafa6bae5a6c066613329dd7107418ab61 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 2 Feb 2025 18:10:15 -0500 Subject: [PATCH] Finalized launch pad mechanics --- .../campaign/advanced-launch-pad-light.png | Bin 0 -> 187 bytes .../campaign/advanced-launch-pad-pod.png | Bin 0 -> 1259 bytes .../blocks/campaign/advanced-launch-pad.png | Bin 0 -> 2782 bytes .../sprites/blocks/campaign/landing-pad.png | Bin 0 -> 2829 bytes core/assets/bundles/bundle.properties | 12 +- core/assets/icons/icons.properties | 2 + core/assets/logicids.dat | Bin 4694 -> 4728 bytes core/src/mindustry/content/Blocks.java | 11 +- core/src/mindustry/content/Fx.java | 22 ++ .../mindustry/content/SerpuloTechTree.java | 4 +- .../entities/effect/RadialEffect.java | 11 +- core/src/mindustry/game/SectorInfo.java | 49 ++-- core/src/mindustry/graphics/Pal.java | 3 +- .../mindustry/ui/dialogs/PlanetDialog.java | 2 +- .../world/blocks/campaign/LandingPad.java | 210 ++++++++++++++++-- .../world/blocks/campaign/LaunchPad.java | 28 ++- core/src/mindustry/world/meta/StatValues.java | 2 +- gradle.properties | 2 +- settings.gradle | 4 - 19 files changed, 310 insertions(+), 52 deletions(-) create mode 100644 core/assets-raw/sprites/blocks/campaign/advanced-launch-pad-light.png create mode 100644 core/assets-raw/sprites/blocks/campaign/advanced-launch-pad-pod.png create mode 100644 core/assets-raw/sprites/blocks/campaign/advanced-launch-pad.png create mode 100644 core/assets-raw/sprites/blocks/campaign/landing-pad.png diff --git a/core/assets-raw/sprites/blocks/campaign/advanced-launch-pad-light.png b/core/assets-raw/sprites/blocks/campaign/advanced-launch-pad-light.png new file mode 100644 index 0000000000000000000000000000000000000000..039c97f90992da673772cf57a10c55305d6daa0e GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RW7>k44ofy`glX=O&z))X##U|c*G3} z0ihiRJ5TP~Y`oce^YoepC-3du{{C(G=d$l<=FBfnoa{GOUs7>y`FwxR%!8k+<2^DD zK6rSpLBx5dTOI$?$M>$UnImQMdeRFejYW=oRa&{bay3l~ww%;hH=99pfyOudm!6Q7?39+60bEb*nZXP7YdSf2_z?``tBfb1!9$ z+zKgI>BEMa2U|TJEn^iB@LV_5bG{PSqeV=rON#`igb1;mn{-F(QrnNm_47SXp4qQ0I`7EB+$GzWaI^Ybeu;Q%0Ev1gyxKAo>5iav;K=~vTL55 zyynXk6N&eGgr3&?*n3HZT}yq^(u~g&RL(wf+`Z~!)n9#0-Hs{BdzaUS?6lglGCuIa zT=(tI>@V-}-6C_=aOf7%oMGJ15x)PiBB$qV1X)aodZFtiv(Rpj+?Zbm@lLa~IAx#h z)L*UYx%=|gW%(brPTH~Jl8;~?-?F0_Cr>tBQl2*ZdBf!uX+OD|`e(cEKXTVvoqdU& z#?+Ll%BmM_Hq^0t-VT_=JS&h#b*X5!^bM_*rqT>E-Gy%}on(A#^WyD2-=HV~T}>yx zMR!kK*L@mgwVy5b_#9{L@_9u^4~0%_xju2)3aWz?`L7& zr;dt~jPJKrzU_TFZAymxvPdDT+}&H{s!a0EWm6}3 zP2A%DDD7;_{6)5QUVJX^`MdTMs{E3AD7`{S`od%87n=_2EK;<6;&5KKVjk}+Rd$94 aBIiwid48RADusc8fx*+&&t;ucLK6V~Yd}!| literal 0 HcmV?d00001 diff --git a/core/assets-raw/sprites/blocks/campaign/advanced-launch-pad.png b/core/assets-raw/sprites/blocks/campaign/advanced-launch-pad.png new file mode 100644 index 0000000000000000000000000000000000000000..de14b807f84cb8b2209d94847f9234008cab2031 GIT binary patch literal 2782 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_R+7>k44ofy`glX=O&z%|3u z#WAE}&f8eq{1A7MxpyZh2{{G0Y*H2PPMNj#Qj}MsM$81JStTVCd4g0Ps9XGR2oO}U zvXZ(Su!BQS=_kL7wrd=xxQ9Zd*P@PlnkVk=nLBspZ^Q5V%s=0o$F=$W-I+U|@3Fpb ztbF{)!;2qTzt6RvoxgMang?IoXGd;Jet&#I_VrC?H1FKKEn7G*-Bw3Ye!^pcGlI92 zE^JDVd?sR{q+mb8+0Nn7gdoq$=cXSi;|dX+Aa_YY{y?F}%Ea4R@j|yX16&M_$z4#n zuPGjoW$n@-`co$3_OgQMAsuEveFJ3lN;}+`by!aSiQv)}Ko!yRtb30#_+|Kpcc7dg?wpixx+iP#{KH0r+{p0DArM>HeKQ-LE!Z3YB zfBO6B|Mu-Ko|S9NzR+WX5Mx8l^EtE6KGSafFi~m#r`_)v=GTZ9r0{Y)y{R94JJ3Lc zbDGFSmHnL&DhE#FHVJV$Jux?&ebzJc@1JJhw_jhduDxzEH&4BO|Ni2xN&i2-v9HL@$#p=zVBw@|;^W17al_$^l z99O;1$#Hp`>dDQYYg|?~Ep2#};G?*|muKmvDNfETmS#nt?rllbZ1SEXK1=bA{+-L} z>`T76a7j;IyYx_&#fOXC6L(l$5V(+I7Qc#5MX}aw_HOpy-=`TZHg$6rbC_JC!6NWv z@_c52Mz)^}XJ0um2{fkF)|Ka%FHvMF(YWC%l`lP6*L-f?>Ph?;RQ8KROzE`#D$S(T z5OVr1zc+*D4|WgfE>nf&IfnC}btOzu(sr%WNnB~GUNGVQ)hz9zC(C9(ZjU%JHH>|U z)&cjO7;EeWi8afB5>&8#gDFM1al|&|I`(q^)jl%fk~9n;YVXzqHyk{ z^ObGf-6h@vj@uai>{_nbq<3Pv&F$1(vE2@LIF2;!0hMdN54jkyd9_c)2}kvZS+ud_XhDvESFvZ8mDPoo(D5Vf?dUl50Ww=eN%} z7AQ7dF`jhc3i~I<*batl?gN{4x{6LvX7Xxtjy)pr%f)ViuVBHo#J;-!FOM&cJMek` zzGcrRG1wd5@QIf{neVi_{6OpegtDcA9j;7#L7O`izs#*k7vQwuSUZjH8o=(QQ+ zHcvKL&L=KS`zGb5UY2xcI>o1XDZs#$rB?0!t!=FwD{eo&pmo6VdEMg4etmJhpYHuhX`+xNwL>$de%?WXb1U!TTYD9E_t@85+J&R1^V=5gW9 zEW0w!Pqod?O7pW9hX@|AU>5Azs4vgMH?d~j+Dk4Ch2MT1_Ki|5IPs)7cbXa#&#P{y z-yU|?%QTf1ID|6Ps?DG6rmGYnd_dsD^JSsU93d{PSay5iL40Mm|2r|YpGm? zW1QGMvn+-Av)eDZFf4BDHg5X1e(vh^9Mc@SYwr8ynRDHi@;kY`J|QHWgO7E0kH>=2 z9}`7?%G|#6{dONqkagPWL&dHMp8LeS1RKSE_U%$SVfp--_5I006TfiWQiv~B@%3(* z^vdD)o|GcUs~p=XiThmGk2X*j)ljDQ4&M(^XaO!70_Di29T>Q$gb%E!+E&rBn zQ}%jsIy+&b+yZ~46PbGw&nJA#K9uAt@W(ygMZo!;L%{qEo74m@gbR9byj{)lXLnav z6=SXFL8*t)r+m(F#w?aw!)j0f!ZYNAwz@?+@OQq_{&{P%qp9*PxdrQ=Fz>R;+UK%n z4aY0)pPO&mR7Xijd%ijSc_=G(+yE?DTb=*2vA+J^Hg!xY6727-lf^UVz*Kv3<*8F~`^6PevS^E9-+vv;kAoeLX<*Pr(3I>bIM{C6#eV;IED0zf2UfRmEG=zO=a`L99cJua%>wj9J6e{bY z>~PL$Vvv&K$p)i*pKTr&UA^`>D>()*NUd(Urqpp*Ut6t7<`bi#ASeZh9n?xn?df!A zu5s{SIeB{V%9#f{;6ZK=S!KaAP9?`af5ZC8 z`u%JfCP8gFOebZzR%&>BR1IZ`kNNd%H}jbrKYz}Dw_0C+=jrhED)V>OB|o}1cb;o~ zeRifi(ZB?PmWL0#Mz2Us-li&k(N@=&a)DF4_55-B&PU!g~|*wkb+Xr@u9&?YeYubMMJ%Qa}B)8X{sZZ{@g^p_8>_ zwsDcv=Z#DLm7m)=@r&zp4(E++3ymer*74>}?*D1Lnrng!6HnOPxk3|yx6SaMZ(Faa zc0Y3qW08^vPwgC@O&XU$b<#VXMK4xF$n(p~$7GoL%=&$6%d3)?xyz%LFJk&RYr2Qo z>d5*z4Z*!>TN#69_-$`yS#~JFaQ*V#(|>%~=chq?1o< zTv&L%u&;O9YNK`M-@kj?cg(LafbsN%ErKeAOP;A-7m5!0wA8NY`wFE7x2KB)Gjy5O zB;0tH(&wz3XYu^TkxyMyr)o3hh+TBw?7q=ySLkeo)@9B~){`uI)mM8PSiDwE?#`GM z{nn%`Ix$ykzwd)`_3&MdVG|YD=Ii9>C+UJ)D|sph>b@&GX6Pt-l}S%zC~hceE#eeh zz`438$Z&3+zVAbGL0OJXeJr&*S9>sD+t)GieUKnSf*|J#)@e18LAO<(c{1N<@8M(3 zc4zY5`rKmHt3N`Y{8?MAjdn^+bO>_DO){vy6~8)lX7GZ#9c$KGyR%rn(wl0sw#GAO z`GL+`Z$9V=Ho9eWTOL_ecXn%bk44ofy`glX=O&z;)cy z#WAE}&f8eq>^bfN_k^|XNG(!0D5@0_`-z=N^PFDDj)?+Gip2%koKNt3C^pwOJl0y_ zC;Pjh=K7m` z%+5z{ru=fKeV*`K=;ZSbmpYEnQT_2LFTc79rkho^X@OOnd7`qQAB0= zf!gMh*Z@l>ZiVSJ4yzniJJ>nw($k1p$P(aXrx3CsR7qYTRy^X|B&G*S@(;uUx@!D( zM^!%YV?P{Qa^Qe(9iKt-vWTNo`PjU3w?DX>#C&)G`;VtzuD^OBox*yoLqjn^@`QP3 z+ndm1{Py!~B$Cf$Y)Ri%-ae_)+4;7x4);&9nt2Hi`5o*ML|r%ZJYkLzJoC5UBzuBu zL80B;-+%ucwJ@LW`1`p3`$^~L-pk*(jQhYc-M~EQrQdP|&-_z&u~SfGGM~(_?$n02 z*990&eLIV8KM*>2K;Z;?i;KW1l?5i7?S9WvI^pZR=+2yP{NIl^Gft~%&~6G*W868j z_vUp0$4sf6Pxt*T$=&r%>u1bL4v%WaV-lB3!=HV-yw#B9)5`Nn_b0x3t?BVparvEN z5}Ws0n=HN<_i1O$Z-z|_J<3d6OIR;!MTs3uJLkRa_QtzcewVASh;-#T?PB-fQO&HU z>$aD-KfC(zbjsdmx74c{tOcVN>it}}e<{eLMW>!c@8P_f-B&w_{e$M2V93pxNzua`MoZ@a*ci>F$ zt&^%1b-bptKrKo|{zB0`rxUYSuQ>U!-u`R+t@ig^mw?jy z?XTB2zuz<0X6El+#bdR{wl;dszAWs){&WG0z$^cxnbxy?hBOJG!$(4cQ##H z%_i^372`6shIdR(m%AgLC7gFStg0vEq&_PxUVKWGnlH2AY}tl6t!&jBJF4f~u=%~e z+RyUovu}ZrPwSLPj&Hx4Ub_57h%wLS@gbdtN1R0us+iVZb5IO9{WiOr zDc&J_YO6wq?2WLKt{rLheX#llVaYaA|~J8 zZ0AxxUaGRcvC~9)?S%bW55D9rz4z$w+0C0G8PtESymr4deDnV(R{;z7@C(kyr3hKOJWpGMv7CXVd#VA_i?DAs=3`*8JUSR(5jU`HH zzyFH2oJrH8SZo;>Y#68hnWQP(_9raL-ofhp6*k72rFkZvTn8kQ*X{e+H>Kib&Ain| z3n~TIec7^oOZ`7PS^q96mLR=|NpWW`s?AUGzNH=bCVBhUi<=pGICh-i7u~}4Gi3JC z%JW}a^UW#yG_?r=4V-(zmGWbCXl%*Ah}_E-@X~Y-@en>x~XlwX>mp> z!>ei6RqsDN+wOb6#(t^Nb>TaIf1f{Oe5>%wtjovN?G6$@c=5yIL-+UX{n?$OEXmLC z;D*$<)CexqYdj{GpR722;pxAz=boCU zzaDSQiW6U1d8obWCUbfD2Z@+ZNB(S)-@oFNYtNrWlO09;6Xvgco+W$h`0k<{k13{Q zo~_(mKh;b)Z0f4Szq07%t$!}wR5NvfZNwuxxpjNbJ$Ur?$w{Y~yQ*vEMLc5ky7+vm zmF`EcD}ifX_wh0P)?o5nw8V9R@4ka)n2KB$NHV#4ZhvZ`lEi7yT(LS6m7FT9gV zHLI*DbeoDit|z|Fag8aWa1HB*M-ki!M-{aiu9cnm#qrWgMEJn08at0` zOFbMs);iCXTC{p=*UZUzg$^AqCIRx-FPr8_FWhyKkMp(i&xTp*&0Y(2neuHIOjjJR z6)!M};Yqp>C-$tz{({{3ACLIu7B@XpZmE6Fv_dd;Nw<=wVq%#@@Y>aJmL01FTc6xo z*wPZHyelA{zy4p6jOzb)=Z{WyWat!jidu3->Au^Wm;=x6A69Snd%r!P+-ie7|MiA8 z^^C?pLK|fH^S>r^EaCX&XUA~4V6l30l&bs$;cdDT+ou(7D3+Yd+QHVEHAi5bXM@`< znNklvuD9Gj8}`*r;Ntca)S1I`DB)pK$Ln9>E*Zz(T^E?+&+=|3!>he~YU#!&uFns! z3wLAuTK8buqNb(q&A#0{8@X4NQ^9P;12M;Ue-G%+;hwPXwnbXM7Sk3618eUXC5^rE zJD#0Xc)7dRuuMZK!Cp@9sw_iR$AVVrQ!Bo0REmffJ>9Z&kJh!&!0hR++m)F5_ITZs zm{)3X<++oEea4hmT2YGAnYXU*?k|0~;;w^_(oT(!ENeTKsPu2OSDGp+a;r%_qqQb@ z!tq;AW=~r2V3txwXAOgCzwU(mqTLMZj>K*$dcMIVO7Xr#vYD58orsm~976g

gKI!@P4MT6A6gv@lfOrlFKEmNSYe`fZn?(O{P%p3Y;Ciw zJ0l+6;$7Hw!&ALk<$z^x&%&}V8~Dp^?tbT0^X;*%WXgNCYk$lfP6>PD>;DdT#2K{K zQsvLZjQnqUOA7;z3EYcxc+2nXQQ0)_B3O`(>ng=-;-rCPVMRZCv9HizQQ^ERq|S`FZp)S zQTJWXtW95|E)dJ{NW?KCL{|BbD&OOvyXi^Ojax1~n{!_NUB9bz(A8E2ts=3OQ~M>{ zvR~Fl?|gIDccE^0!RbnFM%9LZ`XaNLtN6Y!=!TW7wP|I$y{6e=vzVhut?K=N;|vR5 z9=)0J>wsazLZA3E|3&w2{p|i{=2@en9dC=*R?PEV#=q}`(yMJol{^j~SuC>|qh>ps zP6#%O-+So(ouiB9%xGE@>2OB)#{S@q`60h+-8wfU-_}Dmz9VfP-iONyR36>4bPSn&o^CN z#8J~-dXO=}Tj_$@{L^XPN)@GlE}bqdkXj*mLC{(EQ}Fww6S+?}y%qSW_KcB1Xx@Q! zGeTUKX$5xGtTWMgI{Aad%C { + color(Pal.accent); + stroke(e.fout() * 2f + 0.2f); + Lines.circle(e.x, e.y, e.fin() * 26f); + }), + explosion = new Effect(30, e -> { e.scaled(7, i -> { stroke(3f * i.fout()); @@ -1624,6 +1630,15 @@ public class Fx{ }); }), + steamCoolSmoke = new Effect(35f, e -> { + color(Pal.water, Color.lightGray, e.fin(Interp.pow2Out)); + alpha(e.fout(Interp.pow3Out)); + + randLenVectors(e.id, 4, e.finpow() * 7f, e.rotation, 30f, (x, y) -> { + Fill.circle(e.x + x, e.y + y, Math.max(e.fout(), Math.min(1f, e.fin() * 8f)) * 2.8f); + }); + }), + smokePuff = new Effect(30, e -> { color(e.color); @@ -2574,6 +2589,13 @@ public class Fx{ Fill.circle(e.x + Tmp.v1.x, e.y + Tmp.v1.y, 8f * rand.random(0.6f, 1f) * e.fout(0.2f)); }).layer(Layer.groundUnit + 1f), + podLandDust = new Effect(70f, e -> { + color(e.color, e.fout(0.1f)); + rand.setSeed(e.id); + Tmp.v1.trns(e.rotation, e.finpow() * 35f * rand.random(0.2f, 1f)); + Fill.circle(e.x + Tmp.v1.x, e.y + Tmp.v1.y, 5f * rand.random(0.6f, 1f) * e.fout(0.2f)); + }).layer(Layer.groundUnit + 1f), + unitShieldBreak = new Effect(35, e -> { if(!(e.data instanceof Unit unit)) return; diff --git a/core/src/mindustry/content/SerpuloTechTree.java b/core/src/mindustry/content/SerpuloTechTree.java index 989c3b043f..e791f67680 100644 --- a/core/src/mindustry/content/SerpuloTechTree.java +++ b/core/src/mindustry/content/SerpuloTechTree.java @@ -20,8 +20,10 @@ public class SerpuloTechTree{ node(junction, () -> { node(router, () -> { node(advancedLaunchPad, Seq.with(new SectorComplete(extractionOutpost)), () -> { - node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> { + node(landingPad, () -> { + node(interplanetaryAccelerator, Seq.with(new SectorComplete(planetaryTerminal)), () -> { + }); }); }); diff --git a/core/src/mindustry/entities/effect/RadialEffect.java b/core/src/mindustry/entities/effect/RadialEffect.java index 1ac37f172f..e6495c4b0f 100644 --- a/core/src/mindustry/entities/effect/RadialEffect.java +++ b/core/src/mindustry/entities/effect/RadialEffect.java @@ -8,7 +8,7 @@ import mindustry.entities.*; /** Renders one particle effect repeatedly at specified angle intervals. */ public class RadialEffect extends Effect{ public Effect effect = Fx.none; - public float rotationSpacing = 90f, rotationOffset = 0f; + public float rotationSpacing = 90f, rotationOffset = 0f, effectRotationOffset = 0f; public float lengthOffset = 0f; public int amount = 4; @@ -16,14 +16,19 @@ public class RadialEffect extends Effect{ clip = 100f; } - public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset){ + public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset, float effectRotationOffset){ this(); this.amount = amount; this.effect = effect; + this.effectRotationOffset = effectRotationOffset; this.rotationSpacing = spacing; this.lengthOffset = lengthOffset; } + public RadialEffect(Effect effect, int amount, float spacing, float lengthOffset){ + this(effect, amount, spacing, lengthOffset, 0f); + } + @Override public void create(float x, float y, float rotation, Color color, Object data){ if(!shouldCreate()) return; @@ -31,7 +36,7 @@ public class RadialEffect extends Effect{ rotation += rotationOffset; for(int i = 0; i < amount; i++){ - effect.create(x + Angles.trnsx(rotation, lengthOffset), y + Angles.trnsy(rotation, lengthOffset), rotation, color, data); + effect.create(x + Angles.trnsx(rotation, lengthOffset), y + Angles.trnsy(rotation, lengthOffset), rotation + effectRotationOffset, color, data); rotation += rotationSpacing; } } diff --git a/core/src/mindustry/game/SectorInfo.java b/core/src/mindustry/game/SectorInfo.java index 2ccb9cd3f2..8673e8206d 100644 --- a/core/src/mindustry/game/SectorInfo.java +++ b/core/src/mindustry/game/SectorInfo.java @@ -30,6 +30,9 @@ public class SectorInfo{ public ObjectMap rawProduction = new ObjectMap<>(); /** Export statistics. */ public ObjectMap export = new ObjectMap<>(); + //TODO: there is an obvious exploit with launch pad redirection here; pads can be redirected after leaving a sector, which doesn't update calculations. + /** Import statistics, based on what launch pads are actually receiving. TODO: this is not actually used or displayed anywhere (yet) */ + public ObjectMap imports = new ObjectMap<>(); /** Items stored in all cores. */ public ItemSeq items = new ItemSeq(); /** The best available core type. */ @@ -118,10 +121,20 @@ public class SectorInfo{ export.get(item, ExportStat::new).counter += amount; } + /** Updates import statistics. */ + public void handleItemImport(Item item, int amount){ + imports.get(item, ExportStat::new).counter += amount; + } + public float getExport(Item item){ return export.get(item, ExportStat::new).mean; } + public boolean hasExport(Item item){ + var exp = export.get(item); + return exp != null && exp.mean > 0f; + } + public void refreshImportRates(Planet planet){ if(importRateCache == null || importRateCache.length != content.items().size){ importRateCache = new float[content.items().size]; @@ -140,7 +153,7 @@ public class SectorInfo{ return importRateCache; } - /** @return the import rate of an item as item/second. */ + /** @return the import rate of an item as item/second. This is the *raw* max import rate, not what landing pads are actually using. */ public float getImportRate(Planet planet, Item item){ return getImportRates(planet)[item.id]; } @@ -239,19 +252,8 @@ public class SectorInfo{ //refresh throughput if(time.get(refreshPeriod)){ - //refresh export - export.each((item, stat) -> { - //initialize stat after loading - if(!stat.loaded){ - stat.means.fill(stat.mean); - stat.loaded = true; - } - - //add counter, subtract how many items were taken from the core during this time - stat.means.add(Math.max(stat.counter, 0)); - stat.counter = 0; - stat.mean = stat.means.rawMean(); - }); + updateStats(export); + updateStats(imports); if(coreDeltas == null) coreDeltas = new int[content.items().size]; if(productionDeltas == null) productionDeltas = new int[content.items().size]; @@ -268,6 +270,11 @@ public class SectorInfo{ //export can, at most, be the raw items being produced from factories + the items being taken from the core export.get(item).mean = Math.min(export.get(item).mean, rawProduction.get(item).mean + Math.max(-production.get(item).mean, 0)); } + + if(imports.containsKey(item)){ + //import can't exceed max import rate + imports.get(item).mean = Math.min(imports.get(item).mean, getImportRate(state.getPlanet(), item)); + } } Arrays.fill(coreDeltas, 0); @@ -275,6 +282,20 @@ public class SectorInfo{ } } + void updateStats(ObjectMap map){ + map.each((item, stat) -> { + //initialize stat after loading + if(!stat.loaded){ + stat.means.fill(stat.mean); + stat.loaded = true; + } + + stat.means.add(Math.max(stat.counter, 0)); + stat.counter = 0; + stat.mean = stat.means.rawMean(); + }); + } + void updateDelta(Item item, ObjectMap map, int[] deltas){ ExportStat stat = map.get(item, ExportStat::new); if(!stat.loaded){ diff --git a/core/src/mindustry/graphics/Pal.java b/core/src/mindustry/graphics/Pal.java index 9b56be524b..ba63dcdb68 100644 --- a/core/src/mindustry/graphics/Pal.java +++ b/core/src/mindustry/graphics/Pal.java @@ -5,6 +5,7 @@ import arc.graphics.*; public class Pal{ public static Color + water = Color.valueOf("596ab8"), darkOutline = Color.valueOf("2d2f39"), thoriumPink = Color.valueOf("f9a3c7"), coalBlack = Color.valueOf("272727"), @@ -107,7 +108,7 @@ public class Pal{ redderDust = Color.valueOf("ff7b69"), plasticSmoke = Color.valueOf("f1e479"), - + adminChat = Color.valueOf("ff4000"), neoplasmOutline = Color.valueOf("2e191d"), diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 55b564047f..c078fc36ac 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -962,7 +962,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ //import if(sector.hasBase()){ - displayItems(c, 1f, sector.info.importStats(sector.planet), "@sectors.import", t -> { + displayItems(c, 1f, sector.info.imports, "@sectors.import", t -> { sector.info.eachImport(sector.planet, other -> { String ic = other.iconChar(); t.add(Iconc.rightOpen + " " + (ic == null || ic.isEmpty() ? "" : ic + " ") + other.name()).padLeft(10f).row(); diff --git a/core/src/mindustry/world/blocks/campaign/LandingPad.java b/core/src/mindustry/world/blocks/campaign/LandingPad.java index 676c204916..9b7cd5dfbd 100644 --- a/core/src/mindustry/world/blocks/campaign/LandingPad.java +++ b/core/src/mindustry/world/blocks/campaign/LandingPad.java @@ -1,6 +1,8 @@ package mindustry.world.blocks.campaign; import arc.*; +import arc.graphics.*; +import arc.graphics.g2d.*; import arc.math.*; import arc.scene.ui.layout.*; import arc.struct.*; @@ -8,6 +10,7 @@ import arc.util.*; import arc.util.io.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; +import mindustry.entities.*; import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.graphics.*; @@ -16,6 +19,7 @@ import mindustry.type.*; import mindustry.ui.*; import mindustry.world.*; import mindustry.world.blocks.*; +import mindustry.world.blocks.liquid.*; import mindustry.world.consumers.*; import mindustry.world.meta.*; @@ -32,10 +36,19 @@ public class LandingPad extends Block{ }); } - public float cooldownTime = 12f; + public @Load(value = "@-pod", fallback = "advanced-launch-pad-pod") TextureRegion podRegion; + public float arrivalDuration = 150f; + public float cooldownTime = 180f; public float consumeLiquidAmount = 100f; public Liquid consumeLiquid = Liquids.water; + public Effect landEffect = Fx.podLandShockwave; + public Effect coolingEffect = Fx.none; + public float coolingEffectChance = 0.2f; + + public float liquidPad = 2f; + public Color bottomColor = Pal.darkerMetal; + public LandingPad(String name){ super(name); @@ -45,6 +58,8 @@ public class LandingPad extends Block{ update = true; configurable = true; acceptsItems = false; + emitLight = true; + lightRadius = 90f; config(Item.class, (LandingPadBuild build, Item item) -> build.config = item); configClear((LandingPadBuild build) -> build.config = null); @@ -97,21 +112,19 @@ public class LandingPad extends Block{ public @Nullable Item config; //priority collisions are possible, but should be extremely rare public int priority = Mathf.rand.nextInt(); - public float cooldown = 0f; + public float cooldown = 0f, landParticleTimer; + public float arrivingTimer = 0f; + public @Nullable Item arriving; + public float liquidRemoved; public void handleLanding(){ if(!state.isCampaign() || config == null) return; - //TODO animation, etc cooldown = 1f; - items.set(config, itemCapacity); - liquids.remove(consumeLiquid, consumeLiquidAmount); - for(int i = 0; i < 10; i++){ - Fx.steam.at(this); - } - //TODO this is a temporary effect - Fx.shockwave.at(this); + arriving = config; + arrivingTimer = 0f; + liquidRemoved = 0f; state.rules.sector.info.importCooldownTimers.put(config, 0f); } @@ -128,6 +141,9 @@ public class LandingPad extends Block{ float framesBetweenArrival = itemCapacity / importedPerFrame; state.rules.sector.info.importCooldownTimers.increment(item, 0f, 1f / framesBetweenArrival * Time.delta); + }else{ + //nothing is being imported, so reset the timer + state.rules.sector.info.importCooldownTimers.put(item, 0f); } } @@ -140,32 +156,144 @@ public class LandingPad extends Block{ Call.landingPadLanded(first.tile); - //swap priorities, moving this block to the end of the list (if there is only one block waiting, this does nothing) var tmp = first.priority; first.priority = head.priority; head.priority = tmp; - pads.clear(); } }); } } + @Override + public void draw(){ + if(consumeLiquid != null){ + Draw.color(bottomColor); + Fill.square(x, y, size * tilesize/2f - liquidPad); + Draw.color(); + LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, consumeLiquid, liquids.get(consumeLiquid) / liquidCapacity); + } + + super.draw(); + + if(arriving != null){ + float fin = Mathf.clamp(arrivingTimer), fout = 1f - fin; + float alpha = Interp.pow5Out.apply(fin); + float scale = (1f - alpha) * 1.3f + 1f; + float + cx = x, + cy = y + Interp.pow4In.apply(fout) * (100f + Mathf.randomSeedRange(id() + 2, 30f)); + + float rotation = fout * (90f + Mathf.randomSeedRange(id(), 50f)); + + Draw.z(Layer.effect + 0.001f); + + Draw.color(Pal.engine); + + float rad = 0.15f + Interp.pow5Out.apply(Mathf.slope(fin)); + + Fill.light(cx, cy, 10, 25f * (rad + scale-1f), Tmp.c2.set(Pal.engine).a(alpha), Tmp.c1.set(Pal.engine).a(0f)); + + Draw.alpha(alpha); + for(int i = 0; i < 4; i++){ + Drawf.tri(cx, cy, 6f, 40f * (rad + scale-1f), i * 90f + rotation); + } + + Draw.color(); + + Draw.z(Layer.weather - 1); + + scale *= podRegion.scl(); + float rw = podRegion.width * scale, rh = podRegion.height * scale; + + Draw.alpha(alpha); + Drawf.shadow(cx, cy, size * tilesize, fin); + Draw.rect(podRegion, cx, cy, rw, rh, rotation); + + Tmp.v1.trns(225f, Interp.pow3In.apply(fout) * 250f); + + Draw.z(Layer.flyingUnit + 1); + Draw.color(0, 0, 0, 0.22f * alpha); + + Draw.rect(podRegion, cx + Tmp.v1.x, cy + Tmp.v1.y, rw, rh, rotation); + + }else if(cooldown > 0f){ + + Drawf.shadow(x, y, size * tilesize, cooldown); + Draw.alpha(cooldown); + Draw.mixcol(Pal.accent, 1f - cooldown); + Draw.rect(podRegion, x, y); + } + + Draw.reset(); + } + + @Override + public void drawLight(){ + Drawf.light(x, y, lightRadius, Pal.accent, Mathf.clamp(Math.max(cooldown, arrivingTimer * 1.5f))); + } + @Override public void updateTile(){ updateTimers(); + if(arriving != null){ + if(!headless){ //pod particles + float fin = arrivingTimer; + float tsize = Interp.pow5Out.apply(fin); + + landParticleTimer += tsize * Time.delta / 2f; + if(landParticleTimer >= 1f){ + tile.getLinkedTiles(t -> { + if(Mathf.chance(0.1f)){ + Fx.podLandDust.at(t.worldx(), t.worldy(), angleTo(t.worldx(), t.worldy()) + Mathf.range(30f), Tmp.c1.set(t.floor().mapColor).mul(1.5f + Mathf.range(0.15f))); + } + }); + + landParticleTimer = 0f; + } + } + + arrivingTimer += Time.delta / arrivalDuration; + + float toRemove = Math.min(consumeLiquidAmount / arrivalDuration * Time.delta, consumeLiquidAmount - liquidRemoved); + liquidRemoved += toRemove; + + liquids.remove(consumeLiquid, toRemove); + + if(Mathf.chanceDelta(coolingEffectChance * Interp.pow5Out.apply(arrivingTimer))){ + coolingEffect.at(this); + } + + if(arrivingTimer >= 1f){ + //remove any leftovers to make sure it's precise + liquids.remove(consumeLiquid, consumeLiquidAmount - liquidRemoved); + + landEffect.at(this); + Effect.shake(3f, 3f, this); + + items.set(arriving, itemCapacity); + state.getSector().info.handleItemImport(arriving, itemCapacity); + + arriving = null; + arrivingTimer = 0f; + } + } + if(items.total() > 0){ dumpAccumulate(config == null || items.get(config) != items.total() ? null : config); } + if(arriving == null){ + cooldown -= delta() / cooldownTime; + cooldown = Mathf.clamp(cooldown); + } + if(config != null && state.isCampaign()){ - cooldown -= delta() / cooldownTime; - - if(cooldown <= 0f && efficiency > 0f && items.total() == 0 && state.rules.sector.info.importCooldownTimers.get(config, 0f) >= 1f){ + if(cooldown <= 0f && efficiency > 0f && items.total() == 0 && state.rules.sector.info.getImportRate(state.getPlanet(), config) > 0f && state.rules.sector.info.importCooldownTimers.get(config, 0f) >= 1f){ //queue landing for next frame waiting.get(config, Seq::new).add(this); @@ -184,6 +312,58 @@ public class LandingPad extends Block{ @Override public void buildConfiguration(Table table){ ItemSelection.buildTable(LandingPad.this, table, content.items(), () -> config, this::configure, selectionRows, selectionColumns); + + if(!net.client()){ + table.row(); + + table.table(t -> { + t.background(Styles.black6); + + t.button(Icon.downOpen, Styles.clearNonei, 40f, () -> { + if(config != null && state.isCampaign()){ + for(Sector sector : state.getPlanet().sectors){ + if(sector.hasBase() && sector != state.getSector() && sector.info.destination != state.getSector() && sector.info.hasExport(config)){ + sector.info.destination = state.getSector(); + sector.saveInfo(); + } + } + state.getSector().info.refreshImportRates(state.getPlanet()); + } + }).disabled(b -> config == null || !state.isCampaign() || (!state.getPlanet().sectors.contains(s -> s.hasBase() && s.info.hasExport(config) && s.info.destination != state.getSector()))) + .tooltip("@sectors.redirect").get(); + }).fillX().left(); + } + } + + @Override + public void display(Table table){ + super.display(table); + + if(!state.isCampaign() || net.client() || team != player.team()) return; + + table.row(); + table.label(() -> { + if(config == null || !state.isCampaign()){ + return ""; + } + int sources = 0; + float perSecond = 0f; + for(var s : state.getPlanet().sectors){ + if(s != state.getSector() && s.hasBase() && s.info.destination == state.getSector()){ + float amount = s.info.getExport(config); + if(amount > 0){ + sources ++; + perSecond += s.info.getExport(config); + } + } + } + + String str = Core.bundle.format("landing.sources", sources == 0 ? Core.bundle.get("none") : sources); + if(perSecond > 0){ + str += "\n" + Core.bundle.format("landing.import", config.emoji(), (int)(perSecond * 60f)); + } + return str; + }).pad(4).wrap().width(200f).left(); } @Override diff --git a/core/src/mindustry/world/blocks/campaign/LaunchPad.java b/core/src/mindustry/world/blocks/campaign/LaunchPad.java index 1f63f5a17a..cb2014ee98 100644 --- a/core/src/mindustry/world/blocks/campaign/LaunchPad.java +++ b/core/src/mindustry/world/blocks/campaign/LaunchPad.java @@ -22,6 +22,7 @@ import mindustry.logic.*; import mindustry.type.*; import mindustry.ui.*; import mindustry.world.*; +import mindustry.world.blocks.liquid.*; import mindustry.world.meta.*; import static mindustry.Vars.*; @@ -36,6 +37,13 @@ public class LaunchPad extends Block{ public Color lightColor = Color.valueOf("eab678"); public boolean acceptMultipleItems = false; + public float lightStep = 1f; + public int lightSteps = 3; + + public float liquidPad = 2f; + public @Nullable Liquid drawLiquid; + public Color bottomColor = Pal.darkerMetal; + public LaunchPad(String name){ super(name); hasItems = true; @@ -87,6 +95,13 @@ public class LaunchPad extends Block{ @Override public void draw(){ + if(hasLiquids && drawLiquid != null){ + Draw.color(bottomColor); + Fill.square(x, y, size * tilesize/2f - liquidPad); + Draw.color(); + LiquidBlock.drawTiledFrames(block.size, x, y, liquidPad, liquidPad, liquidPad, liquidPad, drawLiquid, liquids.get(drawLiquid) / liquidCapacity); + } + super.draw(); if(!state.isCampaign()) return; @@ -94,13 +109,11 @@ public class LaunchPad extends Block{ if(lightRegion.found()){ Draw.color(lightColor); float progress = Math.min((float)items.total() / itemCapacity, launchCounter / launchTime); - int steps = 3; - float step = 1f; for(int i = 0; i < 4; i++){ - for(int j = 0; j < steps; j++){ - float alpha = Mathf.curve(progress, (float)j / steps, (j+1f) / steps); - float offset = -(j - 1f) * step; + for(int j = 0; j < lightSteps; j++){ + float alpha = Mathf.curve(progress, (float)j / lightSteps, (j+1f) / lightSteps); + float offset = -(j - 1f) * lightStep; Draw.color(Pal.metalGrayDark, lightColor, alpha); Draw.rect(lightRegion, x + Geometry.d8edge(i).x * offset, y + Geometry.d8edge(i).y * offset, i * 90); @@ -110,6 +123,7 @@ public class LaunchPad extends Block{ Draw.reset(); } + Drawf.shadow(x, y, size * tilesize); Draw.rect(podRegion, x, y); Draw.reset(); @@ -169,7 +183,11 @@ public class LaunchPad extends Block{ table.button(Icon.upOpen, Styles.cleari, () -> { ui.planet.showSelect(state.rules.sector, other -> { if(state.isCampaign() && other.planet == state.rules.sector.planet){ + var prev = state.rules.sector.info.destination; state.rules.sector.info.destination = other; + if(prev != null){ + prev.info.refreshImportRates(state.getPlanet()); + } } }); deselect(); diff --git a/core/src/mindustry/world/meta/StatValues.java b/core/src/mindustry/world/meta/StatValues.java index d0be047ebc..8547834614 100644 --- a/core/src/mindustry/world/meta/StatValues.java +++ b/core/src/mindustry/world/meta/StatValues.java @@ -150,7 +150,7 @@ public class StatValues{ t.add(Strings.autoFixed(amount, 2)).style(Styles.outlineLabel); add(t); } - }}).size(iconMed).padRight(3 + (amount != 0 && Strings.autoFixed(amount, 2).length() > 2 ? 8 : 0)).with(s -> withTooltip(s, liquid, false)); + }}).size(iconMed).padRight(3 + (amount != 0 ? (Strings.autoFixed(amount, 2).length() - 1) * 10 : 0)).with(s -> withTooltip(s, liquid, false)); if(perSecond && amount != 0){ t.add(StatUnit.perSecond.localized()).padLeft(2).padRight(5).color(Color.lightGray).style(Styles.outlineLabel); diff --git a/gradle.properties b/gradle.properties index a6c6ea3647..04720cf292 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=99a42db331 +archash=c8004178c4 diff --git a/settings.gradle b/settings.gradle index dafd6bdf99..b18346f652 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,10 +15,6 @@ if(new File(settingsDir, 'local.properties').exists()){ if(System.getenv("JITPACK") == "true") hasSdk = false if(hasSdk){ - //hack: pretend the Android module doesn't exist when imported through IntelliJ - //why? because IntelliJ chokes on the new version of the Android plugin - - //UPDATE: it no longer chokes on AGP with the latest version, but instead gives a completely different error. brilliant. include 'android' }else{ println("No Android SDK found. Skipping Android module.")