mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/1.18
This commit is contained in:
commit
951b616f98
17 changed files with 766 additions and 15 deletions
|
@ -233,6 +233,12 @@
|
||||||
<version>${adventure.version}</version>
|
<version>${adventure.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||||
|
<version>${adventure.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<!-- Other -->
|
<!-- Other -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
|
|
@ -26,12 +26,48 @@
|
||||||
package org.geysermc.connector.inventory;
|
package org.geysermc.connector.inventory;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to determine if rename packets should be sent.
|
* Used to determine if rename packets should be sent and stores
|
||||||
|
* the expected level cost for AnvilInventoryUpdater
|
||||||
*/
|
*/
|
||||||
|
@Getter @Setter
|
||||||
public class AnvilContainer extends Container {
|
public class AnvilContainer extends Container {
|
||||||
|
/**
|
||||||
|
* Stores the level cost received as a window property from Java
|
||||||
|
*/
|
||||||
|
private int javaLevelCost = 0;
|
||||||
|
/**
|
||||||
|
* A flag to specify whether javaLevelCost can be used as it can
|
||||||
|
* be outdated or not sent at all.
|
||||||
|
*/
|
||||||
|
private boolean useJavaLevelCost = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new name of the item as received from Bedrock
|
||||||
|
*/
|
||||||
|
private String newName = null;
|
||||||
|
|
||||||
|
private GeyserItemStack lastInput = GeyserItemStack.EMPTY;
|
||||||
|
private GeyserItemStack lastMaterial = GeyserItemStack.EMPTY;
|
||||||
|
|
||||||
|
private int lastTargetSlot = -1;
|
||||||
|
|
||||||
public AnvilContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
public AnvilContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||||
super(title, id, size, containerType, playerInventory);
|
super(title, id, size, containerType, playerInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GeyserItemStack getInput() {
|
||||||
|
return getItem(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeyserItemStack getMaterial() {
|
||||||
|
return getItem(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeyserItemStack getResult() {
|
||||||
|
return getItem(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import org.geysermc.connector.inventory.CartographyContainer;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.network.translators.Translator;
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
||||||
|
import org.geysermc.connector.utils.ItemUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to send strings to the server and filter out unwanted words.
|
* Used to send strings to the server and filter out unwanted words.
|
||||||
|
@ -47,12 +49,31 @@ public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPack
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
packet.setFromServer(true);
|
packet.setFromServer(true);
|
||||||
session.sendUpstreamPacket(packet);
|
if (session.getOpenInventory() instanceof AnvilContainer anvilContainer) {
|
||||||
|
anvilContainer.setNewName(packet.getText());
|
||||||
|
|
||||||
if (session.getOpenInventory() instanceof AnvilContainer) {
|
String originalName = ItemUtils.getCustomName(anvilContainer.getInput().getNbt());
|
||||||
|
|
||||||
|
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.getLocale());
|
||||||
|
String plainNewName = MessageTranslator.convertToPlainText(packet.getText(), session.getLocale());
|
||||||
|
if (!plainOriginalName.equals(plainNewName)) {
|
||||||
|
// Strip out formatting since Java Edition does not allow it
|
||||||
|
packet.setText(plainNewName);
|
||||||
// Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now
|
// Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now
|
||||||
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(packet.getText());
|
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainNewName);
|
||||||
|
session.sendDownstreamPacket(renameItemPacket);
|
||||||
|
} else {
|
||||||
|
// Restore formatting for item since we're not renaming
|
||||||
|
packet.setText(MessageTranslator.convertMessageLenient(originalName));
|
||||||
|
// Java Edition sends the original custom name when not renaming,
|
||||||
|
// if there isn't a custom name an empty string is sent
|
||||||
|
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(plainOriginalName);
|
||||||
session.sendDownstreamPacket(renameItemPacket);
|
session.sendDownstreamPacket(renameItemPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anvilContainer.setUseJavaLevelCost(false);
|
||||||
|
session.getInventoryTranslator().updateSlot(session, anvilContainer, 1);
|
||||||
|
}
|
||||||
|
session.sendUpstreamPacket(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
|
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.utils.LanguageUtils;
|
import org.geysermc.connector.utils.LanguageUtils;
|
||||||
|
@ -179,6 +180,33 @@ public class MessageTranslator {
|
||||||
return GSON_SERIALIZER.serialize(component);
|
return GSON_SERIALIZER.serialize(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert JSON and legacy format message to plain text
|
||||||
|
*
|
||||||
|
* @param message Message to convert
|
||||||
|
* @param locale Locale to use for translation strings
|
||||||
|
* @return The plain text of the message
|
||||||
|
*/
|
||||||
|
public static String convertToPlainText(String message, String locale) {
|
||||||
|
if (message == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Component messageComponent = null;
|
||||||
|
if (message.startsWith("{") && message.endsWith("}")) {
|
||||||
|
// Message is a JSON object
|
||||||
|
try {
|
||||||
|
messageComponent = GSON_SERIALIZER.deserialize(message);
|
||||||
|
// Translate any components that require it
|
||||||
|
messageComponent = RENDERER.render(messageComponent, locale);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messageComponent == null) {
|
||||||
|
messageComponent = LegacyComponentSerializer.legacySection().deserialize(message);
|
||||||
|
}
|
||||||
|
return PlainTextComponentSerializer.plainText().serialize(messageComponent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a team color to a chat color
|
* Convert a team color to a chat color
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,12 +31,13 @@ import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
|
||||||
import org.geysermc.connector.inventory.AnvilContainer;
|
import org.geysermc.connector.inventory.AnvilContainer;
|
||||||
import org.geysermc.connector.inventory.Inventory;
|
import org.geysermc.connector.inventory.Inventory;
|
||||||
import org.geysermc.connector.inventory.PlayerInventory;
|
import org.geysermc.connector.inventory.PlayerInventory;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
|
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
|
||||||
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
|
import org.geysermc.connector.network.translators.inventory.updater.AnvilInventoryUpdater;
|
||||||
|
|
||||||
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
|
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||||
public AnvilInventoryTranslator() {
|
public AnvilInventoryTranslator() {
|
||||||
super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, UIInventoryUpdater.INSTANCE,
|
super(3, "minecraft:anvil[facing=north]", com.nukkitx.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE,
|
||||||
"minecraft:chipped_anvil", "minecraft:damaged_anvil");
|
"minecraft:chipped_anvil", "minecraft:damaged_anvil");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,4 +75,14 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||||
return new AnvilContainer(name, windowId, this.size, containerType, playerInventory);
|
return new AnvilContainer(name, windowId, this.size, containerType, playerInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
|
||||||
|
// The only property sent by Java is key 0 which is the level cost
|
||||||
|
if (key != 0) return;
|
||||||
|
AnvilContainer anvilContainer = (AnvilContainer) inventory;
|
||||||
|
anvilContainer.setJavaLevelCost(value);
|
||||||
|
anvilContainer.setUseJavaLevelCost(true);
|
||||||
|
updateSlot(session, anvilContainer, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,460 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 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.connector.network.translators.inventory.updater;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket;
|
||||||
|
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||||
|
import com.nukkitx.nbt.NbtMap;
|
||||||
|
import com.nukkitx.nbt.NbtMapBuilder;
|
||||||
|
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
|
||||||
|
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||||
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.inventory.AnvilContainer;
|
||||||
|
import org.geysermc.connector.inventory.GeyserItemStack;
|
||||||
|
import org.geysermc.connector.inventory.Inventory;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.item.Enchantment.JavaEnchantment;
|
||||||
|
import org.geysermc.connector.registry.Registries;
|
||||||
|
import org.geysermc.connector.registry.type.EnchantmentData;
|
||||||
|
import org.geysermc.connector.utils.ItemUtils;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class AnvilInventoryUpdater extends InventoryUpdater {
|
||||||
|
public static final AnvilInventoryUpdater INSTANCE = new AnvilInventoryUpdater();
|
||||||
|
|
||||||
|
private static final int MAX_LEVEL_COST = 40;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
|
||||||
|
super.updateInventory(translator, session, inventory);
|
||||||
|
AnvilContainer anvilContainer = (AnvilContainer) inventory;
|
||||||
|
updateInventoryState(session, anvilContainer);
|
||||||
|
int targetSlot = getTargetSlot(session, anvilContainer);
|
||||||
|
for (int i = 0; i < translator.size; i++) {
|
||||||
|
final int bedrockSlot = translator.javaSlotToBedrock(i);
|
||||||
|
if (bedrockSlot == 50)
|
||||||
|
continue;
|
||||||
|
if (i == targetSlot) {
|
||||||
|
updateTargetSlot(translator, session, anvilContainer, targetSlot);
|
||||||
|
} else {
|
||||||
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||||
|
slotPacket.setContainerId(ContainerId.UI);
|
||||||
|
slotPacket.setSlot(bedrockSlot);
|
||||||
|
slotPacket.setItem(inventory.getItem(i).getItemData(session));
|
||||||
|
session.sendUpstreamPacket(slotPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
|
||||||
|
if (super.updateSlot(translator, session, inventory, javaSlot))
|
||||||
|
return true;
|
||||||
|
AnvilContainer anvilContainer = (AnvilContainer) inventory;
|
||||||
|
updateInventoryState(session, anvilContainer);
|
||||||
|
|
||||||
|
int lastTargetSlot = anvilContainer.getLastTargetSlot();
|
||||||
|
int targetSlot = getTargetSlot(session, anvilContainer);
|
||||||
|
if (targetSlot != javaSlot) {
|
||||||
|
// Update the requested slot
|
||||||
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||||
|
slotPacket.setContainerId(ContainerId.UI);
|
||||||
|
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
|
||||||
|
slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
|
||||||
|
session.sendUpstreamPacket(slotPacket);
|
||||||
|
} else if (lastTargetSlot != javaSlot) {
|
||||||
|
// Update the previous target slot to remove repair cost changes
|
||||||
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||||
|
slotPacket.setContainerId(ContainerId.UI);
|
||||||
|
slotPacket.setSlot(translator.javaSlotToBedrock(lastTargetSlot));
|
||||||
|
slotPacket.setItem(inventory.getItem(lastTargetSlot).getItemData(session));
|
||||||
|
session.sendUpstreamPacket(slotPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTargetSlot(translator, session, anvilContainer, targetSlot);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInventoryState(GeyserSession session, AnvilContainer anvilContainer) {
|
||||||
|
GeyserItemStack input = anvilContainer.getInput();
|
||||||
|
if (!input.equals(anvilContainer.getLastInput())) {
|
||||||
|
anvilContainer.setLastInput(input.copy());
|
||||||
|
anvilContainer.setUseJavaLevelCost(false);
|
||||||
|
|
||||||
|
// Changing the item in the input slot resets the name field on Bedrock, but
|
||||||
|
// does not result in a FilterTextPacket
|
||||||
|
String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.getLocale());
|
||||||
|
ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName);
|
||||||
|
session.sendDownstreamPacket(renameItemPacket);
|
||||||
|
|
||||||
|
anvilContainer.setNewName(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
GeyserItemStack material = anvilContainer.getMaterial();
|
||||||
|
if (!material.equals(anvilContainer.getLastMaterial())) {
|
||||||
|
anvilContainer.setLastMaterial(material.copy());
|
||||||
|
anvilContainer.setUseJavaLevelCost(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param anvilContainer the anvil inventory
|
||||||
|
* @return the slot to change the repair cost
|
||||||
|
*/
|
||||||
|
private int getTargetSlot(GeyserSession session, AnvilContainer anvilContainer) {
|
||||||
|
GeyserItemStack input = anvilContainer.getInput();
|
||||||
|
GeyserItemStack material = anvilContainer.getMaterial();
|
||||||
|
|
||||||
|
if (!material.isEmpty()) {
|
||||||
|
if (!input.isEmpty() && isRepairing(session, input, material)) {
|
||||||
|
// Changing the repair cost on the material item makes it non-stackable
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Prefer changing the material item because it does not reset the name field
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTargetSlot(InventoryTranslator translator, GeyserSession session, AnvilContainer anvilContainer, int slot) {
|
||||||
|
ItemData itemData = anvilContainer.getItem(slot).getItemData(session);
|
||||||
|
itemData = hijackRepairCost(session, anvilContainer, itemData);
|
||||||
|
|
||||||
|
if (slot == 0 && isRenaming(session, anvilContainer, true)) {
|
||||||
|
// Can't change the RepairCost because it resets the name field on Bedrock
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anvilContainer.setLastTargetSlot(slot);
|
||||||
|
|
||||||
|
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||||
|
slotPacket.setContainerId(ContainerId.UI);
|
||||||
|
slotPacket.setSlot(translator.javaSlotToBedrock(slot));
|
||||||
|
slotPacket.setItem(itemData);
|
||||||
|
session.sendUpstreamPacket(slotPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemData hijackRepairCost(GeyserSession session, AnvilContainer anvilContainer, ItemData itemData) {
|
||||||
|
if (itemData.isNull()) {
|
||||||
|
return itemData;
|
||||||
|
}
|
||||||
|
// Fix level count by adjusting repair cost
|
||||||
|
int newRepairCost;
|
||||||
|
if (anvilContainer.isUseJavaLevelCost()) {
|
||||||
|
newRepairCost = anvilContainer.getJavaLevelCost();
|
||||||
|
} else {
|
||||||
|
// Did not receive a ServerWindowPropertyPacket with the level cost
|
||||||
|
newRepairCost = calcLevelCost(session, anvilContainer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bedrockLevelCost = calcLevelCost(session, anvilContainer, true);
|
||||||
|
if (bedrockLevelCost == -1) {
|
||||||
|
// Bedrock is unable to combine/repair the items
|
||||||
|
return itemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
newRepairCost -= bedrockLevelCost;
|
||||||
|
if (newRepairCost == 0) {
|
||||||
|
// No change to the repair cost needed
|
||||||
|
return itemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
NbtMapBuilder tagBuilder = NbtMap.builder();
|
||||||
|
if (itemData.getTag() != null) {
|
||||||
|
newRepairCost += itemData.getTag().getInt("RepairCost", 0);
|
||||||
|
tagBuilder.putAll(itemData.getTag());
|
||||||
|
}
|
||||||
|
tagBuilder.put("RepairCost", newRepairCost);
|
||||||
|
return itemData.toBuilder().tag(tagBuilder.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of levels needed to combine/rename an item
|
||||||
|
*
|
||||||
|
* @param session the geyser session
|
||||||
|
* @param anvilContainer the anvil container
|
||||||
|
* @param bedrock True to count enchantments like Bedrock
|
||||||
|
* @return the number of levels needed
|
||||||
|
*/
|
||||||
|
public int calcLevelCost(GeyserSession session, AnvilContainer anvilContainer, boolean bedrock) {
|
||||||
|
GeyserItemStack input = anvilContainer.getInput();
|
||||||
|
GeyserItemStack material = anvilContainer.getMaterial();
|
||||||
|
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int totalRepairCost = getRepairCost(input);
|
||||||
|
int cost = 0;
|
||||||
|
if (!material.isEmpty()) {
|
||||||
|
totalRepairCost += getRepairCost(material);
|
||||||
|
if (isCombining(session, input, material)) {
|
||||||
|
if (hasDurability(session, input) && input.getJavaId() == material.getJavaId()) {
|
||||||
|
cost += calcMergeRepairCost(session, input, material);
|
||||||
|
}
|
||||||
|
|
||||||
|
int enchantmentLevelCost = calcMergeEnchantmentCost(session, input, material, bedrock);
|
||||||
|
if (enchantmentLevelCost != -1) {
|
||||||
|
cost += enchantmentLevelCost;
|
||||||
|
} else if (cost == 0) {
|
||||||
|
// Can't repair or merge enchantments
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (hasDurability(session, input) && isRepairing(session, input, material)) {
|
||||||
|
cost = calcRepairLevelCost(session, input, material);
|
||||||
|
if (cost == -1) {
|
||||||
|
// No damage to repair
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCost = totalRepairCost + cost;
|
||||||
|
if (isRenaming(session, anvilContainer, bedrock)) {
|
||||||
|
totalCost++;
|
||||||
|
if (cost == 0 && totalCost >= MAX_LEVEL_COST) {
|
||||||
|
// Items can still be renamed when the level cost for renaming exceeds 40
|
||||||
|
totalCost = MAX_LEVEL_COST - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the levels needed to repair an item with its repair material
|
||||||
|
* E.g. iron_sword + iron_ingot
|
||||||
|
*
|
||||||
|
* @param session Geyser session
|
||||||
|
* @param input an item with durability
|
||||||
|
* @param material the item's respective repair material
|
||||||
|
* @return the number of levels needed or 0 if it is not possible to repair any further
|
||||||
|
*/
|
||||||
|
private int calcRepairLevelCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material) {
|
||||||
|
int newDamage = getDamage(input);
|
||||||
|
int unitRepair = Math.min(newDamage, input.getMapping(session).getMaxDamage() / 4);
|
||||||
|
if (unitRepair <= 0) {
|
||||||
|
// No damage to repair
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < material.getAmount(); i++) {
|
||||||
|
newDamage -= unitRepair;
|
||||||
|
unitRepair = Math.min(newDamage, input.getMapping(session).getMaxDamage() / 4);
|
||||||
|
if (unitRepair <= 0) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return material.getAmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the levels cost for repairing items by combining two of the same item
|
||||||
|
*
|
||||||
|
* @param session Geyser session
|
||||||
|
* @param input an item with durability
|
||||||
|
* @param material a matching item
|
||||||
|
* @return the number of levels needed or 0 if it is not possible to repair any further
|
||||||
|
*/
|
||||||
|
private int calcMergeRepairCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material) {
|
||||||
|
// If the material item is damaged 112% or more, then the input item will not be repaired
|
||||||
|
if (getDamage(input) > 0 && getDamage(material) < (material.getMapping(session).getMaxDamage() * 112 / 100)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the levels needed for combining the enchantments of two items
|
||||||
|
*
|
||||||
|
* @param session Geyser session
|
||||||
|
* @param input an item with durability
|
||||||
|
* @param material a matching item
|
||||||
|
* @param bedrock True to count enchantments like Bedrock, False to count like Java
|
||||||
|
* @return the number of levels needed or -1 if no enchantments can be applied
|
||||||
|
*/
|
||||||
|
private int calcMergeEnchantmentCost(GeyserSession session, GeyserItemStack input, GeyserItemStack material, boolean bedrock) {
|
||||||
|
boolean hasCompatible = false;
|
||||||
|
Object2IntMap<JavaEnchantment> combinedEnchantments = getEnchantments(session, input, bedrock);
|
||||||
|
int cost = 0;
|
||||||
|
for (Object2IntMap.Entry<JavaEnchantment> entry : getEnchantments(session, material, bedrock).object2IntEntrySet()) {
|
||||||
|
JavaEnchantment enchantment = entry.getKey();
|
||||||
|
EnchantmentData data = Registries.ENCHANTMENTS.get(enchantment);
|
||||||
|
if (data == null) {
|
||||||
|
GeyserConnector.getInstance().getLogger().debug("Java enchantment not in registry: " + enchantment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canApply = isEnchantedBook(session, input) || data.validItems().contains(input.getJavaId());
|
||||||
|
for (JavaEnchantment incompatible : data.incompatibleEnchantments()) {
|
||||||
|
if (combinedEnchantments.containsKey(incompatible)) {
|
||||||
|
canApply = false;
|
||||||
|
if (!bedrock) {
|
||||||
|
cost++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canApply || (!bedrock && session.getGameMode() == GameMode.CREATIVE)) {
|
||||||
|
int currentLevel = combinedEnchantments.getOrDefault(enchantment, 0);
|
||||||
|
int newLevel = entry.getIntValue();
|
||||||
|
if (newLevel == currentLevel) {
|
||||||
|
newLevel++;
|
||||||
|
}
|
||||||
|
newLevel = Math.max(currentLevel, newLevel);
|
||||||
|
if (newLevel > data.maxLevel()) {
|
||||||
|
newLevel = data.maxLevel();
|
||||||
|
}
|
||||||
|
combinedEnchantments.put(enchantment, newLevel);
|
||||||
|
|
||||||
|
int rarityMultiplier = data.rarityMultiplier();
|
||||||
|
if (isEnchantedBook(session, material) && rarityMultiplier > 1) {
|
||||||
|
rarityMultiplier /= 2;
|
||||||
|
}
|
||||||
|
if (bedrock) {
|
||||||
|
if (newLevel > currentLevel) {
|
||||||
|
hasCompatible = true;
|
||||||
|
}
|
||||||
|
if (enchantment == JavaEnchantment.IMPALING) {
|
||||||
|
// Multiplier is halved on Bedrock for some reason
|
||||||
|
rarityMultiplier /= 2;
|
||||||
|
} else if (enchantment == JavaEnchantment.SWEEPING) {
|
||||||
|
// Doesn't exist on Bedrock
|
||||||
|
rarityMultiplier = 0;
|
||||||
|
}
|
||||||
|
cost += rarityMultiplier * (newLevel - currentLevel);
|
||||||
|
} else {
|
||||||
|
hasCompatible = true;
|
||||||
|
cost += rarityMultiplier * newLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCompatible) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object2IntMap<JavaEnchantment> getEnchantments(GeyserSession session, GeyserItemStack itemStack, boolean bedrock) {
|
||||||
|
if (itemStack.getNbt() == null) {
|
||||||
|
return Object2IntMaps.emptyMap();
|
||||||
|
}
|
||||||
|
Object2IntMap<JavaEnchantment> enchantments = new Object2IntOpenHashMap<>();
|
||||||
|
Tag enchantmentTag;
|
||||||
|
if (isEnchantedBook(session, itemStack)) {
|
||||||
|
enchantmentTag = itemStack.getNbt().get("StoredEnchantments");
|
||||||
|
} else {
|
||||||
|
enchantmentTag = itemStack.getNbt().get("Enchantments");
|
||||||
|
}
|
||||||
|
if (enchantmentTag instanceof ListTag listTag) {
|
||||||
|
for (Tag tag : listTag.getValue()) {
|
||||||
|
if (tag instanceof CompoundTag enchantTag) {
|
||||||
|
if (enchantTag.get("id") instanceof StringTag javaEnchId) {
|
||||||
|
JavaEnchantment enchantment = JavaEnchantment.getByJavaIdentifier(javaEnchId.getValue());
|
||||||
|
if (enchantment == null) {
|
||||||
|
GeyserConnector.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tag javaEnchLvl = enchantTag.get("lvl");
|
||||||
|
if (!(javaEnchLvl instanceof ShortTag || javaEnchLvl instanceof IntTag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Handle duplicate enchantments
|
||||||
|
if (bedrock) {
|
||||||
|
enchantments.putIfAbsent(enchantment, ((Number) javaEnchLvl.getValue()).intValue());
|
||||||
|
} else {
|
||||||
|
enchantments.mergeInt(enchantment, ((Number) javaEnchLvl.getValue()).intValue(), Math::max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return enchantments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEnchantedBook(GeyserSession session, GeyserItemStack itemStack) {
|
||||||
|
return itemStack.getJavaId() == session.getItemMappings().getStoredItems().enchantedBook().getJavaId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCombining(GeyserSession session, GeyserItemStack input, GeyserItemStack material) {
|
||||||
|
return isEnchantedBook(session, material) || (input.getJavaId() == material.getJavaId() && hasDurability(session, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRepairing(GeyserSession session, GeyserItemStack input, GeyserItemStack material) {
|
||||||
|
Set<String> repairMaterials = input.getMapping(session).getRepairMaterials();
|
||||||
|
return repairMaterials != null && repairMaterials.contains(material.getMapping(session).getJavaIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRenaming(GeyserSession session, AnvilContainer anvilContainer, boolean bedrock) {
|
||||||
|
if (anvilContainer.getResult().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// This should really check the name field in all cases, but that requires the localized name
|
||||||
|
// of the item which can change depending on NBT and Minecraft Edition
|
||||||
|
String originalName = ItemUtils.getCustomName(anvilContainer.getInput().getNbt());
|
||||||
|
if (bedrock && originalName != null && anvilContainer.getNewName() != null) {
|
||||||
|
// Check text and formatting
|
||||||
|
String legacyOriginalName = MessageTranslator.convertMessageLenient(originalName, session.getLocale());
|
||||||
|
return !legacyOriginalName.equals(anvilContainer.getNewName());
|
||||||
|
}
|
||||||
|
return !Objects.equals(originalName, ItemUtils.getCustomName(anvilContainer.getResult().getNbt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTagIntValueOr(GeyserItemStack itemStack, String tagName, int defaultValue) {
|
||||||
|
if (itemStack.getNbt() != null) {
|
||||||
|
Tag tag = itemStack.getNbt().get(tagName);
|
||||||
|
if (tag != null && tag.getValue() instanceof Number value) {
|
||||||
|
return value.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRepairCost(GeyserItemStack itemStack) {
|
||||||
|
return getTagIntValueOr(itemStack, "RepairCost", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasDurability(GeyserSession session, GeyserItemStack itemStack) {
|
||||||
|
if (itemStack.getMapping(session).getMaxDamage() > 0) {
|
||||||
|
return getTagIntValueOr(itemStack, "Unbreakable", 0) == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getDamage(GeyserItemStack itemStack) {
|
||||||
|
return getTagIntValueOr(itemStack, "Damage", 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,18 @@ public enum Enchantment {
|
||||||
*/
|
*/
|
||||||
public static final String[] ALL_JAVA_IDENTIFIERS;
|
public static final String[] ALL_JAVA_IDENTIFIERS;
|
||||||
|
|
||||||
|
public static JavaEnchantment getByJavaIdentifier(String javaIdentifier) {
|
||||||
|
if (!javaIdentifier.startsWith("minecraft:")) {
|
||||||
|
javaIdentifier = "minecraft:" + javaIdentifier;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
|
||||||
|
if (ALL_JAVA_IDENTIFIERS[i].equalsIgnoreCase(javaIdentifier)) {
|
||||||
|
return VALUES[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALL_JAVA_IDENTIFIERS = new String[VALUES.length];
|
ALL_JAVA_IDENTIFIERS = new String[VALUES.length];
|
||||||
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
|
for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) {
|
||||||
|
|
|
@ -43,6 +43,7 @@ public class StoredItemMappings {
|
||||||
private final ItemMapping barrier;
|
private final ItemMapping barrier;
|
||||||
private final ItemMapping compass;
|
private final ItemMapping compass;
|
||||||
private final ItemMapping crossbow;
|
private final ItemMapping crossbow;
|
||||||
|
private final ItemMapping enchantedBook;
|
||||||
private final ItemMapping fishingRod;
|
private final ItemMapping fishingRod;
|
||||||
private final ItemMapping lodestoneCompass;
|
private final ItemMapping lodestoneCompass;
|
||||||
private final ItemMapping milkBucket;
|
private final ItemMapping milkBucket;
|
||||||
|
@ -58,6 +59,7 @@ public class StoredItemMappings {
|
||||||
this.barrier = load(itemMappings, "barrier");
|
this.barrier = load(itemMappings, "barrier");
|
||||||
this.compass = load(itemMappings, "compass");
|
this.compass = load(itemMappings, "compass");
|
||||||
this.crossbow = load(itemMappings, "crossbow");
|
this.crossbow = load(itemMappings, "crossbow");
|
||||||
|
this.enchantedBook = load(itemMappings, "enchanted_book");
|
||||||
this.fishingRod = load(itemMappings, "fishing_rod");
|
this.fishingRod = load(itemMappings, "fishing_rod");
|
||||||
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
|
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
|
||||||
this.milkBucket = load(itemMappings, "milk_bucket");
|
this.milkBucket = load(itemMappings, "milk_bucket");
|
||||||
|
|
|
@ -39,12 +39,14 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||||
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
|
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
|
||||||
import org.geysermc.connector.network.translators.world.event.LevelEventTransformer;
|
import org.geysermc.connector.network.translators.world.event.LevelEventTransformer;
|
||||||
|
import org.geysermc.connector.network.translators.item.Enchantment.JavaEnchantment;
|
||||||
import org.geysermc.connector.network.translators.sound.SoundHandler;
|
import org.geysermc.connector.network.translators.sound.SoundHandler;
|
||||||
import org.geysermc.connector.network.translators.sound.SoundInteractionHandler;
|
import org.geysermc.connector.network.translators.sound.SoundInteractionHandler;
|
||||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||||
import org.geysermc.connector.registry.loader.*;
|
import org.geysermc.connector.registry.loader.*;
|
||||||
import org.geysermc.connector.registry.populator.ItemRegistryPopulator;
|
import org.geysermc.connector.registry.populator.ItemRegistryPopulator;
|
||||||
import org.geysermc.connector.registry.populator.RecipeRegistryPopulator;
|
import org.geysermc.connector.registry.populator.RecipeRegistryPopulator;
|
||||||
|
import org.geysermc.connector.registry.type.EnchantmentData;
|
||||||
import org.geysermc.connector.registry.type.ItemMappings;
|
import org.geysermc.connector.registry.type.ItemMappings;
|
||||||
import org.geysermc.connector.registry.type.ParticleMapping;
|
import org.geysermc.connector.registry.type.ParticleMapping;
|
||||||
import org.geysermc.connector.registry.type.SoundMapping;
|
import org.geysermc.connector.registry.type.SoundMapping;
|
||||||
|
@ -82,6 +84,11 @@ public class Registries {
|
||||||
*/
|
*/
|
||||||
public static final VersionedRegistry<Map<RecipeType, List<CraftingData>>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
public static final VersionedRegistry<Map<RecipeType, List<CraftingData>>> CRAFTING_DATA = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry holding data of all the known enchantments.
|
||||||
|
*/
|
||||||
|
public static final SimpleMappedRegistry<JavaEnchantment, EnchantmentData> ENCHANTMENTS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A registry holding a CompoundTag of the known entity identifiers.
|
* A registry holding a CompoundTag of the known entity identifiers.
|
||||||
*/
|
*/
|
||||||
|
@ -140,5 +147,6 @@ public class Registries {
|
||||||
|
|
||||||
// Create registries that require other registries to load first
|
// Create registries that require other registries to load first
|
||||||
POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new);
|
POTION_MIXES = SimpleRegistry.create(PotionMixRegistryLoader::new);
|
||||||
|
ENCHANTMENTS = SimpleMappedRegistry.create("mappings/enchantments.json", EnchantmentRegistryLoader::new);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 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.connector.registry.loader;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
|
import org.geysermc.connector.GeyserConnector;
|
||||||
|
import org.geysermc.connector.network.MinecraftProtocol;
|
||||||
|
import org.geysermc.connector.network.translators.item.Enchantment.JavaEnchantment;
|
||||||
|
import org.geysermc.connector.registry.Registries;
|
||||||
|
import org.geysermc.connector.registry.type.EnchantmentData;
|
||||||
|
import org.geysermc.connector.registry.type.ItemMapping;
|
||||||
|
import org.geysermc.connector.utils.FileUtils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class EnchantmentRegistryLoader implements RegistryLoader<String, Map<JavaEnchantment, EnchantmentData>> {
|
||||||
|
@Override
|
||||||
|
public Map<JavaEnchantment, EnchantmentData> load(String input) {
|
||||||
|
InputStream enchantmentsStream = FileUtils.getResource(input);
|
||||||
|
JsonNode enchantmentsNode;
|
||||||
|
try {
|
||||||
|
enchantmentsNode = GeyserConnector.JSON_MAPPER.readTree(enchantmentsStream);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new AssertionError("Unable to load enchantment data", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<JavaEnchantment, EnchantmentData> enchantments = new EnumMap<>(JavaEnchantment.class);
|
||||||
|
Iterator<Map.Entry<String, JsonNode>> it = enchantmentsNode.fields();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<String, JsonNode> entry = it.next();
|
||||||
|
JavaEnchantment key = JavaEnchantment.getByJavaIdentifier(entry.getKey());
|
||||||
|
JsonNode node = entry.getValue();
|
||||||
|
int rarityMultiplier = switch (node.get("rarity").textValue()) {
|
||||||
|
case "common" -> 1;
|
||||||
|
case "uncommon" -> 2;
|
||||||
|
case "rare" -> 4;
|
||||||
|
case "very_rare" -> 8;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + node.get("rarity").textValue());
|
||||||
|
};
|
||||||
|
int maxLevel = node.get("max_level").asInt();
|
||||||
|
|
||||||
|
EnumSet<JavaEnchantment> incompatibleEnchantments = EnumSet.noneOf(JavaEnchantment.class);
|
||||||
|
JsonNode incompatibleEnchantmentsNode = node.get("incompatible_enchantments");
|
||||||
|
if (incompatibleEnchantmentsNode != null) {
|
||||||
|
for (JsonNode incompatibleNode : incompatibleEnchantmentsNode) {
|
||||||
|
incompatibleEnchantments.add(JavaEnchantment.getByJavaIdentifier(incompatibleNode.textValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IntSet validItems = new IntOpenHashSet();
|
||||||
|
for (JsonNode itemNode : node.get("valid_items")) {
|
||||||
|
String javaIdentifier = itemNode.textValue();
|
||||||
|
ItemMapping itemMapping = Registries.ITEMS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(javaIdentifier);
|
||||||
|
if (itemMapping != null) {
|
||||||
|
validItems.add(itemMapping.getJavaId());
|
||||||
|
} else {
|
||||||
|
throw new NullPointerException("No item entry exists for java identifier: " + javaIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnchantmentData enchantmentData = new EnchantmentData(rarityMultiplier, maxLevel, incompatibleEnchantments, validItems);
|
||||||
|
enchantments.put(key, enchantmentData);
|
||||||
|
}
|
||||||
|
return enchantments;
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,10 +37,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||||
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||||
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
|
||||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
|
||||||
import it.unimi.dsi.fastutil.objects.*;
|
import it.unimi.dsi.fastutil.objects.*;
|
||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.network.translators.item.StoredItemMappings;
|
import org.geysermc.connector.network.translators.item.StoredItemMappings;
|
||||||
|
@ -365,7 +362,12 @@ public class ItemRegistryPopulator {
|
||||||
.bedrockId(bedrockId)
|
.bedrockId(bedrockId)
|
||||||
.bedrockData(mappingItem.getBedrockData())
|
.bedrockData(mappingItem.getBedrockData())
|
||||||
.bedrockBlockId(bedrockBlockId)
|
.bedrockBlockId(bedrockBlockId)
|
||||||
.stackSize(stackSize);
|
.stackSize(stackSize)
|
||||||
|
.maxDamage(mappingItem.getMaxDamage());
|
||||||
|
|
||||||
|
if (mappingItem.getRepairMaterials() != null) {
|
||||||
|
mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials()));
|
||||||
|
}
|
||||||
|
|
||||||
if (mappingItem.getToolType() != null) {
|
if (mappingItem.getToolType() != null) {
|
||||||
if (mappingItem.getToolTier() != null) {
|
if (mappingItem.getToolTier() != null) {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 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.connector.registry.type;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||||
|
import org.geysermc.connector.network.translators.item.Enchantment.JavaEnchantment;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record EnchantmentData(int rarityMultiplier, int maxLevel, Set<JavaEnchantment> incompatibleEnchantments,
|
||||||
|
IntSet validItems) {
|
||||||
|
}
|
|
@ -28,6 +28,8 @@ package org.geysermc.connector.registry.type;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents Geyser's own serialized item information before being processed per-version
|
* Represents Geyser's own serialized item information before being processed per-version
|
||||||
*/
|
*/
|
||||||
|
@ -40,4 +42,6 @@ public class GeyserMappingItem {
|
||||||
@JsonProperty("stack_size") int stackSize = 64;
|
@JsonProperty("stack_size") int stackSize = 64;
|
||||||
@JsonProperty("tool_type") String toolType;
|
@JsonProperty("tool_type") String toolType;
|
||||||
@JsonProperty("tool_tier") String toolTier;
|
@JsonProperty("tool_tier") String toolTier;
|
||||||
|
@JsonProperty("max_damage") int maxDamage = 0;
|
||||||
|
@JsonProperty("repair_materials") List<String> repairMaterials;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,15 @@ import lombok.Value;
|
||||||
import org.geysermc.connector.network.MinecraftProtocol;
|
import org.geysermc.connector.network.MinecraftProtocol;
|
||||||
import org.geysermc.connector.registry.BlockRegistries;
|
import org.geysermc.connector.registry.BlockRegistries;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ItemMapping {
|
public class ItemMapping {
|
||||||
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
||||||
BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
|
BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
|
||||||
64, null, null, null);
|
64, null, null, null, 0, null);
|
||||||
|
|
||||||
String javaIdentifier;
|
String javaIdentifier;
|
||||||
String bedrockIdentifier;
|
String bedrockIdentifier;
|
||||||
|
@ -57,6 +59,10 @@ public class ItemMapping {
|
||||||
|
|
||||||
String translationString;
|
String translationString;
|
||||||
|
|
||||||
|
int maxDamage;
|
||||||
|
|
||||||
|
Set<String> repairMaterials;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets if this item is a block.
|
* Gets if this item is a block.
|
||||||
*
|
*
|
||||||
|
|
|
@ -58,4 +58,19 @@ public class ItemUtils {
|
||||||
}
|
}
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param itemTag the NBT tag of the item
|
||||||
|
* @return the custom name of the item
|
||||||
|
*/
|
||||||
|
public static String getCustomName(CompoundTag itemTag) {
|
||||||
|
if (itemTag != null) {
|
||||||
|
if (itemTag.get("display") instanceof CompoundTag displayTag) {
|
||||||
|
if (displayTag.get("Name") instanceof StringTag nameTag) {
|
||||||
|
return nameTag.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7ff1b6567b56c7b0b8e28786b9bbc30abfaededf
|
Subproject commit 5b6239f0a43ec9a38d65ed53b8d1bfaf564c1c3b
|
|
@ -81,6 +81,16 @@ public class MessageTranslatorTest {
|
||||||
Assert.assertEquals("Unimplemented formatting chars not stripped", "Bold Underline", MessageTranslator.convertMessageLenient("§m§nBold Underline"));
|
Assert.assertEquals("Unimplemented formatting chars not stripped", "Bold Underline", MessageTranslator.convertMessageLenient("§m§nBold Underline"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void convertToPlainText() {
|
||||||
|
Assert.assertEquals("JSON message is not handled properly", "Many colors here", MessageTranslator.convertToPlainText("{\"extra\":[{\"color\":\"red\",\"text\":\"M\"},{\"color\":\"gold\",\"text\":\"a\"},{\"color\":\"yellow\",\"text\":\"n\"},{\"color\":\"green\",\"text\":\"y \"},{\"color\":\"aqua\",\"text\":\"c\"},{\"color\":\"dark_purple\",\"text\":\"o\"},{\"color\":\"red\",\"text\":\"l\"},{\"color\":\"gold\",\"text\":\"o\"},{\"color\":\"yellow\",\"text\":\"r\"},{\"color\":\"green\",\"text\":\"s \"},{\"color\":\"aqua\",\"text\":\"h\"},{\"color\":\"dark_purple\",\"text\":\"e\"},{\"color\":\"red\",\"text\":\"r\"},{\"color\":\"gold\",\"text\":\"e\"}],\"text\":\"\"}", "en_US"));
|
||||||
|
Assert.assertEquals("Legacy formatted message is not handled properly (Colors)", "Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e", "en_US"));
|
||||||
|
Assert.assertEquals("Legacy formatted message is not handled properly (Style)", "Obf Bold Strikethrough Underline Italic Reset", MessageTranslator.convertToPlainText("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US"));
|
||||||
|
Assert.assertEquals("Valid lenient JSON is not handled properly", "Strange", MessageTranslator.convertToPlainText("§rStrange", "en_US"));
|
||||||
|
Assert.assertEquals("Empty message is not handled properly", "", MessageTranslator.convertToPlainText("", "en_US"));
|
||||||
|
Assert.assertEquals("Whitespace is not preserved", " ", MessageTranslator.convertToPlainText(" ", "en_US"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNullTextPacket() {
|
public void testNullTextPacket() {
|
||||||
DefaultComponentSerializer.get().deserialize("null");
|
DefaultComponentSerializer.get().deserialize("null");
|
||||||
|
|
Loading…
Reference in a new issue