mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 22:45:04 +01:00
Re-implement jukebox songs
This commit is contained in:
parent
8f5d1560a2
commit
29c9515d55
7 changed files with 141 additions and 44 deletions
|
@ -27,13 +27,15 @@ package org.geysermc.geyser.item.enchantment;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.item.BedrockEnchantment;
|
||||
import org.geysermc.geyser.session.cache.tags.EnchantmentTag;
|
||||
import org.geysermc.geyser.session.cache.tags.ItemTag;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @param description only populated if {@link #bedrockEnchantment()} is not null.
|
||||
|
@ -59,7 +61,7 @@ public record Enchantment(String identifier,
|
|||
String exclusiveSet = data.getString("exclusive_set", null);
|
||||
EnchantmentTag exclusiveSetTag = exclusiveSet == null ? null : EnchantmentTag.ALL_ENCHANTMENT_TAGS.get(exclusiveSet.substring(1));
|
||||
BedrockEnchantment bedrockEnchantment = BedrockEnchantment.getByJavaIdentifier(entry.getId());
|
||||
String description = bedrockEnchantment == null ? readDescription(data) : null;
|
||||
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null;
|
||||
|
||||
return new Enchantment(entry.getId(), effects, ItemTag.ALL_ITEM_TAGS.get(supportedItems), maxLevel,
|
||||
description, anvilCost, exclusiveSetTag, bedrockEnchantment);
|
||||
|
@ -74,14 +76,4 @@ public record Enchantment(String identifier,
|
|||
}
|
||||
return Set.copyOf(components); // Also ensures any empty sets are consolidated
|
||||
}
|
||||
|
||||
private static String readDescription(NbtMap tag) {
|
||||
NbtMap description = tag.getCompound("description");
|
||||
String translate = description.getString("translate", null);
|
||||
if (translate == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag);
|
||||
return "";
|
||||
}
|
||||
return translate;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.level;
|
||||
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry;
|
||||
|
||||
public record JukeboxSong(String soundEvent, String description) {
|
||||
|
||||
public static JukeboxSong read(RegistryEntry entry) {
|
||||
NbtMap data = entry.getData();
|
||||
String soundEvent = data.getString("sound_event");
|
||||
String description = MessageTranslator.deserializeDescription(data);
|
||||
return new JukeboxSong(soundEvent, description);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,7 @@ import org.geysermc.geyser.inventory.item.BannerPattern;
|
|||
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
|
||||
import org.geysermc.geyser.item.enchantment.Enchantment;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.level.JukeboxSong;
|
||||
import org.geysermc.geyser.level.PaintingType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
||||
|
@ -76,6 +77,7 @@ public final class RegistryCache {
|
|||
register("chat_type", cache -> cache.chatTypes, ($, entry) -> TextDecoration.readChatType(entry));
|
||||
register("dimension_type", cache -> cache.dimensions, ($, entry) -> JavaDimension.read(entry));
|
||||
register("enchantment", cache -> cache.enchantments, ($, entry) -> Enchantment.read(entry));
|
||||
register("jukebox_song", cache -> cache.jukeboxSongs, ($, entry) -> JukeboxSong.read(entry));
|
||||
register("painting_variant", cache -> cache.paintings, ($, entry) -> PaintingType.getByName(entry.getId()));
|
||||
register("trim_material", cache -> cache.trimMaterials, TrimRecipe::readTrimMaterial);
|
||||
register("trim_pattern", cache -> cache.trimPatterns, TrimRecipe::readTrimPattern);
|
||||
|
@ -115,6 +117,7 @@ public final class RegistryCache {
|
|||
*/
|
||||
private final JavaRegistry<JavaDimension> dimensions = new SimpleJavaRegistry<>();
|
||||
private final JavaRegistry<Enchantment> enchantments = new SimpleJavaRegistry<>();
|
||||
private final JavaRegistry<JukeboxSong> jukeboxSongs = new SimpleJavaRegistry<>();
|
||||
private final JavaRegistry<PaintingType> paintings = new SimpleJavaRegistry<>();
|
||||
private final JavaRegistry<TrimMaterial> trimMaterials = new SimpleJavaRegistry<>();
|
||||
private final JavaRegistry<TrimPattern> trimPatterns = new SimpleJavaRegistry<>();
|
||||
|
|
|
@ -28,8 +28,10 @@ package org.geysermc.geyser.session.cache;
|
|||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket;
|
||||
import org.geysermc.geyser.scoreboard.Scoreboard;
|
||||
|
@ -39,6 +41,7 @@ import org.geysermc.geyser.util.ChunkUtils;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WorldCache {
|
||||
private final GeyserSession session;
|
||||
|
@ -61,6 +64,8 @@ public final class WorldCache {
|
|||
private int currentSequence;
|
||||
private final Object2IntMap<Vector3i> unverifiedPredictions = new Object2IntOpenHashMap<>(1);
|
||||
|
||||
private final Map<Vector3i, String> activeRecords = new Object2ObjectOpenHashMap<>(1); // Assume the average player won't be listening to many records
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean editingSignOnFront;
|
||||
|
@ -185,4 +190,15 @@ public final class WorldCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addActiveRecord(Vector3i pos, String bedrockPlaySound) {
|
||||
this.activeRecords.put(pos, bedrockPlaySound);
|
||||
}
|
||||
|
||||
// Implementation note: positions aren't removed unless the server calls, but this seems to match 1.21 Java
|
||||
// client behavior.
|
||||
@Nullable
|
||||
public String removeActiveRecord(Vector3i pos) {
|
||||
return this.activeRecords.remove(pos);
|
||||
}
|
||||
}
|
|
@ -25,28 +25,27 @@
|
|||
|
||||
package org.geysermc.geyser.translator.protocol.java.level;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.event.*;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ParticleType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.JukeboxSong;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.SoundMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.geysermc.geyser.translator.level.event.LevelEventTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.SoundUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.event.*;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.level.ClientboundLevelEventPacket;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@Translator(packet = ClientboundLevelEventPacket.class)
|
||||
|
@ -60,13 +59,25 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||
// Separate case since each RecordEventData in Java is an individual track in Bedrock
|
||||
if (levelEvent == LevelEventType.RECORD) {
|
||||
RecordEventData recordEventData = (RecordEventData) packet.getData();
|
||||
SoundEvent soundEvent = Registries.RECORDS.get(recordEventData.getRecordId());
|
||||
if (soundEvent == null) {
|
||||
JukeboxSong jukeboxSong = session.getRegistryCache().jukeboxSongs().byId(recordEventData.getRecordId());
|
||||
if (jukeboxSong == null) {
|
||||
return;
|
||||
}
|
||||
Vector3i origin = packet.getPosition();
|
||||
Vector3f pos = Vector3f.from(origin.getX() + 0.5f, origin.getY() + 0.5f, origin.getZ() + 0.5f);
|
||||
|
||||
// Prioritize level events because it makes parrots dance.
|
||||
SoundMapping mapping = Registries.SOUNDS.get(jukeboxSong.soundEvent().replace("minecraft:", ""));
|
||||
System.out.println(jukeboxSong.soundEvent() + " " + mapping);
|
||||
SoundEvent soundEvent = null;
|
||||
if (mapping != null) {
|
||||
String bedrock = mapping.getBedrock();
|
||||
if (bedrock != null && !bedrock.isEmpty()) {
|
||||
soundEvent = SoundUtils.toSoundEvent(bedrock);
|
||||
}
|
||||
}
|
||||
|
||||
if (soundEvent != null) {
|
||||
LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket();
|
||||
levelSoundEvent.setIdentifier("");
|
||||
levelSoundEvent.setSound(soundEvent);
|
||||
|
@ -75,8 +86,21 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||
levelSoundEvent.setExtraData(-1);
|
||||
levelSoundEvent.setBabySound(false);
|
||||
session.sendUpstreamPacket(levelSoundEvent);
|
||||
} else {
|
||||
String bedrockSound = SoundUtils.translatePlaySound(jukeboxSong.soundEvent());
|
||||
// Pitch and volume from Java 1.21
|
||||
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
|
||||
playSoundPacket.setPosition(pos);
|
||||
playSoundPacket.setSound(bedrockSound);
|
||||
playSoundPacket.setPitch(1.0f);
|
||||
playSoundPacket.setVolume(4.0f);
|
||||
session.sendUpstreamPacket(playSoundPacket);
|
||||
|
||||
// Send text packet as it seems to be handled in Java Edition client-side.
|
||||
// Special behavior so we can cancel the record on our end
|
||||
session.getWorldCache().addActiveRecord(origin, bedrockSound);
|
||||
}
|
||||
|
||||
// The level event for Java also indicates to show the text packet with the jukebox's description
|
||||
TextPacket textPacket = new TextPacket();
|
||||
textPacket.setType(TextPacket.Type.JUKEBOX_POPUP);
|
||||
textPacket.setNeedsTranslation(true);
|
||||
|
@ -84,8 +108,7 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||
textPacket.setPlatformChatId("");
|
||||
textPacket.setSourceName(null);
|
||||
textPacket.setMessage("record.nowPlaying");
|
||||
String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc";
|
||||
textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(recordString, session.locale())));
|
||||
textPacket.setParameters(Collections.singletonList(MinecraftLocale.getLocaleString(jukeboxSong.description(), session.locale())));
|
||||
session.sendUpstreamPacket(textPacket);
|
||||
return;
|
||||
}
|
||||
|
@ -325,6 +348,9 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||
return;
|
||||
}
|
||||
case STOP_RECORD -> {
|
||||
String bedrockSound = session.getWorldCache().removeActiveRecord(origin);
|
||||
if (bedrockSound == null) {
|
||||
// Vanilla record
|
||||
LevelSoundEventPacket levelSoundEvent = new LevelSoundEventPacket();
|
||||
levelSoundEvent.setIdentifier("");
|
||||
levelSoundEvent.setSound(SoundEvent.STOP_RECORD);
|
||||
|
@ -333,6 +359,12 @@ public class JavaLevelEventTranslator extends PacketTranslator<ClientboundLevelE
|
|||
levelSoundEvent.setExtraData(-1);
|
||||
levelSoundEvent.setBabySound(false);
|
||||
session.sendUpstreamPacket(levelSoundEvent);
|
||||
} else {
|
||||
// Custom record
|
||||
StopSoundPacket stopSound = new StopSoundPacket();
|
||||
stopSound.setSoundName(bedrockSound);
|
||||
session.sendUpstreamPacket(stopSound);
|
||||
}
|
||||
return;
|
||||
}
|
||||
default -> {
|
||||
|
|
|
@ -35,6 +35,7 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
|||
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -424,6 +425,20 @@ public class MessageTranslator {
|
|||
return new String(newChars, 0, count - (whitespacesCount > 0 ? 1 : 0)).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an NbtMap provided from a registry into a string.
|
||||
*/
|
||||
// This may be a Component in the future.
|
||||
public static String deserializeDescription(NbtMap tag) {
|
||||
NbtMap description = tag.getCompound("description");
|
||||
String translate = description.getString("translate", null);
|
||||
if (translate == null) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag);
|
||||
return "";
|
||||
}
|
||||
return translate;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// no-op
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
|
@ -52,7 +51,7 @@ public final class SoundUtils {
|
|||
* @param sound the sound name
|
||||
* @return a sound event from the given sound
|
||||
*/
|
||||
private static @Nullable SoundEvent toSoundEvent(String sound) {
|
||||
public static @Nullable SoundEvent toSoundEvent(String sound) {
|
||||
try {
|
||||
return SoundEvent.valueOf(sound.toUpperCase(Locale.ROOT).replace(".", "_"));
|
||||
} catch (Exception ex) {
|
||||
|
|
Loading…
Reference in a new issue