Skip to content

Commit

Permalink
Different registry implementation; fix banner blocks with ViaVersion
Browse files Browse the repository at this point in the history
  • Loading branch information
Camotoy committed May 1, 2024
1 parent abb1d7d commit 1e8d6b2
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void setWolfAngerTime(IntEntityMetadata entityMetadata) {

// 1.20.5+
public void setWolfVariant(IntEntityMetadata entityMetadata) {
WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().get(entityMetadata.getPrimitiveValue());
WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(entityMetadata.getPrimitiveValue());
if (wolfVariant == null) {
wolfVariant = WolfVariant.PALE;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNul
return;
}

TrimMaterial material = session.getRegistryCache().trimMaterials().get(trim.material().id());
TrimPattern pattern = session.getRegistryCache().trimPatterns().get(trim.pattern().id());
TrimMaterial material = session.getRegistryCache().trimMaterials().byId(trim.material().id());
TrimPattern pattern = session.getRegistryCache().trimPatterns().byId(trim.pattern().id());

NbtMapBuilder trimBuilder = NbtMap.builder();
// bedrock has an uppercase first letter key, and the value is not namespaced
Expand Down
52 changes: 31 additions & 21 deletions core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.common.util.Int2ObjectBiMap;
import org.geysermc.geyser.inventory.item.BannerPattern;
import org.geysermc.geyser.inventory.item.DyeColor;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
import org.geysermc.mcprotocollib.protocol.data.game.Identifier;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
Expand All @@ -57,7 +58,6 @@ public class BannerItem extends BlockItem {
* the correct ominous banner pattern if Bedrock pulls the item from creative.
*/
private static final List<Pair<BannerPattern, DyeColor>> OMINOUS_BANNER_PATTERN;
private static final List<NbtMap> OMINOUS_BANNER_PATTERN_BLOCK;

// TODO fix - we somehow need to be able to get the sessions banner pattern registry, which we don't have where we need this :/
private static final int[] ominousBannerPattern = new int[] { 21, 29, 30, 1, 34, 15, 3, 1 };
Expand All @@ -74,11 +74,6 @@ public class BannerItem extends BlockItem {
Pair.of(BannerPattern.CIRCLE, DyeColor.LIGHT_GRAY),
Pair.of(BannerPattern.BORDER, DyeColor.BLACK)
);

OMINOUS_BANNER_PATTERN_BLOCK = new ArrayList<>();
for (Pair<BannerPattern, DyeColor> pair : OMINOUS_BANNER_PATTERN) {
OMINOUS_BANNER_PATTERN_BLOCK.add(getJavaBannerPatternTag(pair.left(), pair.right()));
}
}

public static boolean isOminous(GeyserSession session, List<BannerPatternLayer> patternLayers) {
Expand All @@ -92,7 +87,7 @@ public static boolean isOminous(GeyserSession session, List<BannerPatternLayer>
!patternLayer.getPattern().isId()) {
return false;
}
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(patternLayer.getPattern().id());
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(patternLayer.getPattern().id());
if (bannerPattern != pair.left()) {
return false;
}
Expand All @@ -101,7 +96,25 @@ public static boolean isOminous(GeyserSession session, List<BannerPatternLayer>
}

public static boolean isOminous(List<NbtMap> blockEntityPatterns) {
return OMINOUS_BANNER_PATTERN_BLOCK.equals(blockEntityPatterns);
// Cannot do a simple NBT equals check here because the IDs may not be full resource locations
// ViaVersion's fault, 1.20.4 -> 1.20.5, but it works on Java so we need to support it.
if (OMINOUS_BANNER_PATTERN.size() != blockEntityPatterns.size()) {
return false;
}
for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) {
NbtMap patternLayer = blockEntityPatterns.get(i);
Pair<BannerPattern, DyeColor> pair = OMINOUS_BANNER_PATTERN.get(i);
DyeColor color = DyeColor.getByJavaIdentifier(patternLayer.getString("color"));
if (color != pair.right()) {
return false;
}
String id = Identifier.formalize(patternLayer.getString("pattern")); // Ouch
BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(id);
if (bannerPattern != pair.left()) {
return false;
}
}
return true;
}

/**
Expand Down Expand Up @@ -133,7 +146,7 @@ static void convertBannerPattern(GeyserSession session, List<BannerPatternLayer>
List<NbtMap> patternList = new ArrayList<>(patterns.size());
for (BannerPatternLayer patternLayer : patterns) {
patternLayer.getPattern().ifId(holder -> {
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(holder.id());
BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(holder.id());
if (bannerPattern != null) {
NbtMap tag = NbtMap.builder()
.putString("Pattern", bannerPattern.getBedrockIdentifier())
Expand All @@ -154,7 +167,8 @@ static void convertBannerPattern(GeyserSession session, List<BannerPatternLayer>
* @return The Bedrock edition format pattern nbt
*/
private static NbtMap getBedrockBannerPattern(NbtMap pattern) {
BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(pattern.getString("pattern"));
// ViaVersion 1.20.4 -> 1.20.5 can send without the namespace
BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(Identifier.formalize(pattern.getString("pattern")));
DyeColor dyeColor = DyeColor.getByJavaIdentifier(pattern.getString("color"));
if (bannerPattern == null || dyeColor == null) {
return null;
Expand All @@ -166,25 +180,21 @@ private static NbtMap getBedrockBannerPattern(NbtMap pattern) {
.build();
}

public static NbtMap getJavaBannerPatternTag(BannerPattern bannerPattern, DyeColor dyeColor) {
return NbtMap.builder()
.putString("pattern", bannerPattern.getJavaIdentifier())
.putString("color", dyeColor.getJavaIdentifier())
.build();
}

/**
* Convert the Bedrock edition banner pattern nbt to Java edition
*
* @param pattern Bedrock edition pattern nbt
* @return The Java edition format pattern layer
*/
public static BannerPatternLayer getJavaBannerPattern(GeyserSession session, NbtMap pattern) {
Int2ObjectBiMap<BannerPattern> registry = session.getRegistryCache().bannerPatterns();
JavaRegistry<BannerPattern> registry = session.getRegistryCache().bannerPatterns();
BannerPattern bannerPattern = BannerPattern.getByBedrockIdentifier(pattern.getString("Pattern"));
DyeColor dyeColor = DyeColor.getById(15 - pattern.getInt("Color"));
if (bannerPattern != null && dyeColor != null && registry.containsValue(bannerPattern)) {
return new BannerPatternLayer(Holder.ofId(registry.get(bannerPattern)), dyeColor.ordinal());
if (bannerPattern != null && dyeColor != null) {
int id = registry.byValue(bannerPattern);
if (id != -1) {
return new BannerPatternLayer(Holder.ofId(id), dyeColor.ordinal());
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,25 @@

import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
import org.cloudburstmc.protocol.common.util.Int2ObjectBiMap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
import org.geysermc.geyser.inventory.item.BannerPattern;
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
import org.geysermc.geyser.level.JavaDimension;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry;
import org.geysermc.geyser.text.TextDecoration;
import org.geysermc.geyser.translator.level.BiomeTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry;
import org.geysermc.mcprotocollib.protocol.packet.configuration.clientbound.ClientboundRegistryDataPacket;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -71,7 +71,7 @@ public final class RegistryCache {
register("trim_material", cache -> cache.trimMaterials, TrimRecipe::readTrimMaterial);
register("trim_pattern", cache -> cache.trimPatterns, TrimRecipe::readTrimPattern);
register("worldgen/biome", (cache, array) -> cache.biomeTranslations = array, BiomeTranslator::loadServerBiome);
registerBannerRegistry(($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId()));
register("banner_pattern", cache -> cache.bannerPatterns, ($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId()));
register("wolf_variant", cache -> cache.wolfVariants, ($, entry) -> WolfEntity.WolfVariant.getByJavaIdentifier(entry.getId()));
}

Expand All @@ -82,16 +82,16 @@ public final class RegistryCache {
* Java -> Bedrock biome network IDs.
*/
private int[] biomeTranslations;
private final Int2ObjectMap<TextDecoration> chatTypes = new Int2ObjectOpenHashMap<>(7);
private final JavaRegistry<TextDecoration> chatTypes = new SimpleJavaRegistry<>();
/**
* All dimensions that the client could possibly connect to.
*/
private final Int2ObjectMap<JavaDimension> dimensions = new Int2ObjectOpenHashMap<>(4);
private final Int2ObjectMap<TrimMaterial> trimMaterials = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<TrimPattern> trimPatterns = new Int2ObjectOpenHashMap<>();
private final JavaRegistry<JavaDimension> dimensions = new SimpleJavaRegistry<>();
private final JavaRegistry<TrimMaterial> trimMaterials = new SimpleJavaRegistry<>();
private final JavaRegistry<TrimPattern> trimPatterns = new SimpleJavaRegistry<>();

private Int2ObjectBiMap<BannerPattern> bannerPatterns = new Int2ObjectBiMap<>();
private final Int2ObjectMap<WolfEntity.WolfVariant> wolfVariants = new Int2ObjectOpenHashMap<>();
private final JavaRegistry<BannerPattern> bannerPatterns = new SimpleJavaRegistry<>();
private final JavaRegistry<WolfEntity.WolfVariant> wolfVariants = new SimpleJavaRegistry<>();

public RegistryCache(GeyserSession session) {
this.session = session;
Expand All @@ -109,47 +109,25 @@ public void load(ClientboundRegistryDataPacket packet) {
}
}

private static <T> void registerBannerRegistry(BiFunction<GeyserSession, RegistryEntry, T> reader) {
REGISTRIES.put("minecraft:banner_pattern", ((registryCache, entries) -> {
// Clear each local cache every time a new registry entry is given to us
// (e.g. proxy server switches)
registryCache.bannerPatterns = new Int2ObjectBiMap<>(); // Cannot clear it, must re-create :(
for (int i = 0; i < entries.size(); i++) {
RegistryEntry entry = entries.get(i);
// This is what Geyser wants to keep as a value for this registry.
T cacheEntry = reader.apply(registryCache.session, entry);
if (cacheEntry != null) {
registryCache.bannerPatterns.put(i, (BannerPattern) cacheEntry);
} else {
// TODO - seems to be possible with viaversion :/
GeyserImpl.getInstance().getLogger().warning("Was not able to translate entry: ");
}
}
}));
}

/**
* @param registry the Java registry resource location, without the "minecraft:" prefix.
* @param localCacheFunction which local field in RegistryCache are we caching entries for this registry?
* @param reader converts the RegistryEntry NBT into a class file
* @param <T> the class that represents these entries.
*/
private static <T> void register(String registry, Function<RegistryCache, Int2ObjectMap<T>> localCacheFunction, BiFunction<GeyserSession, RegistryEntry, T> reader) {
private static <T> void register(String registry, Function<RegistryCache, JavaRegistry<T>> localCacheFunction, BiFunction<GeyserSession, RegistryEntry, T> reader) {
REGISTRIES.put("minecraft:" + registry, (registryCache, entries) -> {
Int2ObjectMap<T> localCache = localCacheFunction.apply(registryCache);
JavaRegistry<T> localCache = localCacheFunction.apply(registryCache);
// Clear each local cache every time a new registry entry is given to us
// (e.g. proxy server switches)
localCache.clear();
List<T> builder = new ArrayList<>(entries.size());
for (int i = 0; i < entries.size(); i++) {
RegistryEntry entry = entries.get(i);
// This is what Geyser wants to keep as a value for this registry.
T cacheEntry = reader.apply(registryCache.session, entry);
localCache.put(i, cacheEntry);
}
// Trim registry down to needed size
if (localCache instanceof Int2ObjectOpenHashMap<T> hashMap) {
hashMap.trim();
builder.add(i, cacheEntry);
}
localCache.reset(builder);
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-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.session.cache.registry;

import org.checkerframework.checker.index.qual.NonNegative;

import java.util.List;

/**
* A wrapper for a list, holding Java registry values.
*/
public interface JavaRegistry<T> {

/**
* Looks up a registry entry by its ID. The object can be null, or not present.
*/
T byId(@NonNegative int id);

/**
* Reverse looks-up an object to return its network ID, or -1.
*/
int byValue(T value);

/**
* Resets the objects by these IDs.
*/
void reset(List<T> values);

/**
* All values of this registry, as a list.
*/
List<T> values();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-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.session.cache.registry;

import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.checkerframework.checker.index.qual.NonNegative;

import java.util.List;

public class SimpleJavaRegistry<T> implements JavaRegistry<T> {
protected final ObjectArrayList<T> values = new ObjectArrayList<>();

@Override
public T byId(@NonNegative int id) {
if (id < 0 || id >= this.values.size()) {
return null;
}
return this.values.get(id);
}

@Override
public int byValue(T value) {
return this.values.indexOf(value);
}

@Override
public void reset(List<T> values) {
this.values.addAll(values);
this.values.trim();
}

@Override
public List<T> values() {
return this.values;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public static void handleChatPacket(GeyserSession session, Component message, in

textPacket.setNeedsTranslation(false);

TextDecoration decoration = session.getRegistryCache().chatTypes().get(chatType);
TextDecoration decoration = session.getRegistryCache().chatTypes().byId(chatType);
if (decoration != null) {
// As of 1.19 - do this to apply all the styling for signed messages
// Though, Bedrock cannot care about the signed stuff.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ public static void sendEmptyChunks(GeyserSession session, Vector3i position, int
* This must be done after the player has switched dimensions so we know what their dimension is
*/
public static void loadDimension(GeyserSession session) {
JavaDimension dimension = session.getRegistryCache().dimensions().get(session.getDimension());
JavaDimension dimension = session.getRegistryCache().dimensions().byId(session.getDimension());
session.setDimensionType(dimension);
int minY = dimension.minY();
int maxY = dimension.maxY();
Expand Down

0 comments on commit 1e8d6b2

Please sign in to comment.