diff --git a/core/src/mindustry/content/Blocks.java b/core/src/mindustry/content/Blocks.java index ae8f695ec0..df1dc5333e 100644 --- a/core/src/mindustry/content/Blocks.java +++ b/core/src/mindustry/content/Blocks.java @@ -290,6 +290,7 @@ public class Blocks{ liquidDrop = Liquids.oil; isLiquid = true; cacheLayer = CacheLayer.tar; + obstructsLight = true; }}; cryofluid = new Floor("pooled-cryofluid"){{ @@ -308,6 +309,8 @@ public class Blocks{ emitLight = true; lightRadius = 25f; lightColor = Color.cyan.cpy().a(0.19f); + obstructsLight = true; + forceDrawLight = true; }}; slag = new Floor("molten-slag"){{ @@ -324,6 +327,8 @@ public class Blocks{ emitLight = true; lightRadius = 40f; lightColor = Color.orange.cpy().a(0.38f); + obstructsLight = true; + forceDrawLight = true; }}; space = new Floor("space"){{ @@ -486,6 +491,7 @@ public class Blocks{ drownTime = 200f; cacheLayer = CacheLayer.arkycite; albedo = 0.9f; + obstructsLight = true; }}; arkyicStone = new Floor("arkyic-stone"){{ @@ -688,6 +694,7 @@ public class Blocks{ sporeCluster = new Prop("spore-cluster"){{ variants = 3; breakSound = Sounds.plantBreak; + obstructsLight = false; }}; redweed = new Seaweed("redweed"){{ @@ -765,6 +772,7 @@ public class Blocks{ variants = 3; customShadow = true; arkyicStone.asFloor().decoration = this; + obstructsLight = false; }}; crystalCluster = new TallBlock("crystal-cluster"){{ diff --git a/core/src/mindustry/graphics/BlockRenderer.java b/core/src/mindustry/graphics/BlockRenderer.java index d6aa1f544f..66a0835455 100644 --- a/core/src/mindustry/graphics/BlockRenderer.java +++ b/core/src/mindustry/graphics/BlockRenderer.java @@ -16,6 +16,7 @@ import mindustry.game.*; import mindustry.game.Teams.*; import mindustry.gen.*; import mindustry.world.*; +import mindustry.world.blocks.environment.*; import mindustry.world.blocks.environment.Floor.*; import mindustry.world.blocks.power.*; @@ -50,6 +51,7 @@ public class BlockRenderer{ private BlockQuadtree blockTree = new BlockQuadtree(new Rect(0, 0, 1, 1)); private BlockLightQuadtree blockLightTree = new BlockLightQuadtree(new Rect(0, 0, 1, 1)); + private OverlayQuadtree overlayTree = new OverlayQuadtree(new Rect(0, 0, 1, 1)); private FloorQuadtree floorTree = new FloorQuadtree(new Rect(0, 0, 1, 1)); public BlockRenderer(){ @@ -76,13 +78,14 @@ public class BlockRenderer{ }); Events.on(TilePreChangeEvent.class, event -> { - if(blockTree == null || floorTree == null) return; + if(blockTree == null || floorTree == null || overlayTree == null) return; if(indexBlock(event.tile)){ blockTree.remove(event.tile); blockLightTree.remove(event.tile); } if(indexFloor(event.tile)) floorTree.remove(event.tile); + if(indexOverlay(event.tile)) overlayTree.remove(event.tile); }); Events.on(TileChangeEvent.class, event -> { @@ -112,6 +115,7 @@ public class BlockRenderer{ public void reload(){ blockTree = new BlockQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); blockLightTree = new BlockLightQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); + overlayTree = new OverlayQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); floorTree = new FloorQuadtree(new Rect(0, 0, world.unitWidth(), world.unitHeight())); shadowEvents.clear(); @@ -233,13 +237,25 @@ public class BlockRenderer{ if(indexFloor(tile)) floorTree.insert(tile); } + public void removeOverlayIndex(Tile tile){ + if(indexOverlay(tile)) overlayTree.remove(tile); + } + + public void addOverlayIndex(Tile tile){ + if(indexOverlay(tile)) overlayTree.insert(tile); + } + boolean indexBlock(Tile tile){ var block = tile.block(); return tile.isCenter() && block != Blocks.air && block.cacheLayer == CacheLayer.normal; } + boolean indexOverlay(Tile tile){ + return !tile.block().obstructsLight && tile.overlay().emitLight && world.getDarkness(tile.x, tile.y) < 3; + } + boolean indexFloor(Tile tile){ - return tile.block() == Blocks.air && tile.floor().emitLight && world.getDarkness(tile.x, tile.y) < 3; + return !tile.block().obstructsLight && tile.floor().emitLight && world.getDarkness(tile.x, tile.y) < 3; } void recordIndex(Tile tile){ @@ -247,6 +263,7 @@ public class BlockRenderer{ blockTree.insert(tile); blockLightTree.insert(tile); } + if(indexOverlay(tile)) overlayTree.insert(tile); if(indexFloor(tile)) floorTree.insert(tile); } @@ -399,6 +416,7 @@ public class BlockRenderer{ //draw floor lights floorTree.intersect(bounds, lightview::add); + overlayTree.intersect(bounds, lightview::add); blockLightTree.intersect(bounds, tile -> { if(tile.block().emitLight && (tile.build == null || procLights.add(tile.build.pos()))){ @@ -518,8 +536,18 @@ public class BlockRenderer{ entity.drawLight(); }else if(tile.block().emitLight){ tile.block().drawEnvironmentLight(tile); - }else if(tile.floor().emitLight && tile.block() == Blocks.air){ //only draw floor light under non-solid blocks - tile.floor().drawEnvironmentLight(tile); + } + + if(!tile.block().obstructsLight){ + Floor floor = tile.floor(); + Floor overlay = tile.overlay(); + + if(!floor.obstructsLight && overlay.emitLight){ + overlay.drawEnvironmentLight(tile); + } + if(floor.forceDrawLight || (!overlay.obstructsLight && floor.emitLight)){ + floor.drawEnvironmentLight(tile); + } } } } @@ -588,6 +616,23 @@ public class BlockRenderer{ } } + static class OverlayQuadtree extends QuadTree{ + public OverlayQuadtree(Rect bounds){ + super(bounds); + } + + @Override + public void hitbox(Tile tile){ + var overlay = tile.overlay(); + tmp.setCentered(tile.worldx(), tile.worldy(), overlay.lightClipSize, overlay.lightClipSize); + } + + @Override + protected QuadTree newChild(Rect rect){ + return new OverlayQuadtree(rect); + } + } + static class FloorQuadtree extends QuadTree{ public FloorQuadtree(Rect bounds){ diff --git a/core/src/mindustry/input/InputHandler.java b/core/src/mindustry/input/InputHandler.java index adc6cd541f..2df087acf0 100644 --- a/core/src/mindustry/input/InputHandler.java +++ b/core/src/mindustry/input/InputHandler.java @@ -197,12 +197,62 @@ public abstract class InputHandler implements InputProcessor, GestureListener{ build.items.set(item, amount); } + @Remote(called = Loc.server, unreliable = true) + public static void setItems(Building build, ItemStack[] items){ + if(build == null || build.items == null) return; + + for(ItemStack stack : items){ + build.items.set(stack.item, stack.amount); + } + } + + @Remote(called = Loc.server, unreliable = true) + public static void setTileItems(Item item, int amount, int[] positions){ + for(int pos : positions){ + Building build = world.build(pos); + if(build != null && build.items != null){ + build.items.set(item, amount); + } + } + } + @Remote(called = Loc.server, unreliable = true) public static void clearItems(Building build){ if(build == null || build.items == null) return; build.items.clear(); } + @Remote(called = Loc.server, unreliable = true) + public static void setLiquid(Building build, Liquid liquid, float amount){ + if(build == null || build.liquids == null) return; + build.liquids.set(liquid, amount); + } + + @Remote(called = Loc.server, unreliable = true) + public static void setLiquids(Building build, LiquidStack[] liquids){ + if(build == null || build.liquids == null) return; + + for(LiquidStack stack : liquids){ + build.liquids.set(stack.liquid, stack.amount); + } + } + + @Remote(called = Loc.server, unreliable = true) + public static void setTileLiquids(Liquid liquid, float amount, int[] positions){ + for(int pos : positions){ + Building build = world.build(pos); + if(build != null && build.liquids != null){ + build.liquids.set(liquid, amount); + } + } + } + + @Remote(called = Loc.server, unreliable = true) + public static void clearLiquids(Building build){ + if(build == null || build.liquids == null) return; + build.liquids.clear(); + } + @Remote(called = Loc.server, unreliable = true) public static void transferItemTo(@Nullable Unit unit, Item item, int amount, float x, float y, Building build){ if(build == null || build.items == null || item == null) return; diff --git a/core/src/mindustry/io/SaveVersion.java b/core/src/mindustry/io/SaveVersion.java index 6973da00fc..7f327661ae 100644 --- a/core/src/mindustry/io/SaveVersion.java +++ b/core/src/mindustry/io/SaveVersion.java @@ -334,6 +334,7 @@ public abstract class SaveVersion extends SaveFileReader{ //set block only if this is the center; otherwise, it's handled elsewhere if(isCenter){ tile.setBlock(block); + if(tile.build != null) tile.build.enabled = true; } //must be assigned after setBlock, because that can reset data diff --git a/core/src/mindustry/io/TypeIO.java b/core/src/mindustry/io/TypeIO.java index 2ffcf008db..c0b1b753d1 100644 --- a/core/src/mindustry/io/TypeIO.java +++ b/core/src/mindustry/io/TypeIO.java @@ -829,6 +829,39 @@ public class TypeIO{ return new ItemStack(readItem(read), read.i()); } + public static void writeItemStacks(Writes write, ItemStack[] stacks){ + write.s(stacks.length); + for(ItemStack stack : stacks){ + writeItems(write, stack); + } + } + + public static ItemStack[] readItemStacks(Reads read){ + short count = read.s(); + ItemStack[] stacks = new ItemStack[count]; + for(int i = 0; i < count; i++) + stacks[i] = readItems(read); + return stacks; + } + + public static void writeLiquidStacks(Writes write, LiquidStack[] stacks){ + write.s(stacks.length); + for(LiquidStack stack : stacks){ + writeLiquid(write, stack.liquid); + write.f(stack.amount); + } + } + + public static LiquidStack[] readLiquidStacks(Reads read){ + short count = read.s(); + LiquidStack[] stacks = new LiquidStack[count]; + for(int i = 0; i < count; i++){ + Liquid liquid = readLiquid(read); + stacks[i] = new LiquidStack(liquid, read.f()); + } + return stacks; + } + public static void writeTeam(Writes write, Team team){ write.b(team == null ? 0 : team.id); } diff --git a/core/src/mindustry/io/versions/LegacySaveVersion.java b/core/src/mindustry/io/versions/LegacySaveVersion.java index ef01f750a9..152aa8cde0 100644 --- a/core/src/mindustry/io/versions/LegacySaveVersion.java +++ b/core/src/mindustry/io/versions/LegacySaveVersion.java @@ -56,6 +56,7 @@ public abstract class LegacySaveVersion extends LegacyRegionSaveVersion{ //do not override occupied cells if(!occupied){ tile.setBlock(block); + if(tile.build != null) tile.build.enabled = true; } if(block.hasBuilding()){ diff --git a/core/src/mindustry/io/versions/ShortChunkSaveVersion.java b/core/src/mindustry/io/versions/ShortChunkSaveVersion.java index ce1300a622..7a1e1630e4 100644 --- a/core/src/mindustry/io/versions/ShortChunkSaveVersion.java +++ b/core/src/mindustry/io/versions/ShortChunkSaveVersion.java @@ -101,6 +101,7 @@ public class ShortChunkSaveVersion extends SaveVersion{ //set block only if this is the center; otherwise, it's handled elsewhere if(isCenter){ tile.setBlock(block); + if(tile.build != null) tile.build.enabled = true; } //must be assigned after setBlock, because that can reset data diff --git a/core/src/mindustry/world/Block.java b/core/src/mindustry/world/Block.java index 9bd181732a..87e68b7471 100644 --- a/core/src/mindustry/world/Block.java +++ b/core/src/mindustry/world/Block.java @@ -321,6 +321,8 @@ public class Block extends UnlockableContent implements Senseable{ public Color lightColor = Color.white.cpy(); /** If true, drawLight() will be called for this block. */ public boolean emitLight = false; + /** If true, this block obstructs light emitted by other blocks. */ + public boolean obstructsLight = true; /** Radius of the light emitted by this block. */ public float lightRadius = 60f; diff --git a/core/src/mindustry/world/blocks/defense/ForceProjector.java b/core/src/mindustry/world/blocks/defense/ForceProjector.java index c4e846fe1e..37111fd26e 100644 --- a/core/src/mindustry/world/blocks/defense/ForceProjector.java +++ b/core/src/mindustry/world/blocks/defense/ForceProjector.java @@ -125,7 +125,7 @@ public class ForceProjector extends Block{ float liquidHeat = (1f + (liquid.heatCapacity - 0.4f) * 0.9f); float regenBoost = ((cooldownNormal * (cooldownLiquid * liquidHeat)) - cooldownNormal) * 60f; float cooldownBoost = (shieldHealth / (cooldownBrokenBase * (cooldownLiquid * liquidHeat)) - shieldHealth / cooldownBrokenBase) / 60f; - + b.table(bt -> { bt.right().defaults().padRight(3).left(); bt.add("[lightgray]+" + Core.bundle.format("bar.regenerationrate", Strings.autoFixed(regenBoost, 2))).pad(5).row(); @@ -156,6 +156,15 @@ public class ForceProjector extends Block{ public boolean broken = true; public float buildup, radscl, hit, warmup, phaseHeat; + @Override + public void setProp(LAccess prop, double value){ + if(prop == LAccess.shield){ + buildup = Math.max(shieldHealth + phaseShieldBoost * phaseHeat - (float)value, 0f); + }else{ + super.setProp(prop, value); + } + } + @Override public float range(){ return realRadius(); diff --git a/core/src/mindustry/world/blocks/environment/Floor.java b/core/src/mindustry/world/blocks/environment/Floor.java index dbd97cafe7..269728b9e2 100644 --- a/core/src/mindustry/world/blocks/environment/Floor.java +++ b/core/src/mindustry/world/blocks/environment/Floor.java @@ -68,6 +68,8 @@ public class Floor extends Block{ public Block decoration = Blocks.air; /** Whether units can draw shadows over this. */ public boolean canShadow = true; + /** If true, this floor ignores the obstructsLight flag of overlays. */ + public boolean forceDrawLight = false; /** Whether this overlay needs a surface to be on. False for floating blocks, like spawns. */ public boolean needsSurface = true; /** If true, cores can be placed on this floor. */ @@ -110,6 +112,7 @@ public class Floor extends Block{ allowRectanglePlacement = true; instantBuild = true; ignoreBuildDarkness = true; + obstructsLight = false; placeEffect = Fx.rotateBlock; } diff --git a/core/src/mindustry/world/blocks/environment/SeaBush.java b/core/src/mindustry/world/blocks/environment/SeaBush.java index 81c0c5fe58..f5c71f29e7 100644 --- a/core/src/mindustry/world/blocks/environment/SeaBush.java +++ b/core/src/mindustry/world/blocks/environment/SeaBush.java @@ -19,6 +19,7 @@ public class SeaBush extends Prop{ public SeaBush(String name){ super(name); variants = 0; + obstructsLight = false; } @Override diff --git a/core/src/mindustry/world/blocks/environment/Seaweed.java b/core/src/mindustry/world/blocks/environment/Seaweed.java index bc1c852f90..bfaad6fa01 100644 --- a/core/src/mindustry/world/blocks/environment/Seaweed.java +++ b/core/src/mindustry/world/blocks/environment/Seaweed.java @@ -9,6 +9,8 @@ public class Seaweed extends Prop{ public Seaweed(String name){ super(name); + + obstructsLight = false; } @Override diff --git a/core/src/mindustry/world/blocks/logic/CanvasBlock.java b/core/src/mindustry/world/blocks/logic/CanvasBlock.java index 566704d6e4..7aee2477e8 100644 --- a/core/src/mindustry/world/blocks/logic/CanvasBlock.java +++ b/core/src/mindustry/world/blocks/logic/CanvasBlock.java @@ -68,6 +68,8 @@ public class CanvasBlock extends Block{ clipSize = Math.max(clipSize, size * 8 - padding); previewPixmap = new Pixmap(canvasSize, canvasSize); + + if(!Mathf.isPowerOfTwo(palette.length)) throw new RuntimeException("Non power-of-two palettes for canvas blocks are not supported."); } @Override @@ -150,7 +152,7 @@ public class CanvasBlock extends Block{ public @Nullable Texture texture; public byte[] data = new byte[Mathf.ceil(canvasSize * canvasSize * bitsPerPixel / 8f)]; public int blending; - + protected boolean updated = false; public void setPixel(int pos, int index){ @@ -286,7 +288,7 @@ public class CanvasBlock extends Block{ } } } - + @Override public double sense(LAccess sensor){ return switch(sensor){ @@ -314,12 +316,12 @@ public class CanvasBlock extends Block{ int[] curColor = {palette[0]}; boolean[] modified = {false}; boolean[] fill = {false}; - + dialog.hidden(() -> { texture.dispose(); pix.dispose(); }); - + dialog.resized(dialog::hide); dialog.cont.table(Tex.pane, body -> { diff --git a/desktop/src/mindustry/desktop/DesktopLauncher.java b/desktop/src/mindustry/desktop/DesktopLauncher.java index e20a4208c4..72a73a00fa 100644 --- a/desktop/src/mindustry/desktop/DesktopLauncher.java +++ b/desktop/src/mindustry/desktop/DesktopLauncher.java @@ -118,17 +118,19 @@ public class DesktopLauncher extends ClientLauncher{ testMobile = Seq.with(args).contains("-testMobile"); if(useDiscord){ - try{ - DiscordRPC.connect(discordID); - Log.info("Initialized Discord rich presence."); - Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC::close)); - }catch(NoDiscordClientException none){ - //don't log if no client is found - useDiscord = false; - }catch(Throwable t){ - useDiscord = false; - Log.warn("Failed to initialize Discord RPC - you are likely using a JVM <16."); - } + Threads.daemon(() -> { + try{ + DiscordRPC.connect(discordID); + Log.info("Initialized Discord rich presence."); + Runtime.getRuntime().addShutdownHook(new Thread(DiscordRPC::close)); + }catch(NoDiscordClientException none){ + //don't log if no client is found + useDiscord = false; + }catch(Throwable t){ + useDiscord = false; + Log.warn("Failed to initialize Discord RPC - you are likely using a JVM <16."); + } + }); } if(useSteam){ diff --git a/gradle.properties b/gradle.properties index 75a9789f5a..b368b24836 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,4 +26,4 @@ org.gradle.caching=true org.gradle.internal.http.socketTimeout=100000 org.gradle.internal.http.connectionTimeout=100000 android.enableR8.fullMode=false -archash=9b4648505a +archash=65d654d634