This commit is contained in:
2026-03-20 13:17:40 +01:00
parent 301d87bb5e
commit af21279b00
6 changed files with 171 additions and 137 deletions

View File

@@ -0,0 +1,65 @@
package dev.tggamesyt.szar.client;
import dev.tggamesyt.szar.BackroomsLightBlockEntity;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.WorldRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import org.joml.Matrix4f;
public class BackroomsLightBlockEntityRenderer implements BlockEntityRenderer<BackroomsLightBlockEntity> {
// Your light texture
private static final Identifier TEXTURE =
new Identifier("szar", "textures/block/white.png");
public BackroomsLightBlockEntityRenderer(BlockEntityRendererFactory.Context ctx) {}
@Override
public void render(BackroomsLightBlockEntity entity, float tickDelta,
MatrixStack matrices, VertexConsumerProvider vertexConsumers,
int light, int overlay) {
float brightness = entity.brightness;
if (brightness <= 0.0f) return; // fully dark, don't render
BlockPos pos = entity.getPos();
int lightLevel = WorldRenderer.getLightmapCoordinates(entity.getWorld(), pos);
VertexConsumer consumer = vertexConsumers.getBuffer(
RenderLayer.getEntityCutoutNoCull(TEXTURE));
matrices.push();
// Center on block, render on bottom face (light faces downward into room)
matrices.translate(0.5, 0.001, 0.5);
matrices.multiply(net.minecraft.util.math.RotationAxis.POSITIVE_X.rotationDegrees(90));
Matrix4f matrix = matrices.peek().getPositionMatrix();
// Apply brightness as color multiplier
int r = (int)(255 * brightness);
int g = (int)(255 * brightness);
int b = (int)(255 * brightness);
float s = 0.5f;
consumer.vertex(matrix, -s, -s, 0).color(r, g, b, 255)
.texture(0, 1).overlay(overlay).light(lightLevel)
.normal(0, -1, 0).next();
consumer.vertex(matrix, s, -s, 0).color(r, g, b, 255)
.texture(1, 1).overlay(overlay).light(lightLevel)
.normal(0, -1, 0).next();
consumer.vertex(matrix, s, s, 0).color(r, g, b, 255)
.texture(1, 0).overlay(overlay).light(lightLevel)
.normal(0, -1, 0).next();
consumer.vertex(matrix, -s, s, 0).color(r, g, b, 255)
.texture(0, 0).overlay(overlay).light(lightLevel)
.normal(0, -1, 0).next();
matrices.pop();
}
}

View File

@@ -367,6 +367,9 @@ public class SzarClient implements ClientModInitializer {
Szar.TRACKER_BLOCK_ENTITY, Szar.TRACKER_BLOCK_ENTITY,
TGTrackerBlockRenderer::new TGTrackerBlockRenderer::new
); );
BlockEntityRendererFactories.register(
Szar.BACKROOMS_LIGHT_ENTITY,
BackroomsLightBlockEntityRenderer::new);
HandledScreens.register(Szar.SLOT_MACHINE_SCREEN_HANDLER_TYPE, SlotMachineScreen::new); HandledScreens.register(Szar.SLOT_MACHINE_SCREEN_HANDLER_TYPE, SlotMachineScreen::new);
HandledScreens.register(Szar.ROULETTE_SCREEN_HANDLER_TYPE, RouletteScreen::new); HandledScreens.register(Szar.ROULETTE_SCREEN_HANDLER_TYPE, RouletteScreen::new);
EntityRendererRegistry.register(Szar.BULLET, BulletRenderer::new); EntityRendererRegistry.register(Szar.BULLET, BulletRenderer::new);

View File

@@ -92,16 +92,11 @@ public class BackroomsChunkGenerator extends ChunkGenerator {
if (isGlowstone) { if (isGlowstone) {
long lightRoll = hash(worldX * 53 + 7, worldZ * 47 + 13); long lightRoll = hash(worldX * 53 + 7, worldZ * 47 + 13);
int roll = (int)(Math.abs(lightRoll) % 100); int roll = (int)(Math.abs(lightRoll) % 100);
if (roll < 95) { if (roll < 98) {
// 95% ON // 98% ON (flickering determined by block entity in generateFeatures)
ceilingBlock = Szar.BACKROOMS_LIGHT.getDefaultState() ceilingBlock = Szar.BACKROOMS_LIGHT.getDefaultState()
.with(BackroomsLightBlock.LIGHT_STATE, .with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.ON); BackroomsLightBlock.LightState.ON);
} else if (roll < 98) {
// 3% FLICKERING_ON
ceilingBlock = Szar.BACKROOMS_LIGHT.getDefaultState()
.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.FLICKERING_ON);
} else { } else {
// 2% missing — ceiling block, no light // 2% missing — ceiling block, no light
ceilingBlock = Szar.CEILING.getDefaultState(); ceilingBlock = Szar.CEILING.getDefaultState();
@@ -273,9 +268,10 @@ public class BackroomsChunkGenerator extends ChunkGenerator {
int chunkX = chunk.getPos().getStartX(); int chunkX = chunk.getPos().getStartX();
int chunkZ = chunk.getPos().getStartZ(); int chunkZ = chunk.getPos().getStartZ();
// Initialize wall block entities
for (int lx = 0; lx < 16; lx++) { for (int lx = 0; lx < 16; lx++) {
for (int lz = 0; lz < 16; lz++) { for (int lz = 0; lz < 16; lz++) {
// Initialize wall block entities
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
mutable.set(chunkX + lx, y, chunkZ + lz); mutable.set(chunkX + lx, y, chunkZ + lz);
if (world.getBlockState(mutable).getBlock() instanceof WallBlock) { if (world.getBlockState(mutable).getBlock() instanceof WallBlock) {
@@ -286,16 +282,20 @@ public class BackroomsChunkGenerator extends ChunkGenerator {
} }
} }
} }
// Inside the lx/lz loop in generateFeatures, after the wall block section:
mutable.set(chunkX + lx, 9, chunkZ + lz); // CEILING_Y = 9 // Initialize light block entities
mutable.set(chunkX + lx, 9, chunkZ + lz);
BlockState lightState = world.getBlockState(mutable); BlockState lightState = world.getBlockState(mutable);
if (lightState.getBlock() instanceof BackroomsLightBlock) { if (lightState.getBlock() instanceof BackroomsLightBlock) {
if (world.getBlockEntity(mutable) == null) { // Always re-set to force block entity creation — no null check needed
world.setBlockState(mutable, lightState, Block.NOTIFY_ALL); world.setBlockState(mutable, lightState, Block.NOTIFY_ALL);
} BlockPos immutable = mutable.toImmutable();
if (world.getBlockEntity(mutable) instanceof BackroomsLightBlockEntity light) { if (world.getBlockEntity(immutable) instanceof BackroomsLightBlockEntity light) {
long typeRoll = hash(chunkX + lx, chunkZ + lz);
light.isFlickering = (Math.abs(typeRoll) % 10) >= 8;
light.flickerOffset = (int)(Math.abs(hash(chunkX + lx, chunkZ + lz)) % 100); light.flickerOffset = (int)(Math.abs(hash(chunkX + lx, chunkZ + lz)) % 100);
light.flickerTimer = light.flickerOffset; // stagger initial timers light.flickerTimer = light.flickerOffset;
light.brightness = 1.0f;
light.markDirty(); light.markDirty();
} }
} }

View File

@@ -15,7 +15,7 @@ import org.jetbrains.annotations.Nullable;
public class BackroomsLightBlock extends BlockWithEntity { public class BackroomsLightBlock extends BlockWithEntity {
public enum LightState implements StringIdentifiable { public enum LightState implements StringIdentifiable {
ON, OFF, FLICKERING_ON, FLICKERING_OFF; ON, OFF;
@Override @Override
public String asString() { public String asString() {
@@ -59,9 +59,6 @@ public class BackroomsLightBlock extends BlockWithEntity {
// Light level based on state // Light level based on state
public static int getLightLevel(BlockState state) { public static int getLightLevel(BlockState state) {
return switch (state.get(LIGHT_STATE)) { return state.get(LIGHT_STATE) == LightState.ON ? 15 : 0;
case ON, FLICKERING_ON -> 15;
case OFF, FLICKERING_OFF -> 0;
};
} }
} }

View File

@@ -10,12 +10,13 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
public class BackroomsLightBlockEntity extends BlockEntity { public class BackroomsLightBlockEntity extends BlockEntity {
public float brightness = 1.0f; // 0.0 = black, 1.0 = full bright
// Random offset so each light flickers at different times // Random offset so each light flickers at different times
public int flickerOffset = 0; public int flickerOffset = 0;
// How many ticks until next state toggle during flicker // How many ticks until next state toggle during flicker
public int flickerTimer = 0; public int flickerTimer = 0;
private boolean initialized = false; private boolean initialized = false;
public boolean isFlickering = false; // true for lights generated as flickering type
public BackroomsLightBlockEntity(BlockPos pos, BlockState state) { public BackroomsLightBlockEntity(BlockPos pos, BlockState state) {
super(Szar.BACKROOMS_LIGHT_ENTITY, pos, state); super(Szar.BACKROOMS_LIGHT_ENTITY, pos, state);
@@ -38,6 +39,7 @@ public class BackroomsLightBlockEntity extends BlockEntity {
nbt.putInt("FlickerOffset", flickerOffset); nbt.putInt("FlickerOffset", flickerOffset);
nbt.putInt("FlickerTimer", flickerTimer); nbt.putInt("FlickerTimer", flickerTimer);
nbt.putBoolean("Initialized", initialized); nbt.putBoolean("Initialized", initialized);
nbt.putFloat("Brightness", brightness);
} }
@Override @Override
@@ -46,6 +48,7 @@ public class BackroomsLightBlockEntity extends BlockEntity {
flickerOffset = nbt.getInt("FlickerOffset"); flickerOffset = nbt.getInt("FlickerOffset");
flickerTimer = nbt.getInt("FlickerTimer"); flickerTimer = nbt.getInt("FlickerTimer");
initialized = nbt.getBoolean("Initialized"); initialized = nbt.getBoolean("Initialized");
brightness = nbt.getFloat("Brightness");
} }
@Override @Override

View File

@@ -6,23 +6,24 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class BackroomsLightManager { public class BackroomsLightManager {
// Global event state
public enum GlobalEvent { NONE, FLICKER, BLACKOUT } public enum GlobalEvent { NONE, FLICKER, BLACKOUT }
public static GlobalEvent currentEvent = GlobalEvent.NONE; public static GlobalEvent currentEvent = GlobalEvent.NONE;
public static int eventTimer = 0; // ticks remaining in current event public static int eventTimer = 0;
public static int cooldownTimer = 0; // ticks until next event check public static int cooldownTimer = 3600;
// Flicker event duration: 3-8 seconds
private static final int FLICKER_DURATION_MIN = 60; private static final int FLICKER_DURATION_MIN = 60;
private static final int FLICKER_DURATION_MAX = 160; private static final int FLICKER_DURATION_MAX = 160;
// Blackout duration: 50-100 seconds
private static final int BLACKOUT_MIN = 1000; private static final int BLACKOUT_MIN = 1000;
private static final int BLACKOUT_MAX = 2000; private static final int BLACKOUT_MAX = 2000;
// Check for new event every ~3 minutes
private static final int EVENT_COOLDOWN = 3600; private static final int EVENT_COOLDOWN = 3600;
public static void register() { public static void register() {
@@ -33,7 +34,6 @@ public class BackroomsLightManager {
ServerWorld backrooms = server.getWorld(Szar.BACKROOMS_KEY); ServerWorld backrooms = server.getWorld(Szar.BACKROOMS_KEY);
if (backrooms == null) return; if (backrooms == null) return;
// Handle event timers
if (currentEvent != GlobalEvent.NONE) { if (currentEvent != GlobalEvent.NONE) {
eventTimer--; eventTimer--;
if (eventTimer <= 0) { if (eventTimer <= 0) {
@@ -42,14 +42,12 @@ public class BackroomsLightManager {
} else { } else {
cooldownTimer--; cooldownTimer--;
if (cooldownTimer <= 0) { if (cooldownTimer <= 0) {
// Roll for new event
int roll = backrooms.random.nextInt(100); int roll = backrooms.random.nextInt(100);
if (roll < 30) { if (roll < 30) {
startBlackout(backrooms); startBlackout(backrooms);
} else if (roll < 63) { // 30% blackout + 33% flicker } else if (roll < 63) {
startFlicker(backrooms); startFlicker(backrooms);
} else { } else {
// No event — reset cooldown
cooldownTimer = EVENT_COOLDOWN; cooldownTimer = EVENT_COOLDOWN;
} }
} }
@@ -67,79 +65,92 @@ public class BackroomsLightManager {
currentEvent = GlobalEvent.BLACKOUT; currentEvent = GlobalEvent.BLACKOUT;
eventTimer = BLACKOUT_MIN + world.random.nextInt(BLACKOUT_MAX - BLACKOUT_MIN); eventTimer = BLACKOUT_MIN + world.random.nextInt(BLACKOUT_MAX - BLACKOUT_MIN);
cooldownTimer = EVENT_COOLDOWN; cooldownTimer = EVENT_COOLDOWN;
// Set all lights to brightness 0
// Immediately turn off all ON lights in loaded chunks forEachLightEntity(world, entity -> {
setAllLightsOff(world); entity.brightness = 0.0f;
entity.markDirty();
});
} }
private static void endEvent(ServerWorld world) { private static void endEvent(ServerWorld world) {
if (currentEvent == GlobalEvent.BLACKOUT) { // Restore all lights to full brightness
// Restore all lights forEachLightEntity(world, entity -> {
setAllLightsOn(world); entity.brightness = 1.0f;
} entity.markDirty();
});
currentEvent = GlobalEvent.NONE; currentEvent = GlobalEvent.NONE;
eventTimer = 0; eventTimer = 0;
cooldownTimer = EVENT_COOLDOWN; cooldownTimer = EVENT_COOLDOWN;
} }
private static void setAllLightsOff(ServerWorld world) { // Called per-light from BackroomsLightBlockEntity.tick
forEachLight(world, (pos, state) -> { public static void tickLight(World world, BlockPos pos, BlockState state,
BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE); BackroomsLightBlockEntity entity) {
if (ls == BackroomsLightBlock.LightState.ON) { if (currentEvent == GlobalEvent.BLACKOUT) return;
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.OFF)); BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE);
} else if (ls == BackroomsLightBlock.LightState.FLICKERING_ON) { if (ls == BackroomsLightBlock.LightState.OFF) return;
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.FLICKERING_OFF)); boolean inFlickerEvent = currentEvent == GlobalEvent.FLICKER;
// Always-flickering lights tick regardless of event
// During flicker event, all ON lights also flicker
if (!entity.isFlickering && !inFlickerEvent) {
// Normal ON light, not in event — ensure full brightness
if (entity.brightness != 1.0f) {
entity.brightness = 1.0f;
entity.markDirty();
} }
}); return;
}
entity.flickerTimer--;
if (entity.flickerTimer > 0) return;
// Random new brightness and timer
float newBrightness;
if (world.random.nextFloat() < 0.3f) {
// 30% chance of a dim flicker
newBrightness = 0.1f + world.random.nextFloat() * 0.4f;
} else {
// 70% chance of full or near-full
newBrightness = 0.7f + world.random.nextFloat() * 0.3f;
}
entity.brightness = newBrightness;
entity.flickerTimer = 2 + world.random.nextInt(8 + (entity.flickerOffset % 5));
entity.markDirty();
} }
private static void setAllLightsOn(ServerWorld world) { private static void forEachLightEntity(ServerWorld world,
forEachLight(world, (pos, state) -> { Consumer<BackroomsLightBlockEntity> consumer) {
BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE); for (WorldChunk chunk : getLoadedChunks(world)) {
if (ls == BackroomsLightBlock.LightState.OFF) {
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.ON));
} else if (ls == BackroomsLightBlock.LightState.FLICKERING_OFF) {
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.FLICKERING_ON));
}
});
}
private static void forEachLight(ServerWorld world, java.util.function.BiConsumer<BlockPos, BlockState> consumer) {
for (net.minecraft.world.chunk.WorldChunk chunk : getLoadedChunks(world)) {
BlockPos.Mutable mutable = new BlockPos.Mutable();
int cx = chunk.getPos().getStartX(); int cx = chunk.getPos().getStartX();
int cz = chunk.getPos().getStartZ(); int cz = chunk.getPos().getStartZ();
BlockPos.Mutable mutable = new BlockPos.Mutable();
for (int lx = 0; lx < 16; lx++) { for (int lx = 0; lx < 16; lx++) {
for (int lz = 0; lz < 16; lz++) { for (int lz = 0; lz < 16; lz++) {
// Ceiling Y in backrooms is 9
mutable.set(cx + lx, 9, cz + lz); mutable.set(cx + lx, 9, cz + lz);
BlockState state = world.getBlockState(mutable); if (world.getBlockEntity(mutable.toImmutable())
if (state.getBlock() instanceof BackroomsLightBlock) { instanceof BackroomsLightBlockEntity entity) {
consumer.accept(mutable.toImmutable(), state); consumer.accept(entity);
} }
} }
} }
} }
} }
private static java.util.List<net.minecraft.world.chunk.WorldChunk> getLoadedChunks(ServerWorld world) { private static List<WorldChunk> getLoadedChunks(ServerWorld world) {
java.util.List<net.minecraft.world.chunk.WorldChunk> chunks = new java.util.ArrayList<>(); List<WorldChunk> chunks = new ArrayList<>();
// Iterate over all players and collect chunks around them
for (net.minecraft.server.network.ServerPlayerEntity player : world.getPlayers()) { for (net.minecraft.server.network.ServerPlayerEntity player : world.getPlayers()) {
int playerChunkX = (int) player.getX() >> 4; int pcx = (int) player.getX() >> 4;
int playerChunkZ = (int) player.getZ() >> 4; int pcz = (int) player.getZ() >> 4;
int viewDistance = world.getServer().getPlayerManager().getViewDistance(); int viewDistance = world.getServer().getPlayerManager().getViewDistance();
for (int cx = playerChunkX - viewDistance; cx <= playerChunkX + viewDistance; cx++) { for (int cx = pcx - viewDistance; cx <= pcx + viewDistance; cx++) {
for (int cz = playerChunkZ - viewDistance; cz <= playerChunkZ + viewDistance; cz++) { for (int cz = pcz - viewDistance; cz <= pcz + viewDistance; cz++) {
if (world.getChunkManager().isChunkLoaded(cx, cz)) { if (world.getChunkManager().isChunkLoaded(cx, cz)) {
net.minecraft.world.chunk.WorldChunk chunk = world.getChunk(cx, cz); WorldChunk chunk = world.getChunk(cx, cz);
if (!chunks.contains(chunk)) { if (!chunks.contains(chunk)) chunks.add(chunk);
chunks.add(chunk);
}
} }
} }
} }
@@ -147,67 +158,22 @@ public class BackroomsLightManager {
return chunks; return chunks;
} }
// Called per-light from BackroomsLightBlockEntity.tick
public static void tickLight(World world, BlockPos pos, BlockState state,
BackroomsLightBlockEntity entity) {
BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE);
// During blackout, lights are already set off — don't touch them
if (currentEvent == GlobalEvent.BLACKOUT) return;
// Only flickering lights and lights during flicker events need ticking
boolean isFlickering = ls == BackroomsLightBlock.LightState.FLICKERING_ON
|| ls == BackroomsLightBlock.LightState.FLICKERING_OFF;
boolean inFlickerEvent = currentEvent == GlobalEvent.FLICKER;
if (!isFlickering && !inFlickerEvent) return;
// Decrement timer
entity.flickerTimer--;
if (entity.flickerTimer > 0) return;
// Toggle state and set new random timer
// Flickering lights: 2-8 ticks per toggle
// Event flicker: same but offset by entity's flickerOffset
int baseTime = 2 + world.random.nextInt(7);
if (inFlickerEvent && !isFlickering) {
// Normal ON light during flicker event — toggle it
if (ls == BackroomsLightBlock.LightState.ON) {
// Apply offset so not all lights flicker simultaneously
if (entity.flickerTimer == 0 && world.getTime() % 3 == entity.flickerOffset % 3) {
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.OFF));
entity.flickerTimer = baseTime;
entity.markDirty();
}
return;
} else if (ls == BackroomsLightBlock.LightState.OFF
&& currentEvent == GlobalEvent.FLICKER) {
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE,
BackroomsLightBlock.LightState.ON));
entity.flickerTimer = baseTime;
entity.markDirty();
return;
}
}
if (isFlickering) {
BackroomsLightBlock.LightState next =
ls == BackroomsLightBlock.LightState.FLICKERING_ON
? BackroomsLightBlock.LightState.FLICKERING_OFF
: BackroomsLightBlock.LightState.FLICKERING_ON;
world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE, next));
entity.flickerTimer = baseTime;
entity.markDirty();
}
}
public static void forceRestoreAllLights(ServerWorld world) { public static void forceRestoreAllLights(ServerWorld world) {
setAllLightsOn(world); forEachLightEntity(world, entity -> {
entity.brightness = 1.0f;
entity.markDirty();
});
currentEvent = GlobalEvent.NONE;
eventTimer = 0;
cooldownTimer = EVENT_COOLDOWN;
} }
public static void forceBlackout(ServerWorld world) { public static void forceBlackout(ServerWorld world) {
setAllLightsOff(world); forEachLightEntity(world, entity -> {
entity.brightness = 0.0f;
entity.markDirty();
});
currentEvent = GlobalEvent.BLACKOUT;
eventTimer = 3600;
} }
} }