From f72b01b68ba12365f1c6a68043e0e3c2e593fb12 Mon Sep 17 00:00:00 2001 From: rardog Date: Thu, 24 Sep 2026 13:09:53 +0300 Subject: [PATCH] Add in-world zone visualizer, note particles, scenario shuffle, and step editor --- .../example/soundmod/client/ClientHooks.java | 1 + .../client/render/ZoneWorldRenderer.java | 138 ++++++++++++++++++ .../client/screen/SoundPlayerScreen.java | 121 ++++++++++++++- .../soundmod/common/ScenarioQueue.java | 3 + .../gameplay/block/SoundPlayerBlock.java | 30 +++- .../gameplay/tile/SoundPlayerTileEntity.java | 15 ++ 6 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/example/soundmod/client/render/ZoneWorldRenderer.java diff --git a/src/main/java/com/example/soundmod/client/ClientHooks.java b/src/main/java/com/example/soundmod/client/ClientHooks.java index cbbcb3a..3793513 100644 --- a/src/main/java/com/example/soundmod/client/ClientHooks.java +++ b/src/main/java/com/example/soundmod/client/ClientHooks.java @@ -55,6 +55,7 @@ public final class ClientHooks { @SubscribeEvent public static void onRenderWorld(RenderWorldLastEvent event) { DebugOverlayRenderer.render(event.getMatrixStack(), event.getPartialTicks()); + com.example.soundmod.client.render.ZoneWorldRenderer.render(event.getMatrixStack(), event.getPartialTicks()); } @SubscribeEvent diff --git a/src/main/java/com/example/soundmod/client/render/ZoneWorldRenderer.java b/src/main/java/com/example/soundmod/client/render/ZoneWorldRenderer.java new file mode 100644 index 0000000..212218f --- /dev/null +++ b/src/main/java/com/example/soundmod/client/render/ZoneWorldRenderer.java @@ -0,0 +1,138 @@ +package com.example.soundmod.client.render; + +import com.example.soundmod.client.screen.SoundPlayerScreen; +import com.example.soundmod.common.ZoneShape; +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.vertex.IVertexBuilder; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.IRenderTypeBuffer; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Matrix3f; +import net.minecraft.util.math.vector.Matrix4f; +import net.minecraft.util.math.vector.Vector3d; + +public final class ZoneWorldRenderer { + private ZoneWorldRenderer() {} + + public static void render(MatrixStack stack, float partialTicks) { + SoundPlayerScreen screen = SoundPlayerScreen.activeScreen; + if (screen == null || !SoundPlayerScreen.showZoneVisualizer) { + return; + } + Minecraft mc = Minecraft.getInstance(); + if (mc.player == null || mc.level == null) { + return; + } + + BlockPos blockPos = screen.getBlockPos(); + if (blockPos == null) { + return; + } + + Vector3d camera = mc.gameRenderer.getMainCamera().getPosition(); + IRenderTypeBuffer.Impl bufferSource = mc.renderBuffers().bufferSource(); + IVertexBuilder lineBuilder = bufferSource.getBuffer(RenderType.lines()); + + // 1. Highlight the edited block itself (soft gold) + double bx = blockPos.getX() - camera.x; + double by = blockPos.getY() - camera.y; + double bz = blockPos.getZ() - camera.z; + AxisAlignedBB blockBox = new AxisAlignedBB(bx, by, bz, bx + 1.0, by + 1.0, bz + 1.0); + WorldRenderer.renderLineBox(stack, lineBuilder, blockBox, 1.0f, 0.85f, 0.2f, 0.9f); + + // 2. Zone center + double cx; + double cy; + double cz; + if (screen.isZoneCustomCoords()) { + cx = (double) screen.getZoneSourceX() + 0.5 - camera.x; + cy = (double) screen.getZoneSourceY() + 0.5 - camera.y; + cz = (double) screen.getZoneSourceZ() + 0.5 - camera.z; + // Draw connecting line from block center to custom center + drawLine(stack, lineBuilder, bx + 0.5, by + 0.5, bz + 0.5, cx, cy, cz, 1.0f, 0.6f, 0.1f, 0.85f); + } else { + cx = bx + 0.5; + cy = by + 0.5; + cz = bz + 0.5; + } + + // 3. Render Zone depending on shape + if (screen.getZoneShape() == ZoneShape.SPHERE) { + float radius = Math.max(0.5f, screen.getZoneRadius()); + // Equatorial and vertical rings in cyan + drawCircleRing(stack, lineBuilder, cx, cy, cz, radius, 0, 0.2f, 0.85f, 1.0f, 0.95f); + drawCircleRing(stack, lineBuilder, cx, cy, cz, radius, 1, 0.2f, 0.85f, 1.0f, 0.75f); + drawCircleRing(stack, lineBuilder, cx, cy, cz, radius, 2, 0.2f, 0.85f, 1.0f, 0.75f); + + // Bounding box around the sphere (light translucent) + AxisAlignedBB sphereBox = new AxisAlignedBB(cx - radius, cy - radius, cz - radius, cx + radius, cy + radius, cz + radius); + WorldRenderer.renderLineBox(stack, lineBuilder, sphereBox, 0.15f, 0.6f, 0.9f, 0.4f); + } else { + // BOX shape + float hx = Math.max(0.5f, screen.getZoneBoxX()); + float hy = Math.max(0.5f, screen.getZoneBoxY()); + float hz = Math.max(0.5f, screen.getZoneBoxZ()); + AxisAlignedBB zoneBox = new AxisAlignedBB(cx - hx, cy - hy, cz - hz, cx + hx, cy + hy, cz + hz); + WorldRenderer.renderLineBox(stack, lineBuilder, zoneBox, 0.25f, 1.0f, 0.5f, 0.95f); + } + + bufferSource.endBatch(RenderType.lines()); + } + + private static void drawCircleRing(MatrixStack stack, IVertexBuilder builder, double cx, double cy, double cz, double r, int plane, float red, float green, float blue, float alpha) { + int segments = 36; + for (int i = 0; i < segments; i++) { + double a1 = (i * 2.0 * Math.PI) / segments; + double a2 = ((i + 1) * 2.0 * Math.PI) / segments; + double x1; + double y1; + double z1; + double x2; + double y2; + double z2; + if (plane == 0) { + x1 = cx + Math.cos(a1) * r; + y1 = cy; + z1 = cz + Math.sin(a1) * r; + x2 = cx + Math.cos(a2) * r; + y2 = cy; + z2 = cz + Math.sin(a2) * r; + } else if (plane == 1) { + x1 = cx + Math.cos(a1) * r; + y1 = cy + Math.sin(a1) * r; + z1 = cz; + x2 = cx + Math.cos(a2) * r; + y2 = cy + Math.sin(a2) * r; + z2 = cz; + } else { + x1 = cx; + y1 = cy + Math.sin(a1) * r; + z1 = cz + Math.cos(a1) * r; + x2 = cx; + y2 = cy + Math.sin(a2) * r; + z2 = cz + Math.cos(a2) * r; + } + drawLine(stack, builder, x1, y1, z1, x2, y2, z2, red, green, blue, alpha); + } + } + + private static void drawLine(MatrixStack stack, IVertexBuilder builder, double x1, double y1, double z1, double x2, double y2, double z2, float r, float g, float b, float a) { + Matrix4f m = stack.last().pose(); + Matrix3f n = stack.last().normal(); + float dx = (float)(x2 - x1); + float dy = (float)(y2 - y1); + float dz = (float)(z2 - z1); + float len = MathHelper.sqrt(dx * dx + dy * dy + dz * dz); + if (len > 1.0e-5f) { + dx /= len; + dy /= len; + dz /= len; + } + builder.vertex(m, (float)x1, (float)y1, (float)z1).color(r, g, b, a).normal(n, dx, dy, dz).endVertex(); + builder.vertex(m, (float)x2, (float)y2, (float)z2).color(r, g, b, a).normal(n, dx, dy, dz).endVertex(); + } +} diff --git a/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java b/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java index 492e43b..7d1367b 100644 --- a/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java +++ b/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java @@ -61,8 +61,13 @@ public class SoundPlayerScreen extends ContainerScreen { private SoundSettings localSettings = new SoundSettings(); private final List sourceScenarioSteps = new ArrayList<>(); private final List zoneScenarioSteps = new ArrayList<>(); + public static SoundPlayerScreen activeScreen; + public static boolean showZoneVisualizer = true; + private boolean sourceScenarioLoop; private boolean zoneScenarioLoop; + private boolean sourceScenarioShuffle; + private boolean zoneScenarioShuffle; private boolean editingZoneScenario; private boolean sourceEnabled = true; private boolean zoneEnabled = true; @@ -123,6 +128,7 @@ public class SoundPlayerScreen extends ContainerScreen { private Button zoneStopOnExitButton; private Button zoneUseCustomCoordsButton; private Button myPosButton; + private Button zoneVisualizerButton; private TextFieldWidget zoneRadiusField; private TextFieldWidget zoneBoxXField; private TextFieldWidget zoneBoxYField; @@ -134,9 +140,11 @@ public class SoundPlayerScreen extends ContainerScreen { // Controls - Scenario private Button scenarioTargetButton; private Button scenarioLoopButton; + private Button scenarioShuffleButton; private TextFieldWidget scenarioSoundField; private TextFieldWidget scenarioDelayField; private Button scenarioUseCurrentBtn; + private Button scenarioSetButton; private Button scenarioAddButton; private Button scenarioRemoveButton; private Button scenarioUpButton; @@ -195,9 +203,11 @@ public class SoundPlayerScreen extends ContainerScreen { this.sourceScenarioSteps.clear(); this.sourceScenarioSteps.addAll(tile.getScenario().steps); this.sourceScenarioLoop = tile.getScenario().loop; + this.sourceScenarioShuffle = tile.getScenario().shuffle; this.zoneScenarioSteps.clear(); this.zoneScenarioSteps.addAll(tile.getZoneScenario().steps); this.zoneScenarioLoop = tile.getZoneScenario().loop; + this.zoneScenarioShuffle = tile.getZoneScenario().shuffle; this.sourceEnabled = tile.isSourceEnabled(); this.zoneEnabled = tile.isZoneEnabled(); this.wizardCompleted = tile.isWizardCompleted(); @@ -214,6 +224,7 @@ public class SoundPlayerScreen extends ContainerScreen { this.zoneSourceY = tile.getZoneSourceY(); this.zoneSourceZ = tile.getZoneSourceZ(); } + activeScreen = this; this.initFieldsAndButtons(); this.addTabButtons(); @@ -363,6 +374,10 @@ public class SoundPlayerScreen extends ContainerScreen { this.zoneBoxXField = this.createField(x + 18, y + 68, 42, this.floatText(this.zoneBoxX, 1)); this.zoneBoxYField = this.createField(x + 78, y + 68, 42, this.floatText(this.zoneBoxY, 1)); this.zoneBoxZField = this.createField(x + 138, y + 68, 42, this.floatText(this.zoneBoxZ, 1)); + this.zoneVisualizerButton = this.addButtonTracked(new Button(x + 150, y + 68, 100, 20, this.boolLabel("Вид в мире", showZoneVisualizer), b -> { + showZoneVisualizer = !showZoneVisualizer; + b.setMessage(this.boolLabel("Вид в мире", showZoneVisualizer)); + }), this.zoneButtons); this.zoneAutoStartButton = this.addButtonTracked(new Button(x, y + 94, 122, 20, this.boolLabel("Авто-вход", this.zoneAutoStartOnEnter), b -> { this.zoneAutoStartOnEnter = !this.zoneAutoStartOnEnter; @@ -389,23 +404,30 @@ public class SoundPlayerScreen extends ContainerScreen { this.zoneSourceZField = this.createField(x + 178, y + 146, 58, Integer.toString(this.zoneSourceZ)); // --- TAB 5: SCENARIO --- - this.scenarioTargetButton = this.addButtonTracked(new Button(x, y + 42, 122, 20, new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Источник"), b -> { + this.scenarioTargetButton = this.addButtonTracked(new Button(x, y + 42, 78, 20, new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Блок"), b -> { this.editingZoneScenario = !this.editingZoneScenario; - b.setMessage(new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Источник")); + b.setMessage(new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Блок")); this.selectedScenarioIndex = -1; - this.scenarioLoopButton.setMessage(this.boolLabel("Цикл очереди", this.activeScenarioLoop())); + this.scenarioLoopButton.setMessage(this.boolLabel("Цикл", this.activeScenarioLoop())); + this.scenarioShuffleButton.setMessage(this.boolLabel("Случайно", this.activeScenarioShuffle())); }), this.scenarioButtons); - this.scenarioLoopButton = this.addButtonTracked(new Button(x + 126, y + 42, 124, 20, this.boolLabel("Цикл очереди", this.activeScenarioLoop()), b -> { + this.scenarioLoopButton = this.addButtonTracked(new Button(x + 80, y + 42, 84, 20, this.boolLabel("Цикл", this.activeScenarioLoop()), b -> { this.setActiveScenarioLoop(!this.activeScenarioLoop()); - b.setMessage(this.boolLabel("Цикл очереди", this.activeScenarioLoop())); + b.setMessage(this.boolLabel("Цикл", this.activeScenarioLoop())); }), this.scenarioButtons); - this.scenarioSoundField = this.createField(x, y + 70, 126, ""); - this.scenarioUseCurrentBtn = this.addButtonTracked(new Button(x + 130, y + 69, 54, 20, new StringTextComponent("Текущ."), b -> { + this.scenarioShuffleButton = this.addButtonTracked(new Button(x + 166, y + 42, 84, 20, this.boolLabel("Случайно", this.activeScenarioShuffle()), b -> { + this.setActiveScenarioShuffle(!this.activeScenarioShuffle()); + b.setMessage(this.boolLabel("Случайно", this.activeScenarioShuffle())); + }), this.scenarioButtons); + + this.scenarioSoundField = this.createField(x, y + 70, 116, ""); + this.scenarioUseCurrentBtn = this.addButtonTracked(new Button(x + 118, y + 69, 44, 20, new StringTextComponent("Тек."), b -> { this.scenarioSoundField.setValue(this.soundIdField.getValue().trim()); }), this.scenarioButtons); - this.scenarioDelayField = this.createField(x + 192, y + 70, 58, "20"); + this.scenarioDelayField = this.createField(x + 164, y + 70, 38, "20"); + this.scenarioSetButton = this.addButtonTracked(new Button(x + 204, y + 69, 46, 20, new StringTextComponent("Задать"), b -> this.updateSelectedScenarioStep()), this.scenarioButtons); this.scenarioAddButton = this.addButtonTracked(new Button(x, y + 96, 60, 20, new StringTextComponent("+ Шаг"), b -> this.addScenarioStep()), this.scenarioButtons); this.scenarioRemoveButton = this.addButtonTracked(new Button(x + 64, y + 96, 60, 20, new StringTextComponent("- Шаг"), b -> this.removeScenarioStep()), this.scenarioButtons); @@ -597,10 +619,12 @@ public class SoundPlayerScreen extends ContainerScreen { payload.put("settings", (INBT) this.localSettings.toNbt()); ScenarioQueue sourceQ = new ScenarioQueue(); sourceQ.loop = this.sourceScenarioLoop; + sourceQ.shuffle = this.sourceScenarioShuffle; sourceQ.steps.addAll(this.sourceScenarioSteps); payload.put("scenario", (INBT) sourceQ.toNbt()); ScenarioQueue zoneQ = new ScenarioQueue(); zoneQ.loop = this.zoneScenarioLoop; + zoneQ.shuffle = this.zoneScenarioShuffle; zoneQ.steps.addAll(this.zoneScenarioSteps); payload.put("zoneScenario", (INBT) zoneQ.toNbt()); payload.putBoolean("sourceEnabled", this.sourceEnabled); @@ -646,10 +670,12 @@ public class SoundPlayerScreen extends ContainerScreen { this.sourceScenarioSteps.clear(); this.sourceScenarioSteps.addAll(sourceQ.steps); this.sourceScenarioLoop = sourceQ.loop; + this.sourceScenarioShuffle = sourceQ.shuffle; ScenarioQueue zoneQ = ScenarioQueue.fromNbt(payload.getCompound("zoneScenario")); this.zoneScenarioSteps.clear(); this.zoneScenarioSteps.addAll(zoneQ.steps); this.zoneScenarioLoop = zoneQ.loop; + this.zoneScenarioShuffle = zoneQ.shuffle; this.sourceEnabled = payload.contains("sourceEnabled") && payload.getBoolean("sourceEnabled"); this.zoneEnabled = payload.contains("zoneEnabled") && payload.getBoolean("zoneEnabled"); this.wizardCompleted = payload.getBoolean("wizardCompleted"); @@ -769,6 +795,70 @@ public class SoundPlayerScreen extends ContainerScreen { } } + private boolean activeScenarioShuffle() { + return this.editingZoneScenario ? this.zoneScenarioShuffle : this.sourceScenarioShuffle; + } + + private void setActiveScenarioShuffle(boolean shuffle) { + if (this.editingZoneScenario) { + this.zoneScenarioShuffle = shuffle; + } else { + this.sourceScenarioShuffle = shuffle; + } + } + + private void updateSelectedScenarioStep() { + List list = this.activeScenarioList(); + if (this.selectedScenarioIndex >= 0 && this.selectedScenarioIndex < list.size()) { + String sid = this.scenarioSoundField.getValue().trim(); + if (!sid.isEmpty()) { + int delay = this.clampInt(this.parseInt(this.scenarioDelayField.getValue(), 20), 0, 36000); + list.set(this.selectedScenarioIndex, new ScenarioQueue.Step(sid, delay)); + this.saveAndSend(false, false); + } + } + } + + public BlockPos getBlockPos() { + return this.menu != null ? this.menu.getPos() : null; + } + + public ZoneShape getZoneShape() { + return this.zoneShape; + } + + public float getZoneRadius() { + return this.zoneRadius; + } + + public float getZoneBoxX() { + return this.zoneBoxX; + } + + public float getZoneBoxY() { + return this.zoneBoxY; + } + + public float getZoneBoxZ() { + return this.zoneBoxZ; + } + + public boolean isZoneCustomCoords() { + return this.zoneUseCustomCoords; + } + + public int getZoneSourceX() { + return this.zoneSourceX; + } + + public int getZoneSourceY() { + return this.zoneSourceY; + } + + public int getZoneSourceZ() { + return this.zoneSourceZ; + } + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { boolean enter = keyCode == 257 || keyCode == 335; List suggestions = this.filteredSoundEntries(); @@ -838,6 +928,9 @@ public class SoundPlayerScreen extends ContainerScreen { int ry = listY + i * 14; if (mouseX >= listX && mouseX <= (listX + 242) && mouseY >= ry && mouseY < (ry + 14)) { this.selectedScenarioIndex = idx; + ScenarioQueue.Step step = list.get(idx); + this.scenarioSoundField.setValue(step.soundId); + this.scenarioDelayField.setValue(Integer.toString(step.delayTicks)); return true; } } @@ -872,12 +965,24 @@ public class SoundPlayerScreen extends ContainerScreen { return super.mouseScrolled(mouseX, mouseY, delta); } + @Override public void onClose() { + if (activeScreen == this) { + activeScreen = null; + } AudioPlaybackService.get().stop("gui_preview"); this.saveAndSend(false, false); super.onClose(); } + @Override + public void removed() { + if (activeScreen == this) { + activeScreen = null; + } + super.removed(); + } + protected void renderBg(MatrixStack matrixStack, float partialTicks, int mouseX, int mouseY) { int x1 = this.leftPos; int y1 = this.topPos; diff --git a/src/main/java/com/example/soundmod/common/ScenarioQueue.java b/src/main/java/com/example/soundmod/common/ScenarioQueue.java index 449d097..6806910 100644 --- a/src/main/java/com/example/soundmod/common/ScenarioQueue.java +++ b/src/main/java/com/example/soundmod/common/ScenarioQueue.java @@ -17,6 +17,7 @@ import net.minecraft.nbt.ListNBT; public class ScenarioQueue { public final List steps = new ArrayList(); public boolean loop; + public boolean shuffle; public CompoundNBT toNbt() { CompoundNBT tag = new CompoundNBT(); @@ -26,6 +27,7 @@ public class ScenarioQueue { } tag.put("steps", list); tag.putBoolean("loop", this.loop); + tag.putBoolean("shuffle", this.shuffle); return tag; } @@ -36,6 +38,7 @@ public class ScenarioQueue { queue.steps.add(Step.fromNbt(list.getCompound(i))); } queue.loop = tag.getBoolean("loop"); + queue.shuffle = tag.getBoolean("shuffle"); return queue; } diff --git a/src/main/java/com/example/soundmod/gameplay/block/SoundPlayerBlock.java b/src/main/java/com/example/soundmod/gameplay/block/SoundPlayerBlock.java index 3b34987..8ca28b6 100644 --- a/src/main/java/com/example/soundmod/gameplay/block/SoundPlayerBlock.java +++ b/src/main/java/com/example/soundmod/gameplay/block/SoundPlayerBlock.java @@ -20,14 +20,17 @@ */ package com.example.soundmod.gameplay.block; +import com.example.soundmod.client.audio.AudioPlaybackService; import com.example.soundmod.gameplay.tile.SoundPlayerTileEntity; import com.example.soundmod.registry.ModTileEntities; +import java.util.Random; import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.container.INamedContainerProvider; +import net.minecraft.particles.ParticleTypes; import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ActionResultType; @@ -36,23 +39,27 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockRayTraceResult; import net.minecraft.world.IBlockReader; import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.network.NetworkHooks; -public class SoundPlayerBlock -extends Block { +public class SoundPlayerBlock extends Block { public SoundPlayerBlock(AbstractBlock.Properties properties) { super(properties); } + @Override public boolean hasTileEntity(BlockState state) { return true; } + @Override public TileEntity createTileEntity(BlockState state, IBlockReader world) { return ((TileEntityType)ModTileEntities.SOUND_PLAYER_TILE.get()).create(); } - public ActionResultType func_225533_a_(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { + @Override + public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit) { TileEntity te = world.getBlockEntity(pos); if (!world.isClientSide && te instanceof SoundPlayerTileEntity && player instanceof ServerPlayerEntity) { SoundPlayerTileEntity soundTe = (SoundPlayerTileEntity)te; @@ -62,14 +69,27 @@ extends Block { NetworkHooks.openGui((ServerPlayerEntity)((ServerPlayerEntity)player), (INamedContainerProvider)soundTe, (BlockPos)pos); } } - return ActionResultType.sidedSuccess((boolean)world.isClientSide); + return ActionResultType.sidedSuccess(world.isClientSide); } - public void func_220069_a(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean moving) { + @Override + public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean moving) { TileEntity te = world.getBlockEntity(pos); if (te instanceof SoundPlayerTileEntity) { ((SoundPlayerTileEntity)te).onRedstoneChanged(world.hasNeighborSignal(pos)); } } + + @Override + @OnlyIn(Dist.CLIENT) + public void animateTick(BlockState state, World world, BlockPos pos, Random rand) { + if (world.isClientSide && AudioPlaybackService.get().isPlaying("sound_player:" + pos.asLong())) { + double px = (double) pos.getX() + 0.5 + (rand.nextDouble() - 0.5) * 0.5; + double py = (double) pos.getY() + 1.05 + rand.nextDouble() * 0.25; + double pz = (double) pos.getZ() + 0.5 + (rand.nextDouble() - 0.5) * 0.5; + double noteColor = (double) rand.nextInt(25) / 24.0; + world.addParticle(ParticleTypes.NOTE, px, py, pz, noteColor, 0.0, 0.0); + } + } } diff --git a/src/main/java/com/example/soundmod/gameplay/tile/SoundPlayerTileEntity.java b/src/main/java/com/example/soundmod/gameplay/tile/SoundPlayerTileEntity.java index 5ae5fc8..65093c5 100644 --- a/src/main/java/com/example/soundmod/gameplay/tile/SoundPlayerTileEntity.java +++ b/src/main/java/com/example/soundmod/gameplay/tile/SoundPlayerTileEntity.java @@ -509,6 +509,21 @@ ITickableTileEntity { --this.scenarioWaitTicks; return; } + if (this.scenario.shuffle) { + int size = this.scenario.steps.size(); + int nextIdx = 0; + if (size > 1) { + do { + nextIdx = this.level.random.nextInt(size); + } while (nextIdx == this.scenarioStepIndex && this.level.random.nextFloat() > 0.15f); + } + this.scenarioStepIndex = nextIdx; + ScenarioQueue.Step step = this.scenario.steps.get(nextIdx); + this.scenarioWaitTicks = Math.max(0, step.delayTicks); + this.emitScenarioStepPlay(step.soundId); + this.setChanged(); + return; + } if (this.scenarioStepIndex >= this.scenario.steps.size()) { if (this.scenario.loop) { this.scenarioStepIndex = 0;