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 7d1367b..b1861af 100644 --- a/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java +++ b/src/main/java/com/example/soundmod/client/screen/SoundPlayerScreen.java @@ -29,7 +29,6 @@ import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.gui.widget.Widget; import net.minecraft.client.gui.widget.button.Button; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.inventory.container.Container; import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.INBT; import net.minecraft.nbt.JsonToNBT; @@ -44,8 +43,8 @@ public class SoundPlayerScreen extends ContainerScreen { private static final int COLOR_BG_MAIN = 0xEE161B22; private static final int COLOR_BG_INNER = 0xEE1F242C; private static final int COLOR_BG_BOX = 0xCC11141B; + private static final int COLOR_BORDER = 0xFF30363D; private static final int COLOR_ACCENT = 0xFF388BFD; - private static final int COLOR_ACCENT_HOVER = 0xFF58A6FF; private static final int COLOR_TEXT_TITLE = 0xFFFFFF; private static final int COLOR_TEXT_LABEL = 0xFFA0A6B2; private static final int COLOR_TEXT_MUTED = 0xFF767F8D; @@ -54,16 +53,16 @@ public class SoundPlayerScreen extends ContainerScreen { private static final int COLOR_RED = 0xFFF85149; private static final String[] CATEGORIES = new String[]{"music", "ambient", "effect", "voice"}; - private static final int SOUND_LIST_ROWS = 5; + private static final int SOUND_LIST_ROWS = 4; private static final int ROW_HEIGHT = 12; + public static SoundPlayerScreen activeScreen; + public static boolean showZoneVisualizer = true; + private Tab activeTab = Tab.BASIC; 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; @@ -88,28 +87,28 @@ public class SoundPlayerScreen extends ContainerScreen { // Controls - Basic private TextFieldWidget soundIdField; private TextFieldWidget searchField; - private TextFieldWidget volumeField; - private TextFieldWidget pitchField; - private TextFieldWidget radiusField; - private TextFieldWidget priorityField; private Button previewButton; - private Button loopButton; - private Button categoryButton; private Button clearFilterButton; private Button volMinusBtn; + private TextFieldWidget volumeField; private Button volPlusBtn; private Button pitchMinusBtn; + private TextFieldWidget pitchField; private Button pitchPlusBtn; + private TextFieldWidget radiusField; + private TextFieldWidget priorityField; + private Button loopButton; + private Button categoryButton; // Controls - Effects private Button use3dButton; private Button attenuationButton; private Button occlusionButton; + private TextFieldWidget occlusionStrengthField; private Button reverbButton; + private TextFieldWidget reverbStrengthField; private Button dopplerButton; private Button crossfadeButton; - private TextFieldWidget occlusionStrengthField; - private TextFieldWidget reverbStrengthField; private TextFieldWidget fadeInField; private TextFieldWidget fadeOutField; @@ -124,15 +123,15 @@ public class SoundPlayerScreen extends ContainerScreen { // Controls - Zone private Button zoneShapeButton; private Button zonePolicyButton; - private Button zoneAutoStartButton; - private Button zoneStopOnExitButton; - private Button zoneUseCustomCoordsButton; - private Button myPosButton; private Button zoneVisualizerButton; private TextFieldWidget zoneRadiusField; private TextFieldWidget zoneBoxXField; private TextFieldWidget zoneBoxYField; private TextFieldWidget zoneBoxZField; + private Button zoneAutoStartButton; + private Button zoneStopOnExitButton; + private Button zoneUseCustomCoordsButton; + private Button myPosButton; private TextFieldWidget zoneSourceXField; private TextFieldWidget zoneSourceYField; private TextFieldWidget zoneSourceZField; @@ -185,17 +184,22 @@ public class SoundPlayerScreen extends ContainerScreen { public SoundPlayerScreen(SoundPlayerContainer container, PlayerInventory inv, ITextComponent title) { super(container, inv, title); - this.imageWidth = 270; - this.imageHeight = 260; + this.imageWidth = 280; + this.imageHeight = 248; } + @Override protected void init() { super.init(); if (this.minecraft != null) { this.minecraft.keyboardHandler.setSendRepeatsToGui(true); } - this.titleLabelX = 10000; - this.titleLabelY = 10000; + + // Hide default container title and inventory labels + this.titleLabelX = -10000; + this.titleLabelY = -10000; + this.inventoryLabelX = -10000; + this.inventoryLabelY = -10000; SoundPlayerTileEntity tile = ((SoundPlayerContainer) this.menu).getTile(); if (tile != null) { @@ -231,216 +235,236 @@ public class SoundPlayerScreen extends ContainerScreen { this.updateTabVisibility(); } + @Override + protected void renderLabels(MatrixStack matrixStack, int mouseX, int mouseY) { + // Suppress default container title ("Sound Player") and player inventory label ("Инвентарь") + } + private void addTabButtons() { this.tabButtons.clear(); - int x = this.leftPos + 8; - int y = this.topPos + 6; + int x = this.leftPos + 9; + int y = this.topPos + 5; - this.tabButtons.add((Button) this.addButton((Widget) new Button(x, y, 44, 18, new StringTextComponent("Основа"), b -> this.switchTab(Tab.BASIC)))); - this.tabButtons.add((Button) this.addButton((Widget) new Button(x + 46, y, 48, 18, new StringTextComponent("Эффекты"), b -> this.switchTab(Tab.EFFECTS)))); - this.tabButtons.add((Button) this.addButton((Widget) new Button(x + 96, y, 46, 18, new StringTextComponent("Триггер"), b -> this.switchTab(Tab.TRIGGER)))); - this.tabButtons.add((Button) this.addButton((Widget) new Button(x + 144, y, 38, 18, new StringTextComponent("Зона"), b -> this.switchTab(Tab.ZONE)))); - this.tabButtons.add((Button) this.addButton((Widget) new Button(x + 184, y, 50, 18, new StringTextComponent("Сценарий"), b -> this.switchTab(Tab.SCENARIO)))); - this.tabButtons.add((Button) this.addButton((Widget) new Button(x + 236, y, 18, 18, new StringTextComponent("?"), b -> this.switchTab(Tab.HELP)))); + this.tabButtons.add((Button) this.addButton(new Button(x, y, 46, 18, new StringTextComponent("Основа"), b -> this.switchTab(Tab.BASIC)))); + this.tabButtons.add((Button) this.addButton(new Button(x + 48, y, 50, 18, new StringTextComponent("Эффекты"), b -> this.switchTab(Tab.EFFECTS)))); + this.tabButtons.add((Button) this.addButton(new Button(x + 100, y, 48, 18, new StringTextComponent("Триггер"), b -> this.switchTab(Tab.TRIGGER)))); + this.tabButtons.add((Button) this.addButton(new Button(x + 150, y, 40, 18, new StringTextComponent("Зона"), b -> this.switchTab(Tab.ZONE)))); + this.tabButtons.add((Button) this.addButton(new Button(x + 192, y, 52, 18, new StringTextComponent("Сценарий"), b -> this.switchTab(Tab.SCENARIO)))); + this.tabButtons.add((Button) this.addButton(new Button(x + 246, y, 16, 18, new StringTextComponent("?"), b -> this.switchTab(Tab.HELP)))); } private void initFieldsAndButtons() { + // Clear all tracking lists to avoid duplicate entries on resize + this.basicFields.clear(); + this.effectsFields.clear(); + this.triggerFields.clear(); + this.zoneFields.clear(); + this.scenarioFields.clear(); + + this.basicButtons.clear(); + this.effectsButtons.clear(); + this.triggerButtons.clear(); + this.zoneButtons.clear(); + this.scenarioButtons.clear(); + int x = this.leftPos + 10; int y = this.topPos; // --- TAB 1: BASIC --- - this.soundIdField = this.createField(x, y + 40, 186, this.localSettings.soundId); - this.previewButton = this.addButtonTracked(new Button(x + 190, y + 39, 60, 20, new StringTextComponent("▶ Тест"), b -> this.togglePreview()), this.basicButtons); + this.soundIdField = this.createField(x, y + 38, 194, this.localSettings.soundId); + this.previewButton = this.addButtonTracked(new Button(x + 208, y + 37, 62, 20, new StringTextComponent("▶ Тест"), b -> this.togglePreview()), this.basicButtons); // Numeric row: Volume (- [field] +), Pitch (- [field] +), Radius, Priority - this.volMinusBtn = this.addButtonTracked(new Button(x, y + 74, 13, 18, new StringTextComponent("-"), b -> this.adjustVolume(-0.1f)), this.basicButtons); - this.volumeField = this.createField(x + 14, y + 74, 32, this.floatText(this.localSettings.volume, 1)); - this.volPlusBtn = this.addButtonTracked(new Button(x + 47, y + 74, 13, 18, new StringTextComponent("+"), b -> this.adjustVolume(0.1f)), this.basicButtons); + this.volMinusBtn = this.addButtonTracked(new Button(x, y + 74, 14, 18, new StringTextComponent("-"), b -> this.adjustVolume(-0.1f)), this.basicButtons); + this.volumeField = this.createField(x + 15, y + 74, 30, this.floatText(this.localSettings.volume, 1)); + this.volPlusBtn = this.addButtonTracked(new Button(x + 46, y + 74, 14, 18, new StringTextComponent("+"), b -> this.adjustVolume(0.1f)), this.basicButtons); - this.pitchMinusBtn = this.addButtonTracked(new Button(x + 65, y + 74, 13, 18, new StringTextComponent("-"), b -> this.adjustPitch(-0.05f)), this.basicButtons); - this.pitchField = this.createField(x + 79, y + 74, 32, this.floatText(this.localSettings.pitch, 2)); - this.pitchPlusBtn = this.addButtonTracked(new Button(x + 112, y + 74, 13, 18, new StringTextComponent("+"), b -> this.adjustPitch(0.05f)), this.basicButtons); + this.pitchMinusBtn = this.addButtonTracked(new Button(x + 74, y + 74, 14, 18, new StringTextComponent("-"), b -> this.adjustPitch(-0.05f)), this.basicButtons); + this.pitchField = this.createField(x + 89, y + 74, 30, this.floatText(this.localSettings.pitch, 2)); + this.pitchPlusBtn = this.addButtonTracked(new Button(x + 120, y + 74, 14, 18, new StringTextComponent("+"), b -> this.adjustPitch(0.05f)), this.basicButtons); - this.radiusField = this.createField(x + 132, y + 74, 52, this.floatText(this.localSettings.radius, 1)); - this.priorityField = this.createField(x + 192, y + 74, 58, Integer.toString(this.localSettings.priority)); + this.radiusField = this.createField(x + 144, y + 74, 52, this.floatText(this.localSettings.radius, 1)); + this.priorityField = this.createField(x + 208, y + 74, 62, Integer.toString(this.localSettings.priority)); // Options row: Loop and Category - this.loopButton = this.addButtonTracked(new Button(x, y + 96, 90, 20, this.boolLabel("Цикл", this.localSettings.loop), b -> { + this.loopButton = this.addButtonTracked(new Button(x, y + 98, 104, 20, this.boolLabel("Цикл", this.localSettings.loop), b -> { this.localSettings.loop = !this.localSettings.loop; b.setMessage(this.boolLabel("Цикл", this.localSettings.loop)); this.sendLiveApply("loop"); }), this.basicButtons); - this.categoryButton = this.addButtonTracked(new Button(x + 94, y + 96, 156, 20, new StringTextComponent("Категория: " + this.categoryRu(this.localSettings.category)), b -> { + this.categoryButton = this.addButtonTracked(new Button(x + 112, y + 98, 158, 20, new StringTextComponent("Категория: " + this.categoryRu(this.localSettings.category)), b -> { this.localSettings.category = this.cycleCategory(this.localSettings.category); b.setMessage(new StringTextComponent("Категория: " + this.categoryRu(this.localSettings.category))); this.sendLiveApply("category"); }), this.basicButtons); // Search row - this.searchField = this.createField(x, y + 130, 186, ""); - this.clearFilterButton = this.addButtonTracked(new Button(x + 190, y + 129, 60, 20, new StringTextComponent("Сброс"), b -> { + this.searchField = this.createField(x, y + 124, 194, ""); + this.clearFilterButton = this.addButtonTracked(new Button(x + 208, y + 123, 62, 20, new StringTextComponent("Сброс"), b -> { this.searchField.setValue(""); this.selectedSoundListIndex = -1; this.soundListScroll = 0; }), this.basicButtons); // --- TAB 2: EFFECTS --- - this.use3dButton = this.addButtonTracked(new Button(x, y + 42, 122, 20, this.boolLabel("3D звук", this.localSettings.use3d), b -> { + this.use3dButton = this.addButtonTracked(new Button(x, y + 42, 128, 20, this.boolLabel("3D звук", this.localSettings.use3d), b -> { this.localSettings.use3d = !this.localSettings.use3d; b.setMessage(this.boolLabel("3D звук", this.localSettings.use3d)); this.sendLiveApply("use3d"); }), this.effectsButtons); - this.attenuationButton = this.addButtonTracked(new Button(x + 126, y + 42, 124, 20, new StringTextComponent("Затух.: " + this.attenuationRu(this.localSettings.attenuationMode)), b -> { + this.attenuationButton = this.addButtonTracked(new Button(x + 132, y + 42, 138, 20, new StringTextComponent("Затух.: " + this.attenuationRu(this.localSettings.attenuationMode)), b -> { this.localSettings.attenuationMode = this.cycleAttenuation(this.localSettings.attenuationMode); b.setMessage(new StringTextComponent("Затух.: " + this.attenuationRu(this.localSettings.attenuationMode))); this.sendLiveApply("attenuation"); }), this.effectsButtons); - this.occlusionButton = this.addButtonTracked(new Button(x, y + 68, 122, 20, this.boolLabel("Окклюзия", this.localSettings.occlusion), b -> { + this.occlusionButton = this.addButtonTracked(new Button(x, y + 68, 132, 20, this.boolLabel("Окклюзия (стены)", this.localSettings.occlusion), b -> { this.localSettings.occlusion = !this.localSettings.occlusion; - b.setMessage(this.boolLabel("Окклюзия", this.localSettings.occlusion)); + b.setMessage(this.boolLabel("Окклюзия (стены)", this.localSettings.occlusion)); this.sendLiveApply("occlusion"); }), this.effectsButtons); - this.occlusionStrengthField = this.createField(x + 180, y + 69, 70, this.floatText(this.localSettings.occlusionStrength, 2)); + this.occlusionStrengthField = this.createField(x + 214, y + 69, 56, this.floatText(this.localSettings.occlusionStrength, 2)); - this.reverbButton = this.addButtonTracked(new Button(x, y + 94, 122, 20, this.boolLabel("Реверберация", this.localSettings.reverb), b -> { + this.reverbButton = this.addButtonTracked(new Button(x, y + 94, 132, 20, this.boolLabel("Реверберация", this.localSettings.reverb), b -> { this.localSettings.reverb = !this.localSettings.reverb; b.setMessage(this.boolLabel("Реверберация", this.localSettings.reverb)); this.sendLiveApply("reverb"); }), this.effectsButtons); - this.reverbStrengthField = this.createField(x + 180, y + 95, 70, this.floatText(this.localSettings.reverbStrength, 2)); + this.reverbStrengthField = this.createField(x + 214, y + 95, 56, this.floatText(this.localSettings.reverbStrength, 2)); - this.dopplerButton = this.addButtonTracked(new Button(x, y + 120, 122, 20, this.boolLabel("Допплер", this.localSettings.doppler), b -> { + this.dopplerButton = this.addButtonTracked(new Button(x, y + 120, 128, 20, this.boolLabel("Допплер", this.localSettings.doppler), b -> { this.localSettings.doppler = !this.localSettings.doppler; b.setMessage(this.boolLabel("Допплер", this.localSettings.doppler)); this.sendLiveApply("doppler"); }), this.effectsButtons); - this.crossfadeButton = this.addButtonTracked(new Button(x + 126, y + 120, 124, 20, this.boolLabel("Кроссфейд", this.localSettings.crossfade), b -> { + this.crossfadeButton = this.addButtonTracked(new Button(x + 132, y + 120, 138, 20, this.boolLabel("Кроссфейд", this.localSettings.crossfade), b -> { this.localSettings.crossfade = !this.localSettings.crossfade; b.setMessage(this.boolLabel("Кроссфейд", this.localSettings.crossfade)); this.sendLiveApply("crossfade"); }), this.effectsButtons); - this.fadeInField = this.createField(x, y + 160, 120, Integer.toString(this.localSettings.fadeInTicks)); - this.fadeOutField = this.createField(x + 126, y + 160, 124, Integer.toString(this.localSettings.fadeOutTicks)); + this.fadeInField = this.createField(x + 88, y + 147, 44, Integer.toString(this.localSettings.fadeInTicks)); + this.fadeOutField = this.createField(x + 226, y + 147, 44, Integer.toString(this.localSettings.fadeOutTicks)); // --- TAB 3: TRIGGER --- - this.activationModeButton = this.addButtonTracked(new Button(x, y + 42, 130, 20, new StringTextComponent("Режим: " + this.modeRu(this.localSettings.activationMode)), b -> { - this.localSettings.activationMode = this.cycleActivation(this.localSettings.activationMode); + this.activationModeButton = this.addButtonTracked(new Button(x, y + 42, 128, 20, new StringTextComponent("Режим: " + this.modeRu(this.localSettings.activationMode)), b -> { + this.localSettings.activationMode = this.cycleMode(this.localSettings.activationMode); b.setMessage(new StringTextComponent("Режим: " + this.modeRu(this.localSettings.activationMode))); - this.redstoneModeButton.visible = this.localSettings.activationMode == ActivationMode.REDSTONE; - this.redstoneModeButton.active = this.localSettings.activationMode == ActivationMode.REDSTONE; + this.updateTabVisibility(); this.saveAndSend(false, false); }), this.triggerButtons); - this.redstoneModeButton = this.addButtonTracked(new Button(x + 134, y + 42, 116, 20, new StringTextComponent("Сигнал: " + this.redstoneRu(this.localSettings.redstoneMode)), b -> { + this.redstoneModeButton = this.addButtonTracked(new Button(x + 132, y + 42, 138, 20, new StringTextComponent("Сигнал: " + this.redstoneRu(this.localSettings.redstoneMode)), b -> { this.localSettings.redstoneMode = this.cycleRedstone(this.localSettings.redstoneMode); b.setMessage(new StringTextComponent("Сигнал: " + this.redstoneRu(this.localSettings.redstoneMode))); this.saveAndSend(false, false); }), this.triggerButtons); - this.startDelayField = this.createField(x, y + 78, 120, Integer.toString(this.localSettings.startDelayTicks)); - this.repeatDelayField = this.createField(x + 126, y + 78, 124, Integer.toString(this.localSettings.repeatDelayTicks)); + this.startDelayField = this.createField(x + 94, y + 69, 40, Integer.toString(this.localSettings.startDelayTicks)); + this.repeatDelayField = this.createField(x + 230, y + 69, 40, Integer.toString(this.localSettings.repeatDelayTicks)); - this.sourceEnabledButton = this.addButtonTracked(new Button(x, y + 114, 122, 20, this.boolLabel("Источник", this.sourceEnabled), b -> { + this.sourceEnabledButton = this.addButtonTracked(new Button(x, y + 94, 128, 20, this.boolLabel("Источник", this.sourceEnabled), b -> { this.sourceEnabled = !this.sourceEnabled; b.setMessage(this.boolLabel("Источник", this.sourceEnabled)); this.saveAndSend(false, false); }), this.triggerButtons); - this.zoneEnabledButton = this.addButtonTracked(new Button(x + 126, y + 114, 124, 20, this.boolLabel("Триггер зоны", this.zoneEnabled), b -> { + this.zoneEnabledButton = this.addButtonTracked(new Button(x + 132, y + 94, 138, 20, this.boolLabel("Триггер зоны", this.zoneEnabled), b -> { this.zoneEnabled = !this.zoneEnabled; b.setMessage(this.boolLabel("Триггер зоны", this.zoneEnabled)); this.saveAndSend(false, false); }), this.triggerButtons); // --- TAB 4: ZONE --- - this.zoneShapeButton = this.addButtonTracked(new Button(x, y + 42, 122, 20, new StringTextComponent("Форма: " + this.shapeRu(this.zoneShape)), b -> { + this.zoneShapeButton = this.addButtonTracked(new Button(x, y + 42, 84, 20, new StringTextComponent(this.zoneShape == ZoneShape.SPHERE ? "Сфера" : "Бокс"), b -> { this.zoneShape = this.zoneShape == ZoneShape.SPHERE ? ZoneShape.BOX : ZoneShape.SPHERE; - b.setMessage(new StringTextComponent("Форма: " + this.shapeRu(this.zoneShape))); + b.setMessage(new StringTextComponent(this.zoneShape == ZoneShape.SPHERE ? "Сфера" : "Бокс")); + this.updateTabVisibility(); this.saveAndSend(false, false); }), this.zoneButtons); - this.zonePolicyButton = this.addButtonTracked(new Button(x + 126, y + 42, 124, 20, new StringTextComponent("Приоритет: " + this.policyRu(this.zonePolicy)), b -> { + this.zonePolicyButton = this.addButtonTracked(new Button(x + 88, y + 42, 94, 20, new StringTextComponent("Приоритет: " + this.policyRu(this.zonePolicy)), b -> { this.zonePolicy = this.cyclePolicy(this.zonePolicy); b.setMessage(new StringTextComponent("Приоритет: " + this.policyRu(this.zonePolicy))); this.saveAndSend(false, false); }), this.zoneButtons); - this.zoneRadiusField = this.createField(x + 85, y + 68, 60, this.floatText(this.zoneRadius, 1)); - 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 -> { + this.zoneVisualizerButton = this.addButtonTracked(new Button(x + 186, y + 42, 84, 20, this.boolLabel("Вид зоны", showZoneVisualizer), b -> { showZoneVisualizer = !showZoneVisualizer; - b.setMessage(this.boolLabel("Вид в мире", 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.zoneRadiusField = this.createField(x + 144, y + 69, 60, this.floatText(this.zoneRadius, 1)); + this.zoneBoxXField = this.createField(x + 144, y + 69, 38, this.floatText(this.zoneBoxX, 1)); + this.zoneBoxYField = this.createField(x + 186, y + 69, 38, this.floatText(this.zoneBoxY, 1)); + this.zoneBoxZField = this.createField(x + 228, y + 69, 38, this.floatText(this.zoneBoxZ, 1)); + + this.zoneAutoStartButton = this.addButtonTracked(new Button(x, y + 94, 128, 20, this.boolLabel("Авто-вход", this.zoneAutoStartOnEnter), b -> { this.zoneAutoStartOnEnter = !this.zoneAutoStartOnEnter; b.setMessage(this.boolLabel("Авто-вход", this.zoneAutoStartOnEnter)); this.saveAndSend(false, false); }), this.zoneButtons); - this.zoneStopOnExitButton = this.addButtonTracked(new Button(x + 126, y + 94, 124, 20, this.boolLabel("Стоп-выход", this.zoneStopOnExit), b -> { + this.zoneStopOnExitButton = this.addButtonTracked(new Button(x + 132, y + 94, 138, 20, this.boolLabel("Стоп-выход", this.zoneStopOnExit), b -> { this.zoneStopOnExit = !this.zoneStopOnExit; b.setMessage(this.boolLabel("Стоп-выход", this.zoneStopOnExit)); this.saveAndSend(false, false); }), this.zoneButtons); - this.zoneUseCustomCoordsButton = this.addButtonTracked(new Button(x, y + 120, 136, 20, this.boolLabel("Смещение звука", this.zoneUseCustomCoords), b -> { + this.zoneUseCustomCoordsButton = this.addButtonTracked(new Button(x, y + 120, 142, 20, this.boolLabel("Смещение центра", this.zoneUseCustomCoords), b -> { this.zoneUseCustomCoords = !this.zoneUseCustomCoords; - b.setMessage(this.boolLabel("Смещение звука", this.zoneUseCustomCoords)); + b.setMessage(this.boolLabel("Смещение центра", this.zoneUseCustomCoords)); + this.updateTabVisibility(); this.saveAndSend(false, false); }), this.zoneButtons); - this.myPosButton = this.addButtonTracked(new Button(x + 140, y + 120, 110, 20, new StringTextComponent("Моя позиция"), b -> this.applyPlayerCoordsToZone()), this.zoneButtons); + this.myPosButton = this.addButtonTracked(new Button(x + 146, y + 120, 124, 20, new StringTextComponent("Моя позиция"), b -> this.applyPlayerCoordsToZone()), this.zoneButtons); - this.zoneSourceXField = this.createField(x + 18, y + 146, 58, Integer.toString(this.zoneSourceX)); - this.zoneSourceYField = this.createField(x + 98, y + 146, 58, Integer.toString(this.zoneSourceY)); - this.zoneSourceZField = this.createField(x + 178, y + 146, 58, Integer.toString(this.zoneSourceZ)); + this.zoneSourceXField = this.createField(x + 104, y + 147, 46, Integer.toString(this.zoneSourceX)); + this.zoneSourceYField = this.createField(x + 164, y + 147, 46, Integer.toString(this.zoneSourceY)); + this.zoneSourceZField = this.createField(x + 224, y + 147, 46, Integer.toString(this.zoneSourceZ)); // --- TAB 5: SCENARIO --- - this.scenarioTargetButton = this.addButtonTracked(new Button(x, y + 42, 78, 20, new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Блок"), b -> { + this.scenarioTargetButton = this.addButtonTracked(new Button(x, y + 42, 80, 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.scenarioShuffleButton.setMessage(this.boolLabel("Случайно", this.activeScenarioShuffle())); }), this.scenarioButtons); - this.scenarioLoopButton = this.addButtonTracked(new Button(x + 80, y + 42, 84, 20, this.boolLabel("Цикл", this.activeScenarioLoop()), b -> { + this.scenarioLoopButton = this.addButtonTracked(new Button(x + 84, y + 42, 88, 20, this.boolLabel("Цикл", this.activeScenarioLoop()), b -> { this.setActiveScenarioLoop(!this.activeScenarioLoop()); b.setMessage(this.boolLabel("Цикл", this.activeScenarioLoop())); }), this.scenarioButtons); - this.scenarioShuffleButton = this.addButtonTracked(new Button(x + 166, y + 42, 84, 20, this.boolLabel("Случайно", this.activeScenarioShuffle()), b -> { + this.scenarioShuffleButton = this.addButtonTracked(new Button(x + 176, y + 42, 94, 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 = this.createField(x, y + 66, 116, ""); + this.scenarioUseCurrentBtn = this.addButtonTracked(new Button(x + 118, y + 65, 42, 20, new StringTextComponent("Тек."), b -> { this.scenarioSoundField.setValue(this.soundIdField.getValue().trim()); }), this.scenarioButtons); - 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.scenarioDelayField = this.createField(x + 210, y + 66, 34, "20"); + this.scenarioSetButton = this.addButtonTracked(new Button(x + 248, y + 65, 22, 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); - this.scenarioUpButton = this.addButtonTracked(new Button(x + 128, y + 96, 58, 20, new StringTextComponent("▲ Вверх"), b -> this.moveScenarioStep(-1)), this.scenarioButtons); - this.scenarioDownButton = this.addButtonTracked(new Button(x + 190, y + 96, 60, 20, new StringTextComponent("▼ Вниз"), b -> this.moveScenarioStep(1)), this.scenarioButtons); + this.scenarioAddButton = this.addButtonTracked(new Button(x, y + 88, 60, 20, new StringTextComponent("+ Шаг"), b -> this.addScenarioStep()), this.scenarioButtons); + this.scenarioRemoveButton = this.addButtonTracked(new Button(x + 64, y + 88, 56, 20, new StringTextComponent("- Шаг"), b -> this.removeScenarioStep()), this.scenarioButtons); + this.scenarioUpButton = this.addButtonTracked(new Button(x + 128, y + 88, 68, 20, new StringTextComponent("▲ Вверх"), b -> this.moveScenarioStep(-1)), this.scenarioButtons); + this.scenarioDownButton = this.addButtonTracked(new Button(x + 202, y + 88, 68, 20, new StringTextComponent("▼ Вниз"), b -> this.moveScenarioStep(1)), this.scenarioButtons); // --- FIXED BOTTOM ROW (ALWAYS VISIBLE) --- - int by = y + this.imageHeight - 26; - this.saveButton = (Button) this.addButton((Widget) new Button(x - 2, by, 64, 20, new StringTextComponent("Сохранить"), b -> this.saveAndSend(false, false))); - this.copyConfigButton = (Button) this.addButton((Widget) new Button(x + 64, by, 44, 20, new StringTextComponent("Копия"), b -> this.copyConfigToClipboard())); - this.pasteConfigButton = (Button) this.addButton((Widget) new Button(x + 110, by, 48, 20, new StringTextComponent("Вставка"), b -> this.pasteConfigFromClipboard())); - this.playNowButton = (Button) this.addButton((Widget) new Button(x + 160, by, 46, 20, new StringTextComponent("▶ Старт"), b -> this.saveAndSend(true, false))); - this.stopNowButton = (Button) this.addButton((Widget) new Button(x + 208, by, 44, 20, new StringTextComponent("⏹ Стоп"), b -> this.saveAndSend(false, true))); + int by = y + this.imageHeight - 28; + this.saveButton = (Button) this.addButton(new Button(x, by, 64, 20, new StringTextComponent("Сохранить"), b -> this.saveAndSend(false, false))); + this.copyConfigButton = (Button) this.addButton(new Button(x + 68, by, 44, 20, new StringTextComponent("Копия"), b -> this.copyConfigToClipboard())); + this.pasteConfigButton = (Button) this.addButton(new Button(x + 116, by, 48, 20, new StringTextComponent("Вставка"), b -> this.pasteConfigFromClipboard())); + this.playNowButton = (Button) this.addButton(new Button(x + 168, by, 46, 20, new StringTextComponent("▶ Старт"), b -> this.saveAndSend(true, false))); + this.stopNowButton = (Button) this.addButton(new Button(x + 218, by, 52, 20, new StringTextComponent("⏹ Стоп"), b -> this.saveAndSend(false, true))); // Field groupings this.addFieldTracked(this.soundIdField, this.basicFields); @@ -494,58 +518,64 @@ public class SoundPlayerScreen extends ContainerScreen { this.redstoneModeButton.active = modeVisible; } - // In Zone tab: toggle Sphere vs Box fields + // In Zone tab: toggle Sphere vs Box fields and Custom coords if (this.activeTab == Tab.ZONE) { boolean isSphere = this.zoneShape == ZoneShape.SPHERE; this.zoneRadiusField.visible = isSphere; this.zoneBoxXField.visible = !isSphere; this.zoneBoxYField.visible = !isSphere; this.zoneBoxZField.visible = !isSphere; + + boolean custom = this.zoneUseCustomCoords; + this.zoneSourceXField.visible = custom; + this.zoneSourceYField.visible = custom; + this.zoneSourceZField.visible = custom; } } + private void adjustVolume(float delta) { + float v = Math.round((this.localSettings.volume + delta) * 10.0f) / 10.0f; + this.localSettings.volume = Math.max(0.0f, Math.min(2.0f, v)); + this.volumeField.setValue(this.floatText(this.localSettings.volume, 1)); + this.sendLiveApply("volume"); + } + + private void adjustPitch(float delta) { + float p = Math.round((this.localSettings.pitch + delta) * 100.0f) / 100.0f; + this.localSettings.pitch = Math.max(0.2f, Math.min(2.0f, p)); + this.pitchField.setValue(this.floatText(this.localSettings.pitch, 2)); + this.sendLiveApply("pitch"); + } + private void togglePreview() { if (AudioPlaybackService.get().isPlaying("gui_preview")) { AudioPlaybackService.get().stop("gui_preview"); - this.previewButton.setMessage(new StringTextComponent("▶ Тест")); - } else { - String sid = this.soundIdField.getValue().trim(); - if (sid.isEmpty()) { - return; - } - this.applyFromFields(); - ClientPlayerEntity player = Minecraft.getInstance().player; - BlockPos p = player != null ? player.blockPosition() : ((SoundPlayerContainer) this.menu).getPos(); - AudioPlaybackService.get().play("gui_preview", p, this.localSettings); - this.previewButton.setMessage(new StringTextComponent("⏹ Стоп")); + return; } - } - - private void adjustVolume(float delta) { this.applyFromFields(); - this.localSettings.volume = this.clampFloat(this.localSettings.volume + delta, 0.0f, 8.0f); - this.volumeField.setValue(this.floatText(this.localSettings.volume, 1)); - this.sendLiveApply("volume_adjust"); - } - - private void adjustPitch(float delta) { - this.applyFromFields(); - this.localSettings.pitch = this.clampFloat(this.localSettings.pitch + delta, 0.05f, 4.0f); - this.pitchField.setValue(this.floatText(this.localSettings.pitch, 2)); - this.sendLiveApply("pitch_adjust"); + String sid = this.localSettings.soundId.trim(); + if (sid.isEmpty()) { + return; + } + ClientPlayerEntity player = Minecraft.getInstance().player; + BlockPos pos = player != null ? player.blockPosition() : BlockPos.ZERO; + SoundSettings previewSettings = SoundSettings.sanitizedCopy(this.localSettings); + previewSettings.loop = false; + AudioPlaybackService.get().play("gui_preview", pos, previewSettings); } private void applyPlayerCoordsToZone() { - ClientPlayerEntity p = Minecraft.getInstance().player; - if (p != null) { - this.zoneSourceX = (int) Math.floor(p.getX()); - this.zoneSourceY = (int) Math.floor(p.getY()); - this.zoneSourceZ = (int) Math.floor(p.getZ()); + ClientPlayerEntity player = Minecraft.getInstance().player; + if (player != null) { + this.zoneSourceX = (int) Math.floor(player.getX()); + this.zoneSourceY = (int) Math.floor(player.getY()); + this.zoneSourceZ = (int) Math.floor(player.getZ()); + this.zoneUseCustomCoords = true; + this.zoneUseCustomCoordsButton.setMessage(this.boolLabel("Смещение центра", true)); this.zoneSourceXField.setValue(Integer.toString(this.zoneSourceX)); this.zoneSourceYField.setValue(Integer.toString(this.zoneSourceY)); this.zoneSourceZField.setValue(Integer.toString(this.zoneSourceZ)); - this.zoneUseCustomCoords = true; - this.zoneUseCustomCoordsButton.setMessage(this.boolLabel("Смещение звука", true)); + this.updateTabVisibility(); this.saveAndSend(false, false); } } @@ -566,7 +596,10 @@ public class SoundPlayerScreen extends ContainerScreen { private TextFieldWidget createField(int x, int y, int w, String value) { TextFieldWidget f = new TextFieldWidget(this.font, x, y, w, 18, StringTextComponent.EMPTY); f.setValue(value); - this.children.add(f); + f.setMaxLength(256); + f.setTextColor(0xFFFFFF); + f.setBordered(true); + this.addButton((Widget) f); return f; } @@ -601,7 +634,7 @@ public class SoundPlayerScreen extends ContainerScreen { CompoundNBT payload = this.exportPayload(); payload.putBoolean("requestPlayNow", requestPlayNow); payload.putBoolean("requestStopNow", requestStopNow); - NetworkHandler.CHANNEL.sendToServer((Object) new C2SUpdateConfigPacket(((SoundPlayerContainer) this.menu).getPos(), payload)); + NetworkHandler.CHANNEL.sendToServer(new C2SUpdateConfigPacket(((SoundPlayerContainer) this.menu).getPos(), payload)); } private void sendLiveApply(String reason) { @@ -698,24 +731,28 @@ public class SoundPlayerScreen extends ContainerScreen { } private void applyFromFields() { - this.localSettings.soundId = this.soundIdField.getValue().trim(); - this.localSettings.volume = this.clampFloat(this.parseFloat(this.volumeField.getValue(), this.localSettings.volume), 0.0f, 8.0f); - this.localSettings.pitch = this.clampFloat(this.parseFloat(this.pitchField.getValue(), this.localSettings.pitch), 0.05f, 4.0f); - this.localSettings.radius = this.clampFloat(this.parseFloat(this.radiusField.getValue(), this.localSettings.radius), 0.1f, 512.0f); - this.localSettings.priority = this.clampInt(this.parseInt(this.priorityField.getValue(), this.localSettings.priority), 0, 100); - this.localSettings.reverbStrength = this.clampFloat(this.parseFloat(this.reverbStrengthField.getValue(), this.localSettings.reverbStrength), 0.0f, 1.0f); - this.localSettings.occlusionStrength = this.clampFloat(this.parseFloat(this.occlusionStrengthField.getValue(), this.localSettings.occlusionStrength), 0.0f, 1.0f); - this.localSettings.fadeInTicks = this.clampInt(this.parseInt(this.fadeInField.getValue(), this.localSettings.fadeInTicks), 0, 36000); - this.localSettings.fadeOutTicks = this.clampInt(this.parseInt(this.fadeOutField.getValue(), this.localSettings.fadeOutTicks), 0, 36000); - this.localSettings.startDelayTicks = this.clampInt(this.parseInt(this.startDelayField.getValue(), this.localSettings.startDelayTicks), 0, 36000); - this.localSettings.repeatDelayTicks = this.clampInt(this.parseInt(this.repeatDelayField.getValue(), this.localSettings.repeatDelayTicks), 0, 36000); - this.zoneRadius = this.clampFloat(this.parseFloat(this.zoneRadiusField.getValue(), this.zoneRadius), 1.0f, 256.0f); - this.zoneBoxX = this.clampFloat(this.parseFloat(this.zoneBoxXField.getValue(), this.zoneBoxX), 0.5f, 128.0f); - this.zoneBoxY = this.clampFloat(this.parseFloat(this.zoneBoxYField.getValue(), this.zoneBoxY), 0.5f, 128.0f); - this.zoneBoxZ = this.clampFloat(this.parseFloat(this.zoneBoxZField.getValue(), this.zoneBoxZ), 0.5f, 128.0f); - this.zoneSourceX = this.parseInt(this.zoneSourceXField.getValue(), this.zoneSourceX); - this.zoneSourceY = this.parseInt(this.zoneSourceYField.getValue(), this.zoneSourceY); - this.zoneSourceZ = this.parseInt(this.zoneSourceZField.getValue(), this.zoneSourceZ); + if (this.soundIdField != null) this.localSettings.soundId = this.soundIdField.getValue().trim(); + if (this.volumeField != null) this.localSettings.volume = this.clampFloat(this.parseFloat(this.volumeField.getValue(), 1.0f), 0.0f, 2.0f); + if (this.pitchField != null) this.localSettings.pitch = this.clampFloat(this.parseFloat(this.pitchField.getValue(), 1.0f), 0.2f, 2.0f); + if (this.radiusField != null) this.localSettings.radius = this.clampFloat(this.parseFloat(this.radiusField.getValue(), 16.0f), 1.0f, 256.0f); + if (this.priorityField != null) this.localSettings.priority = this.clampInt(this.parseInt(this.priorityField.getValue(), 5), 0, 100); + + if (this.occlusionStrengthField != null) this.localSettings.occlusionStrength = this.clampFloat(this.parseFloat(this.occlusionStrengthField.getValue(), 0.8f), 0.0f, 1.0f); + if (this.reverbStrengthField != null) this.localSettings.reverbStrength = this.clampFloat(this.parseFloat(this.reverbStrengthField.getValue(), 0.35f), 0.0f, 1.0f); + if (this.fadeInField != null) this.localSettings.fadeInTicks = this.clampInt(this.parseInt(this.fadeInField.getValue(), 0), 0, 72000); + if (this.fadeOutField != null) this.localSettings.fadeOutTicks = this.clampInt(this.parseInt(this.fadeOutField.getValue(), 0), 0, 72000); + + if (this.startDelayField != null) this.localSettings.startDelayTicks = this.clampInt(this.parseInt(this.startDelayField.getValue(), 0), 0, 72000); + if (this.repeatDelayField != null) this.localSettings.repeatDelayTicks = this.clampInt(this.parseInt(this.repeatDelayField.getValue(), 0), 0, 72000); + + if (this.zoneRadiusField != null) this.zoneRadius = this.clampFloat(this.parseFloat(this.zoneRadiusField.getValue(), 12.0f), 1.0f, 128.0f); + if (this.zoneBoxXField != null) this.zoneBoxX = this.clampFloat(this.parseFloat(this.zoneBoxXField.getValue(), 8.0f), 0.5f, 64.0f); + if (this.zoneBoxYField != null) this.zoneBoxY = this.clampFloat(this.parseFloat(this.zoneBoxYField.getValue(), 4.0f), 0.5f, 64.0f); + if (this.zoneBoxZField != null) this.zoneBoxZ = this.clampFloat(this.parseFloat(this.zoneBoxZField.getValue(), 8.0f), 0.5f, 64.0f); + + if (this.zoneSourceXField != null) this.zoneSourceX = this.parseInt(this.zoneSourceXField.getValue(), 0); + if (this.zoneSourceYField != null) this.zoneSourceY = this.parseInt(this.zoneSourceYField.getValue(), 0); + if (this.zoneSourceZField != null) this.zoneSourceZ = this.parseInt(this.zoneSourceZField.getValue(), 0); } private void refreshControlsFromModel() { @@ -742,20 +779,26 @@ public class SoundPlayerScreen extends ContainerScreen { this.attenuationButton.setMessage(new StringTextComponent("Затух.: " + this.attenuationRu(this.localSettings.attenuationMode))); this.use3dButton.setMessage(this.boolLabel("3D звук", this.localSettings.use3d)); this.dopplerButton.setMessage(this.boolLabel("Допплер", this.localSettings.doppler)); - this.occlusionButton.setMessage(this.boolLabel("Окклюзия", this.localSettings.occlusion)); + this.occlusionButton.setMessage(this.boolLabel("Окклюзия (стены)", this.localSettings.occlusion)); this.reverbButton.setMessage(this.boolLabel("Реверберация", this.localSettings.reverb)); this.crossfadeButton.setMessage(this.boolLabel("Кроссфейд", this.localSettings.crossfade)); this.activationModeButton.setMessage(new StringTextComponent("Режим: " + this.modeRu(this.localSettings.activationMode))); this.redstoneModeButton.setMessage(new StringTextComponent("Сигнал: " + this.redstoneRu(this.localSettings.redstoneMode))); this.sourceEnabledButton.setMessage(this.boolLabel("Источник", this.sourceEnabled)); this.zoneEnabledButton.setMessage(this.boolLabel("Триггер зоны", this.zoneEnabled)); - this.zoneShapeButton.setMessage(new StringTextComponent("Форма: " + this.shapeRu(this.zoneShape))); + this.zoneShapeButton.setMessage(new StringTextComponent(this.zoneShape == ZoneShape.SPHERE ? "Сфера" : "Бокс")); + this.zonePolicyButton.setMessage(new StringTextComponent("Приоритет: " + this.policyRu(this.zonePolicy))); this.zoneAutoStartButton.setMessage(this.boolLabel("Авто-вход", this.zoneAutoStartOnEnter)); this.zoneStopOnExitButton.setMessage(this.boolLabel("Стоп-выход", this.zoneStopOnExit)); - this.zonePolicyButton.setMessage(new StringTextComponent("Приоритет: " + this.policyRu(this.zonePolicy))); - this.zoneUseCustomCoordsButton.setMessage(this.boolLabel("Смещение звука", this.zoneUseCustomCoords)); - this.scenarioTargetButton.setMessage(new StringTextComponent(this.editingZoneScenario ? "Шаги: Зона" : "Шаги: Источник")); - this.scenarioLoopButton.setMessage(this.boolLabel("Цикл очереди", this.activeScenarioLoop())); + this.zoneUseCustomCoordsButton.setMessage(this.boolLabel("Смещение центра", this.zoneUseCustomCoords)); + this.scenarioLoopButton.setMessage(this.boolLabel("Цикл", this.activeScenarioLoop())); + if (this.scenarioShuffleButton != null) { + this.scenarioShuffleButton.setMessage(this.boolLabel("Случайно", this.activeScenarioShuffle())); + } + if (this.zoneVisualizerButton != null) { + this.zoneVisualizerButton.setMessage(this.boolLabel("Вид зоны", showZoneVisualizer)); + } + this.updateTabVisibility(); } private void addScenarioStep() { @@ -859,6 +902,7 @@ public class SoundPlayerScreen extends ContainerScreen { return this.zoneSourceZ; } + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { boolean enter = keyCode == 257 || keyCode == 335; List suggestions = this.filteredSoundEntries(); @@ -875,37 +919,49 @@ public class SoundPlayerScreen extends ContainerScreen { } if (enter && this.selectedSoundListIndex >= 0 && this.selectedSoundListIndex < suggestions.size()) { this.soundIdField.setValue(suggestions.get(this.selectedSoundListIndex).soundId); + this.sendLiveApply("sound_select_enter"); return true; } } - for (TextFieldWidget field : this.allFields()) { - if (!field.visible || !field.keyPressed(keyCode, scanCode, modifiers)) continue; - if (enter && this.isLiveNumericField(field)) { - this.sendLiveApply("enter_numeric"); + + if (this.activeTab == Tab.SCENARIO && (this.scenarioSoundField.isFocused() || this.scenarioDelayField.isFocused())) { + if (enter) { + this.addScenarioStep(); + return true; } + } + + if (enter && this.isLiveNumericField(this.getFocusedField())) { + this.sendLiveApply("numeric_enter"); return true; } + return super.keyPressed(keyCode, scanCode, modifiers); } - public boolean charTyped(char codePoint, int modifiers) { - for (TextFieldWidget field : this.allFields()) { - if (!field.visible || !field.charTyped(codePoint, modifiers)) continue; - return true; - } - return super.charTyped(codePoint, modifiers); + private TextFieldWidget getFocusedField() { + if (this.volumeField != null && this.volumeField.isFocused()) return this.volumeField; + if (this.pitchField != null && this.pitchField.isFocused()) return this.pitchField; + if (this.radiusField != null && this.radiusField.isFocused()) return this.radiusField; + if (this.priorityField != null && this.priorityField.isFocused()) return this.priorityField; + if (this.reverbStrengthField != null && this.reverbStrengthField.isFocused()) return this.reverbStrengthField; + if (this.occlusionStrengthField != null && this.occlusionStrengthField.isFocused()) return this.occlusionStrengthField; + if (this.fadeInField != null && this.fadeInField.isFocused()) return this.fadeInField; + if (this.fadeOutField != null && this.fadeOutField.isFocused()) return this.fadeOutField; + return null; } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.activeTab == Tab.BASIC) { int listX = this.leftPos + 10; - int listY = this.topPos + 152; + int listY = this.topPos + 146; List suggestions = this.filteredSoundEntries(); int shown = Math.min(SOUND_LIST_ROWS, Math.max(0, suggestions.size() - this.soundListScroll)); for (int i = 0; i < shown; ++i) { int idx = this.soundListScroll + i; int ry = listY + i * ROW_HEIGHT; - if (mouseX >= listX && mouseX <= (listX + 242) && mouseY >= ry && mouseY < (ry + ROW_HEIGHT)) { + if (mouseX >= listX && mouseX <= (listX + 260) && mouseY >= ry && mouseY < (ry + ROW_HEIGHT)) { long now = System.currentTimeMillis(); boolean doubleClick = (idx == this.selectedSoundListIndex) && (now - this.lastSoundClickTime < 380L); this.lastSoundClickTime = now; @@ -920,13 +976,13 @@ public class SoundPlayerScreen extends ContainerScreen { } if (this.activeTab == Tab.SCENARIO) { int listX = this.leftPos + 10; - int listY = this.topPos + 124; + int listY = this.topPos + 112; List list = this.activeScenarioList(); int shown = Math.min(5, Math.max(0, list.size() - this.scenarioListScroll)); for (int i = 0; i < shown; ++i) { int idx = this.scenarioListScroll + i; int ry = listY + i * 14; - if (mouseX >= listX && mouseX <= (listX + 242) && mouseY >= ry && mouseY < (ry + 14)) { + if (mouseX >= listX && mouseX <= (listX + 260) && mouseY >= ry && mouseY < (ry + 14)) { this.selectedScenarioIndex = idx; ScenarioQueue.Step step = list.get(idx); this.scenarioSoundField.setValue(step.soundId); @@ -938,6 +994,7 @@ public class SoundPlayerScreen extends ContainerScreen { return super.mouseClicked(mouseX, mouseY, button); } + @Override public boolean mouseScrolled(double mouseX, double mouseY, double delta) { if (this.activeTab == Tab.BASIC) { List suggestions = this.filteredSoundEntries(); @@ -956,7 +1013,7 @@ public class SoundPlayerScreen extends ContainerScreen { } } if (this.activeTab == Tab.HELP) { - int maxScroll = Math.max(0, this.helpLines().size() - 8); + int maxScroll = Math.max(0, this.helpLines().size() - 10); if (maxScroll > 0) { this.helpScroll = this.clampInt(this.helpScroll + (delta > 0.0 ? -1 : 1), 0, maxScroll); return true; @@ -983,6 +1040,7 @@ public class SoundPlayerScreen extends ContainerScreen { super.removed(); } + @Override protected void renderBg(MatrixStack matrixStack, float partialTicks, int mouseX, int mouseY) { int x1 = this.leftPos; int y1 = this.topPos; @@ -994,30 +1052,32 @@ public class SoundPlayerScreen extends ContainerScreen { // Main panel SoundPlayerScreen.fill(matrixStack, x1, y1, x2, y2, COLOR_BG_MAIN); // Outer border - SoundPlayerScreen.fill(matrixStack, x1, y1, x2, y1 + 1, 0xFF30363D); - SoundPlayerScreen.fill(matrixStack, x1, y2 - 1, x2, y2, 0xFF30363D); - SoundPlayerScreen.fill(matrixStack, x1, y1, x1 + 1, y2, 0xFF30363D); - SoundPlayerScreen.fill(matrixStack, x2 - 1, y1, x2, y2, 0xFF30363D); + SoundPlayerScreen.fill(matrixStack, x1, y1, x2, y1 + 1, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x1, y2 - 1, x2, y2, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x1, y1, x1 + 1, y2, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x2 - 1, y1, x2, y2, COLOR_BORDER); // Header separator - SoundPlayerScreen.fill(matrixStack, x1 + 6, y1 + 26, x2 - 6, y1 + 27, 0xFF30363D); + SoundPlayerScreen.fill(matrixStack, x1 + 6, y1 + 25, x2 - 6, y1 + 26, COLOR_BORDER); // Content area background - SoundPlayerScreen.fill(matrixStack, x1 + 6, y1 + 28, x2 - 6, y2 - 50, COLOR_BG_INNER); + SoundPlayerScreen.fill(matrixStack, x1 + 6, y1 + 27, x2 - 6, y2 - 50, COLOR_BG_INNER); - // Active tab underline indicator + // Active tab underline indicator (accent colored bar spanning the active button's width) for (int i = 0; i < this.tabButtons.size() && i < Tab.values().length; i++) { if (this.activeTab.ordinal() == i) { Button btn = this.tabButtons.get(i); - SoundPlayerScreen.fill(matrixStack, btn.x, btn.y + btn.getHeight() - 2, btn.x + btn.getHeight(), btn.y + btn.getHeight(), COLOR_ACCENT); + SoundPlayerScreen.fill(matrixStack, btn.x, btn.y + btn.getHeight() - 2, btn.x + btn.getWidth(), btn.y + btn.getHeight(), COLOR_ACCENT); } } - // Status bar area - SoundPlayerScreen.fill(matrixStack, x1 + 6, y2 - 48, x2 - 6, y2 - 28, COLOR_BG_BOX); - SoundPlayerScreen.fill(matrixStack, x1 + 6, y2 - 49, x2 - 6, y2 - 48, 0xFF30363D); + // Status bar area (dedicated dark bar background) + SoundPlayerScreen.fill(matrixStack, x1 + 6, y2 - 48, x2 - 6, y2 - 30, COLOR_BG_BOX); + SoundPlayerScreen.fill(matrixStack, x1 + 6, y2 - 49, x2 - 6, y2 - 48, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x1 + 6, y2 - 30, x2 - 6, y2 - 29, COLOR_BORDER); } + @Override public void render(MatrixStack matrixStack, int mouseX, int mouseY, float partialTicks) { this.renderBackground(matrixStack); super.render(matrixStack, mouseX, mouseY, partialTicks); @@ -1039,50 +1099,48 @@ public class SoundPlayerScreen extends ContainerScreen { // Render tab contents text labels switch (this.activeTab) { case BASIC: - this.font.draw(matrixStack, new StringTextComponent("Файл звука:"), (float) x, (float) (y + 30), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("Громк."), (float) x, (float) (y + 63), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Pitch"), (float) (x + 65), (float) (y + 63), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Радиус (б)"), (float) (x + 132), (float) (y + 63), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Приоритет"), (float) (x + 192), (float) (y + 63), COLOR_TEXT_MUTED); - - this.font.draw(matrixStack, new StringTextComponent("Поиск в списке:"), (float) x, (float) (y + 120), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Файл звука:"), (float) x, (float) (y + 28), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Громкость"), (float) x, (float) (y + 63), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Pitch"), (float) (x + 74), (float) (y + 63), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Радиус (б)"), (float) (x + 144), (float) (y + 63), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Приоритет"), (float) (x + 208), (float) (y + 63), COLOR_TEXT_MUTED); this.drawSoundSuggestions(matrixStack, mouseX, mouseY); break; case EFFECTS: - this.font.draw(matrixStack, new StringTextComponent("Аудио эффекты и пространственный звук"), (float) x, (float) (y + 30), COLOR_TEXT_TITLE); - this.font.draw(matrixStack, new StringTextComponent("Сила (0.0 - 1.0):"), (float) (x + 128), (float) (y + 73), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("Сила (0.0 - 1.0):"), (float) (x + 128), (float) (y + 99), COLOR_TEXT_LABEL); - - this.font.draw(matrixStack, new StringTextComponent("Fade In (появление, тиков):"), (float) x, (float) (y + 148), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("Fade Out (затухание, тиков):"), (float) (x + 126), (float) (y + 148), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("⚡ Эффекты обновляются сразу во время игры"), (float) x, (float) (y + 188), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Эффекты и 3D позиционирование"), (float) x, (float) (y + 28), COLOR_TEXT_TITLE); + this.font.draw(matrixStack, new StringTextComponent("Сила:"), (float) (x + 176), (float) (y + 73), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Сила:"), (float) (x + 176), (float) (y + 99), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Fade In:"), (float) x, (float) (y + 151), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Fade Out:"), (float) (x + 148), (float) (y + 151), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("⚡ Изменения применяются мгновенно во время звука"), (float) x, (float) (y + 176), COLOR_TEXT_MUTED); break; case TRIGGER: - this.font.draw(matrixStack, new StringTextComponent("Условия запуска звука"), (float) x, (float) (y + 30), COLOR_TEXT_TITLE); - this.font.draw(matrixStack, new StringTextComponent("Задержка старта (тиков, 20т = 1 сек):"), (float) x, (float) (y + 66), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("Интервал повтора (тиков, 0 = без повтора):"), (float) (x + 126), (float) (y + 66), COLOR_TEXT_LABEL); - this.drawTriggerHelpBox(matrixStack, x, y + 142); + this.font.draw(matrixStack, new StringTextComponent("Режимы активации проигрывателя"), (float) x, (float) (y + 28), COLOR_TEXT_TITLE); + this.font.draw(matrixStack, new StringTextComponent("Старт (тиков):"), (float) x, (float) (y + 73), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Интервал:"), (float) (x + 150), (float) (y + 73), COLOR_TEXT_LABEL); + this.drawTriggerHelpBox(matrixStack, x, y + 124); break; case ZONE: - this.font.draw(matrixStack, new StringTextComponent("Настройка триггерной зоны"), (float) x, (float) (y + 30), COLOR_TEXT_TITLE); + this.font.draw(matrixStack, new StringTextComponent("Настройка триггерной зоны"), (float) x, (float) (y + 28), COLOR_TEXT_TITLE); if (this.zoneShape == ZoneShape.SPHERE) { - this.font.draw(matrixStack, new StringTextComponent("Радиус сферы:"), (float) x, (float) (y + 73), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("Радиус зоны (блоков):"), (float) x, (float) (y + 73), COLOR_TEXT_LABEL); } else { - this.font.draw(matrixStack, new StringTextComponent("X:"), (float) x, (float) (y + 73), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Y:"), (float) (x + 64), (float) (y + 73), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Z:"), (float) (x + 124), (float) (y + 73), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Полуразмеры X / Y / Z:"), (float) x, (float) (y + 73), COLOR_TEXT_LABEL); + } + if (this.zoneUseCustomCoords) { + this.font.draw(matrixStack, new StringTextComponent("Центр звука:"), (float) x, (float) (y + 151), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("X"), (float) (x + 92), (float) (y + 151), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Y"), (float) (x + 152), (float) (y + 151), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Z"), (float) (x + 212), (float) (y + 151), COLOR_TEXT_MUTED); } - this.font.draw(matrixStack, new StringTextComponent("Координаты центра звука:"), (float) x, (float) (y + 144), COLOR_TEXT_LABEL); - this.font.draw(matrixStack, new StringTextComponent("X:"), (float) x, (float) (y + 151), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Y:"), (float) (x + 80), (float) (y + 151), COLOR_TEXT_MUTED); - this.font.draw(matrixStack, new StringTextComponent("Z:"), (float) (x + 160), (float) (y + 151), COLOR_TEXT_MUTED); break; case SCENARIO: - this.font.draw(matrixStack, new StringTextComponent("Очередь воспроизведения (Сценарий)"), (float) x, (float) (y + 30), COLOR_TEXT_TITLE); + this.font.draw(matrixStack, new StringTextComponent("Очередь воспроизведения"), (float) x, (float) (y + 28), COLOR_TEXT_TITLE); + this.font.draw(matrixStack, new StringTextComponent("Пауза:"), (float) (x + 172), (float) (y + 71), COLOR_TEXT_LABEL); this.drawScenarioList(matrixStack, mouseX, mouseY); break; @@ -1092,29 +1150,30 @@ public class SoundPlayerScreen extends ContainerScreen { } // --- STATUS BAR (Above action buttons) --- - int statusY = y + this.imageHeight - 45; + int statusY = y + this.imageHeight - 43; int statusColor = this.colorForStatus(status); String statusPrefix = status == ClientSoundCache.Status.AVAILABLE ? "● " : (status == ClientSoundCache.Status.TRANSFERRING ? "⏳ " : "✖ "); String statusText = statusPrefix + this.statusRu(status); - this.font.draw(matrixStack, new StringTextComponent(statusText), (float) x, (float) statusY, statusColor); ClientTransferManager.TransferProgress progress = ClientTransferManager.get().progressBySoundId(soundId); if (progress != null) { - String prog = progress.percent + "% (" + (progress.receivedBytes / 1024) + "/" + (progress.expectedBytes / 1024) + " KB)"; - this.font.draw(matrixStack, new StringTextComponent(prog), (float) (x + 80), (float) statusY, COLOR_YELLOW); + statusText = "⏳ Загрузка: " + progress.percent + "% (" + (progress.receivedBytes / 1024) + "/" + (progress.expectedBytes / 1024) + " KB)"; + statusColor = COLOR_YELLOW; } + this.font.draw(matrixStack, new StringTextComponent(statusText), (float) x, (float) statusY, statusColor); String blockState = playingNow ? "● Блок: Играет" : "○ Блок: Ожидание"; int blockStateColor = playingNow ? COLOR_GREEN : COLOR_TEXT_MUTED; - this.font.draw(matrixStack, new StringTextComponent(blockState), (float) (x + 150), (float) statusY, blockStateColor); + this.font.draw(matrixStack, new StringTextComponent(blockState), (float) (x + 160), (float) statusY, blockStateColor); this.drawContextTooltips(matrixStack, mouseX, mouseY); this.renderTooltip(matrixStack, mouseX, mouseY); } private void drawTriggerHelpBox(MatrixStack matrixStack, int x, int y) { - SoundPlayerScreen.fill(matrixStack, x, y, x + 250, y + 42, 0x55000000); - this.font.draw(matrixStack, new StringTextComponent("Описание выбранного режима:"), (float) (x + 4), (float) (y + 4), COLOR_TEXT_TITLE); + SoundPlayerScreen.fill(matrixStack, x, y, x + 260, y + 54, 0x55000000); + SoundPlayerScreen.fill(matrixStack, x, y, x + 260, y + 1, COLOR_BORDER); + this.font.draw(matrixStack, new StringTextComponent("Описание выбранного режима:"), (float) (x + 6), (float) (y + 6), COLOR_TEXT_TITLE); String desc; switch (this.localSettings.activationMode) { case CLICK: @@ -1134,7 +1193,8 @@ public class SoundPlayerScreen extends ContainerScreen { default: desc = ""; } - this.font.draw(matrixStack, new StringTextComponent(desc), (float) (x + 4), (float) (y + 18), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent(desc), (float) (x + 6), (float) (y + 22), COLOR_TEXT_LABEL); + this.font.draw(matrixStack, new StringTextComponent("С Shift+ПКМ по блоку можно переключать воспроизведение."), (float) (x + 6), (float) (y + 36), COLOR_TEXT_MUTED); } private boolean isLiveNumericField(TextFieldWidget field) { @@ -1144,17 +1204,19 @@ public class SoundPlayerScreen extends ContainerScreen { private void drawSoundSuggestions(MatrixStack matrixStack, int mouseX, int mouseY) { List suggestions = this.filteredSoundEntries(); int x = this.leftPos + 10; - int y = this.topPos + 152; - int listWidth = 244; + int y = this.topPos + 146; + int listWidth = 260; int listHeight = SOUND_LIST_ROWS * ROW_HEIGHT; // Background box for list - SoundPlayerScreen.fill(matrixStack, x - 1, y - 1, x + listWidth + 1, y + listHeight + 1, COLOR_BG_BOX); - SoundPlayerScreen.fill(matrixStack, x - 1, y - 1, x + listWidth + 1, y, 0xFF30363D); - SoundPlayerScreen.fill(matrixStack, x - 1, y + listHeight, x + listWidth + 1, y + listHeight + 1, 0xFF30363D); + SoundPlayerScreen.fill(matrixStack, x, y, x + listWidth, y + listHeight, COLOR_BG_BOX); + SoundPlayerScreen.fill(matrixStack, x, y, x + listWidth, y + 1, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x, y + listHeight - 1, x + listWidth, y + listHeight, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x, y, x + 1, y + listHeight, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x + listWidth - 1, y, x + listWidth, y + listHeight, COLOR_BORDER); if (suggestions.isEmpty()) { - this.font.draw(matrixStack, new StringTextComponent("Звуки не найдены. Проверьте customsounds/"), (float) (x + 4), (float) (y + 4), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Звуки не найдены. Положите файлы в sounds/"), (float) (x + 4), (float) (y + 4), COLOR_TEXT_MUTED); return; } @@ -1166,23 +1228,21 @@ public class SoundPlayerScreen extends ContainerScreen { boolean isHovered = mouseX >= x && mouseX <= (x + listWidth) && mouseY >= rowY && mouseY < (rowY + ROW_HEIGHT); if (idx == this.selectedSoundListIndex) { - SoundPlayerScreen.fill(matrixStack, x, rowY, x + listWidth, rowY + ROW_HEIGHT, 0x44388BFD); + SoundPlayerScreen.fill(matrixStack, x + 1, rowY, x + listWidth - 1, rowY + ROW_HEIGHT, 0x44388BFD); } else if (isHovered) { - SoundPlayerScreen.fill(matrixStack, x, rowY, x + listWidth, rowY + ROW_HEIGHT, 0x22FFFFFF); + SoundPlayerScreen.fill(matrixStack, x + 1, rowY, x + listWidth - 1, rowY + ROW_HEIGHT, 0x22FFFFFF); } - String name = this.trimRow(e.soundId, 24); + String name = this.trimRow(e.soundId, 26); ClientSoundCache.Status rowStatus = ClientSoundCache.get().status(e.soundId); int nameColor = idx == this.selectedSoundListIndex ? COLOR_TEXT_TITLE : (isHovered ? 0xFFFFFFFF : 0xFFC9D1D9); this.font.draw(matrixStack, new StringTextComponent(name), (float) (x + 4), (float) (rowY + 2), nameColor); - this.font.draw(matrixStack, new StringTextComponent(this.statusRu(rowStatus)), (float) (x + 170), (float) (rowY + 2), this.colorForStatus(rowStatus)); + this.font.draw(matrixStack, new StringTextComponent(this.statusRu(rowStatus)), (float) (x + 190), (float) (rowY + 2), this.colorForStatus(rowStatus)); } if (suggestions.size() > SOUND_LIST_ROWS) { this.drawSimpleScrollbar(matrixStack, x + listWidth - 4, y, listHeight, this.soundListScroll, suggestions.size(), SOUND_LIST_ROWS); } - - this.font.draw(matrixStack, new StringTextComponent("💡 Двойной клик по звуку для предпрослушивания"), (float) x, (float) (y + listHeight + 4), COLOR_TEXT_MUTED); } private List filteredSoundEntries() { @@ -1198,25 +1258,27 @@ public class SoundPlayerScreen extends ContainerScreen { if (!query.isEmpty() && !e.soundId.toLowerCase().contains(query)) continue; out.add(e); } - if (this.selectedSoundListIndex >= out.size()) { - this.selectedSoundListIndex = out.isEmpty() ? -1 : out.size() - 1; - } - this.soundListScroll = this.clampInt(this.soundListScroll, 0, Math.max(0, out.size() - SOUND_LIST_ROWS)); return out; } private void drawScenarioList(MatrixStack matrixStack, int mouseX, int mouseY) { int x = this.leftPos + 10; - int y = this.topPos + 124; - int listWidth = 244; + int y = this.topPos + 112; + int listWidth = 260; int listHeight = 5 * 14; - - SoundPlayerScreen.fill(matrixStack, x - 1, y - 1, x + listWidth + 1, y + listHeight + 1, COLOR_BG_BOX); List list = this.activeScenarioList(); - int shown = Math.min(5, Math.max(0, list.size() - this.scenarioListScroll)); + + SoundPlayerScreen.fill(matrixStack, x, y, x + listWidth, y + listHeight, COLOR_BG_BOX); + SoundPlayerScreen.fill(matrixStack, x, y, x + listWidth, y + 1, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x, y + listHeight - 1, x + listWidth, y + listHeight, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x, y, x + 1, y + listHeight, COLOR_BORDER); + SoundPlayerScreen.fill(matrixStack, x + listWidth - 1, y, x + listWidth, y + listHeight, COLOR_BORDER); + if (list.isEmpty()) { - this.font.draw(matrixStack, new StringTextComponent("Очередь пуста. Добавьте шаг выше."), (float) (x + 6), (float) (y + 6), COLOR_TEXT_MUTED); + this.font.draw(matrixStack, new StringTextComponent("Очередь пуста. Добавьте шаги кнопкой выше."), (float) (x + 6), (float) (y + 6), COLOR_TEXT_MUTED); + return; } + int shown = Math.min(5, Math.max(0, list.size() - this.scenarioListScroll)); for (int i = 0; i < shown; ++i) { int idx = this.scenarioListScroll + i; ScenarioQueue.Step s = list.get(idx); @@ -1224,32 +1286,31 @@ public class SoundPlayerScreen extends ContainerScreen { boolean isHovered = mouseX >= x && mouseX <= (x + listWidth) && mouseY >= ry && mouseY < (ry + 14); if (idx == this.selectedScenarioIndex) { - SoundPlayerScreen.fill(matrixStack, x, ry, x + listWidth, ry + 14, 0x44388BFD); + SoundPlayerScreen.fill(matrixStack, x + 1, ry, x + listWidth - 1, ry + 14, 0x44388BFD); } else if (isHovered) { - SoundPlayerScreen.fill(matrixStack, x, ry, x + listWidth, ry + 14, 0x22FFFFFF); + SoundPlayerScreen.fill(matrixStack, x + 1, ry, x + listWidth - 1, ry + 14, 0x22FFFFFF); } - String row = (idx + 1) + ". " + s.soundId + " (пауза: " + (s.delayTicks / 20.0f) + "с / " + s.delayTicks + "т)"; + String row = (idx + 1) + ". " + s.soundId + " (пауза: " + (s.delayTicks / 20.0f) + "с)"; this.font.draw(matrixStack, new StringTextComponent(this.trimRow(row, 36)), (float) (x + 4), (float) (ry + 3), idx == this.selectedScenarioIndex ? COLOR_TEXT_TITLE : COLOR_TEXT_LABEL); } if (list.size() > 5) { this.drawSimpleScrollbar(matrixStack, x + listWidth - 4, y, listHeight, this.scenarioListScroll, list.size(), 5); } - this.font.draw(matrixStack, new StringTextComponent("Всего шагов: " + list.size()), (float) (x + 160), (float) (this.topPos + 30), COLOR_TEXT_MUTED); } private void drawHelp(MatrixStack matrixStack) { int x = this.leftPos + 12; int y = this.topPos + 32; List lines = this.helpLines(); - int shown = Math.min(8, Math.max(0, lines.size() - this.helpScroll)); + int shown = Math.min(10, Math.max(0, lines.size() - this.helpScroll)); for (int i = 0; i < shown; ++i) { int idx = this.helpScroll + i; int color = idx == 0 ? COLOR_TEXT_TITLE : COLOR_TEXT_LABEL; - this.font.draw(matrixStack, new StringTextComponent(lines.get(idx)), (float) x, (float) (y + i * 13), color); + this.font.draw(matrixStack, new StringTextComponent(lines.get(idx)), (float) x, (float) (y + i * 14), color); } - if (lines.size() > 8) { - this.drawSimpleScrollbar(matrixStack, this.leftPos + 258, y, 104, this.helpScroll, lines.size(), 8); + if (lines.size() > 10) { + this.drawSimpleScrollbar(matrixStack, this.leftPos + 268, y, 140, this.helpScroll, lines.size(), 10); } } @@ -1257,49 +1318,161 @@ public class SoundPlayerScreen extends ContainerScreen { ArrayList lines = new ArrayList<>(); lines.add("Справка по Sound Player:"); lines.add("• Основа: выберите soundId, настройте громкость и радиус."); - lines.add("• ▶ Тест: мгновенное прослушивание выбранного звука."); + lines.add("• ▶ Тест: мгновенное предпрослушивание выбранного звука."); + lines.add("• 2x клик по списку: запускает предпрослушивание трека."); lines.add("• Эффекты: 3D звук, приглушение за стенами (окклюзия), эхо."); lines.add("• Триггер: настройка запуска редстоуном, кликом или таймером."); - lines.add("• Зона: музыка включается при входе игрока в зону и выключается при выходе."); + lines.add("• Зона: музыка включается при входе в зону и гаснет при выходе."); + lines.add("• Вид зоны: отображает 3D границы зоны прямо в игровом мире."); lines.add("• Моя позиция: смещает центр звучания зоны в точку игрока."); - lines.add("• Сценарий: последовательный плейлист треков с паузами."); + lines.add("• Сценарий: последовательный или случайный плейлист треков."); lines.add("• Копия/Вставка: перенос всей конфигурации между блоками."); return lines; } private void drawSimpleScrollbar(MatrixStack ms, int x, int y, int h, int scroll, int total, int window) { - SoundPlayerScreen.fill(ms, x, y, x + 4, y + h, 0x44000000); - int thumb = Math.max(8, (int) ((double) window / (double) total * (double) h)); - int maxScroll = Math.max(1, total - window); - int thumbY = y + (int) ((double) scroll / (double) maxScroll * (double) Math.max(0, h - thumb)); - SoundPlayerScreen.fill(ms, x, thumbY, x + 4, thumbY + thumb, COLOR_ACCENT); + SoundPlayerScreen.fill(ms, x, y, x + 3, y + h, 0x44000000); + int thumbH = Math.max(8, h * window / total); + int maxScroll = total - window; + int thumbY = y + (maxScroll <= 0 ? 0 : scroll * (h - thumbH) / maxScroll); + SoundPlayerScreen.fill(ms, x, thumbY, x + 3, thumbY + thumbH, COLOR_ACCENT); } - private int colorForStatus(ClientSoundCache.Status status) { - switch (status) { - case AVAILABLE: - return COLOR_GREEN; - case TRANSFERRING: - return COLOR_YELLOW; + private String trimRow(String text, int maxChars) { + if (text == null) return ""; + return text.length() <= maxChars ? text : text.substring(0, Math.max(0, maxChars - 3)) + "..."; + } + + private ITextComponent boolLabel(String title, boolean v) { + return new StringTextComponent(title + ": " + (v ? "ВКЛ" : "ВЫКЛ")); + } + + private String categoryRu(String c) { + switch (c) { + case "music": return "музыка"; + case "ambient": return "фон"; + case "voice": return "голос"; + case "effect": return "эффект"; + default: return c; + } + } + + private String cycleCategory(String c) { + for (int i = 0; i < CATEGORIES.length; ++i) { + if (!CATEGORIES[i].equals(c)) continue; + return CATEGORIES[(i + 1) % CATEGORIES.length]; + } + return CATEGORIES[0]; + } + + private String attenuationRu(AttenuationMode m) { + switch (m) { + case LINEAR: return "линейное"; + case EXPONENTIAL: return "экспонента"; + case LOGARITHMIC: return "логарифм"; + case NONE: return "выкл"; + default: return m.name(); + } + } + + private AttenuationMode cycleAttenuation(AttenuationMode m) { + AttenuationMode[] vals = AttenuationMode.values(); + return vals[(m.ordinal() + 1) % vals.length]; + } + + private String modeRu(ActivationMode m) { + switch (m) { + case CLICK: return "Клик"; + case REDSTONE: return "Редстоун"; + case AUTO: return "Авто"; + case TIMER: return "Таймер"; + default: return m.name(); + } + } + + private ActivationMode cycleMode(ActivationMode m) { + ActivationMode[] vals = ActivationMode.values(); + return vals[(m.ordinal() + 1) % vals.length]; + } + + private String redstoneRu(RedstoneMode m) { + switch (m) { + case HOLD: return "Удержание"; + case EDGE: return "Импульс"; + default: return m.name(); + } + } + + private RedstoneMode cycleRedstone(RedstoneMode m) { + RedstoneMode[] vals = RedstoneMode.values(); + return vals[(m.ordinal() + 1) % vals.length]; + } + + private String policyRu(ZoneIntersectionPolicy p) { + switch (p) { + case HIGHEST_PRIORITY: return "Макс"; + case BLEND: return "Смесь"; + case QUEUE: return "Очередь"; + default: return p.name(); + } + } + + private ZoneIntersectionPolicy cyclePolicy(ZoneIntersectionPolicy p) { + ZoneIntersectionPolicy[] vals = ZoneIntersectionPolicy.values(); + return vals[(p.ordinal() + 1) % vals.length]; + } + + private ZoneShape parseZoneShape(String raw) { + try { + return ZoneShape.valueOf(raw); + } catch (Exception e) { + return ZoneShape.SPHERE; + } + } + + private ZoneIntersectionPolicy parseZonePolicy(String raw) { + try { + return ZoneIntersectionPolicy.valueOf(raw); + } catch (Exception e) { + return ZoneIntersectionPolicy.HIGHEST_PRIORITY; + } + } + + private String statusRu(ClientSoundCache.Status s) { + switch (s) { + case AVAILABLE: return "Доступен"; + case TRANSFERRING: return "Загрузка..."; + case ERROR: return "Ошибка"; + case MISSING: return "Отсутствует"; + case UNSUPPORTED: return "Не подд."; + default: return s.name(); + } + } + + private int colorForStatus(ClientSoundCache.Status s) { + switch (s) { + case AVAILABLE: return COLOR_GREEN; + case TRANSFERRING: return COLOR_YELLOW; case ERROR: case MISSING: - case UNSUPPORTED: - return COLOR_RED; + case UNSUPPORTED: return COLOR_RED; + default: return COLOR_TEXT_MUTED; } - return COLOR_TEXT_MUTED; } private void drawContextTooltips(MatrixStack matrixStack, int mouseX, int mouseY) { if (this.previewButton != null && this.previewButton.isHovered()) { - this.renderTooltip(matrixStack, new StringTextComponent("Предпрослушивание звука с текущими параметрами"), mouseX, mouseY); + this.renderTooltip(matrixStack, new StringTextComponent("Предпрослушивание выбранного звука"), mouseX, mouseY); } else if (this.myPosButton != null && this.myPosButton.isHovered()) { this.renderTooltip(matrixStack, new StringTextComponent("Вставить текущие координаты игрока как центр звука"), mouseX, mouseY); + } else if (this.zoneVisualizerButton != null && this.zoneVisualizerButton.isHovered()) { + this.renderTooltip(matrixStack, new StringTextComponent("Отображать 3D границы зоны в игровом мире"), mouseX, mouseY); } else if (this.copyConfigButton != null && this.copyConfigButton.isHovered()) { this.renderTooltip(matrixStack, new StringTextComponent("Скопировать все настройки блока в буфер обмена"), mouseX, mouseY); } else if (this.pasteConfigButton != null && this.pasteConfigButton.isHovered()) { this.renderTooltip(matrixStack, new StringTextComponent("Вставить настройки блока из буфера обмена"), mouseX, mouseY); } else if (this.playNowButton != null && this.playNowButton.isHovered()) { - this.renderTooltip(matrixStack, new StringTextComponent("Принудительно запустить воспроизведение блока в мире"), mouseX, mouseY); + this.renderTooltip(matrixStack, new StringTextComponent("Принудительно запустить звук этого блока в мире"), mouseX, mouseY); } else if (this.stopNowButton != null && this.stopNowButton.isHovered()) { this.renderTooltip(matrixStack, new StringTextComponent("Остановить звук этого блока"), mouseX, mouseY); } @@ -1323,152 +1496,32 @@ public class SoundPlayerScreen extends ContainerScreen { } } - private List allFields() { - ArrayList all = new ArrayList<>(); - all.addAll(this.basicFields); - all.addAll(this.effectsFields); - all.addAll(this.triggerFields); - all.addAll(this.zoneFields); - all.addAll(this.scenarioFields); - return all; - } - - private ActivationMode cycleActivation(ActivationMode current) { - ActivationMode[] all = ActivationMode.values(); - return all[(current.ordinal() + 1) % all.length]; - } - - private RedstoneMode cycleRedstone(RedstoneMode current) { - return current == RedstoneMode.EDGE ? RedstoneMode.HOLD : RedstoneMode.EDGE; - } - - private AttenuationMode cycleAttenuation(AttenuationMode current) { - AttenuationMode[] all = AttenuationMode.values(); - return all[(current.ordinal() + 1) % all.length]; - } - - private String cycleCategory(String current) { - for (int i = 0; i < CATEGORIES.length; ++i) { - if (!CATEGORIES[i].equals(current)) continue; - return CATEGORIES[(i + 1) % CATEGORIES.length]; - } - return CATEGORIES[0]; - } - - private ZoneIntersectionPolicy cyclePolicy(ZoneIntersectionPolicy current) { - ZoneIntersectionPolicy[] all = ZoneIntersectionPolicy.values(); - return all[(current.ordinal() + 1) % all.length]; - } - - private ZoneShape parseZoneShape(String raw) { + private float parseFloat(String s, float def) { try { - return ZoneShape.valueOf(raw); - } catch (Exception ignored) { - return ZoneShape.SPHERE; + return Float.parseFloat(s.trim().replace(',', '.')); + } catch (Exception e) { + return def; } } - private ZoneIntersectionPolicy parseZonePolicy(String raw) { + private int parseInt(String s, int def) { try { - return ZoneIntersectionPolicy.valueOf(raw); - } catch (Exception ignored) { - return ZoneIntersectionPolicy.HIGHEST_PRIORITY; + return Integer.parseInt(s.trim()); + } catch (Exception e) { + return def; } } - private StringTextComponent boolLabel(String key, boolean value) { - return new StringTextComponent(key + ": " + (value ? "ВКЛ" : "ВЫКЛ")); + private float clampFloat(float v, float min, float max) { + return Math.max(min, Math.min(max, v)); } - private String floatText(float value, int digits) { - return String.format("%." + digits + "f", Float.valueOf(value)).replace(',', '.'); + private int clampInt(int v, int min, int max) { + return Math.max(min, Math.min(max, v)); } - private float parseFloat(String raw, float fallback) { - try { - return Float.parseFloat(raw.trim().replace(',', '.')); - } catch (Exception ignored) { - return fallback; - } - } - - private int parseInt(String raw, int fallback) { - try { - return Integer.parseInt(raw.trim()); - } catch (Exception ignored) { - return fallback; - } - } - - private float clampFloat(float value, float min, float max) { - return Math.max(min, Math.min(max, value)); - } - - private int clampInt(int value, int min, int max) { - return Math.max(min, Math.min(max, value)); - } - - private String trimRow(String text, int maxChars) { - if (text.length() <= maxChars) { - return text; - } - return text.substring(0, Math.max(0, maxChars - 1)) + "…"; - } - - private String redstoneRu(RedstoneMode mode) { - return mode == RedstoneMode.HOLD ? "HOLD" : "EDGE"; - } - - private String categoryRu(String category) { - if ("music".equals(category)) return "музыка"; - if ("ambient".equals(category)) return "фон"; - if ("effect".equals(category)) return "эффект"; - if ("voice".equals(category)) return "голос"; - return category; - } - - private String attenuationRu(AttenuationMode mode) { - switch (mode) { - case NONE: return "нет"; - case LINEAR: return "линейное"; - case EXPONENTIAL: return "экспоненц."; - case LOGARITHMIC: return "логарифм."; - } - return mode.name(); - } - - private String modeRu(ActivationMode mode) { - switch (mode) { - case CLICK: return "Клик"; - case REDSTONE: return "Редстоун"; - case AUTO: return "Авто"; - case TIMER: return "Таймер"; - } - return mode.name(); - } - - private String shapeRu(ZoneShape shape) { - return shape == ZoneShape.BOX ? "Куб" : "Сфера"; - } - - private String policyRu(ZoneIntersectionPolicy policy) { - switch (policy) { - case HIGHEST_PRIORITY: return "Приоритет"; - case BLEND: return "Смешивание"; - case QUEUE: return "Очередь"; - } - return policy.name(); - } - - private String statusRu(ClientSoundCache.Status status) { - switch (status) { - case AVAILABLE: return "Доступен"; - case TRANSFERRING: return "Загрузка…"; - case ERROR: return "Ошибка"; - case MISSING: return "Отсутствует"; - case UNSUPPORTED: return "Не поддеж."; - } - return status.name(); + private String floatText(float v, int decimals) { + return String.format(java.util.Locale.US, "%." + decimals + "f", Float.valueOf(v)); } private enum Tab {