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 zcmey){+D}#ayg}5|4UgW{-+RmXlizQWy88L> zrT=H@i7zjx+qZ`ANzS3GIa~}6)NUwU%<8h=!D;4UI;(B}4$jhyEi;eF@8GoBGAl=t z;endR%8N>w3=OjnEDdX3!^n{5#MxatpMl}dj>>KxliBZ^{I8j96!`z{-rJK=CgzR( zQ-3i0PiJ_fHf5uTyn+$K0pI!s8^q)l5*Zq1PgvCYgJEXi1^NB|y}FJ|%C7a3uQ6Gy z++BN8cEXYiw|ss)oPD&Nt*qaDF6Y7@{_$r*4OiS-pwV}wCE!n~vXk(klJ##-WhE`` zt69mp)GSi^h^eCLzg>#UI9Kiq%XaeBS#|s2tOJ?db2ydf6dx*&pEuFa;%~+~m-^+F zjMt86fmZ>K>*iEEl(*i-94&=C5ST&sV~`4*IASv^;zl;;VC#(|kJTq@L7N zhDoPbMC2JBT)m(F@ayUCDLq&Cz6JVwZPk>0yTr)xR%{m!!wnNf5&4O;E~Fdk=R{jy z;qkpNXZ023p3RSIi<&L(Ufi2L`B97Bkxkt7PUp@~|M0@^ui*mOyl!KLo!=f9-b~OG zerLP;M^&cNb)g*T?XKQuW88BY8t%1JwEs3x%nQzTdh2pIWt!H@z7?`dcPVH7_bPRs z@^I3-6G_wZ!_*$k_;7jte6hz%d^XJKDBRSW+!We*bc>(!%z{tPxUBs$no>2LE?o^& zi@3J^40nC1rm)H*Wov8W&6B=Op8Q<cE|p!5+-2F;lUVjm|?;%t^; zRkRd7re-(E_+fH%#T$$IzQ-02bob#>jwU@X!ML8&2CI_hOke!9q|USGg_=kHOsTu7 zo+}e$FCG7zHCNhT_WM=WGqyc>@WEqi(=M_2X-vxT)0%!9(vjU~GBF{S^;B zKjyBw<+EXqVRrIS_quL&LB3CJZ=XIY`CFX(RzoD-NaMvTx7^J<8n&w%W^*q5!B9F= zpx(T1?&IV6_b1kGKhxqTcTLbKl}UAiiLl1w6+Q=;mp=QLEwqZ+@NxvI#6q zTEd5z&pz^1dZpK>Wa6mx?*fOfM{4C-&YSjE!j$FX_FGkboCwNhH)4~WJ~?*>eYg;Q zz%%n}(&DxJF@J1yeQx|XdQ?9-_s}nP@%jBrQ#pnG9QdZY)*Lz2(%HTK_@S0p-jk+> z)-cRA=L?f8n0eGa%Qry!!K3;G$5^H^>isi|5?&RT{4{5g;lf|8dFRxEgm19z_)(*x jHe=73%QxGy57i%kc-c6*^n3&Z0|SGntDnm{r-UW|9az$< delta 994 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 delta 1347 zcmdnN^O$RbVZE%Ui(^Q|oVRl~=7|^zw9OUOcVJu;QF1o6ILK?liv_G!a!i^N1GKWT zw4^3faIq|E=j;5oBs*`mN4P=-;|iz7Zw6_({cr1!Jx;OL3@VDOPJ6!pW7YLrMDl_WA%e}HMcWjRjJ2UUWi?+MRa<)6?+Aj)do?n%dYn5Ezb5YO9&mmfHp>+L4 zy+f%>H~*jEI^p@O-v>9`mPqbdS(#Y2pT+)H{Ddl#GP7MP`LolQmdEzqwAE}dEOnW% zVYzS1|5*yNBZ{}&qVt8SGH6dCmfm1`QkzUwjB>Y>{MC3 zh{;FtA;-SlZf?K%#jYG?j`iy%uDowq@cMt<->@{>BJ<>(d)eKscVptqE^HD*{0nBZCXxSIJuTy`eDD_^49zFi`!y<)lH09 z#=9XF>xut-yYPx?BERH% zzQCE$E4h_4G)oI^IUisBYlcC1WM=iwkh!7qir7OHFo972j zZ?R3;^FTT2+vwbBO1&g7K~nIt#X+vb{>K~HY?!xv-|a0B zy~6gZn#h%&j(zbm^>te&JQ8+oneu}*{+76Qjx%rFi}z9mx-v_S>EB=pj5AC0iTjW| z-}}Jcf`BKfvZ@D{|C`VJvbEW6R@9Pqr^)_jbuVeV&h~$$mX`$>i4NV9_N+J%(t}ptD0=};5Rk@5~O=+vBF*jKEB_E1e zE$HncAS*1kIpv4gk*QPG?kx!TGq+GvyMHa#zO(FH zr|J=1W;uo9V)lVDYn6N-Wyv!!0`+#kzWGZmsw{9q#L>G{?r{()Hu6!RivQqJ9VFp;-^?bnItYht2-pJ+fMKZdy2*u zn;r8Dc;btf=rQXV2hTL%R_M`o5@u5PKIuw>u9Gm6MDtDWAmPOv`x#sI+1%9;U-5x~ Pfq}u()z4*}Q$iB}?0IqB diff --git a/core/src/mindustry/ClientLauncher.java b/core/src/mindustry/ClientLauncher.java index 23daaf01eb..9a14807ee4 100644 --- a/core/src/mindustry/ClientLauncher.java +++ b/core/src/mindustry/ClientLauncher.java @@ -8,7 +8,6 @@ import arc.graphics.*; import arc.graphics.g2d.*; import arc.math.*; import arc.util.*; -import arc.util.async.*; import mindustry.ai.*; import mindustry.core.*; import mindustry.ctype.*; @@ -18,7 +17,7 @@ import mindustry.gen.*; import mindustry.graphics.*; import mindustry.maps.*; import mindustry.mod.*; -import mindustry.net.Net; +import mindustry.net.*; import mindustry.ui.*; import static arc.Core.*; diff --git a/core/src/mindustry/ai/Pathfinder.java b/core/src/mindustry/ai/Pathfinder.java index 378a052ccd..8e78ccb284 100644 --- a/core/src/mindustry/ai/Pathfinder.java +++ b/core/src/mindustry/ai/Pathfinder.java @@ -5,7 +5,6 @@ import arc.func.*; import arc.math.geom.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import mindustry.annotations.Annotations.*; import mindustry.content.*; import mindustry.core.*; diff --git a/core/src/mindustry/async/AsyncCore.java b/core/src/mindustry/async/AsyncCore.java index df07bda1ac..9987bc31bd 100644 --- a/core/src/mindustry/async/AsyncCore.java +++ b/core/src/mindustry/async/AsyncCore.java @@ -2,7 +2,7 @@ package mindustry.async; import arc.*; import arc.struct.*; -import arc.util.async.*; +import arc.util.*; import mindustry.game.EventType.*; import java.util.concurrent.*; diff --git a/core/src/mindustry/content/SectorPresets.java b/core/src/mindustry/content/SectorPresets.java index 71647cab35..6da5003b1c 100644 --- a/core/src/mindustry/content/SectorPresets.java +++ b/core/src/mindustry/content/SectorPresets.java @@ -110,6 +110,7 @@ public class SectorPresets{ onset = new SectorPreset("onset", erekir, 10){{ addStartingItems = true; + alwaysUnlocked = true; captureWave = 3; difficulty = 1; }}; diff --git a/core/src/mindustry/core/Renderer.java b/core/src/mindustry/core/Renderer.java index b613ca78fc..e035c57af6 100644 --- a/core/src/mindustry/core/Renderer.java +++ b/core/src/mindustry/core/Renderer.java @@ -12,7 +12,6 @@ import arc.math.geom.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import mindustry.*; import mindustry.content.*; import mindustry.game.EventType.*; diff --git a/core/src/mindustry/editor/MapGenerateDialog.java b/core/src/mindustry/editor/MapGenerateDialog.java index c1a25b4e6d..4c90d18f84 100644 --- a/core/src/mindustry/editor/MapGenerateDialog.java +++ b/core/src/mindustry/editor/MapGenerateDialog.java @@ -10,7 +10,6 @@ import arc.scene.ui.ImageButton.*; import arc.scene.ui.layout.*; import arc.struct.*; import arc.util.*; -import arc.util.async.*; import mindustry.game.*; import mindustry.gen.*; import mindustry.graphics.*; @@ -23,6 +22,8 @@ import mindustry.ui.dialogs.*; import mindustry.world.*; import mindustry.world.blocks.environment.*; +import java.util.concurrent.*; + import static mindustry.Vars.*; @SuppressWarnings("unchecked") @@ -36,8 +37,8 @@ public class MapGenerateDialog extends BaseDialog{ int scaling = mobile ? 3 : 1; Table filterTable; - AsyncExecutor executor = new AsyncExecutor(1); - AsyncResult 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.*;