From eaf96fcc86f50e1f442b39fa86fb740883d4cf48 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sat, 19 Feb 2022 14:53:06 -0500 Subject: [PATCH] WIP dynamic fog + bugfixes + cleanup --- .../sprites/blocks/units/fabricator-top.png | Bin 1015 -> 1533 bytes .../sprites/blocks/units/fabricator.png | Bin 1379 -> 1592 bytes core/src/mindustry/ClientLauncher.java | 3 +- core/src/mindustry/ai/Pathfinder.java | 1 - core/src/mindustry/async/AsyncCore.java | 2 +- core/src/mindustry/content/SectorPresets.java | 1 + core/src/mindustry/core/Renderer.java | 1 - .../mindustry/editor/MapGenerateDialog.java | 12 +- core/src/mindustry/game/FogControl.java | 381 +++++++++++++----- core/src/mindustry/game/Saves.java | 4 +- core/src/mindustry/graphics/FogRenderer.java | 39 +- .../mindustry/graphics/MinimapRenderer.java | 4 +- core/src/mindustry/maps/Maps.java | 1 - core/src/mindustry/mod/Mods.java | 8 +- core/src/mindustry/net/ArcNetProvider.java | 3 +- core/src/mindustry/net/BeControl.java | 4 +- core/src/mindustry/net/Net.java | 1 - .../mindustry/ui/dialogs/PlanetDialog.java | 1 + core/src/mindustry/world/Block.java | 7 + core/src/mindustry/world/meta/BlockFlag.java | 5 +- gradle.properties | 2 +- tools/src/mindustry/tools/Generators.java | 1 - 22 files changed, 339 insertions(+), 142 deletions(-) diff --git a/core/assets-raw/sprites/blocks/units/fabricator-top.png b/core/assets-raw/sprites/blocks/units/fabricator-top.png index 1a2301dfc14a444b94c359883f6e305463e5014f..7b0d16be3cba6a75c1553705b29b1af251c388c4 100644 GIT binary patch delta 1516 zcmVBYyw^b5ch_0Itp)=>Px)w@E}nRCt{2oxg8XM-a#7u1T5{DTpYN z?yj+L8KnFJDWD52MI@O>Jz%bOeS!I;qFvTEXfHsmkE-LWn}=8$^;Z20N~Z%Z{hXJ_hgLJ z4)#$`O0_8+?W8h6+Fgna0N6Ak6d#eA&}T6(Y+F0j3{B9StJEWmv*UeogE`&Ny=Rl+ zgf7Ig3x7h+&zJD%PW>^U9d3&=0J-+yG1*`#6Yf2`_i z8JhStv1{iE6ip&y(QY>16@9Oz_#x6vz(GNa%dF-BbbHI?r58+->8^k#P zDVZ=9@sW}VgBk@PITOYvK9V!xV_SIaIDd}gIF92uj^jAp;iU+IE=mzzjLR^pVw{(w z^>ltoa_A;VZYMmP}oA0XEXtw0w@JO|Sx)xuS0wZ6Rg80HL9lkV? zU{p&J5nr4u!PBs|3@AOSfSU=S z_yBd85Pvt0o#V%E-;sZ}%!E!J))7Kw0ws_%6DZ@TOuz%B=J9J1ssl*GM^Ywmj!v!# zoUBQi5NBx@CRBAn8u0d|*46yRdMQTzC{r!VHVFY%!W`&X?A;STqCi85?kYeM+!P?OhK zQKU|8QWH`;+J_0LHl-${9^5K5fxY9UkwxkmqA{f%q}s3?hL&P+9>4b6KY!QA_PLc) zRt9i}b)`%wi4Tw$Mw1~mu}a(e{o{q%+og&dBb|w+GJzw0u1(8Qgfvzv6T$<{;!Psz zE`cI6K`Q>x2u(<3d94ZAKh#zuA6~yYdhwC7<9%}2U1LJ>LyJ=I&{x1oY4u{Gtq)xM zJS|Mv_{GI9V{O9v2N$1@rDX{dPCyQqMSR4{go%r<$HIi_OOlsWd;ps_4ptd(nZ-wH zO;8));(wsV1eF0U{x(_@s$OH|Y@#ut`bBQeCQ=jF2DtcqBqo@=%+uM1Yr=oW;nOo) SbMMkv!=zK{`KGfckVjqvpN*&V?fll8}NtYxTt!5ZNhSPtu=y$fKy zBF}3XDqlb&oCmf>@GgWU;%+TNi+?qqdfz@`YA}-#Jhf6b>2(^XRB3Nfa4g=KXe4mRkKL)1yp{>2rc=g2(#xFQ2C)EXysoc%%1xSDnCR7 zRK6Tjhkves%AXoxH~GRc5q6X>ED_*fNBKf{`NEF!h4Au|BuSDaNs=THxbrts?1N!miMEp{3Rl=Zq#OO zTUh=hE6EQXq4iS*+6c_OBg4Be`@ zE`Puy9~EYuQF}g1grS>trGhVHJz8u{npz`k`u%3JLSyIq1yWs QvH$=807*qoM6N<$g25W$pa1{> diff --git a/core/assets-raw/sprites/blocks/units/fabricator.png b/core/assets-raw/sprites/blocks/units/fabricator.png index 4e1478888aa09071aeff90a5241618ab6d736046..7997bb60f3230f9891e32eba97923cccda25d807 100644 GIT binary patch delta 1562 zcmV+#2IcwV3b+iAF@N$&L_t(|ob8>xY8yci$Hzw=A%z11H&P?x)?i4Hv`I*jJcPl9 zpvrY>S7{RPQ-qlGNoP>&phjv8g7__*YL)eAKW1iU=Jw9|fx*)0c4y~*yL-2{v%B|c z_W5Bt9fLHdCrk8iuWlyd`D3WT#%8s=g~ehenrICLlg^t`CVvVI0DzO|%9$Vl}}zAIru3KKmO=dR#DhU~Ki%E2&jk0YaH*IN#4p3r(B3 zA|#ujb|#_!b5IJxNnewT`8`}7#fa~%U!1GsRUUmMyr0WE;*Y^KvOs}{8iYmSEOd`C$c zLj!$}1mHN|@%z_L_PKpSQiP2F9Ot_#_3i5ywm~5@YLaxCE;KN)PJ~F4^{+j+T8O>@5ruhCiy2}fMJhj zc1?CHXy<%=+9GTOkaj+|%4!bi)nbz`LIa?8r}7)`CkCMQCp-RxisU^$dsi%nNI z6Wv)64z0VI5^Aw2DZqGgBsP~aR-i6l;%M81@n2K`|Ao7j2sOMx)3OQczi50w2+XW6 zt$+6ewSak;qxes}573&?Py-reL(dJRAo1vq$-~DwKPlAif;4MFC~fQfq)@#L)i6Aj zrgeT2s6Pit766l$b$+FoJcvmYgp!7JekGU)08myS&bik4aWNABpdXI3zKu2&fJ&d5 z5-wE>@D$hy&@np!I;Ls?tmcMxiKzfuQ-2I=$&Z7X0DW?#r3h8Ll9W!PPfeG)0M>yP zeF*IEQ5C>C)Tj@kO>NW!u%2L5F0Bpb6 zkIjgn&Vul-qFTf%u_;z7u>MOf+Lt?-iex~X6k0Z6{g-TlFpEuK(FlkuMcXEf|9`4@ zCE0}twb;Z8)TUq&5J#mJn>K;kS|V@);f2zsExQ~_i}&v!yPqs!Gec7GzIIXj=B zH?e3FdZ5iNSw>wm?(BRv*l2nG0*fu(-zMk3=u3GAW}mFn%o6QGqFvV1j#3!kvG)!qWMrYJ#Z%m|@$l_Y>BbmXi`8I~0M1VtB2Kglm@}^nfN-11JX7 zIQh%s0t(dx(I%*`B3Rbmwg}>VK;J~@qUrQSQ0avGB0`t_mJ{J0c@sj~BA~s7J^%m! M07*qoM6N<$f)s1$Y5)KL delta 1347 zcmV-J1-$yW4C4xrF@GINL_t(|ob8>lZWJ*PhMg5JKmnpybj@9JQc95MpanV}0x6MD zDr#yf8jyGj1fquyiT0#xZktF~Ab0_&LV@-$W^Iq{f5zivKPggjS$k&Zzwvt4_WJ1A z*^7(ydTCnV>AfB4xVK+c%%0%rhTX<&w?l0|qELgMdTebvV}Fd%E<#Q~TM?lff6*?& zWu>$Km_?B1n)|`9+Zbbvsd-^`zXU(}UyyndSI4lrLVS4t4p!H4bzIc6c{5;HcJR%ShNH)FZIM^XmN(8$RIat62;8;SP1MN%_?lmTw= z*XyNNkEKOYG9fB}ubVZC5QZv3vti zOvpL0l5&!^IDCZkf<_Ta9rTrd{`FgOUA{70KIQ`JhJSwk^bt}ILIkehqw6syp&J_F z&Ffc)Y7Xr8=acPiPA1rOBJuf{N^u6DgFk`{7}xa$Q76GS}$jpG3lJ{1daa(Wg$hO1Xo0>UTur=iQ$F=<@0M1&f_kJ&~ApAtW> z0QN8=mk2e3@ACl}{E<-njvhXGauF(QLz(NsHGe*!h1B5NOvMj=5te?OD+$NBu{?_4 zymhy2*ht$$!PX7s%!H}BrR||OWWdY2e`g@yyz_JN;2XF7vyG<~M{{ICC>WCV?0C6Q z;y1>ti&aL&w=Ng??V;2qVGkLv4pEs~sS6?~DRpq#L&vN7m@rpaX?wX;ofW}Vrk1@N zRDX{LNH<+IP%Xe!U>2Y`g#ZO=#55wQ{J304{>bWva#5z{!q z3Sp1qu?9Q?wD-G95L>7{^(GY5jEKHp9Djbakm3_Xgp~LNU)mQdY(oux=GDCBdiup9ATIgFc#Cq=!P2Pt7gTD@B`6>LzAsoM6T2 zlj`w+tH3NkW!TmU=ia)P%+Kd~`^vmX&muNAw1!V5G_^<%H8=E9Ez+YPy&w)Df`8OQ z#p+|iT&odFL=YVl7PDmd7Q~g5t-Wwi{GD+rCy1IGs-?M^LS&(K4$=!dYSjzLfRocR z=<-ybsHJ;JB7H=O3cCiNGNCm1p<_`#-hc2IQjamV9}HDE9=p zt|drYb~%&?(QCkVJ0x#RBN@zG5Pv@U_D>k02$a2#*vub%nLyp(Eo&dKnSXIbNZDO4 zc^@&2JCbU^-QA{6uUkV#0O+3v^=< result; + ExecutorService executor = Threads.executor(1); + Future result; boolean generating; long[] buffer1, buffer2; @@ -369,7 +370,10 @@ public class MapGenerateDialog extends BaseDialog{ void apply(){ if(result != null){ - result.get(); + //ignore errors yay + try{ + result.get(); + }catch(Exception e){} } buffer1 = null; diff --git a/core/src/mindustry/game/FogControl.java b/core/src/mindustry/game/FogControl.java index 29f2e37724..9a53f82565 100644 --- a/core/src/mindustry/game/FogControl.java +++ b/core/src/mindustry/game/FogControl.java @@ -1,6 +1,7 @@ package mindustry.game; import arc.*; +import arc.struct.Bits; import arc.struct.*; import arc.util.*; import mindustry.*; @@ -9,40 +10,48 @@ import mindustry.game.EventType.*; import mindustry.gen.*; import mindustry.io.SaveFileReader.*; import mindustry.io.*; +import mindustry.world.meta.*; import java.io.*; import static mindustry.Vars.*; +//TODO bitset + dynamic FoW public class FogControl implements CustomChunk{ private static volatile int ww, wh; - private static final int buildLight = 0; + private static final int staticUpdateInterval = 1000 / 25; //25 FPS + private static final Object notifyStatic = new Object(), notifyDynamic = new Object(); - private final Object sync = new Object(); - /** indexed by [team] [packed array tile pos] */ - private @Nullable boolean[][] fog; + /** indexed by team */ + private volatile @Nullable FogData[] fog; - private final LongSeq events = new LongSeq(); - private @Nullable Thread fogThread; + private final LongSeq staticEvents = new LongSeq(); + private final LongSeq dynamicEventQueue = new LongSeq(), unitEventQueue = new LongSeq(); + /** access must be synchronized; accessed from both threads */ + private final LongSeq dynamicEvents = new LongSeq(100); - private boolean read = false; + private @Nullable Thread staticFogThread; + private @Nullable Thread dynamicFogThread; + + private boolean read = false, justLoaded = false; public FogControl(){ Events.on(ResetEvent.class, e -> { - clear(); + stop(); }); Events.on(WorldLoadEvent.class, e -> { - clear(); + stop(); + justLoaded = true; ww = world.width(); wh = world.height(); - //all old buildings have light around them + //all old buildings have static light scheduled around them if(state.rules.fog && read){ for(var build : Groups.build){ - synchronized(events){ - events.add(FogEvent.get(build.tile.x, build.tile.y, buildLight + build.block.size, build.team.id)); + synchronized(staticEvents){ + staticEvents.add(FogEvent.get(build.tile.x, build.tile.y, build.block.size, build.team.id)); } } @@ -51,10 +60,15 @@ public class FogControl implements CustomChunk{ }); Events.on(TileChangeEvent.class, event -> { - if(state.rules.fog && event.tile.build != null && event.tile.isCenter()){ - synchronized(events){ + if(state.rules.fog && event.tile.build != null && event.tile.isCenter() && !event.tile.build.team.isAI() && event.tile.block().flags.contains(BlockFlag.hasFogRadius)){ + var data = data(event.tile.team()); + if(data != null){ + data.dynamicUpdated = true; + } + + synchronized(staticEvents){ //TODO event per team? - pushEvent(FogEvent.get(event.tile.x, event.tile.y, buildLight + event.tile.block().size, event.tile.build.team.id)); + pushEvent(FogEvent.get(event.tile.x, event.tile.y, event.tile.block().fogRadius, event.tile.build.team.id)); } } }); @@ -62,28 +76,41 @@ public class FogControl implements CustomChunk{ SaveVersion.addCustomChunk("fogdata", this); } - public @Nullable boolean[] getData(Team team){ - return fog == null ? null : fog[team.id]; + public @Nullable Bits getDiscovered(Team team){ + return fog == null || fog[team.id] == null ? null : fog[team.id].staticData; } - public boolean isCovered(Team team, int x, int y){ - var data = getData(team); + public boolean isVisible(Team team, int x, int y){ + if(!state.rules.fog) return true; + + var data = data(team); if(data == null || x < 0 || y < 0 || x >= ww || y >= wh) return false; - return !data[x + y * ww]; + return data.read.get(x + y * ww); } - void clear(){ + @Nullable FogData data(Team team){ + return fog == null || fog[team.id] == null ? null : fog[team.id]; + } + + void stop(){ fog = null; //I don't care whether the fog thread crashes here, it's about to die anyway - events.clear(); - if(fogThread != null){ - fogThread.interrupt(); - fogThread = null; + staticEvents.clear(); + if(staticFogThread != null){ + staticFogThread.interrupt(); + staticFogThread = null; + } + + dynamicEvents.clear(); + if(dynamicFogThread != null){ + dynamicFogThread.interrupt(); + dynamicFogThread = null; + Log.info("end dynamic fog"); } } void pushEvent(long event){ - events.add(event); + staticEvents.add(event); if(!headless && FogEvent.team(event) == Vars.player.team().id){ renderer.fog.handleEvent(event); } @@ -91,122 +118,213 @@ public class FogControl implements CustomChunk{ public void update(){ if(fog == null){ - fog = new boolean[256][]; + fog = new FogData[256]; } - if(fogThread == null && !net.client()){ - fogThread = new FogThread(); - fogThread.setDaemon(true); - fogThread.start(); + //TODO should it be clientside...? + if(staticFogThread == null && !net.client()){ + staticFogThread = new StaticFogThread(); + staticFogThread.setDaemon(true); + staticFogThread.start(); } + if(dynamicFogThread == null){ + dynamicFogThread = new DynamicFogThread(); + dynamicFogThread.setDaemon(true); + dynamicFogThread.start(); + } + + //TODO force update all fog on world load + + //TODO dynamic fog initialization + + //clear to prepare for queuing fog radius from units and buildings + dynamicEventQueue.clear(); + for(var team : state.teams.present){ + //AI teams do not have fog if(!team.team.isAI()){ + //separate for each team + unitEventQueue.clear(); - if(fog[team.team.id] == null){ - fog[team.team.id] = new boolean[world.width() * world.height()]; + FogData data = fog[team.team.id]; + + if(data == null){ + data = fog[team.team.id] = new FogData(); + data.dynamicUpdated = true; } - synchronized(events){ + synchronized(staticEvents){ //TODO slow? for(var unit : team.units){ int tx = unit.tileX(), ty = unit.tileY(), pos = tx + ty * ww; + long event = FogEvent.get(tx, ty, (int)unit.type.fogRadius, team.team.id); + + //always update the dynamic events, but only *flush* the results when necessary? + unitEventQueue.add(event); + if(unit.lastFogPos != pos){ - pushEvent(FogEvent.get(tx, ty, (int)unit.type.fogRadius, team.team.id)); + pushEvent(event); unit.lastFogPos = pos; + data.dynamicUpdated = true; } } } + + //if it's time for an update, flush *everything* onto the update queue + if(data.dynamicUpdated && Time.timeSinceMillis(data.lastDynamicMs) > staticUpdateInterval){ + data.dynamicUpdated = false; + data.lastDynamicMs = Time.millis(); + + //add building updates + for(var build : indexer.getFlagged(team.team, BlockFlag.hasFogRadius)){ + dynamicEventQueue.add(FogEvent.get(build.tileX(), build.tileY(), build.block.fogRadius, 0)); + } + + //add unit updates + dynamicEventQueue.addAll(unitEventQueue); + } + } + } + + if(dynamicEventQueue.size > 0){ + //flush unit events over when something happens + synchronized(dynamicEvents){ + dynamicEvents.clear(); + dynamicEvents.addAll(dynamicEventQueue); + } + dynamicEventQueue.clear(); + + //force update so visibility doesn't have a pop-in + if(justLoaded){ + updateDynamic(new Bits(256)); + justLoaded = false; + } + + //notify that it's time for rendering + //TODO this WILL block until it is done rendering, which is inherently problematic. + synchronized(notifyDynamic){ + notifyDynamic.notify(); } } //wake up, it's time to draw some circles - if(events.size > 0 && fogThread != null){ - synchronized(sync){ - sync.notify(); + if(staticEvents.size > 0 && staticFogThread != null){ + synchronized(notifyStatic){ + notifyStatic.notify(); } } } - public class FogThread extends Thread{ + class StaticFogThread extends Thread{ + + StaticFogThread(){ + super("StaticFogThread"); + } @Override public void run(){ while(true){ try{ - synchronized(sync){ + synchronized(notifyStatic){ try{ //wait until an event happens - sync.wait(); + notifyStatic.wait(); }catch(InterruptedException e){ //end thread return; } } - //I really don't like synchronizing here, but there should be some performance benefit at least - synchronized(events){ - int size = events.size; + //I really don't like synchronizing here, but there should be *some* performance benefit at least + synchronized(staticEvents){ + int size = staticEvents.size; for(int i = 0; i < size; i++){ - long event = events.items[i]; + long event = staticEvents.items[i]; int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event); - var arr = fog[team]; - if(arr != null){ - circle(arr, x, y, rad); + var data = fog[team]; + if(data != null){ + circle(data.staticData, x, y, rad); } } - events.clear(); + staticEvents.clear(); } //ignore, don't want to crash this thread }catch(Exception e){} } } + } - void circle(boolean[] arr, int x, int y, int radius){ - int f = 1 - radius; - int ddFx = 1, ddFy = -2 * radius; - int px = 0, py = radius; + class DynamicFogThread extends Thread{ + final Bits cleared = new Bits(); - hline(arr, x, x, y + radius); - hline(arr, x, x, y - radius); - hline(arr, x - radius, x + radius, y); - - while(px < py){ - if(f >= 0){ - py--; - ddFy += 2; - f += ddFy; - } - px++; - ddFx += 2; - f += ddFx; - hline(arr, x - px, x + px, y + py); - hline(arr, x - px, x + px, y - py); - hline(arr, x - py, x + py, y + px); - hline(arr, x - py, x + py, y - px); - } + DynamicFogThread(){ + super("DynamicFogThread"); } - void hline(boolean[] arr, int x1, int x2, int y){ - if(y < 0 || y >= wh) return; - int tmp; + @Override + public void run(){ - if(x1 > x2){ - tmp = x1; - x1 = x2; - x2 = tmp; + while(true){ + try{ + synchronized(notifyDynamic){ + try{ + //wait until an event happens + notifyDynamic.wait(); + }catch(InterruptedException e){ + //end thread + return; + } + } + + updateDynamic(cleared); + + //ignore, don't want to crash this thread + }catch(Exception e){ + //log for debugging + e.printStackTrace(); + } } + } + } - if(x1 >= ww) return; - if(x2 < 0) return; + void updateDynamic(Bits cleared){ + cleared.clear(); - if(x1 < 0) x1 = 0; - if(x2 >= ww) x2 = ww - 1; - x2++; - int off = y * ww; + //ugly sync + synchronized(dynamicEvents){ + int size = dynamicEvents.size; - while(x1 != x2){ - arr[off + x1++] = true; + //draw step + for(int i = 0; i < size; i++){ + long event = dynamicEvents.items[i]; + int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event); + + var data = fog[team]; + if(data != null){ + + //clear the buffer, since it is being re-drawn + if(!cleared.get(team)){ + cleared.set(team); + + data.write.clear(); + } + + circle(data.write, x, y, rad); + } + } + dynamicEvents.clear(); + } + + //swap step, no need for synchronization or anything + for(int i = 0; i < 256; i++){ + if(cleared.get(i)){ + var data = fog[i]; + + //swap buffers, flushing the data that was just drawn + Bits temp = data.read; + data.read = data.write; + data.write = temp; } } } @@ -225,15 +343,15 @@ public class FogControl implements CustomChunk{ for(int i = 0; i < 256; i++){ if(fog[i] != null){ stream.writeByte(i); - boolean[] data = fog[i]; + Bits data = fog[i].staticData; + int size = ww * wh; - int pos = 0, size = data.length; + int pos = 0; while(pos < size){ int consecutives = 0; - boolean cur = data[pos]; + boolean cur = data.get(pos); while(consecutives < 127 && pos < size){ - boolean next = data[pos]; - if(cur != next){ + if(cur != data.get(pos)){ break; } @@ -249,16 +367,21 @@ public class FogControl implements CustomChunk{ @Override public void read(DataInput stream) throws IOException{ - if(fog == null) fog = new boolean[256][]; + if(fog == null) fog = new FogData[256]; int teams = stream.readUnsignedByte(); int w = stream.readShort(), h = stream.readShort(); int len = w * h; + ww = w; + wh = h; + for(int ti = 0; ti < teams; ti++){ int team = stream.readUnsignedByte(); + fog[team] = new FogData(); + int pos = 0; - boolean[] bools = fog[team] = new boolean[w * h]; + Bits bools = fog[team].staticData; while(pos < len){ int data = stream.readByte() & 0xff; @@ -266,9 +389,9 @@ public class FogControl implements CustomChunk{ int consec = data & 0b0111_1111; if(sign){ - for(int i = 0; i < consec; i++){ - bools[pos ++] = true; - } + //TODO disabled for testing? + //bools.set(pos, pos + consec); + pos += consec; }else{ pos += consec; } @@ -284,6 +407,72 @@ public class FogControl implements CustomChunk{ return state.rules.fog && fog != null; } + static void circle(Bits arr, int x, int y, int radius){ + int f = 1 - radius; + int ddFx = 1, ddFy = -2 * radius; + int px = 0, py = radius; + + hline(arr, x, x, y + radius); + hline(arr, x, x, y - radius); + hline(arr, x - radius, x + radius, y); + + while(px < py){ + if(f >= 0){ + py--; + ddFy += 2; + f += ddFy; + } + px++; + ddFx += 2; + f += ddFx; + hline(arr, x - px, x + px, y + py); + hline(arr, x - px, x + px, y - py); + hline(arr, x - py, x + py, y + px); + hline(arr, x - py, x + py, y - px); + } + } + + static void hline(Bits arr, int x1, int x2, int y){ + if(y < 0 || y >= wh) return; + int tmp; + + if(x1 > x2){ + tmp = x1; + x1 = x2; + x2 = tmp; + } + + if(x1 >= ww) return; + if(x2 < 0) return; + + if(x1 < 0) x1 = 0; + if(x2 >= ww) x2 = ww - 1; + x2++; + int off = y * ww; + + arr.set(off + x1, off + x2); + } + + static class FogData{ + /** dynamic double-buffered data for dynamic (live) coverage */ + volatile Bits read, write; + /** static map exploration fog*/ + final Bits staticData; + + /** last dynamic update timestamp. */ + long lastDynamicMs = 0; + /** if true, a dynamic fog update must be scheduled. */ + boolean dynamicUpdated; + + FogData(){ + int len = ww * wh; + + read = new Bits(len); + write = new Bits(len); + staticData = new Bits(len); + } + } + @Struct class FogEventStruct{ @StructField(16) diff --git a/core/src/mindustry/game/Saves.java b/core/src/mindustry/game/Saves.java index 870d5a9634..dfb704ed3a 100644 --- a/core/src/mindustry/game/Saves.java +++ b/core/src/mindustry/game/Saves.java @@ -6,7 +6,6 @@ import arc.files.*; import arc.graphics.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import mindustry.*; import mindustry.core.GameState.*; import mindustry.game.EventType.*; @@ -18,6 +17,7 @@ import mindustry.type.*; import java.io.*; import java.text.*; import java.util.*; +import java.util.concurrent.*; import static mindustry.Vars.*; @@ -25,7 +25,7 @@ public class Saves{ Seq saves = new Seq<>(); @Nullable SaveSlot current; private @Nullable SaveSlot lastSectorSave; - AsyncExecutor previewExecutor = new AsyncExecutor(1); + ExecutorService previewExecutor = Threads.executor(1); private boolean saving; private float time; diff --git a/core/src/mindustry/graphics/FogRenderer.java b/core/src/mindustry/graphics/FogRenderer.java index d831efb195..9de4b25b44 100644 --- a/core/src/mindustry/graphics/FogRenderer.java +++ b/core/src/mindustry/graphics/FogRenderer.java @@ -17,7 +17,7 @@ import static mindustry.Vars.*; /** Highly experimental fog-of-war renderer. */ public class FogRenderer{ - private FrameBuffer buffer = new FrameBuffer(); + private FrameBuffer staticFog = new FrameBuffer(); private LongSeq events = new LongSeq(); private Rect rect = new Rect(); private @Nullable Team lastTeam; @@ -33,16 +33,16 @@ public class FogRenderer{ events.add(event); } - public Texture getTexture(){ - return buffer.getTexture(); + public Texture getStaticTexture(){ + return staticFog.getTexture(); } public void drawFog(){ //there is no fog. - if(fogControl.getData(player.team()) == null) return; + if(fogControl.getDiscovered(player.team()) == null) return; //resize if world size changes - boolean clear = buffer.resizeCheck(world.width(), world.height()); + boolean clear = staticFog.resizeCheck(world.width(), world.height()); if(player.team() != lastTeam){ copyFromCpu(); @@ -53,16 +53,16 @@ public class FogRenderer{ //grab events if(clear || events.size > 0){ //set projection to whole map - Draw.proj(0, 0, buffer.getWidth(), buffer.getHeight()); + Draw.proj(0, 0, staticFog.getWidth(), staticFog.getHeight()); //if the buffer resized, it contains garbage now, clear it. if(clear){ - buffer.begin(Color.black); + staticFog.begin(Color.black); }else{ - buffer.begin(); + staticFog.begin(); } - ScissorStack.push(rect.set(1, 1, buffer.getWidth() - 2, buffer.getHeight() - 2)); + ScissorStack.push(rect.set(1, 1, staticFog.getWidth() - 2, staticFog.getHeight() - 2)); Draw.color(Color.white); @@ -80,29 +80,30 @@ public class FogRenderer{ events.clear(); - buffer.end(); + staticFog.end(); ScissorStack.pop(); Draw.proj(Core.camera); } - buffer.getTexture().setFilter(TextureFilter.linear); + staticFog.getTexture().setFilter(TextureFilter.linear); Draw.shader(Shaders.fog); - Draw.fbo(buffer.getTexture(), world.width(), world.height(), tilesize); + Draw.fbo(staticFog.getTexture(), world.width(), world.height(), tilesize); Draw.shader(); } public void copyFromCpu(){ - buffer.resize(world.width(), world.height()); - buffer.begin(Color.black); - Draw.proj(0, 0, buffer.getWidth(), buffer.getHeight()); + staticFog.resize(world.width(), world.height()); + staticFog.begin(Color.black); + Draw.proj(0, 0, staticFog.getWidth(), staticFog.getHeight()); Draw.color(); int ww = world.width(), wh = world.height(); - boolean[] data = fogControl.getData(player.team()); + var data = fogControl.getDiscovered(player.team()); + int len = world.width() * world.height(); if(data != null){ - for(int i = 0; i < data.length; i++){ - if(data[i]){ + for(int i = 0; i < len; i++){ + if(data.get(i)){ //TODO slow, could do scanlines instead at the very least. int x = i % ww, y = i / ww; @@ -114,7 +115,7 @@ public class FogRenderer{ } } - buffer.end(); + staticFog.end(); Draw.proj(Core.camera); } diff --git a/core/src/mindustry/graphics/MinimapRenderer.java b/core/src/mindustry/graphics/MinimapRenderer.java index c445842fe6..18e084f76f 100644 --- a/core/src/mindustry/graphics/MinimapRenderer.java +++ b/core/src/mindustry/graphics/MinimapRenderer.java @@ -144,9 +144,9 @@ public class MinimapRenderer{ zoom = z; } Draw.shader(Shaders.fog); - renderer.fog.getTexture().setFilter(TextureFilter.nearest); + renderer.fog.getStaticTexture().setFilter(TextureFilter.nearest); //crisp pixels - Tmp.tr1.set(renderer.fog.getTexture()); + Tmp.tr1.set(renderer.fog.getStaticTexture()); Tmp.tr1.set(region.u, 1f - region.v, region.u2, 1f - region.v2); Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h); Draw.shader(); diff --git a/core/src/mindustry/maps/Maps.java b/core/src/mindustry/maps/Maps.java index ba738767c6..27028e98a9 100644 --- a/core/src/mindustry/maps/Maps.java +++ b/core/src/mindustry/maps/Maps.java @@ -9,7 +9,6 @@ import arc.graphics.*; import arc.struct.IntSet.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import arc.util.io.*; import arc.util.serialization.*; import mindustry.*; diff --git a/core/src/mindustry/mod/Mods.java b/core/src/mindustry/mod/Mods.java index d496a9f998..8c24c4d0ff 100644 --- a/core/src/mindustry/mod/Mods.java +++ b/core/src/mindustry/mod/Mods.java @@ -11,7 +11,6 @@ import arc.graphics.g2d.TextureAtlas.*; import arc.scene.ui.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import arc.util.io.*; import arc.util.serialization.*; import arc.util.serialization.Jval.*; @@ -27,13 +26,14 @@ import mindustry.ui.*; import java.io.*; import java.util.*; +import java.util.concurrent.*; import static mindustry.Vars.*; public class Mods implements Loadable{ private static final String[] metaFiles = {"mod.json", "mod.hjson", "plugin.json", "plugin.hjson"}; - private AsyncExecutor async = new AsyncExecutor(); + private ExecutorService async = Threads.executor(); private Json json = new Json(); private @Nullable Scripts scripts; private ContentParser parser = new ContentParser(); @@ -128,7 +128,7 @@ public class Mods implements Loadable{ packer = new MultiPacker(); //all packing tasks to await - var tasks = new Seq>(); + var tasks = new Seq>(); eachEnabled(mod -> { Seq sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png")); @@ -179,7 +179,7 @@ public class Mods implements Loadable{ } } - private void packSprites(Seq sprites, LoadedMod mod, boolean prefix, Seq> tasks){ + private void packSprites(Seq sprites, LoadedMod mod, boolean prefix, Seq> tasks){ boolean linear = Core.settings.getBool("linear", true); for(Fi file : sprites){ diff --git a/core/src/mindustry/net/ArcNetProvider.java b/core/src/mindustry/net/ArcNetProvider.java index 907ec63920..f20f190c34 100644 --- a/core/src/mindustry/net/ArcNetProvider.java +++ b/core/src/mindustry/net/ArcNetProvider.java @@ -8,7 +8,6 @@ import arc.net.FrameworkMessage.*; import arc.struct.*; import arc.util.*; import arc.util.Log.*; -import arc.util.async.*; import arc.util.io.*; import mindustry.net.Net.*; import mindustry.net.Packets.*; @@ -25,7 +24,7 @@ import static mindustry.Vars.*; public class ArcNetProvider implements NetProvider{ final Client client; final Prov packetSupplier = () -> new DatagramPacket(new byte[512], 512); - final AsyncExecutor executor = new AsyncExecutor(Math.max(Runtime.getRuntime().availableProcessors(), 6)); + final ExecutorService executor = Threads.executor(Math.max(Runtime.getRuntime().availableProcessors(), 6)); final Server server; final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); diff --git a/core/src/mindustry/net/BeControl.java b/core/src/mindustry/net/BeControl.java index 3f4a7faa76..817f2d6bcb 100644 --- a/core/src/mindustry/net/BeControl.java +++ b/core/src/mindustry/net/BeControl.java @@ -4,7 +4,6 @@ import arc.*; import arc.files.*; import arc.func.*; import arc.util.*; -import arc.util.async.*; import arc.util.serialization.*; import mindustry.*; import mindustry.core.*; @@ -18,6 +17,7 @@ import mindustry.ui.dialogs.*; import java.io.*; import java.net.*; +import java.util.concurrent.*; import static mindustry.Vars.*; @@ -25,7 +25,7 @@ import static mindustry.Vars.*; public class BeControl{ private static final int updateInterval = 60; - private AsyncExecutor executor = new AsyncExecutor(1); + private ExecutorService executor = Threads.executor(1); private boolean checkUpdates = true; private boolean updateAvailable; private String updateUrl; diff --git a/core/src/mindustry/net/Net.java b/core/src/mindustry/net/Net.java index 2463c8fed9..d4dbf72b0b 100644 --- a/core/src/mindustry/net/Net.java +++ b/core/src/mindustry/net/Net.java @@ -5,7 +5,6 @@ import arc.func.*; import arc.net.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import mindustry.gen.*; import mindustry.net.Packets.*; import mindustry.net.Streamable.*; diff --git a/core/src/mindustry/ui/dialogs/PlanetDialog.java b/core/src/mindustry/ui/dialogs/PlanetDialog.java index 0e93173719..83124bbbde 100644 --- a/core/src/mindustry/ui/dialogs/PlanetDialog.java +++ b/core/src/mindustry/ui/dialogs/PlanetDialog.java @@ -562,6 +562,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{ selected = null; launchSector = null; if(state.planet != planet){ + newPresets.clear(); state.planet = planet; rebuildList(); } diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index df7b0cc865..65ae60bcfe 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -249,6 +249,9 @@ public class Block extends UnlockableContent implements Senseable{ /** Radius of the light emitted by this block. */ public float lightRadius = 60f; + /** How much fog this block uncovers, in tiles. Cannot be dynamic. <= 0 to disable. */ + public int fogRadius = -1; + /** The sound that this block makes while active. One sound loop. Do not overuse. */ public Sound loopSound = Sounds.none; /** Active sound base volume. */ @@ -1038,6 +1041,10 @@ public class Block extends UnlockableContent implements Senseable{ hasShadow = false; } + if(fogRadius > 0){ + flags = flags.with(BlockFlag.hasFogRadius); + } + //initialize default health based on size if(health == -1){ boolean round = false; diff --git a/core/src/mindustry/world/meta/BlockFlag.java b/core/src/mindustry/world/meta/BlockFlag.java index 628c29fe70..762fb016c9 100644 --- a/core/src/mindustry/world/meta/BlockFlag.java +++ b/core/src/mindustry/world/meta/BlockFlag.java @@ -23,10 +23,11 @@ public enum BlockFlag{ /** Blocks that extinguishes fires. */ extinguisher, - //single-block identifiers + //special, internal identifiers launchPad, unitCargoUnloadPoint, - unitAssembler; + unitAssembler, + hasFogRadius; public final static BlockFlag[] all = values(); diff --git a/gradle.properties b/gradle.properties index d53b87638a..f322efd21b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,4 +24,4 @@ android.useAndroidX=true #used for slow jitpack builds; TODO see if this actually works org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 -archash=6418606527 +archash=54bf3f5289 diff --git a/tools/src/mindustry/tools/Generators.java b/tools/src/mindustry/tools/Generators.java index f1da161fa8..61078c2aaf 100644 --- a/tools/src/mindustry/tools/Generators.java +++ b/tools/src/mindustry/tools/Generators.java @@ -9,7 +9,6 @@ import arc.math.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import arc.util.noise.*; import mindustry.ctype.*; import mindustry.game.*;