Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-27 08:30:12 +01:00
Fix villagers for 1.18.10
Includes working around pre-1.14 ONLY on pre-1.14 by checking the tags packet. Fixes #2828
Dieser Commit ist enthalten in:
Ursprung
9ea59d616e
Commit
746cd94dd1
@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class MerchantContainer extends Container {
|
public class MerchantContainer extends Container {
|
||||||
|
@Getter @Setter
|
||||||
private Entity villager;
|
private Entity villager;
|
||||||
|
@Setter
|
||||||
private VillagerTrade[] villagerTrades;
|
private VillagerTrade[] villagerTrades;
|
||||||
|
@Getter @Setter
|
||||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||||
|
|
||||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||||
super(title, id, size, containerType, playerInventory);
|
super(title, id, size, containerType, playerInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onTradeSelected(GeyserSession session, int slot) {
|
||||||
|
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||||
|
VillagerTrade trade = villagerTrades[slot];
|
||||||
|
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||||
|
// TODO this logic doesn't add up
|
||||||
|
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
|
||||||
|
session.getPlayerEntity().updateBedrockMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,6 +361,11 @@ public class GeyserSession implements GeyserConnection, CommandSender {
|
|||||||
@Setter
|
@Setter
|
||||||
private Int2ObjectMap<IntList> stonecutterRecipes;
|
private Int2ObjectMap<IntList> stonecutterRecipes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to work around 1.13's different behavior in villager trading menus.
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private boolean emulatePost1_14Logic = true;
|
||||||
/**
|
/**
|
||||||
* Starting in 1.17, Java servers expect the <code>carriedItem</code> parameter of the serverbound click container
|
* Starting in 1.17, Java servers expect the <code>carriedItem</code> parameter of the serverbound click container
|
||||||
* packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot
|
* packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot
|
||||||
|
@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.ints.IntList;
|
|||||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -61,7 +62,7 @@ public class TagCache {
|
|||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPacket(ClientboundUpdateTagsPacket packet) {
|
public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
||||||
Map<String, int[]> blockTags = packet.getTags().get("minecraft:block");
|
Map<String, int[]> blockTags = packet.getTags().get("minecraft:block");
|
||||||
this.leaves = IntList.of(blockTags.get("minecraft:leaves"));
|
this.leaves = IntList.of(blockTags.get("minecraft:leaves"));
|
||||||
this.wool = IntList.of(blockTags.get("minecraft:wool"));
|
this.wool = IntList.of(blockTags.get("minecraft:wool"));
|
||||||
@ -79,6 +80,13 @@ public class TagCache {
|
|||||||
this.flowers = IntList.of(itemTags.get("minecraft:flowers"));
|
this.flowers = IntList.of(itemTags.get("minecraft:flowers"));
|
||||||
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
||||||
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
|
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
|
||||||
|
|
||||||
|
// Hack btw
|
||||||
|
boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1;
|
||||||
|
session.setEmulatePost1_14Logic(emulatePost1_14Logic);
|
||||||
|
if (session.getGeyser().getLogger().isDebug()) {
|
||||||
|
session.getGeyser().getLogger().debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
@ -26,14 +26,17 @@
|
|||||||
package org.geysermc.geyser.translator.inventory;
|
package org.geysermc.geyser.translator.inventory;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
|
||||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest;
|
import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest;
|
||||||
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
|
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
|
||||||
|
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData;
|
||||||
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
|
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
|
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||||
|
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
import org.geysermc.geyser.entity.type.Entity;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.inventory.Inventory;
|
import org.geysermc.geyser.inventory.Inventory;
|
||||||
@ -44,6 +47,9 @@ import org.geysermc.geyser.inventory.BedrockContainerSlot;
|
|||||||
import org.geysermc.geyser.inventory.SlotType;
|
import org.geysermc.geyser.inventory.SlotType;
|
||||||
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
|
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
|
||||||
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||||
|
import org.geysermc.geyser.util.InventoryUtils;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||||
private final InventoryUpdater updater;
|
private final InventoryUpdater updater;
|
||||||
@ -131,6 +137,46 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||||
|
if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) {
|
||||||
|
return super.translateCraftingRequest(session, inventory, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Behavior as of 1.18.10.
|
||||||
|
// We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to
|
||||||
|
// store a map of net ID to trade index on our end.
|
||||||
|
int tradeChoice = ((CraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1;
|
||||||
|
ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice);
|
||||||
|
session.sendDownstreamPacket(packet);
|
||||||
|
|
||||||
|
if (session.isEmulatePost1_14Logic()) {
|
||||||
|
// 1.18 Java cooperates nicer than older versions
|
||||||
|
if (inventory instanceof MerchantContainer merchantInventory) {
|
||||||
|
merchantInventory.onTradeSelected(session, tradeChoice);
|
||||||
|
}
|
||||||
|
return translateRequest(session, inventory, request);
|
||||||
|
} else {
|
||||||
|
// 1.18 servers works fine without a workaround, but ViaVersion needs to work around 1.13 servers,
|
||||||
|
// so we need to work around that with the delay. Specifically they force a window refresh after a
|
||||||
|
// trade packet has been sent.
|
||||||
|
session.scheduleInEventLoop(() -> {
|
||||||
|
if (inventory instanceof MerchantContainer merchantInventory) {
|
||||||
|
merchantInventory.onTradeSelected(session, tradeChoice);
|
||||||
|
// Ignore output since we don't want to send a delayed response packet back to the client
|
||||||
|
translateRequest(session, inventory, request);
|
||||||
|
|
||||||
|
// Resync items once more
|
||||||
|
updateInventory(session, inventory);
|
||||||
|
InventoryUtils.updateCursor(session);
|
||||||
|
}
|
||||||
|
}, 100, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
// Revert this request, for now
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||||
// We're not crafting here
|
// We're not crafting here
|
||||||
|
@ -25,11 +25,8 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.bedrock.entity;
|
package org.geysermc.geyser.translator.protocol.bedrock.entity;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade;
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
|
||||||
import org.geysermc.geyser.inventory.Inventory;
|
import org.geysermc.geyser.inventory.Inventory;
|
||||||
import org.geysermc.geyser.inventory.MerchantContainer;
|
import org.geysermc.geyser.inventory.MerchantContainer;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
@ -50,21 +47,14 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case COMPLETE_TRADE -> {
|
case COMPLETE_TRADE -> {
|
||||||
|
// Not sent as of 1.18.10
|
||||||
ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData());
|
ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData());
|
||||||
session.sendDownstreamPacket(selectTradePacket);
|
session.sendDownstreamPacket(selectTradePacket);
|
||||||
|
|
||||||
session.scheduleInEventLoop(() -> {
|
session.scheduleInEventLoop(() -> {
|
||||||
SessionPlayerEntity villager = session.getPlayerEntity();
|
|
||||||
Inventory openInventory = session.getOpenInventory();
|
Inventory openInventory = session.getOpenInventory();
|
||||||
if (openInventory instanceof MerchantContainer merchantInventory) {
|
if (openInventory instanceof MerchantContainer merchantInventory) {
|
||||||
VillagerTrade[] trades = merchantInventory.getVillagerTrades();
|
merchantInventory.onTradeSelected(session, packet.getData());
|
||||||
if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) {
|
|
||||||
VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()];
|
|
||||||
openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
|
||||||
// TODO this logic doesn't add up
|
|
||||||
villager.addFakeTradeExperience(trade.getXp());
|
|
||||||
villager.updateBedrockMetadata();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, 100, TimeUnit.MILLISECONDS);
|
}, 100, TimeUnit.MILLISECONDS);
|
||||||
return;
|
return;
|
||||||
|
@ -30,11 +30,45 @@ import org.geysermc.geyser.session.GeyserSession;
|
|||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Translator(packet = ClientboundUpdateTagsPacket.class)
|
@Translator(packet = ClientboundUpdateTagsPacket.class)
|
||||||
public class JavaUpdateTagsTranslator extends PacketTranslator<ClientboundUpdateTagsPacket> {
|
public class JavaUpdateTagsTranslator extends PacketTranslator<ClientboundUpdateTagsPacket> {
|
||||||
|
private final Map<String, Map<String, int[]>> previous = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
||||||
session.getTagCache().loadPacket(packet);
|
for (Map.Entry<String, Map<String, int[]>> entry : packet.getTags().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append(entry.getKey()).append("={");
|
||||||
|
for (Map.Entry<String, int[]> tag : entry.getValue().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||||
|
builder.append(tag.getKey()).append('=').append(Arrays.toString(tag.getValue())).append(", ");
|
||||||
|
}
|
||||||
|
System.out.println(builder.append("}").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous.isEmpty()) {
|
||||||
|
previous.putAll(packet.getTags());
|
||||||
|
} else {
|
||||||
|
for (Map.Entry<String, Map<String, int[]>> entry : packet.getTags().entrySet()) {
|
||||||
|
Map<String, int[]> oldTags = previous.get(entry.getKey());
|
||||||
|
for (Map.Entry<String, int[]> newTag : entry.getValue().entrySet()) {
|
||||||
|
int[] oldValue = oldTags.get(newTag.getKey());
|
||||||
|
if (oldValue == null) {
|
||||||
|
System.out.println("Tag " + newTag.getKey() + " not found!!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!Arrays.equals(Arrays.stream(oldValue).sorted().toArray(), Arrays.stream(newTag.getValue()).sorted().toArray())) {
|
||||||
|
System.out.println(entry.getKey() + ": " + newTag.getKey() + " has different values! " + Arrays.toString(Arrays.stream(oldValue).sorted().toArray()) + " " + Arrays.toString(Arrays.stream(newTag.getValue()).sorted().toArray()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.getTagCache().loadPacket(session, packet);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren