Compare commits

...

5 commits

Author SHA1 Message Date
WMF
f48900d6de
Quality of Life changes (#11370)
* overlay light support, bulk Call.setItem implementations, Call.setLiquid implementations

* Update core/src/mindustry/graphics/BlockRenderer.java

---------

Co-authored-by: Anuken <arnukren@gmail.com>
2025-11-16 23:43:54 -05:00
Anuken
bb4534afde Fixed #11371 2025-11-16 23:42:22 -05:00
Anuken
e8fc33ca9e Fixed #11366 2025-11-16 23:34:52 -05:00
Anuken
11542f8e17 Fixed #11350 2025-11-16 23:26:51 -05:00
Anuken
45e744949e Fixed #11361 2025-11-16 23:23:24 -05:00
15 changed files with 181 additions and 21 deletions

View file

@ -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"){{

View file

@ -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<Tile>{
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<Tile> newChild(Rect rect){
return new OverlayQuadtree(rect);
}
}
static class FloorQuadtree extends QuadTree<Tile>{
public FloorQuadtree(Rect bounds){

View file

@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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()){

View file

@ -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

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -19,6 +19,7 @@ public class SeaBush extends Prop{
public SeaBush(String name){
super(name);
variants = 0;
obstructsLight = false;
}
@Override

View file

@ -9,6 +9,8 @@ public class Seaweed extends Prop{
public Seaweed(String name){
super(name);
obstructsLight = false;
}
@Override

View file

@ -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 -> {

View file

@ -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){

View file

@ -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