aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md16
-rw-r--r--dependencies.gradle4
-rw-r--r--gradle.properties2
-rw-r--r--src/main/java/com/github/bartimaeusnek/bartworks/common/net/BW_Network.java9
-rw-r--r--src/main/java/gregtech/api/enums/GT_Values.java5
-rw-r--r--src/main/java/gregtech/api/enums/ItemList.java10
-rw-r--r--src/main/java/gregtech/api/enums/MetaTileEntityIDs.java5
-rw-r--r--src/main/java/gregtech/api/enums/Textures.java3
-rw-r--r--src/main/java/gregtech/api/gui/modularui/GT_UITextures.java2
-rw-r--r--src/main/java/gregtech/api/metatileentity/CommonMetaTileEntity.java34
-rw-r--r--src/main/java/gregtech/api/net/GT_PacketTypes.java1
-rw-r--r--src/main/java/gregtech/api/net/GT_Packet_MusicSystemData.java58
-rw-r--r--src/main/java/gregtech/api/net/IGT_NetworkHandler.java4
-rw-r--r--src/main/java/gregtech/api/util/GT_MusicSystem.java666
-rw-r--r--src/main/java/gregtech/client/ElectricJukeboxSound.java95
-rw-r--r--src/main/java/gregtech/client/ISeekingSound.java14
-rw-r--r--src/main/java/gregtech/client/SeekingOggCodec.java98
-rw-r--r--src/main/java/gregtech/common/GT_Client.java18
-rw-r--r--src/main/java/gregtech/common/GT_Network.java9
-rw-r--r--src/main/java/gregtech/common/GT_Proxy.java17
-rw-r--r--src/main/java/gregtech/common/items/GT_WirelessHeadphones.java118
-rw-r--r--src/main/java/gregtech/common/misc/GT_Command.java23
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/basic/GT_MetaTileEntity_BetterJukebox.java707
-rw-r--r--src/main/java/gregtech/loaders/preload/GT_Loader_Item_Block_And_Fluid.java2
-rw-r--r--src/main/java/gregtech/loaders/preload/GT_Loader_MetaTileEntities.java40
-rw-r--r--src/main/java/gregtech/mixin/Mixin.java6
-rw-r--r--src/main/java/gtPlusPlus/core/block/machine/Machine_SuperJukebox.java18
-rw-r--r--src/main/resources/META-INF/ggfab_at.cfg2
-rw-r--r--src/main/resources/META-INF/gregtech_at.cfg (renamed from src/main/resources/META-INF/tectech_at.cfg)8
-rw-r--r--src/main/resources/assets/gregtech/lang/en_US.lang6
-rw-r--r--src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_SIDE_JUKEBOX.pngbin0 -> 149 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/blocks/iconsets/OVERLAY_TOP_JUKEBOX.pngbin0 -> 208 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/gui/overlay_button/shuffle.pngbin0 -> 354 bytes
-rw-r--r--src/main/resources/assets/gregtech/textures/items/gt.WirelessHeadphones.pngbin0 -> 232 bytes
-rw-r--r--src/main/resources/assets/miscutils/lang/en_US.lang1
-rw-r--r--src/main/resources/soundmeta/durations.json16
-rw-r--r--src/mixin/java/gregtech/mixin/mixins/early/minecraft/SoundManagerInnerMixin.java33
-rw-r--r--src/mixin/java/gregtech/mixin/mixins/early/minecraft/SoundManagerMixin.java52
38 files changed, 2090 insertions, 12 deletions
diff --git a/README.md b/README.md
index ceb985caa5..6e512251a8 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,22 @@ the chance of strange errors.
Some textures/ideas have been taken from future versions of GT and texture pack authors for GTNH. Credit goes to Jimbno
for the UU-Tex texture pack and its contributions to the base pack here: https://github.com/Jimbno/UU-Tex.
+## Music duration metadata
+
+The electric jukebox requires duration metadata to specify how many milliseconds each disk plays for.
+These can be included in mods' jar resources under `soundmeta/durations.json`, or in the pack config directory at `config/soundmeta/durations.json`.
+The format is a simple key-value map of sound IDs mapping to millisecond counts, and can be generated from the client automatically using `/gt dump_music_durations`.
+
+```json
+{
+ "soundDurationsMs": {
+ "minecraft:11": 71112,
+ "minecraft:13": 178086,
+ "minecraft:blocks": 345914
+ }
+}
+```
+
## License
GT5-Unofficial is free software: you can redistribute it and/or modify it under the terms of the
diff --git a/dependencies.gradle b/dependencies.gradle
index bcc5705cd7..ef6223d7fb 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -49,6 +49,9 @@ dependencies {
compileOnlyApi('com.github.GTNewHorizons:ThaumicTinkerer:2.10.1:dev')
compileOnlyApi("com.github.GTNewHorizons:Mobs-Info:0.4.1-GTNH:dev")
compileOnlyApi("com.github.GTNewHorizons:Navigator:1.0.6:dev")
+ implementation('com.github.GTNewHorizons:Baubles:1.0.4:dev') {transitive=false}
+ // Required to prevent an older bauble api from Extra Utilities from loading first in the javac classpath
+ compileOnly('com.github.GTNewHorizons:Baubles:1.0.4:dev') {transitive=false}
devOnlyNonPublishable("com.github.GTNewHorizons:Infernal-Mobs:1.8.1-GTNH:dev")
@@ -89,7 +92,6 @@ dependencies {
compileOnly("com.github.GTNewHorizons:CraftTweaker:3.3.1:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:BetterLoadingScreen:1.7.0-GTNH:dev") { transitive = false }
- implementation('com.github.GTNewHorizons:Baubles:1.0.4:dev') {transitive=false}
compileOnly('com.github.GTNewHorizons:SC2:2.1.1:dev') {transitive=false}
compileOnly('com.github.GTNewHorizons:Binnie:2.4.1:dev') {transitive = false}
compileOnly('curse.maven:PlayerAPI-228969:2248928') {transitive=false}
diff --git a/gradle.properties b/gradle.properties
index 07b09c6bc5..7af9b10202 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -80,7 +80,7 @@ apiPackage =
# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/
# There can be multiple files in a space-separated list.
# Example value: mymodid_at.cfg nei_at.cfg
-accessTransformersFile = ggfab_at.cfg tectech_at.cfg
+accessTransformersFile = gregtech_at.cfg
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
usesMixins = true
diff --git a/src/main/java/com/github/bartimaeusnek/bartworks/common/net/BW_Network.java b/src/main/java/com/github/bartimaeusnek/bartworks/common/net/BW_Network.java
index b5e002ef29..01085e2b31 100644
--- a/src/main/java/com/github/bartimaeusnek/bartworks/common/net/BW_Network.java
+++ b/src/main/java/com/github/bartimaeusnek/bartworks/common/net/BW_Network.java
@@ -121,6 +121,15 @@ public class BW_Network extends MessageToMessageCodec<FMLProxyPacket, GT_Packet_
}
@Override
+ public void sendToAll(@Nonnull GT_Packet aPacket) {
+ this.mChannel.get(Side.SERVER)
+ .attr(FMLOutboundHandler.FML_MESSAGETARGET)
+ .set(FMLOutboundHandler.OutboundTarget.ALL);
+ this.mChannel.get(Side.SERVER)
+ .writeAndFlush(aPacket);
+ }
+
+ @Override
public void sendToServer(@Nonnull GT_Packet aPacket) {
this.mChannel.get(Side.CLIENT)
.attr(FMLOutboundHandler.FML_MESSAGETARGET)
diff --git a/src/main/java/gregtech/api/enums/GT_Values.java b/src/main/java/gregtech/api/enums/GT_Values.java
index a27c5ee43b..a7fde53b66 100644
--- a/src/main/java/gregtech/api/enums/GT_Values.java
+++ b/src/main/java/gregtech/api/enums/GT_Values.java
@@ -677,6 +677,11 @@ public class GT_Values {
+ "Gold";
public static final String AuthorVolence = "Author: " + EnumChatFormatting.AQUA + "Volence";
+ public static final String AuthorEigenRaven = "Author: " + EnumChatFormatting.DARK_PURPLE
+ + "Eigen"
+ + EnumChatFormatting.BOLD
+ + "Raven";
+
public static final String AuthorNotAPenguin = "Author: " + EnumChatFormatting.WHITE
+ EnumChatFormatting.BOLD
+ "Not"
diff --git a/src/main/java/gregtech/api/enums/ItemList.java b/src/main/java/gregtech/api/enums/ItemList.java
index 77fc88bd30..6533aca304 100644
--- a/src/main/java/gregtech/api/enums/ItemList.java
+++ b/src/main/java/gregtech/api/enums/ItemList.java
@@ -2518,7 +2518,15 @@ public enum ItemList implements IItemContainer {
Automation_ChestBuffer_UEV,
Automation_ChestBuffer_UIV,
Automation_ChestBuffer_UMV,
- Automation_ChestBuffer_UXV,;
+ Automation_ChestBuffer_UXV,
+ BetterJukebox_LV,
+ BetterJukebox_MV,
+ BetterJukebox_HV,
+ BetterJukebox_EV,
+ BetterJukebox_IV,
+ WirelessHeadphones,
+ // semicolon after the comment to reduce merge conflicts
+ ;
public static final ItemList[] DYE_ONLY_ITEMS = { Color_00, Color_01, Color_02, Color_03, Color_04, Color_05,
Color_06, Color_07, Color_08, Color_09, Color_10, Color_11, Color_12, Color_13, Color_14, Color_15 },
diff --git a/src/main/java/gregtech/api/enums/MetaTileEntityIDs.java b/src/main/java/gregtech/api/enums/MetaTileEntityIDs.java
index 6772f4dc12..464a8b2710 100644
--- a/src/main/java/gregtech/api/enums/MetaTileEntityIDs.java
+++ b/src/main/java/gregtech/api/enums/MetaTileEntityIDs.java
@@ -1301,6 +1301,11 @@ public enum MetaTileEntityIDs {
lsc(13106),
tfftHatch(13109),
WORMHOLE_GENERATOR_CONTROLLER(13115),
+ BETTER_JUKEBOX_LV(14301),
+ BETTER_JUKEBOX_MV(14302),
+ BETTER_JUKEBOX_HV(14303),
+ BETTER_JUKEBOX_EV(14304),
+ BETTER_JUKEBOX_IV(14305),
MegaChemicalReactor(13366),
MegaOilCracker(13367),
ExtremeEntityCrusherController(14201),
diff --git a/src/main/java/gregtech/api/enums/Textures.java b/src/main/java/gregtech/api/enums/Textures.java
index 049fd97361..53125ec454 100644
--- a/src/main/java/gregtech/api/enums/Textures.java
+++ b/src/main/java/gregtech/api/enums/Textures.java
@@ -637,6 +637,7 @@ public class Textures {
OVERLAY_TOP_STEAM_FURNACE_GLOW,
OVERLAY_TOP_STEAM_ALLOY_SMELTER,
OVERLAY_TOP_STEAM_ALLOY_SMELTER_GLOW,
+ OVERLAY_TOP_JUKEBOX,
OVERLAY_TOP_STEAM_MACERATOR,
OVERLAY_TOP_STEAM_MACERATOR_GLOW,
@@ -757,6 +758,7 @@ public class Textures {
OVERLAY_SIDE_SCANNER_GLOW,
OVERLAY_SIDE_INDUSTRIAL_APIARY,
OVERLAY_SIDE_INDUSTRIAL_APIARY_GLOW,
+ OVERLAY_SIDE_JUKEBOX,
OVERLAY_TOP_POTIONBREWER_ACTIVE,
OVERLAY_TOP_POTIONBREWER_ACTIVE_GLOW,
@@ -1895,6 +1897,7 @@ public class Textures {
TURBINE_SMALL,
TURBINE_LARGE,
TURBINE_HUGE,
+ WIRELESS_HEADPHONES,
POCKET_MULTITOOL_CLOSED,
POCKET_MULTITOOL_BRANCHCUTTER,
POCKET_MULTITOOL_FILE,
diff --git a/src/main/java/gregtech/api/gui/modularui/GT_UITextures.java b/src/main/java/gregtech/api/gui/modularui/GT_UITextures.java
index b456026dbd..98612ad936 100644
--- a/src/main/java/gregtech/api/gui/modularui/GT_UITextures.java
+++ b/src/main/java/gregtech/api/gui/modularui/GT_UITextures.java
@@ -338,6 +338,8 @@ public class GT_UITextures {
public static final UITexture OVERLAY_BUTTON_ARROW_GREEN_DOWN = UITexture
.fullImage(GregTech.ID, "gui/overlay_button/arrow_green_down");
public static final UITexture OVERLAY_BUTTON_CYCLIC = UITexture.fullImage(GregTech.ID, "gui/overlay_button/cyclic");
+ public static final UITexture OVERLAY_BUTTON_SHUFFLE = UITexture
+ .fullImage(GregTech.ID, "gui/overlay_button/shuffle");
public static final UITexture OVERLAY_BUTTON_EMIT_ENERGY = UITexture
.fullImage(GregTech.ID, "gui/overlay_button/emit_energy");
public static final UITexture OVERLAY_BUTTON_EMIT_REDSTONE = UITexture
diff --git a/src/main/java/gregtech/api/metatileentity/CommonMetaTileEntity.java b/src/main/java/gregtech/api/metatileentity/CommonMetaTileEntity.java
index 5a2e88b242..d0b7a2e194 100644
--- a/src/main/java/gregtech/api/metatileentity/CommonMetaTileEntity.java
+++ b/src/main/java/gregtech/api/metatileentity/CommonMetaTileEntity.java
@@ -13,6 +13,9 @@ import com.gtnewhorizons.modularui.api.screen.ModularWindow;
import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
import appeng.api.crafting.ICraftingIconProvider;
+import appeng.api.implementations.tiles.ISoundP2PHandler;
+import appeng.me.cache.helpers.TunnelCollection;
+import appeng.parts.p2p.PartP2PSound;
import gregtech.GT_Mod;
import gregtech.api.GregTech_API;
import gregtech.api.enums.ItemList;
@@ -29,7 +32,7 @@ import gregtech.api.util.GT_Log;
import gregtech.api.util.GT_Utility;
public abstract class CommonMetaTileEntity extends CoverableTileEntity
- implements IGregTechTileEntity, ICraftingIconProvider {
+ implements IGregTechTileEntity, ICraftingIconProvider, ISoundP2PHandler {
protected boolean mNeedsBlockUpdate = true, mNeedsUpdate = true, mSendClientData = false, mInventoryChanged = false;
@@ -337,4 +340,33 @@ public abstract class CommonMetaTileEntity extends CoverableTileEntity
}
return builder.build();
}
+
+ @Override
+ public boolean allowSoundProxying(PartP2PSound p2p) {
+ if (hasValidMetaTileEntity() && getMetaTileEntity() instanceof ISoundP2PHandler metaHandler) {
+ return metaHandler.allowSoundProxying(p2p);
+ }
+ return ISoundP2PHandler.super.allowSoundProxying(p2p);
+ }
+
+ @Override
+ public void onSoundP2PAttach(PartP2PSound p2p) {
+ if (hasValidMetaTileEntity() && getMetaTileEntity() instanceof ISoundP2PHandler metaHandler) {
+ metaHandler.onSoundP2PAttach(p2p);
+ }
+ }
+
+ @Override
+ public void onSoundP2PDetach(PartP2PSound p2p) {
+ if (hasValidMetaTileEntity() && getMetaTileEntity() instanceof ISoundP2PHandler metaHandler) {
+ metaHandler.onSoundP2PDetach(p2p);
+ }
+ }
+
+ @Override
+ public void onSoundP2POutputUpdate(PartP2PSound p2p, TunnelCollection<PartP2PSound> outputs) {
+ if (hasValidMetaTileEntity() && getMetaTileEntity() instanceof ISoundP2PHandler metaHandler) {
+ metaHandler.onSoundP2POutputUpdate(p2p, outputs);
+ }
+ }
}
diff --git a/src/main/java/gregtech/api/net/GT_PacketTypes.java b/src/main/java/gregtech/api/net/GT_PacketTypes.java
index 18fc5134ba..f59a7918a9 100644
--- a/src/main/java/gregtech/api/net/GT_PacketTypes.java
+++ b/src/main/java/gregtech/api/net/GT_PacketTypes.java
@@ -31,6 +31,7 @@ public enum GT_PacketTypes {
MULTI_TILE_ENTITY(18, new GT_Packet_MultiTileEntity(true)),
SEND_OREGEN_PATTERN(19, new GT_Packet_SendOregenPattern()),
TOOL_SWITCH_MODE(20, new GT_Packet_ToolSwitchMode()),
+ MUSIC_SYSTEM_DATA(21, new GT_Packet_MusicSystemData()),
// merge conflict prevention comment, keep a trailing comma above
;
diff --git a/src/main/java/gregtech/api/net/GT_Packet_MusicSystemData.java b/src/main/java/gregtech/api/net/GT_Packet_MusicSystemData.java
new file mode 100644
index 0000000000..13ebf49205
--- /dev/null
+++ b/src/main/java/gregtech/api/net/GT_Packet_MusicSystemData.java
@@ -0,0 +1,58 @@
+package gregtech.api.net;
+
+import net.minecraft.world.IBlockAccess;
+
+import com.google.common.io.ByteArrayDataInput;
+
+import gregtech.api.util.GT_MusicSystem;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+public class GT_Packet_MusicSystemData extends GT_Packet_New {
+
+ ByteBuf storedData;
+
+ public GT_Packet_MusicSystemData() {
+ super(true);
+ }
+
+ public GT_Packet_MusicSystemData(ByteBuf data) {
+ super(false);
+ this.storedData = data;
+ }
+
+ @Override
+ public byte getPacketID() {
+ return GT_PacketTypes.MUSIC_SYSTEM_DATA.id;
+ }
+
+ @Override
+ public void encode(ByteBuf aOut) {
+ if (storedData == null) {
+ return;
+ }
+ storedData.markReaderIndex();
+ final int len = storedData.readableBytes();
+ aOut.writeInt(len);
+ aOut.writeBytes(storedData);
+ storedData.resetReaderIndex();
+ }
+
+ @Override
+ public GT_Packet_New decode(ByteArrayDataInput aData) {
+ final int len = aData.readInt();
+ final byte[] fullData = new byte[len];
+ aData.readFully(fullData);
+ return new GT_Packet_MusicSystemData(Unpooled.wrappedBuffer(fullData));
+ }
+
+ @Override
+ public void process(IBlockAccess aWorld) {
+ if (aWorld == null || storedData == null) {
+ return;
+ }
+ storedData.markReaderIndex();
+ GT_MusicSystem.ClientSystem.loadUpdatedSources(storedData);
+ storedData.resetReaderIndex();
+ }
+}
diff --git a/src/main/java/gregtech/api/net/IGT_NetworkHandler.java b/src/main/java/gregtech/api/net/IGT_NetworkHandler.java
index 07c4a37030..8436a89e9b 100644
--- a/src/main/java/gregtech/api/net/IGT_NetworkHandler.java
+++ b/src/main/java/gregtech/api/net/IGT_NetworkHandler.java
@@ -12,6 +12,10 @@ public interface IGT_NetworkHandler {
void sendToAllAround(GT_Packet aPacket, TargetPoint aPosition);
+ default void sendToAll(GT_Packet aPacket) {
+ throw new UnsupportedOperationException("sendToAll not implemented");
+ }
+
void sendToServer(GT_Packet aPacket);
void sendPacketToAllPlayersInRange(World aWorld, GT_Packet aPacket, int aX, int aZ);
diff --git a/src/main/java/gregtech/api/util/GT_MusicSystem.java b/src/main/java/gregtech/api/util/GT_MusicSystem.java
new file mode 100644
index 0000000000..3a171e0395
--- /dev/null
+++ b/src/main/java/gregtech/api/util/GT_MusicSystem.java
@@ -0,0 +1,666 @@
+package gregtech.api.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.audio.SoundEventAccessorComposite;
+import net.minecraft.client.audio.SoundRegistry;
+import net.minecraft.inventory.IInventory;
+import net.minecraft.item.ItemRecord;
+import net.minecraft.item.ItemStack;
+import net.minecraft.launchwrapper.Launch;
+import net.minecraft.util.ResourceLocation;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.jetbrains.annotations.ApiStatus;
+import org.joml.Vector4i;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.jcraft.jorbis.VorbisFile;
+
+import baubles.api.BaublesApi;
+import cpw.mods.fml.common.network.ByteBufUtils;
+import gregtech.GT_Mod;
+import gregtech.api.enums.GT_Values;
+import gregtech.api.net.GT_Packet_MusicSystemData;
+import gregtech.client.ElectricJukeboxSound;
+import gregtech.common.items.GT_WirelessHeadphones;
+import gregtech.common.tileentities.machines.basic.GT_MetaTileEntity_BetterJukebox;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+
+/**
+ * A system that keeps track of jukebox music tracks playing in different locations.
+ * Compared to vanilla jukebox handling, this allows music to resume playing after reloading the chunk the jukeboxes are
+ * in.
+ * It also allows the headphone item to modify the hearing range of a given disc, including other dimensions.
+ * <p>
+ * Vector4i coordinates point to X,Y,Z,Dimension of the source
+ *
+ * @author eigenraven
+ */
+public final class GT_MusicSystem {
+
+ private GT_MusicSystem() {}
+
+ public static final class MusicSource {
+
+ /** Flag keeping track of when the data of this source needs to be sent to clients again */
+ public boolean modified;
+ public final UUID sourceID;
+ /** Currently playing track */
+ public ResourceLocation currentRecord;
+ /** Headphone range */
+ public GT_MetaTileEntity_BetterJukebox.HeadphoneLimit headphoneLimit;
+ /**
+ * {@link System#currentTimeMillis()} at the time this record started playing, in server time
+ */
+ public long startedPlayingAtMs;
+ /** Time the record was playing for at the time it was serialized. */
+ public long playingForMs;
+ /** The origin of this source used for wireless headphone range calculations */
+ public final Vector4i originPosition = new Vector4i();
+ /** Number of blocks from {@link MusicSource#originPosition} the headphones can work */
+ public int headphoneBlockRange;
+ /** Densely packed parameters for each "emitter" associated with the source, for fast iteration */
+ public int[] emitterParameters; // [{x,y,z,dim,volume}, {x, ...}, {x, ...}, ...]
+ /** Offsets into the parameters array */
+ public static final int EMITTER_X = 0;
+ /** Offsets into the parameters array */
+ public static final int EMITTER_Y = 1;
+ /** Offsets into the parameters array */
+ public static final int EMITTER_Z = 2;
+ /** Offsets into the parameters array */
+ public static final int EMITTER_DIMENSION = 3;
+ /** 100 times the floating point "volume" used by the sound system for range calculations */
+ public static final int EMITTER_VOLUME_X_100 = 4;
+ /** Iteration stride for packed emitter parameters */
+ public static final int EMITTER_STRIDE = EMITTER_VOLUME_X_100 + 1;
+
+ public MusicSource(UUID sourceID) {
+ this.sourceID = sourceID;
+ }
+
+ public void resizeEmitterArray(int count) {
+ int len = count * EMITTER_STRIDE;
+ if (emitterParameters == null || emitterParameters.length != len) {
+ if (emitterParameters == null) {
+ emitterParameters = new int[len];
+ } else {
+ emitterParameters = Arrays.copyOf(emitterParameters, len);
+ }
+ modified = true;
+ }
+ }
+
+ public void setEmitter(int index, Vector4i position, float volume) {
+ int arrIndex = index * EMITTER_STRIDE;
+ if (arrIndex < 0 || arrIndex >= emitterParameters.length) {
+ throw new IndexOutOfBoundsException(
+ "Trying to access emitter with index " + index
+ + " in an array of "
+ + emitterParameters.length / EMITTER_STRIDE);
+ }
+
+ if (emitterParameters[arrIndex + EMITTER_X] != position.x) {
+ modified = true;
+ emitterParameters[arrIndex + EMITTER_X] = position.x;
+ }
+ if (emitterParameters[arrIndex + EMITTER_Y] != position.y) {
+ modified = true;
+ emitterParameters[arrIndex + EMITTER_Y] = position.y;
+ }
+ if (emitterParameters[arrIndex + EMITTER_Z] != position.z) {
+ modified = true;
+ emitterParameters[arrIndex + EMITTER_Z] = position.z;
+ }
+ if (emitterParameters[arrIndex + EMITTER_DIMENSION] != position.w) {
+ modified = true;
+ emitterParameters[arrIndex + EMITTER_DIMENSION] = position.w;
+ }
+ final int intVolume = (int) (volume * 100.0f);
+ if (emitterParameters[arrIndex + EMITTER_VOLUME_X_100] != intVolume) {
+ modified = true;
+ emitterParameters[arrIndex + EMITTER_VOLUME_X_100] = intVolume;
+ }
+ }
+
+ /** x squared */
+ private static int sq(int x) {
+ return x * x;
+ }
+
+ /** @return Index of closest emitter in range, or -1 if none is nearby. */
+ public int closestEmitter(int x, int y, int z, int dim) {
+ int closest = -1;
+ int closestDistanceSq = Integer.MAX_VALUE;
+ final int emittersCount = emitterParameters.length / EMITTER_STRIDE;
+ for (int i = 0; i < emittersCount; i++) {
+ final int offset = i * EMITTER_STRIDE;
+ final int eDim = emitterParameters[offset + EMITTER_DIMENSION];
+ if (eDim != dim) {
+ continue;
+ }
+ final int eX = emitterParameters[offset + EMITTER_X];
+ final int eY = emitterParameters[offset + EMITTER_Y];
+ final int eZ = emitterParameters[offset + EMITTER_Z];
+ final int distanceSq = sq(x - eX) + sq(y - eY) + sq(z - eZ);
+ if (distanceSq < closestDistanceSq) {
+ closestDistanceSq = distanceSq;
+ closest = i;
+ }
+ }
+ return closest;
+ }
+
+ public boolean inHeadphoneRange(int x, int y, int z, int dim) {
+ return switch (headphoneLimit) {
+ case BETWEEN_DIMENSIONS -> true;
+ case INSIDE_DIMENSION -> dim == originPosition.w;
+ case BLOCK_RANGE -> dim == originPosition.w
+ && originPosition.distanceSquared(x, y, z, dim) <= sq(headphoneBlockRange);
+ };
+ }
+
+ public void encode(final ByteBuf target) {
+ target.writeLong(sourceID.getMostSignificantBits());
+ target.writeLong(sourceID.getLeastSignificantBits());
+ if (currentRecord != null) {
+ final int duration = getMusicRecordDurations().getOrDefault(currentRecord, Integer.MAX_VALUE);
+ if (playingForMs >= duration) {
+ // Record already finished playing, let's not send it to the client anymore.
+ target.writeBoolean(false);
+ } else {
+ target.writeBoolean(true);
+ ByteBufUtils.writeUTF8String(target, currentRecord.getResourceDomain());
+ ByteBufUtils.writeUTF8String(target, currentRecord.getResourcePath());
+ }
+ } else {
+ target.writeBoolean(false);
+ }
+ target.writeByte((byte) headphoneLimit.ordinal());
+ ByteBufUtils.writeVarInt(target, headphoneBlockRange, 5);
+ target.writeLong(startedPlayingAtMs);
+ target.writeLong(playingForMs);
+ ByteBufUtils.writeVarInt(target, originPosition.x, 5);
+ ByteBufUtils.writeVarInt(target, originPosition.y, 5);
+ ByteBufUtils.writeVarInt(target, originPosition.z, 5);
+ ByteBufUtils.writeVarInt(target, originPosition.w, 5);
+ ByteBufUtils.writeVarInt(target, emitterParameters.length, 5);
+ for (int emitterParameter : emitterParameters) {
+ ByteBufUtils.writeVarInt(target, emitterParameter, 5);
+ }
+ }
+
+ public static MusicSource decode(final ByteBuf bytes) {
+ final long uuidMsb = bytes.readLong();
+ final long uuidLsb = bytes.readLong();
+ final MusicSource source = new MusicSource(new UUID(uuidMsb, uuidLsb));
+ final boolean hasRecord = bytes.readBoolean();
+ if (hasRecord) {
+ final String domain = ByteBufUtils.readUTF8String(bytes);
+ final String path = ByteBufUtils.readUTF8String(bytes);
+ source.currentRecord = new ResourceLocation(domain, path);
+ }
+ source.headphoneLimit = GT_MetaTileEntity_BetterJukebox.HeadphoneLimit.ENTRIES.get(bytes.readByte());
+ source.headphoneBlockRange = ByteBufUtils.readVarInt(bytes, 5);
+ source.startedPlayingAtMs = bytes.readLong();
+ source.playingForMs = bytes.readLong();
+ final int originX = ByteBufUtils.readVarInt(bytes, 5);
+ final int originY = ByteBufUtils.readVarInt(bytes, 5);
+ final int originZ = ByteBufUtils.readVarInt(bytes, 5);
+ final int originW = ByteBufUtils.readVarInt(bytes, 5);
+ source.originPosition.set(originX, originY, originZ, originW);
+ final int emittersLength = ByteBufUtils.readVarInt(bytes, 5);
+ source.emitterParameters = new int[emittersLength];
+ for (int i = 0; i < emittersLength; i++) {
+ source.emitterParameters[i] = ByteBufUtils.readVarInt(bytes, 5);
+ }
+
+ return source;
+ }
+
+ public void setRecord(final ResourceLocation record) {
+ setRecord(record, 0);
+ }
+
+ public void setRecord(final ResourceLocation record, long seekOffset) {
+ modified = true;
+ currentRecord = record;
+ playingForMs = seekOffset;
+ startedPlayingAtMs = System.currentTimeMillis() - seekOffset;
+ }
+ }
+
+ public static final class ServerSystem {
+
+ static final Object2ObjectOpenHashMap<UUID, MusicSource> musicSources = new Object2ObjectOpenHashMap<>(32);
+ static boolean musicSourcesDirty = false;
+
+ // Everything is synchronized to allow calling into here from the client when singleplayer synchronization is
+ // needed.
+
+ public static synchronized MusicSource registerOrGetMusicSource(UUID uuid) {
+ return musicSources.computeIfAbsent(uuid, (UUID id) -> {
+ musicSourcesDirty = true;
+ return new MusicSource(id);
+ });
+ }
+
+ public static synchronized void removeMusicSource(UUID uuid) {
+ musicSources.remove(uuid);
+ musicSourcesDirty = true;
+ }
+
+ public static synchronized void reset() {
+ musicSources.clear();
+ musicSourcesDirty = true;
+ }
+
+ public static synchronized ByteBuf serialize() {
+ final ByteBuf out = Unpooled.buffer();
+ ByteBufUtils.writeVarInt(out, musicSources.size(), 5);
+ musicSources.forEach((uuid, source) -> source.encode(out));
+ return out;
+ }
+
+ private static boolean tickAnyDirty;
+
+ public static syn