Misc fog improvements

This commit is contained in:
Anuken
2022-03-01 10:24:20 -05:00
parent 8b51ba17ec
commit 97cbc3d345
23 changed files with 214 additions and 45 deletions

View File

@@ -85,6 +85,7 @@ public class Blocks{
scrapWall, scrapWallLarge, scrapWallHuge, scrapWallGigantic, thruster, //ok, these names are getting ridiculous, but at least I don't have humongous walls yet
//defense - erekir
radar,
buildTower,
regenProjector, barrierProjector,
//campaign only
@@ -1693,6 +1694,14 @@ public class Blocks{
tendrils = 4;
}};
radar = new Radar("radar"){{
requirements(Category.effect, BuildVisibility.fogOnly, with(Items.silicon, 30, Items.graphite, 30));
outlineColor = Color.valueOf("4a4b53");
fogRadius = 27;
consumePower(0.2f);
}};
buildTower = new BuildTurret("build-tower"){{
requirements(Category.effect, with(Items.silicon, 80, Items.carbide, 30, Items.oxide, 40, Items.thorium, 30));
outlineColor = Pal.darkOutline;
@@ -3247,7 +3256,6 @@ public class Blocks{
coolantMultiplier = 6f;
unitFilter = u -> !u.spawnedByCore;
shootShake = 1f;
ammoPerShot = 5;
draw = new DrawTurret("reinforced-");
@@ -3488,8 +3496,6 @@ public class Blocks{
}});
}};
unitFilter = u -> !u.spawnedByCore;
shoot = new ShootAlternate(){{
spread = 4.7f;
shots = 4;

View File

@@ -3396,6 +3396,9 @@ public class UnitTypes{
hitSize = 9f;
engineSize = 0;
fogRadius = 0f;
targetable = false;
setEnginesMirror(
new UnitEngine(21 / 4f, 19 / 4f, 2.2f, 45f),
new UnitEngine(23 / 4f, -22 / 4f, 2.2f, 315f)
@@ -3452,6 +3455,9 @@ public class UnitTypes{
armor = 2f;
hitSize = 11f;
fogRadius = 0f;
targetable = false;
engineOffset = 7.2f;
engineSize = 3.1f;
@@ -3522,6 +3528,9 @@ public class UnitTypes{
hitSize = 12f;
buildBeamOffset = 8f;
fogRadius = 0f;
targetable = false;
engineOffset = 7.5f;
engineSize = 3.4f;

View File

@@ -446,6 +446,11 @@ abstract class BuildingComp implements Posc, Teamc, Healthc, Buildingc, Timerc,
return nearby(Geometry.d4(rotation + 3).x * trns, Geometry.d4(rotation + 3).y * trns);
}
/** Any class that overrides this method and changes the value must call Vars.fogControl.forceUpdate(team). */
public float fogRadius(){
return block.fogRadius;
}
public int pos(){
return tile.pos();
}

View File

@@ -37,7 +37,7 @@ abstract class MechComp implements Posc, Flyingc, Hitboxc, Unitc, Mechc, Elevati
float lastExtend = walkExtension;
if(extendScl < lastExtend && base % 2f > 1f && !isFlying()){
if(!headless && extendScl < lastExtend && base % 2f > 1f && !isFlying() && !inFogTo(player.team())){
int side = -Mathf.sign(extend);
float width = hitSize / 2f * side, length = type.mechStride * 1.35f;

View File

@@ -30,7 +30,7 @@ abstract class TankComp implements Posc, Flyingc, Hitboxc, Unitc, ElevationMovec
@Override
public void update(){
//dust
if(walked && !headless){
if(walked && !headless && !inFogTo(player.team())){
treadEffectTime += Time.delta;
if(treadEffectTime >= 6f && type.treadRects.length > 0){
var treadRegion = type.treadRegion;

View File

@@ -1,6 +1,7 @@
package mindustry.game;
import arc.*;
import arc.math.*;
import arc.struct.Bits;
import arc.struct.*;
import arc.util.*;
@@ -19,7 +20,7 @@ import static mindustry.Vars.*;
public final class FogControl implements CustomChunk{
private static volatile int ww, wh;
private static final int staticUpdateInterval = 1000 / 25; //25 FPS
private static final int dynamicUpdateInterval = 1000 / 25; //25 FPS
private static final Object notifyStatic = new Object(), notifyDynamic = new Object();
/** indexed by team */
@@ -48,11 +49,11 @@ public final class FogControl implements CustomChunk{
wh = world.height();
//all old buildings have static light scheduled around them
if(state.rules.fog){
if(state.rules.fog && state.rules.staticFog){
synchronized(staticEvents){
for(var build : Groups.build){
if(build.block.flags.contains(BlockFlag.hasFogRadius)){
staticEvents.add(FogEvent.get(build.tile.x, build.tile.y, build.block.fogRadius, build.team.id));
staticEvents.add(FogEvent.get(build.tile.x, build.tile.y, Mathf.round(build.fogRadius()), build.team.id));
}
}
}
@@ -66,9 +67,11 @@ public final class FogControl implements CustomChunk{
data.dynamicUpdated = true;
}
synchronized(staticEvents){
//TODO event per team?
pushEvent(FogEvent.get(event.tile.x, event.tile.y, event.tile.block().fogRadius, event.tile.build.team.id));
if(state.rules.staticFog){
synchronized(staticEvents){
//TODO event per team?
pushEvent(FogEvent.get(event.tile.x, event.tile.y, Mathf.round(event.tile.build.fogRadius()), event.tile.build.team.id));
}
}
}
});
@@ -83,7 +86,7 @@ public final class FogControl implements CustomChunk{
}
});
SaveVersion.addCustomChunk("fogdata", this);
SaveVersion.addCustomChunk("static-fog-data", this);
}
public @Nullable Bits getDiscovered(Team team){
@@ -124,12 +127,26 @@ public final class FogControl implements CustomChunk{
}
void pushEvent(long event){
if(!state.rules.staticFog) return;
staticEvents.add(event);
if(!headless && FogEvent.team(event) == Vars.player.team().id){
renderer.fog.handleEvent(event);
}
}
public void forceUpdate(Team team, Building build){
if(state.rules.fog && fog[team.id] != null){
fog[team.id].dynamicUpdated = true;
if(state.rules.staticFog){
synchronized(staticEvents){
pushEvent(FogEvent.get(build.tile.x, build.tile.y, Mathf.round(build.fogRadius()), build.team.id));
}
}
}
}
public void update(){
if(fog == null){
fog = new FogData[256];
@@ -168,6 +185,7 @@ public final class FogControl implements CustomChunk{
//TODO slow?
for(var unit : team.units){
int tx = unit.tileX(), ty = unit.tileY(), pos = tx + ty * ww;
if(unit.type.fogRadius <= 0f) continue;
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?
@@ -182,13 +200,13 @@ public final class FogControl implements CustomChunk{
}
//if it's time for an update, flush *everything* onto the update queue
if(data.dynamicUpdated && Time.timeSinceMillis(data.lastDynamicMs) > staticUpdateInterval){
if(data.dynamicUpdated && Time.timeSinceMillis(data.lastDynamicMs) > dynamicUpdateInterval){
data.dynamicUpdated = false;
data.lastDynamicMs = Time.millis();
//add building updates
for(var build : indexer.getFlagged(team.team, BlockFlag.hasFogRadius)){
dynamicEventQueue.add(FogEvent.get(build.tile.x, build.tile.y, build.block.fogRadius, build.team.id));
dynamicEventQueue.add(FogEvent.get(build.tile.x, build.tile.y, Mathf.round(build.fogRadius()), build.team.id));
}
//add unit updates
@@ -219,7 +237,7 @@ public final class FogControl implements CustomChunk{
}
//wake up, it's time to draw some circles
if(staticEvents.size > 0 && staticFogThread != null){
if(state.rules.staticFog && staticEvents.size > 0 && staticFogThread != null){
synchronized(notifyStatic){
notifyStatic.notify();
}
@@ -310,6 +328,8 @@ public final class FogControl implements CustomChunk{
long event = dynamicEvents.items[i];
int x = FogEvent.x(event), y = FogEvent.y(event), rad = FogEvent.radius(event), team = FogEvent.team(event);
if(rad <= 0) continue;
var data = fog[team];
if(data != null){
@@ -412,7 +432,7 @@ public final class FogControl implements CustomChunk{
@Override
public boolean shouldWrite(){
return state.rules.fog && fog != null;
return state.rules.fog && state.rules.staticFog && fog != null;
}
static void circle(Bits arr, int x, int y, int radius){

View File

@@ -115,6 +115,8 @@ public class Rules{
public ObjectSet<Item> hiddenBuildItems = new ObjectSet<>();
/** HIGHLY UNSTABLE/EXPERIMENTAL. DO NOT USE THIS. */
public boolean fog = false;
/** If fog = true, this is whether static (black) fog is enabled. */
public boolean staticFog = true;
/** Color for static, undiscovered fog of war areas. */
public Color staticColor = new Color(0f, 0f, 0f, 1f);
/** Color for discovered but un-monitored fog of war areas. */

View File

@@ -412,7 +412,10 @@ public class BlockRenderer{
if(build != null){
if(visible){
if(!build.wasVisible) updateShadow(build);
if(!build.wasVisible){
updateShadow(build);
renderer.minimap.update(tile);
}
build.wasVisible = true;
}

View File

@@ -51,7 +51,7 @@ public final class FogRenderer{
dynamicFog.resize(world.width(), world.height());
if(player.team() != lastTeam){
if(state.rules.staticFog && player.team() != lastTeam){
copyFromCpu();
lastTeam = player.team();
clearStatic = false;
@@ -59,8 +59,6 @@ public final class FogRenderer{
//draw dynamic fog every frame
{
Core.camera.bounds(Tmp.r1);
Draw.proj(0, 0, staticFog.getWidth() * tilesize, staticFog.getHeight() * tilesize);
dynamicFog.begin(Color.black);
ScissorStack.push(rect.set(1, 1, staticFog.getWidth() - 2, staticFog.getHeight() - 2));
@@ -68,11 +66,11 @@ public final class FogRenderer{
Team team = player.team();
for(var build : indexer.getFlagged(team, BlockFlag.hasFogRadius)){
poly(Tmp.r1, build.x, build.y, build.block.fogRadius * tilesize);
poly(build.x, build.y, build.fogRadius() * tilesize);
}
for(var unit : team.data().units){
poly(Tmp.r1, unit.x, unit.y, unit.type.fogRadius * tilesize);
poly(unit.x, unit.y, unit.type.fogRadius * tilesize);
}
dynamicFog.end();
@@ -81,7 +79,7 @@ public final class FogRenderer{
}
//grab static events
if(clearStatic || events.size > 0){
if(state.rules.staticFog && (clearStatic || events.size > 0)){
//set projection to whole map
Draw.proj(0, 0, staticFog.getWidth(), staticFog.getHeight());
@@ -107,22 +105,23 @@ public final class FogRenderer{
Draw.proj(Core.camera);
}
staticFog.getTexture().setFilter(TextureFilter.linear);
if(state.rules.staticFog){
staticFog.getTexture().setFilter(TextureFilter.linear);
}
dynamicFog.getTexture().setFilter(TextureFilter.linear);
Draw.shader(Shaders.fog);
Draw.color(state.rules.dynamicColor);
Draw.fbo(dynamicFog.getTexture(), world.width(), world.height(), tilesize);
Draw.color(state.rules.staticColor);
Draw.fbo(staticFog.getTexture(), world.width(), world.height(), tilesize);
if(state.rules.staticFog){
Draw.color(state.rules.staticColor);
Draw.fbo(staticFog.getTexture(), world.width(), world.height(), tilesize);
}
Draw.shader();
}
void poly(Rect check, float x, float y, float rad){
//todo clipping messes up the minimap
//if(check.overlaps(x - rad, y - rad, rad * 2f, rad * 2f)){
void poly(float x, float y, float rad){
Fill.poly(x, y, 20, rad);
//}
}
void renderEvent(long e){

View File

@@ -150,7 +150,6 @@ public class MinimapRenderer{
Texture staticTex = renderer.fog.getStaticTexture(), dynamicTex = renderer.fog.getDynamicTexture();
//crisp pixels
staticTex.setFilter(TextureFilter.nearest);
dynamicTex.setFilter(TextureFilter.nearest);
Tmp.tr1.set(dynamicTex);
@@ -159,10 +158,14 @@ public class MinimapRenderer{
Draw.color(state.rules.dynamicColor);
Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
Tmp.tr1.texture = staticTex;
//must be black to fit with borders
Draw.color(0f, 0f, 0f, state.rules.staticColor.a);
Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
if(state.rules.staticFog){
staticTex.setFilter(TextureFilter.nearest);
Tmp.tr1.texture = staticTex;
//must be black to fit with borders
Draw.color(0f, 0f, 0f, state.rules.staticColor.a);
Draw.rect(Tmp.tr1, x + w/2f, y + h/2f, w, h);
}
Draw.color();
Draw.shader();
@@ -252,13 +255,20 @@ public class MinimapRenderer{
Units.nearby((dx - sz) * tilesize, (dy - sz) * tilesize, sz * 2 * tilesize, sz * 2 * tilesize, units::add);
}
private Block realBlock(Tile tile){
//TODO dynamically update on visibility change; right now it's just entirely hidden
return tile.build == null ? tile.block() : state.rules.fog && tile.build.team != player.team() ? Blocks.air : tile.block();
}
private int colorFor(Tile tile){
if(tile == null) return 0;
int bc = tile.block().minimapColor(tile);
Color color = Tmp.c1.set(bc == 0 ? MapIO.colorFor(tile.block(), tile.floor(), tile.overlay(), tile.team()) : bc);
Block real = realBlock(tile);
int bc = real.minimapColor(tile);
Color color = Tmp.c1.set(bc == 0 ? MapIO.colorFor(real, tile.floor(), tile.overlay(), tile.team()) : bc);
color.mul(1f - Mathf.clamp(world.getDarkness(tile.x, tile.y) / 4f));
if(tile.block() == Blocks.air && tile.y < world.height() - 1 && world.tile(tile.x, tile.y + 1).block().solid){
if(real == Blocks.air && tile.y < world.height() - 1 && realBlock(world.tile(tile.x, tile.y + 1)).solid){
color.mul(0.7f);
}else if(tile.floor().isLiquid && (tile.y >= world.height() - 1 || !world.tile(tile.x, tile.y + 1).floor().isLiquid)){
color.mul(0.84f, 0.84f, 0.9f, 1f);

View File

@@ -312,8 +312,8 @@ public class TypeIO{
/** @return the maximum acceptable amount of plans to send over the network */
public static int getMaxPlans(Queue<BuildPlan> plans){
//limit to 10 to prevent buffer overflows
int used = Math.min(plans.size, 10);
//limit to prevent buffer overflows
int used = Math.min(plans.size, 15);
int totalLength = 0;
//prevent buffer overflow by checking config length

View File

@@ -169,6 +169,7 @@ public class UnitType extends UnlockableContent{
public float hitSize = 6f;
public float itemOffsetY = 3f;
public float lightRadius = -1f, lightOpacity = 0.6f;
/** Fog view radius in tiles. <0 for automatic radius. */
public float fogRadius = -1f;
public Color lightColor = Pal.powerLight;
public boolean drawCell = true, drawItems = true, drawShields = true, drawBody = true;

View File

@@ -624,7 +624,7 @@ public class SettingsMenuDialog extends BaseDialog{
button(bundle.get("settings.reset", "Reset to Defaults"), () -> {
for(Setting setting : list){
if(setting.name == null || setting.title == null) continue;
settings.put(setting.name, settings.getDefault(setting.name));
settings.remove(setting.name);
}
rebuild();
}).margin(14).width(240f).pad(6);

View File

@@ -119,6 +119,12 @@ public class PlacementFragment extends Fragment{
if(Core.input.keyTap(Binding.pick) && player.isBuilder() && !Core.scene.hasDialog()){ //mouse eyedropper select
var build = world.buildWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
//can't middle click buildings in fog
if(build != null && build.inFogTo(player.team())){
build = null;
}
Block tryRecipe = build == null ? null : build instanceof ConstructBuild c ? c.current : build.block;
Object tryConfig = build == null || !build.block.copyConfig ? null : build.config();
@@ -577,7 +583,7 @@ public class PlacementFragment extends Fragment{
Tile hoverTile = world.tileWorld(Core.input.mouseWorld().x, Core.input.mouseWorld().y);
if(hoverTile != null){
//if the tile has a building, display it
if(hoverTile.build != null && hoverTile.build.displayable()){
if(hoverTile.build != null && hoverTile.build.displayable() && !hoverTile.build.inFogTo(player.team())){
return nextFlowBuild = hoverTile.build;
}

View File

@@ -0,0 +1,106 @@
package mindustry.world.blocks.defense;
import arc.graphics.*;
import arc.graphics.g2d.*;
import arc.math.*;
import arc.struct.*;
import arc.util.io.*;
import mindustry.*;
import mindustry.annotations.Annotations.*;
import mindustry.gen.*;
import mindustry.graphics.*;
import mindustry.world.*;
import mindustry.world.meta.*;
import static mindustry.Vars.*;
public class Radar extends Block{
public float discoveryTime = 60f * 7f;
public float rotateSpeed = 2f;
public @Load("@-base") TextureRegion baseRegion;
public @Load("@-glow") TextureRegion glowRegion;
public Color glowColor = Pal.turretHeat;
public float glowScl = 5f, glowMag = 0.6f;
public Radar(String name){
super(name);
update = solid = true;
flags = EnumSet.of(BlockFlag.hasFogRadius);
outlineIcon = true;
fogRadius = 10;
}
@Override
public TextureRegion[] icons(){
return new TextureRegion[]{baseRegion, region};
}
@Override
public void drawPlace(int x, int y, int rotation, boolean valid){
super.drawPlace(x, y, rotation, valid);
Drawf.dashCircle(x * tilesize + offset, y * tilesize + offset, fogRadius * tilesize, Pal.accent);
}
public class RadarBuild extends Building{
public float progress;
public float lastRadius = 0f;
public float smoothEfficiency = 1f;
public float totalProgress;
@Override
public float fogRadius(){
return fogRadius * progress * smoothEfficiency;
}
@Override
public void updateTile(){
smoothEfficiency = Mathf.lerpDelta(smoothEfficiency, efficiency, 0.05f);
if(Math.abs(fogRadius() - lastRadius) >= 0.5f){
Vars.fogControl.forceUpdate(team, this);
lastRadius = fogRadius();
}
progress += edelta() / discoveryTime;
progress = Mathf.clamp(progress);
totalProgress += efficiency * edelta();
}
@Override
public void drawSelect(){
Drawf.dashCircle(x, y, fogRadius() * tilesize, Pal.accent);
}
@Override
public void draw(){
Draw.rect(baseRegion, x, y);
Draw.rect(region, x, y, rotateSpeed * totalProgress);
Drawf.additive(glowRegion, glowColor, glowColor.a * (1f - glowMag + Mathf.absin(glowScl, glowMag)), x, y, rotateSpeed * totalProgress, Layer.blockAdditive);
}
@Override
public float progress(){
return progress;
}
@Override
public void write(Writes write){
super.write(write);
write.f(progress);
}
@Override
public void read(Reads read, byte revision){
super.read(read, revision);
progress = read.f();
}
}
}

View File

@@ -104,7 +104,7 @@ public class CoreBlock extends StorageBlock{
public void init(){
//assign to update clipSize internally
lightRadius = 30f + 20f * size;
fogRadius = Math.max(fogRadius, (int)(lightRadius / 8f * 2f));
fogRadius = Math.max(fogRadius, (int)(lightRadius / 8f * 3f));
emitLight = true;
super.init();

View File

@@ -13,7 +13,8 @@ public enum BuildVisibility{
campaignOnly(() -> Vars.state == null || Vars.state.isCampaign()),
lightingOnly(() -> Vars.state == null || Vars.state.rules.lighting || Vars.state.isCampaign()),
berylliumOnly(() -> !Vars.state.rules.hiddenBuildItems.contains(Items.beryllium)),
ammoOnly(() -> Vars.state == null || Vars.state.rules.unitAmmo);
ammoOnly(() -> Vars.state == null || Vars.state.rules.unitAmmo),
fogOnly(() -> Vars.state == null || Vars.state.rules.fog);
private final Boolp visible;