mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
Debug rendering for oxygen (VERY LAGGY BUT WORKS)
This commit is contained in:
@@ -1,16 +1,23 @@
|
|||||||
package net.xevianlight.aphelion;
|
package net.xevianlight.aphelion;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.neoforged.api.distmarker.Dist;
|
import net.neoforged.api.distmarker.Dist;
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||||
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
|
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
|
||||||
import net.neoforged.neoforge.event.AddReloadListenerEvent;
|
import net.neoforged.neoforge.event.AddReloadListenerEvent;
|
||||||
|
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
import net.xevianlight.aphelion.block.dummy.renderer.MultiblockDummyRenderer;
|
import net.xevianlight.aphelion.block.dummy.renderer.MultiblockDummyRenderer;
|
||||||
import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer;
|
import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer;
|
||||||
import net.xevianlight.aphelion.client.AphelionConfig;
|
import net.xevianlight.aphelion.client.AphelionConfig;
|
||||||
|
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||||
|
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||||
import net.xevianlight.aphelion.planet.AphelionPlanetJSONLoader;
|
import net.xevianlight.aphelion.planet.AphelionPlanetJSONLoader;
|
||||||
import net.xevianlight.aphelion.core.init.*;
|
import net.xevianlight.aphelion.core.init.*;
|
||||||
import net.xevianlight.aphelion.fluid.BaseFluidType;
|
import net.xevianlight.aphelion.fluid.BaseFluidType;
|
||||||
@@ -132,7 +139,7 @@ public class Aphelion {
|
|||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void registerBER(EntityRenderersEvent.RegisterRenderers event) {
|
public static void registerBER(EntityRenderersEvent.RegisterRenderers event) {
|
||||||
event.registerBlockEntityRenderer(ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), MultiblockDummyRenderer::new);
|
event.registerBlockEntityRenderer(ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), MultiblockDummyRenderer::new);
|
||||||
event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
// event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
@@ -146,5 +153,10 @@ public class Aphelion {
|
|||||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||||
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new);
|
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onClientTick(ClientTickEvent.Post e) {
|
||||||
|
EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,34 @@
|
|||||||
package net.xevianlight.aphelion.block.custom;
|
package net.xevianlight.aphelion.block.custom;
|
||||||
|
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
|
import net.minecraft.world.level.block.RenderShape;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||||
|
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||||
|
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class OxygenTestBlock extends Block implements EntityBlock {
|
public class OxygenTestBlock extends BaseEntityBlock {
|
||||||
|
|
||||||
public OxygenTestBlock(Properties properties) {
|
public OxygenTestBlock(Properties properties) {
|
||||||
super(properties);
|
super(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final MapCodec<OxygenTestBlock> CODEC = simpleCodec(OxygenTestBlock::new);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||||
|
return CODEC;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||||
return new OxygenTestBlockEntity(blockPos, blockState);
|
return new OxygenTestBlockEntity(blockPos, blockState);
|
||||||
@@ -22,4 +37,29 @@ public class OxygenTestBlock extends Block implements EntityBlock {
|
|||||||
public static Properties getProperties() {
|
public static Properties getProperties() {
|
||||||
return Properties.of();
|
return Properties.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
|
||||||
|
if (level.isClientSide) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return createTickerHelper(blockEntityType, ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), (level1, blockPos, blockState, oxygenTestBlockEntity) -> oxygenTestBlockEntity.tick(level1, blockPos, blockState));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderShape getRenderShape(BlockState pState) {
|
||||||
|
return RenderShape.MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||||
|
if (state.getBlock() != newState.getBlock()) {
|
||||||
|
BlockEntity blockEntity= level.getBlockEntity(pos);
|
||||||
|
if (blockEntity instanceof OxygenTestBlockEntity oxygenTestBlockEntity) {
|
||||||
|
oxygenTestBlockEntity.removeEnclosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package net.xevianlight.aphelion.block.entity.custom;
|
package net.xevianlight.aphelion.block.entity.custom;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.core.SectionPos;
|
|
||||||
import net.minecraft.core.Vec3i;
|
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import static net.xevianlight.aphelion.Aphelion.LOGGER;
|
import static net.xevianlight.aphelion.Aphelion.LOGGER;
|
||||||
|
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||||
|
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||||
|
import net.xevianlight.aphelion.util.FloodFill3D;
|
||||||
import org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
|
import org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -43,11 +42,25 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
|||||||
return level != null && fastBlockState(pos).isAir();
|
return level != null && fastBlockState(pos).isAir();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final int MAX_RANGE = 100;
|
public static final int MAX_RANGE = 16;
|
||||||
public boolean isInRange(BlockPos pos1, BlockPos pos2) {
|
public boolean isInRange(BlockPos pos1, BlockPos pos2) {
|
||||||
return Math.abs(pos1.getX() - pos2.getX()) + Math.abs(pos1.getY() - pos2.getY()) + Math.abs(pos1.getZ() - pos2.getZ()) <= MAX_RANGE;
|
return Math.abs(pos1.getX() - pos2.getX()) + Math.abs(pos1.getY() - pos2.getY()) + Math.abs(pos1.getZ() - pos2.getZ()) <= MAX_RANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeEnclosed() {
|
||||||
|
if (enclosedCache != null) {
|
||||||
|
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||||
|
if (singleplayerServer != null) {
|
||||||
|
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||||
|
if (serverLevel != null) {
|
||||||
|
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||||
|
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||||
|
enclosedCache = Set.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 256*256*256 grid of booleans
|
/// 256*256*256 grid of booleans
|
||||||
private class BigBoolGrid {
|
private class BigBoolGrid {
|
||||||
int bitsSize;
|
int bitsSize;
|
||||||
@@ -94,7 +107,7 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
|||||||
// wrapping order is X->Z->Y, so we go along the X axis,
|
// wrapping order is X->Z->Y, so we go along the X axis,
|
||||||
// wrap to the Z axis to make a square, and once the square is full
|
// wrap to the Z axis to make a square, and once the square is full
|
||||||
// we step up once along the Y axis.
|
// we step up once along the Y axis.
|
||||||
int bitPos = inX & 7; // bottom 4 bits of X is bit pos
|
int bitPos = inX & 15; // bottom 4 bits of X is bit pos
|
||||||
int bit = (1 << bitPos);
|
int bit = (1 << bitPos);
|
||||||
|
|
||||||
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y
|
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y
|
||||||
@@ -106,22 +119,29 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<BlockPos> enclosedCache;
|
private Set<BlockPos> enclosedCache;
|
||||||
public List<BlockPos> getEnclosedBlocks() {
|
|
||||||
if (level == null) return List.of();
|
|
||||||
if (enclosedCache != null) return enclosedCache;
|
public Set<BlockPos> getEnclosedCache() {
|
||||||
|
return enclosedCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<BlockPos> getEnclosedBlocks() {
|
||||||
|
if (level == null) return Set.of();
|
||||||
|
// if (enclosedCache != null) return enclosedCache;
|
||||||
|
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
List<BlockPos> enclosedBlocks = new ArrayList<>();
|
Set<BlockPos> enclosedBlocks = FloodFill3D.run(level, getBlockPos(), 6000, FloodFill3D.TEST_FULL_SEAL, true);
|
||||||
// make this bitch BIIIIIG
|
|
||||||
BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
|
||||||
|
|
||||||
// It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
// // make this bitch BIIIIIG
|
||||||
// maybe a bit more, IDK how exactly it scales to blocks.
|
// BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
||||||
Stack<BlockPos> stack = new Stack<>();
|
//
|
||||||
Stack<Integer> radiusStack = new Stack<>();
|
// // It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
||||||
|
// // maybe a bit more, IDK how exactly it scales to blocks.
|
||||||
stack.add(this.getBlockPos());
|
// Stack<BlockPos> stack = new Stack<>();
|
||||||
|
// Stack<Integer> radiusStack = new Stack<>();
|
||||||
|
//
|
||||||
|
// stack.add(this.getBlockPos());
|
||||||
|
|
||||||
// Do flood fill out from this block
|
// Do flood fill out from this block
|
||||||
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest).
|
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest).
|
||||||
@@ -130,21 +150,48 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
|||||||
// and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
|
// and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
|
||||||
|
|
||||||
|
|
||||||
BlockPos ourPos = getBlockPos();
|
// BlockPos ourPos = getBlockPos();
|
||||||
while (!stack.isEmpty()) {
|
// while (!stack.isEmpty()) {
|
||||||
BlockPos spreadFromPos = stack.pop();
|
// BlockPos spreadFromPos = stack.pop();
|
||||||
for (Direction d : Direction.values()) {
|
// for (Direction d : Direction.values()) {
|
||||||
BlockPos relativePos = spreadFromPos.relative(d);
|
// BlockPos relativePos = spreadFromPos.relative(d);
|
||||||
|
//
|
||||||
|
// if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
||||||
|
// // seen.add runs seen.contains under the hood,
|
||||||
|
// // + this is the most expensive operation.
|
||||||
|
// // should save a lot of time!
|
||||||
|
// if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
||||||
|
// enclosedBlocks.add(relativePos);
|
||||||
|
// stack.add(relativePos);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||||
// seen.add runs seen.contains under the hood,
|
if (singleplayerServer != null) {
|
||||||
// + this is the most expensive operation.
|
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||||
// should save a lot of time!
|
if (serverLevel != null) {
|
||||||
if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
|
||||||
enclosedBlocks.add(relativePos);
|
// Build a set of longs for the newly computed blocks (order-independent)
|
||||||
stack.add(relativePos);
|
boolean changed = isChanged(enclosedBlocks);
|
||||||
|
// boolean changed = false;
|
||||||
|
if (changed) {
|
||||||
|
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||||
|
|
||||||
|
// Revert old affected area back to defaults
|
||||||
|
if (enclosedCache != null) {
|
||||||
|
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply oxygen to new affected area
|
||||||
|
savedData.setOxygen(serverLevel, enclosedBlocks, true);
|
||||||
|
|
||||||
|
LOGGER.info("Saved data for {} blocks to leveldata", enclosedBlocks.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the cache no matter what (so next compare is correct)
|
||||||
|
enclosedCache = enclosedBlocks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long durationNanos = System.nanoTime() - start;
|
long durationNanos = System.nanoTime() - start;
|
||||||
@@ -152,12 +199,49 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
|||||||
LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
|
LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
|
||||||
|
|
||||||
enclosedCache = enclosedBlocks;
|
enclosedCache = enclosedBlocks;
|
||||||
return enclosedBlocks;
|
|
||||||
|
return enclosedCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isChanged(Set<BlockPos> enclosedBlocks) {
|
||||||
|
LongOpenHashSet newSet = new LongOpenHashSet(enclosedBlocks.size());
|
||||||
|
for (BlockPos p : enclosedBlocks) {
|
||||||
|
newSet.add(p.asLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a set of longs for the cached blocks (if any)
|
||||||
|
LongOpenHashSet oldSet = null;
|
||||||
|
if (enclosedCache != null) {
|
||||||
|
oldSet = new LongOpenHashSet(enclosedCache.size());
|
||||||
|
for (BlockPos p : enclosedCache) {
|
||||||
|
oldSet.add(p.asLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only save if the set of affected blocks has changed
|
||||||
|
boolean changed = (oldSet == null) || !oldSet.equals(newSet);
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void helper() {
|
private void helper() {
|
||||||
var myVar = new BlockPos(1, 1, 1).hashCode();
|
var myVar = new BlockPos(1, 1, 1).hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ticks = 0;
|
||||||
|
int refreshAfter = 20;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void tick(Level level, BlockPos pos, BlockState blockState) {
|
||||||
|
if (level.isClientSide) return;
|
||||||
|
ticks++;
|
||||||
|
if (ticks >= refreshAfter) {
|
||||||
|
getEnclosedBlocks();
|
||||||
|
ticks = 0;
|
||||||
|
|
||||||
|
// UNCOMMENT FOR DEBUG ONLY!!! EXTREMELY TPS INTENSIVE!!!
|
||||||
|
// EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import net.minecraft.core.BlockPos;
|
|||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.core.Vec3i;
|
import net.minecraft.core.Vec3i;
|
||||||
import net.minecraft.world.phys.AABB;
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.xevianlight.aphelion.Aphelion;
|
||||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
@@ -23,7 +24,10 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
|||||||
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
|
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
|
||||||
|
|
||||||
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
|
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
|
||||||
return be.getEnclosedBlocks();
|
// cache = be.getEnclosedBlocks();
|
||||||
|
// if (cache != null)
|
||||||
|
// return cache;
|
||||||
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -31,6 +35,8 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
|||||||
return AABB.ofSize(blockEntity.getBlockPos().getCenter(), OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2);
|
return AABB.ofSize(blockEntity.getBlockPos().getCenter(), OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<BlockPos> cache;
|
||||||
|
|
||||||
private Set<BlockPos> relativePositionsCache;
|
private Set<BlockPos> relativePositionsCache;
|
||||||
@Override
|
@Override
|
||||||
// If in debug mode, renders a model made from the blocks
|
// If in debug mode, renders a model made from the blocks
|
||||||
@@ -42,7 +48,7 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
|||||||
// Renderers are relative to our block pos, so transform all points to be relative to block pos as well
|
// Renderers are relative to our block pos, so transform all points to be relative to block pos as well
|
||||||
List<BlockPos> positionsToRender = toBlockPositions(be);
|
List<BlockPos> positionsToRender = toBlockPositions(be);
|
||||||
BlockPos originPos = be.getBlockPos();
|
BlockPos originPos = be.getBlockPos();
|
||||||
if (true) return;
|
// if (true) return;
|
||||||
|
|
||||||
Set<BlockPos> relativePositions;
|
Set<BlockPos> relativePositions;
|
||||||
if (relativePositionsCache != null) relativePositions = relativePositionsCache;
|
if (relativePositionsCache != null) relativePositions = relativePositionsCache;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package net.xevianlight.aphelion.client;
|
|||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
@@ -11,6 +13,7 @@ import net.xevianlight.aphelion.Aphelion;
|
|||||||
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
||||||
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
|
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
|
||||||
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
|
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
|
||||||
|
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||||
|
|
||||||
@@ -51,5 +54,12 @@ public class AphelionDebugOverlay {
|
|||||||
// event.getLeft().add(" Sky: " + rendererSummary);
|
// event.getLeft().add(" Sky: " + rendererSummary);
|
||||||
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
|
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
|
||||||
event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination());
|
event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination());
|
||||||
|
var server = mc.getSingleplayerServer();
|
||||||
|
ServerLevel singlePlayerLevel;
|
||||||
|
if (server != null) {
|
||||||
|
singlePlayerLevel = server.getLevel(mc.level.dimension());
|
||||||
|
if (singlePlayerLevel != null)
|
||||||
|
event.getLeft().add(" Oxygen: " + EnvironmentSavedData.get(singlePlayerLevel).hasOxygen(singlePlayerLevel, mc.player.blockPosition()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package net.xevianlight.aphelion.client;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
|
||||||
|
public final class ClientOxygenCache {
|
||||||
|
public static final LongOpenHashSet OXYGEN = new LongOpenHashSet();
|
||||||
|
public static BlockPos lastCenter = BlockPos.ZERO;
|
||||||
|
public static long lastUpdateGameTime = -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package net.xevianlight.aphelion.client;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.RenderStateShard;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.neoforged.api.distmarker.Dist;
|
||||||
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
|
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
|
||||||
|
import net.xevianlight.aphelion.Aphelion;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
|
||||||
|
public final class OxygenDebugRender {
|
||||||
|
|
||||||
|
// Untextured translucent quads (POSITION_COLOR only)
|
||||||
|
private static final RenderType OXYGEN_FILL = RenderType.create(
|
||||||
|
"aphelion_oxygen_fill",
|
||||||
|
DefaultVertexFormat.POSITION_COLOR,
|
||||||
|
VertexFormat.Mode.QUADS,
|
||||||
|
256,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
RenderType.CompositeState.builder()
|
||||||
|
.setShaderState(RenderStateShard.POSITION_COLOR_SHADER)
|
||||||
|
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||||
|
.setCullState(RenderStateShard.NO_CULL)
|
||||||
|
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
|
||||||
|
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
|
||||||
|
.createCompositeState(true)
|
||||||
|
);
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRenderLevel(RenderLevelStageEvent event) {
|
||||||
|
// One stage only (pick one that exists and looks good)
|
||||||
|
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return;
|
||||||
|
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.level == null || mc.player == null) return;
|
||||||
|
if (!mc.gui.getDebugOverlay().showDebugScreen()) return;
|
||||||
|
|
||||||
|
PoseStack poseStack = event.getPoseStack();
|
||||||
|
var cam = mc.gameRenderer.getMainCamera();
|
||||||
|
var camPos = cam.getPosition();
|
||||||
|
|
||||||
|
poseStack.pushPose();
|
||||||
|
poseStack.translate(-camPos.x, -camPos.y, -camPos.z);
|
||||||
|
|
||||||
|
MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource();
|
||||||
|
VertexConsumer vc = bufferSource.getBuffer(OXYGEN_FILL);
|
||||||
|
|
||||||
|
// Render surface faces only (fast + pretty)
|
||||||
|
for (long l : ClientOxygenCache.OXYGEN) {
|
||||||
|
BlockPos p = BlockPos.of(l);
|
||||||
|
drawSurfaceFaces(poseStack, vc, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
poseStack.popPose();
|
||||||
|
bufferSource.endBatch(OXYGEN_FILL);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawSurfaceFaces(PoseStack poseStack, VertexConsumer vc, BlockPos p) {
|
||||||
|
// Neighbor checks: only render faces exposed to non-oxygen
|
||||||
|
boolean up = ClientOxygenCache.OXYGEN.contains(p.above().asLong());
|
||||||
|
boolean down = ClientOxygenCache.OXYGEN.contains(p.below().asLong());
|
||||||
|
boolean north = ClientOxygenCache.OXYGEN.contains(p.north().asLong());
|
||||||
|
boolean south = ClientOxygenCache.OXYGEN.contains(p.south().asLong());
|
||||||
|
boolean east = ClientOxygenCache.OXYGEN.contains(p.east().asLong());
|
||||||
|
boolean west = ClientOxygenCache.OXYGEN.contains(p.west().asLong());
|
||||||
|
|
||||||
|
if (up && down && north && south && east && west) return;
|
||||||
|
|
||||||
|
final float eps = 0.0025f;
|
||||||
|
float x0 = p.getX() + eps;
|
||||||
|
float y0 = p.getY() + eps;
|
||||||
|
float z0 = p.getZ() + eps;
|
||||||
|
float x1 = p.getX() + 1 - eps;
|
||||||
|
float y1 = p.getY() + 1 - eps;
|
||||||
|
float z1 = p.getZ() + 1 - eps;
|
||||||
|
|
||||||
|
// Color (ARGB-ish but as floats)
|
||||||
|
float r = 0.2f, g = 0.8f, b = 1.0f, a = 0.18f;
|
||||||
|
|
||||||
|
Matrix4f mat = poseStack.last().pose();
|
||||||
|
|
||||||
|
// IMPORTANT: vertex winding should be consistent (counter-clockwise)
|
||||||
|
if (!up) quad(mat, vc, x0,y1,z0, x1,y1,z0, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||||
|
if (!down) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y0,z0, x0,y0,z0, r,g,b,a);
|
||||||
|
|
||||||
|
if (!north) quad(mat, vc, x1,y0,z0, x0,y0,z0, x0,y1,z0, x1,y1,z0, r,g,b,a);
|
||||||
|
if (!south) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||||
|
|
||||||
|
if (!east) quad(mat, vc, x1,y0,z1, x1,y0,z0, x1,y1,z0, x1,y1,z1, r,g,b,a);
|
||||||
|
if (!west) quad(mat, vc, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, r,g,b,a);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void quad(
|
||||||
|
Matrix4f mat, VertexConsumer vc,
|
||||||
|
float x0, float y0, float z0,
|
||||||
|
float x1, float y1, float z1,
|
||||||
|
float x2, float y2, float z2,
|
||||||
|
float x3, float y3, float z3,
|
||||||
|
float r, float g, float b, float a
|
||||||
|
) {
|
||||||
|
// POSITION_COLOR format: ONLY position + color.
|
||||||
|
vc.addVertex(mat, x0, y0, z0).setColor(r, g, b, a);
|
||||||
|
vc.addVertex(mat, x1, y1, z1).setColor(r, g, b, a);
|
||||||
|
vc.addVertex(mat, x2, y2, z2).setColor(r, g, b, a);
|
||||||
|
vc.addVertex(mat, x3, y3, z3).setColor(r, g, b, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package net.xevianlight.aphelion.core.saveddata;
|
package net.xevianlight.aphelion.core.saveddata;
|
||||||
|
|
||||||
|
import com.jcraft.jorbis.Block;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
@@ -9,6 +12,7 @@ import net.minecraft.server.level.ServerLevel;
|
|||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.saveddata.SavedData;
|
import net.minecraft.world.level.saveddata.SavedData;
|
||||||
import net.xevianlight.aphelion.Aphelion;
|
import net.xevianlight.aphelion.Aphelion;
|
||||||
|
import net.xevianlight.aphelion.client.ClientOxygenCache;
|
||||||
import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData;
|
import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData;
|
||||||
import net.xevianlight.aphelion.planet.Planet;
|
import net.xevianlight.aphelion.planet.Planet;
|
||||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||||
@@ -60,7 +64,7 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
|
|
||||||
if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; }
|
if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; }
|
||||||
|
|
||||||
long[] positions = tag.getLongArray("Positions");
|
long[] positions = tag.getLongArray("Position");
|
||||||
int[] values = tag.getIntArray("Value");
|
int[] values = tag.getIntArray("Value");
|
||||||
|
|
||||||
int length = Math.min(positions.length, values.length);
|
int length = Math.min(positions.length, values.length);
|
||||||
@@ -89,7 +93,7 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDataForPosition(Level level, BlockPos pos, EnvironmentData data) {
|
public void setDataForPosition(Level level, BlockPos pos, EnvironmentData data) {
|
||||||
putOrRemove(pos.asLong(), data.pack());
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasOxygen(Level level, BlockPos pos) {
|
public boolean hasOxygen(Level level, BlockPos pos) {
|
||||||
@@ -100,8 +104,8 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
public void setOxygen(Level level, BlockPos pos, boolean value) {
|
public void setOxygen(Level level, BlockPos pos, boolean value) {
|
||||||
var data = getDataForPosition(level, pos);
|
var data = getDataForPosition(level, pos);
|
||||||
data.setOxygen(value);
|
data.setOxygen(value);
|
||||||
Aphelion.LOGGER.info("Set oxygen for {} to {}", pos, value);
|
// Aphelion.LOGGER.info("Set oxygen for {} to {}", pos, value);
|
||||||
putOrRemove(pos.asLong(), data.pack());
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOxygen(Level level, Collection<BlockPos> positions, boolean value) {
|
public void setOxygen(Level level, Collection<BlockPos> positions, boolean value) {
|
||||||
@@ -112,8 +116,14 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
|
|
||||||
public void resetOxygen(Level level, BlockPos pos) {
|
public void resetOxygen(Level level, BlockPos pos) {
|
||||||
var data = getDataForPosition(level, pos);
|
var data = getDataForPosition(level, pos);
|
||||||
data.setOxygen(EnvironmentData.DEFAULT_OXYGEN);
|
data.setOxygen(defaultData(level).hasOxygen());
|
||||||
putOrRemove(pos.asLong(), data.pack());
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetOxygen(Level level, Collection<BlockPos> positions) {
|
||||||
|
for (BlockPos pos : positions) {
|
||||||
|
resetOxygen(level, pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getGravity(Level level, BlockPos pos) {
|
public float getGravity(Level level, BlockPos pos) {
|
||||||
@@ -124,7 +134,7 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
public void setGravity(Level level, BlockPos pos, float value) {
|
public void setGravity(Level level, BlockPos pos, float value) {
|
||||||
var data = getDataForPosition(level, pos);
|
var data = getDataForPosition(level, pos);
|
||||||
data.setGravity(value);
|
data.setGravity(value);
|
||||||
putOrRemove(pos.asLong(), data.pack());
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGravity(Level level, Collection<BlockPos> positions, float value) {
|
public void setGravity(Level level, Collection<BlockPos> positions, float value) {
|
||||||
@@ -133,6 +143,12 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resetGravity(Level level, BlockPos pos) {
|
||||||
|
var data = getDataForPosition(level, pos);
|
||||||
|
data.setGravity(defaultData(level).getGravity());
|
||||||
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
|
}
|
||||||
|
|
||||||
public short getTemperature(Level level, BlockPos pos) {
|
public short getTemperature(Level level, BlockPos pos) {
|
||||||
var data = getDataForPosition(level, pos);
|
var data = getDataForPosition(level, pos);
|
||||||
return data.getTemperature();
|
return data.getTemperature();
|
||||||
@@ -141,7 +157,7 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
public void setTemperature(Level level, BlockPos pos, short value) {
|
public void setTemperature(Level level, BlockPos pos, short value) {
|
||||||
var data = getDataForPosition(level, pos);
|
var data = getDataForPosition(level, pos);
|
||||||
data.setTemperature(value);
|
data.setTemperature(value);
|
||||||
putOrRemove(pos.asLong(), data.pack());
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTemperature(Level level, Collection<BlockPos> positions, short value) {
|
public void setTemperature(Level level, Collection<BlockPos> positions, short value) {
|
||||||
@@ -150,8 +166,14 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putOrRemove(long key, int packed) {
|
public void resetTemperature(Level level, BlockPos pos) {
|
||||||
if (packed == EnvironmentData.DEFAULT_PACKED) {
|
var data = getDataForPosition(level, pos);
|
||||||
|
data.setTemperature(defaultData(level).getTemperature());
|
||||||
|
putOrRemove(level, pos.asLong(), data.pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putOrRemove(Level level, long key, int packed) {
|
||||||
|
if (packed == defaultPacked(level)) {
|
||||||
envData.remove(key);
|
envData.remove(key);
|
||||||
} else {
|
} else {
|
||||||
envData.put(key, packed);
|
envData.put(key, packed);
|
||||||
@@ -159,10 +181,82 @@ public class EnvironmentSavedData extends SavedData {
|
|||||||
setDirty();
|
setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int defaultPacked(Level level) {
|
||||||
|
Planet planet = PlanetCache.getByDimensionOrNull(level.dimension());
|
||||||
|
if (planet == null) return EnvironmentData.DEFAULT_PACKED;
|
||||||
|
|
||||||
|
// NOTE: adjust gravity/temperature defaults to whatever your data model intends
|
||||||
|
EnvironmentData planetData = new EnvironmentData(
|
||||||
|
planet.oxygen(),
|
||||||
|
EnvironmentData.DEFAULT_TEMPERATURE,
|
||||||
|
(short) planet.gravity()
|
||||||
|
);
|
||||||
|
return planetData.pack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EnvironmentData defaultData(Level level) {
|
||||||
|
return EnvironmentData.unpack(defaultPacked(level));
|
||||||
|
}
|
||||||
|
|
||||||
public static EnvironmentSavedData get(ServerLevel level) {
|
public static EnvironmentSavedData get(ServerLevel level) {
|
||||||
return level.getDataStorage().computeIfAbsent(
|
return level.getDataStorage().computeIfAbsent(
|
||||||
new Factory<>(EnvironmentSavedData::create, EnvironmentSavedData::load),
|
new Factory<>(EnvironmentSavedData::create, EnvironmentSavedData::load),
|
||||||
NAME
|
NAME
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void refreshFromIntegratedServerIfNeeded(Minecraft mc, int radius, int maxBlocks) {
|
||||||
|
if (mc.level == null || mc.player == null) return;
|
||||||
|
|
||||||
|
long gameTime = mc.level.getGameTime();
|
||||||
|
if (ClientOxygenCache.lastUpdateGameTime != -1 && gameTime - ClientOxygenCache.lastUpdateGameTime < 20) return; // every 1s
|
||||||
|
|
||||||
|
BlockPos center = mc.player.blockPosition();
|
||||||
|
if (center.distManhattan(ClientOxygenCache.lastCenter) < 1) return; // don’t refresh if player barely moved
|
||||||
|
|
||||||
|
var server = mc.getSingleplayerServer();
|
||||||
|
if (server == null) return;
|
||||||
|
|
||||||
|
// IMPORTANT: execute on server thread
|
||||||
|
server.execute(() -> {
|
||||||
|
var serverLevel = server.getLevel(mc.level.dimension());
|
||||||
|
if (serverLevel == null) return;
|
||||||
|
|
||||||
|
EnvironmentSavedData env = EnvironmentSavedData.get(serverLevel);
|
||||||
|
|
||||||
|
LongOpenHashSet found = new LongOpenHashSet();
|
||||||
|
|
||||||
|
int r = radius;
|
||||||
|
int scanned = 0;
|
||||||
|
|
||||||
|
// Scan a cube-ish region
|
||||||
|
for (int dy = -r; dy <= r; dy++) {
|
||||||
|
for (int dz = -r; dz <= r; dz++) {
|
||||||
|
for (int dx = -r; dx <= r; dx++) {
|
||||||
|
if (found.size() >= maxBlocks) break;
|
||||||
|
|
||||||
|
BlockPos p = center.offset(dx, dy, dz);
|
||||||
|
|
||||||
|
// optional: skip non-air or skip solid blocks (visual preference)
|
||||||
|
// if (!serverLevel.getBlockState(p).isAir()) continue;
|
||||||
|
|
||||||
|
if (env.hasOxygen(serverLevel, p)) {
|
||||||
|
found.add(p.asLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
scanned++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy results back to client thread safely
|
||||||
|
mc.execute(() -> {
|
||||||
|
ClientOxygenCache.OXYGEN.clear();
|
||||||
|
ClientOxygenCache.OXYGEN.addAll(found);
|
||||||
|
ClientOxygenCache.lastCenter = center;
|
||||||
|
ClientOxygenCache.lastUpdateGameTime = gameTime;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package net.xevianlight.aphelion.util;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standalone flood-fill utility.
|
||||||
|
* - Traverses blocks starting from origin
|
||||||
|
* - Visits only positions allowed by Passable predicate
|
||||||
|
* - Bounded by maxRange (Manhattan distance)
|
||||||
|
* - Returns all visited positions (excluding origin by default)
|
||||||
|
*/
|
||||||
|
public final class TechnoFloodFill {
|
||||||
|
|
||||||
|
private TechnoFloodFill() {}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Passable {
|
||||||
|
boolean test(Level level, BlockPos pos, BlockState state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convenience predicate: treat air as passable. */
|
||||||
|
public static final Passable AIR_ONLY = (level, pos, state) -> state.isAir();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a bounded flood fill.
|
||||||
|
*
|
||||||
|
* @param level the world
|
||||||
|
* @param origin starting position (typically BE position)
|
||||||
|
* @param maxRange Manhattan range limit (|dx|+|dy|+|dz|)
|
||||||
|
* @param passable which blocks can be entered/added
|
||||||
|
* @param includeOrigin whether to include origin in the returned list
|
||||||
|
*/
|
||||||
|
public static List<BlockPos> run(Level level, BlockPos origin, int maxRange, Passable passable, boolean includeOrigin) {
|
||||||
|
if (level == null) return List.of();
|
||||||
|
|
||||||
|
// Choose a grid size big enough to cover maxRange in all axes.
|
||||||
|
// We need coordinates within [-maxRange, +maxRange] around origin.
|
||||||
|
// So size must be >= (2*maxRange + 1). Next power-of-two for cheap indexing.
|
||||||
|
int needed = 2 * maxRange + 1;
|
||||||
|
int sizePow2 = nextPow2(needed);
|
||||||
|
int bits = Integer.numberOfTrailingZeros(sizePow2); // since pow2
|
||||||
|
BigVisitedGrid seen = new BigVisitedGrid(bits, origin.getX(), origin.getY(), origin.getZ());
|
||||||
|
|
||||||
|
// Chunk-cached blockstate fetch (no BE state needed)
|
||||||
|
ChunkCache chunkCache = new ChunkCache(level);
|
||||||
|
|
||||||
|
List<BlockPos> out = new ArrayList<>();
|
||||||
|
Deque<BlockPos> stack = new ArrayDeque<>();
|
||||||
|
|
||||||
|
if (includeOrigin) {
|
||||||
|
out.add(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark origin visited so we don't bounce back into it.
|
||||||
|
seen.add(origin.getX(), origin.getY(), origin.getZ());
|
||||||
|
stack.push(origin);
|
||||||
|
|
||||||
|
while (!stack.isEmpty()) {
|
||||||
|
BlockPos from = stack.pop();
|
||||||
|
|
||||||
|
for (Direction d : Direction.values()) {
|
||||||
|
BlockPos next = from.relative(d);
|
||||||
|
|
||||||
|
if (!inRangeManhattan(origin, next, maxRange)) continue;
|
||||||
|
|
||||||
|
// visited check first: cheapest early-out
|
||||||
|
if (!seen.add(next.getX(), next.getY(), next.getZ())) continue;
|
||||||
|
|
||||||
|
BlockState st = chunkCache.getBlockState(next);
|
||||||
|
if (!passable.test(level, next, st)) continue;
|
||||||
|
|
||||||
|
out.add(next);
|
||||||
|
stack.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean inRangeManhattan(BlockPos a, BlockPos b, int max) {
|
||||||
|
int dx = Math.abs(a.getX() - b.getX());
|
||||||
|
int dy = Math.abs(a.getY() - b.getY());
|
||||||
|
int dz = Math.abs(a.getZ() - b.getZ());
|
||||||
|
return dx + dy + dz <= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int nextPow2(int x) {
|
||||||
|
// smallest power of 2 >= x, with a minimum of 8
|
||||||
|
int v = 1;
|
||||||
|
while (v < x) v <<= 1;
|
||||||
|
return Math.max(v, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple chunk cache for repeated reads in flood fill.
|
||||||
|
*/
|
||||||
|
private static final class ChunkCache {
|
||||||
|
private final Level level;
|
||||||
|
private LevelChunk lastChunk;
|
||||||
|
private int lastCx = Integer.MIN_VALUE;
|
||||||
|
private int lastCz = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
private ChunkCache(Level level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState getBlockState(BlockPos pos) {
|
||||||
|
int cx = pos.getX() >> 4;
|
||||||
|
int cz = pos.getZ() >> 4;
|
||||||
|
|
||||||
|
if (cx != lastCx || cz != lastCz || lastChunk == null) {
|
||||||
|
lastChunk = level.getChunk(cx, cz); // may load/generate; okay for your current usage
|
||||||
|
lastCx = cx;
|
||||||
|
lastCz = cz;
|
||||||
|
}
|
||||||
|
return lastChunk.getBlockState(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packed visited grid centered on an origin.
|
||||||
|
*
|
||||||
|
* Uses an int[] with 32 bits per word:
|
||||||
|
* - size = 2^bits (must be power-of-two)
|
||||||
|
* - Word index layout: ((y * size) + z) * wordsPerRow + (x / 32)
|
||||||
|
* - Bit inside the word: (x % 32)
|
||||||
|
*/
|
||||||
|
private static final class BigVisitedGrid {
|
||||||
|
private final int bits;
|
||||||
|
private final int size;
|
||||||
|
private final int wordsPerRow;
|
||||||
|
private final int[] words;
|
||||||
|
private final int xOff, yOff, zOff;
|
||||||
|
|
||||||
|
BigVisitedGrid(int bits, int xOrigin, int yOrigin, int zOrigin) {
|
||||||
|
if (bits < 3) throw new IllegalArgumentException("Grid too small (bits=" + bits + ")");
|
||||||
|
if (bits > 12) throw new IllegalArgumentException("Grid too large (bits=" + bits + ")");
|
||||||
|
|
||||||
|
this.bits = bits;
|
||||||
|
this.size = 1 << bits;
|
||||||
|
|
||||||
|
// Center origin at middle of grid
|
||||||
|
this.xOff = -xOrigin + (size / 2);
|
||||||
|
this.yOff = -yOrigin + (size / 2);
|
||||||
|
this.zOff = -zOrigin + (size / 2);
|
||||||
|
|
||||||
|
if ((size & 31) != 0) {
|
||||||
|
// to keep wordsPerRow integer
|
||||||
|
throw new IllegalArgumentException("Grid size must be divisible by 32, got " + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.wordsPerRow = size >>> 5; // size / 32
|
||||||
|
int totalWords = wordsPerRow * size * size; // (y,z) rows * x-words
|
||||||
|
this.words = new int[totalWords];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if it was NOT previously visited and is now marked visited
|
||||||
|
*/
|
||||||
|
boolean add(int x, int y, int z) {
|
||||||
|
int inX = x + xOff;
|
||||||
|
int inY = y + yOff;
|
||||||
|
int inZ = z + zOff;
|
||||||
|
|
||||||
|
// Bounds check (fast and safe)
|
||||||
|
if ((inX | inY | inZ) < 0 || inX >= size || inY >= size || inZ >= size) {
|
||||||
|
return false; // out of grid => treat as "already seen" to prevent expansion
|
||||||
|
}
|
||||||
|
|
||||||
|
int wordIndex = ((inY * size) + inZ) * wordsPerRow + (inX >>> 5);
|
||||||
|
int bit = 1 << (inX & 31);
|
||||||
|
|
||||||
|
int prev = words[wordIndex];
|
||||||
|
if ((prev & bit) != 0) return false;
|
||||||
|
|
||||||
|
words[wordIndex] = prev | bit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
"orbit_distance": 1,
|
"orbit_distance": 1,
|
||||||
"star_system": "aphelon:sol",
|
"star_system": "aphelon:sol",
|
||||||
"gravity": 1,
|
"gravity": 1,
|
||||||
"oxygen": true
|
"oxygen": false
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user