WIP dynamic fog + bugfixes + cleanup

This commit is contained in:
Anuken
2022-02-19 14:53:06 -05:00
parent 63eeaae22d
commit eaf96fcc86
22 changed files with 339 additions and 142 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -8,7 +8,6 @@ import arc.graphics.*;
import arc.graphics.g2d.*; import arc.graphics.g2d.*;
import arc.math.*; import arc.math.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.ai.*; import mindustry.ai.*;
import mindustry.core.*; import mindustry.core.*;
import mindustry.ctype.*; import mindustry.ctype.*;
@@ -18,7 +17,7 @@ import mindustry.gen.*;
import mindustry.graphics.*; import mindustry.graphics.*;
import mindustry.maps.*; import mindustry.maps.*;
import mindustry.mod.*; import mindustry.mod.*;
import mindustry.net.Net; import mindustry.net.*;
import mindustry.ui.*; import mindustry.ui.*;
import static arc.Core.*; import static arc.Core.*;

View File

@@ -5,7 +5,6 @@ import arc.func.*;
import arc.math.geom.*; import arc.math.geom.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.annotations.Annotations.*; import mindustry.annotations.Annotations.*;
import mindustry.content.*; import mindustry.content.*;
import mindustry.core.*; import mindustry.core.*;

View File

@@ -2,7 +2,7 @@ package mindustry.async;
import arc.*; import arc.*;
import arc.struct.*; import arc.struct.*;
import arc.util.async.*; import arc.util.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;
import java.util.concurrent.*; import java.util.concurrent.*;

View File

@@ -110,6 +110,7 @@ public class SectorPresets{
onset = new SectorPreset("onset", erekir, 10){{ onset = new SectorPreset("onset", erekir, 10){{
addStartingItems = true; addStartingItems = true;
alwaysUnlocked = true;
captureWave = 3; captureWave = 3;
difficulty = 1; difficulty = 1;
}}; }};

View File

@@ -12,7 +12,6 @@ import arc.math.geom.*;
import arc.scene.ui.layout.*; import arc.scene.ui.layout.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.*; import mindustry.*;
import mindustry.content.*; import mindustry.content.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;

View File

@@ -10,7 +10,6 @@ import arc.scene.ui.ImageButton.*;
import arc.scene.ui.layout.*; import arc.scene.ui.layout.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.game.*; import mindustry.game.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.graphics.*; import mindustry.graphics.*;
@@ -23,6 +22,8 @@ import mindustry.ui.dialogs.*;
import mindustry.world.*; import mindustry.world.*;
import mindustry.world.blocks.environment.*; import mindustry.world.blocks.environment.*;
import java.util.concurrent.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -36,8 +37,8 @@ public class MapGenerateDialog extends BaseDialog{
int scaling = mobile ? 3 : 1; int scaling = mobile ? 3 : 1;
Table filterTable; Table filterTable;
AsyncExecutor executor = new AsyncExecutor(1); ExecutorService executor = Threads.executor(1);
AsyncResult<Void> result; Future<?> result;
boolean generating; boolean generating;
long[] buffer1, buffer2; long[] buffer1, buffer2;
@@ -369,7 +370,10 @@ public class MapGenerateDialog extends BaseDialog{
void apply(){ void apply(){
if(result != null){ if(result != null){
//ignore errors yay
try{
result.get(); result.get();
}catch(Exception e){}
} }
buffer1 = null; buffer1 = null;

View File

@@ -1,6 +1,7 @@
package mindustry.game; package mindustry.game;
import arc.*; import arc.*;
import arc.struct.Bits;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import mindustry.*; import mindustry.*;
@@ -9,40 +10,48 @@ import mindustry.game.EventType.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.io.SaveFileReader.*; import mindustry.io.SaveFileReader.*;
import mindustry.io.*; import mindustry.io.*;
import mindustry.world.meta.*;
import java.io.*; import java.io.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
//TODO bitset + dynamic FoW
public class FogControl implements CustomChunk{ public class FogControl implements CustomChunk{
private static volatile int ww, wh; 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 */
/** indexed by [team] [packed array tile pos] */ private volatile @Nullable FogData[] fog;
private @Nullable boolean[][] fog;
private final LongSeq events = new LongSeq(); private final LongSeq staticEvents = new LongSeq();
private @Nullable Thread fogThread; 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(){ public FogControl(){
Events.on(ResetEvent.class, e -> { Events.on(ResetEvent.class, e -> {
clear(); stop();
}); });
Events.on(WorldLoadEvent.class, e -> { Events.on(WorldLoadEvent.class, e -> {
clear(); stop();
justLoaded = true;
ww = world.width(); ww = world.width();
wh = world.height(); wh = world.height();
//all old buildings have light around them //all old buildings have static light scheduled around them
if(state.rules.fog && read){ if(state.rules.fog && read){
for(var build : Groups.build){ for(var build : Groups.build){
synchronized(events){ synchronized(staticEvents){
events.add(FogEvent.get(build.tile.x, build.tile.y, buildLight + build.block.size, build.team.id)); 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 -> { Events.on(TileChangeEvent.class, event -> {
if(state.rules.fog && event.tile.build != null && event.tile.isCenter()){ if(state.rules.fog && event.tile.build != null && event.tile.isCenter() && !event.tile.build.team.isAI() && event.tile.block().flags.contains(BlockFlag.hasFogRadius)){
synchronized(events){ var data = data(event.tile.team());
if(data != null){
data.dynamicUpdated = true;
}
synchronized(staticEvents){
//TODO event per team? //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); SaveVersion.addCustomChunk("fogdata", this);
} }
public @Nullable boolean[] getData(Team team){ public @Nullable Bits getDiscovered(Team team){
return fog == null ? null : fog[team.id]; return fog == null || fog[team.id] == null ? null : fog[team.id].staticData;
} }
public boolean isCovered(Team team, int x, int y){ public boolean isVisible(Team team, int x, int y){
var data = getData(team); if(!state.rules.fog) return true;
var data = data(team);
if(data == null || x < 0 || y < 0 || x >= ww || y >= wh) return false; 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; fog = null;
//I don't care whether the fog thread crashes here, it's about to die anyway //I don't care whether the fog thread crashes here, it's about to die anyway
events.clear(); staticEvents.clear();
if(fogThread != null){ if(staticFogThread != null){
fogThread.interrupt(); staticFogThread.interrupt();
fogThread = null; staticFogThread = null;
}
dynamicEvents.clear();
if(dynamicFogThread != null){
dynamicFogThread.interrupt();
dynamicFogThread = null;
Log.info("end dynamic fog");
} }
} }
void pushEvent(long event){ void pushEvent(long event){
events.add(event); staticEvents.add(event);
if(!headless && FogEvent.team(event) == Vars.player.team().id){ if(!headless && FogEvent.team(event) == Vars.player.team().id){
renderer.fog.handleEvent(event); renderer.fog.handleEvent(event);
} }
@@ -91,78 +118,296 @@ public class FogControl implements CustomChunk{
public void update(){ public void update(){
if(fog == null){ if(fog == null){
fog = new boolean[256][]; fog = new FogData[256];
} }
if(fogThread == null && !net.client()){ //TODO should it be clientside...?
fogThread = new FogThread(); if(staticFogThread == null && !net.client()){
fogThread.setDaemon(true); staticFogThread = new StaticFogThread();
fogThread.start(); 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){ for(var team : state.teams.present){
//AI teams do not have fog
if(!team.team.isAI()){ if(!team.team.isAI()){
//separate for each team
unitEventQueue.clear();
if(fog[team.team.id] == null){ FogData data = fog[team.team.id];
fog[team.team.id] = new boolean[world.width() * world.height()];
if(data == null){
data = fog[team.team.id] = new FogData();
data.dynamicUpdated = true;
} }
synchronized(events){ synchronized(staticEvents){
//TODO slow? //TODO slow?
for(var unit : team.units){ for(var unit : team.units){
int tx = unit.tileX(), ty = unit.tileY(), pos = tx + ty * ww; 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){ if(unit.lastFogPos != pos){
pushEvent(FogEvent.get(tx, ty, (int)unit.type.fogRadius, team.team.id)); pushEvent(event);
unit.lastFogPos = pos; 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 //wake up, it's time to draw some circles
if(events.size > 0 && fogThread != null){ if(staticEvents.size > 0 && staticFogThread != null){
synchronized(sync){ synchronized(notifyStatic){
sync.notify(); notifyStatic.notify();
} }
} }
} }
public class FogThread extends Thread{ class StaticFogThread extends Thread{
StaticFogThread(){
super("StaticFogThread");
}
@Override @Override
public void run(){ public void run(){
while(true){ while(true){
try{ try{
synchronized(sync){ synchronized(notifyStatic){
try{ try{
//wait until an event happens //wait until an event happens
sync.wait(); notifyStatic.wait();
}catch(InterruptedException e){ }catch(InterruptedException e){
//end thread //end thread
return; return;
} }
} }
//I really don't like synchronizing here, but there should be some performance benefit at least //I really don't like synchronizing here, but there should be *some* performance benefit at least
synchronized(events){ synchronized(staticEvents){
int size = events.size; int size = staticEvents.size;
for(int i = 0; i < size; i++){ 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); int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event);
var arr = fog[team]; var data = fog[team];
if(arr != null){ if(data != null){
circle(arr, x, y, rad); circle(data.staticData, x, y, rad);
} }
} }
events.clear(); staticEvents.clear();
} }
//ignore, don't want to crash this thread //ignore, don't want to crash this thread
}catch(Exception e){} }catch(Exception e){}
} }
} }
}
void circle(boolean[] arr, int x, int y, int radius){ class DynamicFogThread extends Thread{
final Bits cleared = new Bits();
DynamicFogThread(){
super("DynamicFogThread");
}
@Override
public void run(){
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();
}
}
}
}
void updateDynamic(Bits cleared){
cleared.clear();
//ugly sync
synchronized(dynamicEvents){
int size = dynamicEvents.size;
//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;
}
}
}
@Override
public void write(DataOutput stream) throws IOException{
int used = 0;
for(int i = 0; i < 256; i++){
if(fog[i] != null) used ++;
}
stream.writeByte(used);
stream.writeShort(world.width());
stream.writeShort(world.height());
for(int i = 0; i < 256; i++){
if(fog[i] != null){
stream.writeByte(i);
Bits data = fog[i].staticData;
int size = ww * wh;
int pos = 0;
while(pos < size){
int consecutives = 0;
boolean cur = data.get(pos);
while(consecutives < 127 && pos < size){
if(cur != data.get(pos)){
break;
}
consecutives ++;
pos ++;
}
int mask = (cur ? 0b1000_0000 : 0);
stream.write(mask | (consecutives));
}
}
}
}
@Override
public void read(DataInput stream) throws IOException{
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;
Bits bools = fog[team].staticData;
while(pos < len){
int data = stream.readByte() & 0xff;
boolean sign = (data & 0b1000_0000) != 0;
int consec = data & 0b0111_1111;
if(sign){
//TODO disabled for testing?
//bools.set(pos, pos + consec);
pos += consec;
}else{
pos += consec;
}
}
}
read = true;
}
@Override
public boolean shouldWrite(){
return state.rules.fog && fog != null;
}
static void circle(Bits arr, int x, int y, int radius){
int f = 1 - radius; int f = 1 - radius;
int ddFx = 1, ddFy = -2 * radius; int ddFx = 1, ddFy = -2 * radius;
int px = 0, py = radius; int px = 0, py = radius;
@@ -187,7 +432,7 @@ public class FogControl implements CustomChunk{
} }
} }
void hline(boolean[] arr, int x1, int x2, int y){ static void hline(Bits arr, int x1, int x2, int y){
if(y < 0 || y >= wh) return; if(y < 0 || y >= wh) return;
int tmp; int tmp;
@@ -205,83 +450,27 @@ public class FogControl implements CustomChunk{
x2++; x2++;
int off = y * ww; int off = y * ww;
while(x1 != x2){ arr.set(off + x1, off + x2);
arr[off + x1++] = true;
}
}
} }
@Override static class FogData{
public void write(DataOutput stream) throws IOException{ /** dynamic double-buffered data for dynamic (live) coverage */
int used = 0; volatile Bits read, write;
for(int i = 0; i < 256; i++){ /** static map exploration fog*/
if(fog[i] != null) used ++; final Bits staticData;
}
stream.writeByte(used); /** last dynamic update timestamp. */
stream.writeShort(world.width()); long lastDynamicMs = 0;
stream.writeShort(world.height()); /** if true, a dynamic fog update must be scheduled. */
boolean dynamicUpdated;
for(int i = 0; i < 256; i++){ FogData(){
if(fog[i] != null){ int len = ww * wh;
stream.writeByte(i);
boolean[] data = fog[i];
int pos = 0, size = data.length; read = new Bits(len);
while(pos < size){ write = new Bits(len);
int consecutives = 0; staticData = new Bits(len);
boolean cur = data[pos];
while(consecutives < 127 && pos < size){
boolean next = data[pos];
if(cur != next){
break;
} }
consecutives ++;
pos ++;
}
int mask = (cur ? 0b1000_0000 : 0);
stream.write(mask | (consecutives));
}
}
}
}
@Override
public void read(DataInput stream) throws IOException{
if(fog == null) fog = new boolean[256][];
int teams = stream.readUnsignedByte();
int w = stream.readShort(), h = stream.readShort();
int len = w * h;
for(int ti = 0; ti < teams; ti++){
int team = stream.readUnsignedByte();
int pos = 0;
boolean[] bools = fog[team] = new boolean[w * h];
while(pos < len){
int data = stream.readByte() & 0xff;
boolean sign = (data & 0b1000_0000) != 0;
int consec = data & 0b0111_1111;
if(sign){
for(int i = 0; i < consec; i++){
bools[pos ++] = true;
}
}else{
pos += consec;
}
}
}
read = true;
}
@Override
public boolean shouldWrite(){
return state.rules.fog && fog != null;
} }
@Struct @Struct

View File

@@ -6,7 +6,6 @@ import arc.files.*;
import arc.graphics.*; import arc.graphics.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.*; import mindustry.*;
import mindustry.core.GameState.*; import mindustry.core.GameState.*;
import mindustry.game.EventType.*; import mindustry.game.EventType.*;
@@ -18,6 +17,7 @@ import mindustry.type.*;
import java.io.*; import java.io.*;
import java.text.*; import java.text.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
@@ -25,7 +25,7 @@ public class Saves{
Seq<SaveSlot> saves = new Seq<>(); Seq<SaveSlot> saves = new Seq<>();
@Nullable SaveSlot current; @Nullable SaveSlot current;
private @Nullable SaveSlot lastSectorSave; private @Nullable SaveSlot lastSectorSave;
AsyncExecutor previewExecutor = new AsyncExecutor(1); ExecutorService previewExecutor = Threads.executor(1);
private boolean saving; private boolean saving;
private float time; private float time;

View File

@@ -17,7 +17,7 @@ import static mindustry.Vars.*;
/** Highly experimental fog-of-war renderer. */ /** Highly experimental fog-of-war renderer. */
public class FogRenderer{ public class FogRenderer{
private FrameBuffer buffer = new FrameBuffer(); private FrameBuffer staticFog = new FrameBuffer();
private LongSeq events = new LongSeq(); private LongSeq events = new LongSeq();
private Rect rect = new Rect(); private Rect rect = new Rect();
private @Nullable Team lastTeam; private @Nullable Team lastTeam;
@@ -33,16 +33,16 @@ public class FogRenderer{
events.add(event); events.add(event);
} }
public Texture getTexture(){ public Texture getStaticTexture(){
return buffer.getTexture(); return staticFog.getTexture();
} }
public void drawFog(){ public void drawFog(){
//there is no fog. //there is no fog.
if(fogControl.getData(player.team()) == null) return; if(fogControl.getDiscovered(player.team()) == null) return;
//resize if world size changes //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){ if(player.team() != lastTeam){
copyFromCpu(); copyFromCpu();
@@ -53,16 +53,16 @@ public class FogRenderer{
//grab events //grab events
if(clear || events.size > 0){ if(clear || events.size > 0){
//set projection to whole map //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 the buffer resized, it contains garbage now, clear it.
if(clear){ if(clear){
buffer.begin(Color.black); staticFog.begin(Color.black);
}else{ }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); Draw.color(Color.white);
@@ -80,29 +80,30 @@ public class FogRenderer{
events.clear(); events.clear();
buffer.end(); staticFog.end();
ScissorStack.pop(); ScissorStack.pop();
Draw.proj(Core.camera); Draw.proj(Core.camera);
} }
buffer.getTexture().setFilter(TextureFilter.linear); staticFog.getTexture().setFilter(TextureFilter.linear);
Draw.shader(Shaders.fog); Draw.shader(Shaders.fog);
Draw.fbo(buffer.getTexture(), world.width(), world.height(), tilesize); Draw.fbo(staticFog.getTexture(), world.width(), world.height(), tilesize);
Draw.shader(); Draw.shader();
} }
public void copyFromCpu(){ public void copyFromCpu(){
buffer.resize(world.width(), world.height()); staticFog.resize(world.width(), world.height());
buffer.begin(Color.black); staticFog.begin(Color.black);
Draw.proj(0, 0, buffer.getWidth(), buffer.getHeight()); Draw.proj(0, 0, staticFog.getWidth(), staticFog.getHeight());
Draw.color(); Draw.color();
int ww = world.width(), wh = world.height(); 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){ if(data != null){
for(int i = 0; i < data.length; i++){ for(int i = 0; i < len; i++){
if(data[i]){ if(data.get(i)){
//TODO slow, could do scanlines instead at the very least. //TODO slow, could do scanlines instead at the very least.
int x = i % ww, y = i / ww; int x = i % ww, y = i / ww;
@@ -114,7 +115,7 @@ public class FogRenderer{
} }
} }
buffer.end(); staticFog.end();
Draw.proj(Core.camera); Draw.proj(Core.camera);
} }

View File

@@ -144,9 +144,9 @@ public class MinimapRenderer{
zoom = z; zoom = z;
} }
Draw.shader(Shaders.fog); Draw.shader(Shaders.fog);
renderer.fog.getTexture().setFilter(TextureFilter.nearest); renderer.fog.getStaticTexture().setFilter(TextureFilter.nearest);
//crisp pixels //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); 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.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
Draw.shader(); Draw.shader();

View File

@@ -9,7 +9,6 @@ import arc.graphics.*;
import arc.struct.IntSet.*; import arc.struct.IntSet.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import arc.util.io.*; import arc.util.io.*;
import arc.util.serialization.*; import arc.util.serialization.*;
import mindustry.*; import mindustry.*;

View File

@@ -11,7 +11,6 @@ import arc.graphics.g2d.TextureAtlas.*;
import arc.scene.ui.*; import arc.scene.ui.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import arc.util.io.*; import arc.util.io.*;
import arc.util.serialization.*; import arc.util.serialization.*;
import arc.util.serialization.Jval.*; import arc.util.serialization.Jval.*;
@@ -27,13 +26,14 @@ import mindustry.ui.*;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
public class Mods implements Loadable{ public class Mods implements Loadable{
private static final String[] metaFiles = {"mod.json", "mod.hjson", "plugin.json", "plugin.hjson"}; 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 Json json = new Json();
private @Nullable Scripts scripts; private @Nullable Scripts scripts;
private ContentParser parser = new ContentParser(); private ContentParser parser = new ContentParser();
@@ -128,7 +128,7 @@ public class Mods implements Loadable{
packer = new MultiPacker(); packer = new MultiPacker();
//all packing tasks to await //all packing tasks to await
var tasks = new Seq<AsyncResult<Runnable>>(); var tasks = new Seq<Future<Runnable>>();
eachEnabled(mod -> { eachEnabled(mod -> {
Seq<Fi> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png")); Seq<Fi> sprites = mod.root.child("sprites").findAll(f -> f.extension().equals("png"));
@@ -179,7 +179,7 @@ public class Mods implements Loadable{
} }
} }
private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<AsyncResult<Runnable>> tasks){ private void packSprites(Seq<Fi> sprites, LoadedMod mod, boolean prefix, Seq<Future<Runnable>> tasks){
boolean linear = Core.settings.getBool("linear", true); boolean linear = Core.settings.getBool("linear", true);
for(Fi file : sprites){ for(Fi file : sprites){

View File

@@ -8,7 +8,6 @@ import arc.net.FrameworkMessage.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.Log.*; import arc.util.Log.*;
import arc.util.async.*;
import arc.util.io.*; import arc.util.io.*;
import mindustry.net.Net.*; import mindustry.net.Net.*;
import mindustry.net.Packets.*; import mindustry.net.Packets.*;
@@ -25,7 +24,7 @@ import static mindustry.Vars.*;
public class ArcNetProvider implements NetProvider{ public class ArcNetProvider implements NetProvider{
final Client client; final Client client;
final Prov<DatagramPacket> packetSupplier = () -> new DatagramPacket(new byte[512], 512); final Prov<DatagramPacket> 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 Server server;
final CopyOnWriteArrayList<ArcConnection> connections = new CopyOnWriteArrayList<>(); final CopyOnWriteArrayList<ArcConnection> connections = new CopyOnWriteArrayList<>();

View File

@@ -4,7 +4,6 @@ import arc.*;
import arc.files.*; import arc.files.*;
import arc.func.*; import arc.func.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import arc.util.serialization.*; import arc.util.serialization.*;
import mindustry.*; import mindustry.*;
import mindustry.core.*; import mindustry.core.*;
@@ -18,6 +17,7 @@ import mindustry.ui.dialogs.*;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
import java.util.concurrent.*;
import static mindustry.Vars.*; import static mindustry.Vars.*;
@@ -25,7 +25,7 @@ import static mindustry.Vars.*;
public class BeControl{ public class BeControl{
private static final int updateInterval = 60; private static final int updateInterval = 60;
private AsyncExecutor executor = new AsyncExecutor(1); private ExecutorService executor = Threads.executor(1);
private boolean checkUpdates = true; private boolean checkUpdates = true;
private boolean updateAvailable; private boolean updateAvailable;
private String updateUrl; private String updateUrl;

View File

@@ -5,7 +5,6 @@ import arc.func.*;
import arc.net.*; import arc.net.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import mindustry.gen.*; import mindustry.gen.*;
import mindustry.net.Packets.*; import mindustry.net.Packets.*;
import mindustry.net.Streamable.*; import mindustry.net.Streamable.*;

View File

@@ -562,6 +562,7 @@ public class PlanetDialog extends BaseDialog implements PlanetInterfaceRenderer{
selected = null; selected = null;
launchSector = null; launchSector = null;
if(state.planet != planet){ if(state.planet != planet){
newPresets.clear();
state.planet = planet; state.planet = planet;
rebuildList(); rebuildList();
} }

View File

@@ -249,6 +249,9 @@ public class Block extends UnlockableContent implements Senseable{
/** Radius of the light emitted by this block. */ /** Radius of the light emitted by this block. */
public float lightRadius = 60f; 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. */ /** The sound that this block makes while active. One sound loop. Do not overuse. */
public Sound loopSound = Sounds.none; public Sound loopSound = Sounds.none;
/** Active sound base volume. */ /** Active sound base volume. */
@@ -1038,6 +1041,10 @@ public class Block extends UnlockableContent implements Senseable{
hasShadow = false; hasShadow = false;
} }
if(fogRadius > 0){
flags = flags.with(BlockFlag.hasFogRadius);
}
//initialize default health based on size //initialize default health based on size
if(health == -1){ if(health == -1){
boolean round = false; boolean round = false;

View File

@@ -23,10 +23,11 @@ public enum BlockFlag{
/** Blocks that extinguishes fires. */ /** Blocks that extinguishes fires. */
extinguisher, extinguisher,
//single-block identifiers //special, internal identifiers
launchPad, launchPad,
unitCargoUnloadPoint, unitCargoUnloadPoint,
unitAssembler; unitAssembler,
hasFogRadius;
public final static BlockFlag[] all = values(); public final static BlockFlag[] all = values();

View File

@@ -24,4 +24,4 @@ android.useAndroidX=true
#used for slow jitpack builds; TODO see if this actually works #used for slow jitpack builds; TODO see if this actually works
org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.socketTimeout=100000
org.gradle.internal.http.connectionTimeout=100000 org.gradle.internal.http.connectionTimeout=100000
archash=6418606527 archash=54bf3f5289

View File

@@ -9,7 +9,6 @@ import arc.math.*;
import arc.math.geom.*; import arc.math.geom.*;
import arc.struct.*; import arc.struct.*;
import arc.util.*; import arc.util.*;
import arc.util.async.*;
import arc.util.noise.*; import arc.util.noise.*;
import mindustry.ctype.*; import mindustry.ctype.*;
import mindustry.game.*; import mindustry.game.*;