mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00: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
This commit is contained in:
parent
9ea59d616e
commit
746cd94dd1
6 changed files with 111 additions and 16 deletions
|
@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class MerchantContainer extends Container {
|
||||
@Getter @Setter
|
||||
private Entity villager;
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory 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
|
||||
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
|
||||
* 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 org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -61,7 +62,7 @@ public class TagCache {
|
|||
clear();
|
||||
}
|
||||
|
||||
public void loadPacket(ClientboundUpdateTagsPacket packet) {
|
||||
public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
||||
Map<String, int[]> blockTags = packet.getTags().get("minecraft:block");
|
||||
this.leaves = IntList.of(blockTags.get("minecraft:leaves"));
|
||||
this.wool = IntList.of(blockTags.get("minecraft:wool"));
|
||||
|
@ -79,6 +80,13 @@ public class TagCache {
|
|||
this.flowers = IntList.of(itemTags.get("minecraft:flowers"));
|
||||
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
||||
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() {
|
||||
|
|
|
@ -26,14 +26,17 @@
|
|||
package org.geysermc.geyser.translator.inventory;
|
||||
|
||||
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.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest;
|
||||
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.SetEntityLinkPacket;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
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.updater.InventoryUpdater;
|
||||
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||
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
|
||||
public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
// We're not crafting here
|
||||
|
|
|
@ -25,11 +25,8 @@
|
|||
|
||||
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.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.MerchantContainer;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -50,21 +47,14 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
|
|||
return;
|
||||
}
|
||||
case COMPLETE_TRADE -> {
|
||||
// Not sent as of 1.18.10
|
||||
ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData());
|
||||
session.sendDownstreamPacket(selectTradePacket);
|
||||
|
||||
session.scheduleInEventLoop(() -> {
|
||||
SessionPlayerEntity villager = session.getPlayerEntity();
|
||||
Inventory openInventory = session.getOpenInventory();
|
||||
if (openInventory instanceof MerchantContainer merchantInventory) {
|
||||
VillagerTrade[] trades = merchantInventory.getVillagerTrades();
|
||||
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();
|
||||
}
|
||||
merchantInventory.onTradeSelected(session, packet.getData());
|
||||
}
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
|
|
|
@ -30,11 +30,45 @@ import org.geysermc.geyser.session.GeyserSession;
|
|||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Translator(packet = ClientboundUpdateTagsPacket.class)
|
||||
public class JavaUpdateTagsTranslator extends PacketTranslator<ClientboundUpdateTagsPacket> {
|
||||
private final Map<String, Map<String, int[]>> previous = new HashMap<>();
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue