diff --git a/src/client/java/dev/tggamesyt/szar/client/BackroomsLightBlockEntityRenderer.java b/src/client/java/dev/tggamesyt/szar/client/BackroomsLightBlockEntityRenderer.java new file mode 100644 index 0000000..1f9a1f1 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/BackroomsLightBlockEntityRenderer.java @@ -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 { + + // 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(); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 3926f00..f928cfc 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -367,6 +367,9 @@ public class SzarClient implements ClientModInitializer { Szar.TRACKER_BLOCK_ENTITY, TGTrackerBlockRenderer::new ); + BlockEntityRendererFactories.register( + Szar.BACKROOMS_LIGHT_ENTITY, + BackroomsLightBlockEntityRenderer::new); HandledScreens.register(Szar.SLOT_MACHINE_SCREEN_HANDLER_TYPE, SlotMachineScreen::new); HandledScreens.register(Szar.ROULETTE_SCREEN_HANDLER_TYPE, RouletteScreen::new); EntityRendererRegistry.register(Szar.BULLET, BulletRenderer::new); diff --git a/src/main/java/dev/tggamesyt/szar/BackroomsChunkGenerator.java b/src/main/java/dev/tggamesyt/szar/BackroomsChunkGenerator.java index bf29f6a..d5e3735 100644 --- a/src/main/java/dev/tggamesyt/szar/BackroomsChunkGenerator.java +++ b/src/main/java/dev/tggamesyt/szar/BackroomsChunkGenerator.java @@ -92,16 +92,11 @@ public class BackroomsChunkGenerator extends ChunkGenerator { if (isGlowstone) { long lightRoll = hash(worldX * 53 + 7, worldZ * 47 + 13); int roll = (int)(Math.abs(lightRoll) % 100); - if (roll < 95) { - // 95% ON + if (roll < 98) { + // 98% ON (flickering determined by block entity in generateFeatures) ceilingBlock = Szar.BACKROOMS_LIGHT.getDefaultState() .with(BackroomsLightBlock.LIGHT_STATE, BackroomsLightBlock.LightState.ON); - } else if (roll < 98) { - // 3% FLICKERING_ON - ceilingBlock = Szar.BACKROOMS_LIGHT.getDefaultState() - .with(BackroomsLightBlock.LIGHT_STATE, - BackroomsLightBlock.LightState.FLICKERING_ON); } else { // 2% missing — ceiling block, no light ceilingBlock = Szar.CEILING.getDefaultState(); @@ -273,9 +268,10 @@ public class BackroomsChunkGenerator extends ChunkGenerator { int chunkX = chunk.getPos().getStartX(); int chunkZ = chunk.getPos().getStartZ(); - // Initialize wall block entities for (int lx = 0; lx < 16; lx++) { for (int lz = 0; lz < 16; lz++) { + + // Initialize wall block entities for (int y = 0; y < 64; y++) { mutable.set(chunkX + lx, y, chunkZ + lz); 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); if (lightState.getBlock() instanceof BackroomsLightBlock) { - if (world.getBlockEntity(mutable) == null) { - world.setBlockState(mutable, lightState, Block.NOTIFY_ALL); - } - if (world.getBlockEntity(mutable) instanceof BackroomsLightBlockEntity light) { + // Always re-set to force block entity creation — no null check needed + world.setBlockState(mutable, lightState, Block.NOTIFY_ALL); + BlockPos immutable = mutable.toImmutable(); + 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.flickerTimer = light.flickerOffset; // stagger initial timers + light.flickerTimer = light.flickerOffset; + light.brightness = 1.0f; light.markDirty(); } } diff --git a/src/main/java/dev/tggamesyt/szar/BackroomsLightBlock.java b/src/main/java/dev/tggamesyt/szar/BackroomsLightBlock.java index b31618c..6f71a31 100644 --- a/src/main/java/dev/tggamesyt/szar/BackroomsLightBlock.java +++ b/src/main/java/dev/tggamesyt/szar/BackroomsLightBlock.java @@ -15,7 +15,7 @@ import org.jetbrains.annotations.Nullable; public class BackroomsLightBlock extends BlockWithEntity { public enum LightState implements StringIdentifiable { - ON, OFF, FLICKERING_ON, FLICKERING_OFF; + ON, OFF; @Override public String asString() { @@ -59,9 +59,6 @@ public class BackroomsLightBlock extends BlockWithEntity { // Light level based on state public static int getLightLevel(BlockState state) { - return switch (state.get(LIGHT_STATE)) { - case ON, FLICKERING_ON -> 15; - case OFF, FLICKERING_OFF -> 0; - }; + return state.get(LIGHT_STATE) == LightState.ON ? 15 : 0; } } \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/BackroomsLightBlockEntity.java b/src/main/java/dev/tggamesyt/szar/BackroomsLightBlockEntity.java index 172a848..9c09c5a 100644 --- a/src/main/java/dev/tggamesyt/szar/BackroomsLightBlockEntity.java +++ b/src/main/java/dev/tggamesyt/szar/BackroomsLightBlockEntity.java @@ -10,12 +10,13 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; 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 public int flickerOffset = 0; // How many ticks until next state toggle during flicker public int flickerTimer = 0; private boolean initialized = false; + public boolean isFlickering = false; // true for lights generated as flickering type public BackroomsLightBlockEntity(BlockPos pos, BlockState state) { super(Szar.BACKROOMS_LIGHT_ENTITY, pos, state); @@ -38,6 +39,7 @@ public class BackroomsLightBlockEntity extends BlockEntity { nbt.putInt("FlickerOffset", flickerOffset); nbt.putInt("FlickerTimer", flickerTimer); nbt.putBoolean("Initialized", initialized); + nbt.putFloat("Brightness", brightness); } @Override @@ -46,6 +48,7 @@ public class BackroomsLightBlockEntity extends BlockEntity { flickerOffset = nbt.getInt("FlickerOffset"); flickerTimer = nbt.getInt("FlickerTimer"); initialized = nbt.getBoolean("Initialized"); + brightness = nbt.getFloat("Brightness"); } @Override diff --git a/src/main/java/dev/tggamesyt/szar/BackroomsLightManager.java b/src/main/java/dev/tggamesyt/szar/BackroomsLightManager.java index 0bdcf4b..cbd482e 100644 --- a/src/main/java/dev/tggamesyt/szar/BackroomsLightManager.java +++ b/src/main/java/dev/tggamesyt/szar/BackroomsLightManager.java @@ -6,23 +6,24 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; 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 { - // Global event state public enum GlobalEvent { NONE, FLICKER, BLACKOUT } public static GlobalEvent currentEvent = GlobalEvent.NONE; - public static int eventTimer = 0; // ticks remaining in current event - public static int cooldownTimer = 0; // ticks until next event check + public static int eventTimer = 0; + 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_MAX = 160; - // Blackout duration: 50-100 seconds private static final int BLACKOUT_MIN = 1000; private static final int BLACKOUT_MAX = 2000; - // Check for new event every ~3 minutes private static final int EVENT_COOLDOWN = 3600; public static void register() { @@ -33,7 +34,6 @@ public class BackroomsLightManager { ServerWorld backrooms = server.getWorld(Szar.BACKROOMS_KEY); if (backrooms == null) return; - // Handle event timers if (currentEvent != GlobalEvent.NONE) { eventTimer--; if (eventTimer <= 0) { @@ -42,14 +42,12 @@ public class BackroomsLightManager { } else { cooldownTimer--; if (cooldownTimer <= 0) { - // Roll for new event int roll = backrooms.random.nextInt(100); if (roll < 30) { startBlackout(backrooms); - } else if (roll < 63) { // 30% blackout + 33% flicker + } else if (roll < 63) { startFlicker(backrooms); } else { - // No event — reset cooldown cooldownTimer = EVENT_COOLDOWN; } } @@ -67,79 +65,92 @@ public class BackroomsLightManager { currentEvent = GlobalEvent.BLACKOUT; eventTimer = BLACKOUT_MIN + world.random.nextInt(BLACKOUT_MAX - BLACKOUT_MIN); cooldownTimer = EVENT_COOLDOWN; - - // Immediately turn off all ON lights in loaded chunks - setAllLightsOff(world); + // Set all lights to brightness 0 + forEachLightEntity(world, entity -> { + entity.brightness = 0.0f; + entity.markDirty(); + }); } private static void endEvent(ServerWorld world) { - if (currentEvent == GlobalEvent.BLACKOUT) { - // Restore all lights - setAllLightsOn(world); - } + // Restore all lights to full brightness + forEachLightEntity(world, entity -> { + entity.brightness = 1.0f; + entity.markDirty(); + }); currentEvent = GlobalEvent.NONE; eventTimer = 0; cooldownTimer = EVENT_COOLDOWN; } - private static void setAllLightsOff(ServerWorld world) { - forEachLight(world, (pos, state) -> { - BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE); - if (ls == BackroomsLightBlock.LightState.ON) { - world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE, - BackroomsLightBlock.LightState.OFF)); - } else if (ls == BackroomsLightBlock.LightState.FLICKERING_ON) { - world.setBlockState(pos, state.with(BackroomsLightBlock.LIGHT_STATE, - BackroomsLightBlock.LightState.FLICKERING_OFF)); + // Called per-light from BackroomsLightBlockEntity.tick + public static void tickLight(World world, BlockPos pos, BlockState state, + BackroomsLightBlockEntity entity) { + if (currentEvent == GlobalEvent.BLACKOUT) return; + + BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE); + if (ls == BackroomsLightBlock.LightState.OFF) return; + + 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) { - forEachLight(world, (pos, state) -> { - BackroomsLightBlock.LightState ls = state.get(BackroomsLightBlock.LIGHT_STATE); - 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 consumer) { - for (net.minecraft.world.chunk.WorldChunk chunk : getLoadedChunks(world)) { - BlockPos.Mutable mutable = new BlockPos.Mutable(); + private static void forEachLightEntity(ServerWorld world, + Consumer consumer) { + for (WorldChunk chunk : getLoadedChunks(world)) { int cx = chunk.getPos().getStartX(); int cz = chunk.getPos().getStartZ(); + BlockPos.Mutable mutable = new BlockPos.Mutable(); for (int lx = 0; lx < 16; lx++) { for (int lz = 0; lz < 16; lz++) { - // Ceiling Y in backrooms is 9 mutable.set(cx + lx, 9, cz + lz); - BlockState state = world.getBlockState(mutable); - if (state.getBlock() instanceof BackroomsLightBlock) { - consumer.accept(mutable.toImmutable(), state); + if (world.getBlockEntity(mutable.toImmutable()) + instanceof BackroomsLightBlockEntity entity) { + consumer.accept(entity); } } } } } - private static java.util.List getLoadedChunks(ServerWorld world) { - java.util.List chunks = new java.util.ArrayList<>(); - // Iterate over all players and collect chunks around them + private static List getLoadedChunks(ServerWorld world) { + List chunks = new ArrayList<>(); for (net.minecraft.server.network.ServerPlayerEntity player : world.getPlayers()) { - int playerChunkX = (int) player.getX() >> 4; - int playerChunkZ = (int) player.getZ() >> 4; + int pcx = (int) player.getX() >> 4; + int pcz = (int) player.getZ() >> 4; int viewDistance = world.getServer().getPlayerManager().getViewDistance(); - for (int cx = playerChunkX - viewDistance; cx <= playerChunkX + viewDistance; cx++) { - for (int cz = playerChunkZ - viewDistance; cz <= playerChunkZ + viewDistance; cz++) { + for (int cx = pcx - viewDistance; cx <= pcx + viewDistance; cx++) { + for (int cz = pcz - viewDistance; cz <= pcz + viewDistance; cz++) { if (world.getChunkManager().isChunkLoaded(cx, cz)) { - net.minecraft.world.chunk.WorldChunk chunk = world.getChunk(cx, cz); - if (!chunks.contains(chunk)) { - chunks.add(chunk); - } + WorldChunk chunk = world.getChunk(cx, cz); + if (!chunks.contains(chunk)) chunks.add(chunk); } } } @@ -147,67 +158,22 @@ public class BackroomsLightManager { 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) { - 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) { - setAllLightsOff(world); + forEachLightEntity(world, entity -> { + entity.brightness = 0.0f; + entity.markDirty(); + }); + currentEvent = GlobalEvent.BLACKOUT; + eventTimer = 3600; } } \ No newline at end of file