forked from GeyserMC/Geyser
Add basic villager trading support (incomplete)
This commit implements basic functionality for villager trading. This is still incomplete and is buggy in areas such as with villager trades that have more than one input and trade inputs and outputs containing NBT. Co-authored-by: DoctorMacc <toy.fighter1@gmail.com>
This commit is contained in:
parent
c6527fa723
commit
30e38b3a2f
7 changed files with 353 additions and 0 deletions
|
@ -27,10 +27,13 @@ package org.geysermc.connector.entity.living.merchant;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.EntityData;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
|
@ -66,6 +69,10 @@ public class VillagerEntity extends AbstractMerchantEntity {
|
|||
VILLAGER_REGIONS.put(6, 6);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
|
||||
public VillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsExcepti
|
|||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
||||
import com.github.steveice10.mc.protocol.data.SubProtocol;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
|
||||
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
|
||||
|
@ -149,9 +150,18 @@ public class GeyserSession implements CommandSender {
|
|||
@Setter
|
||||
private boolean interacting;
|
||||
|
||||
@Setter
|
||||
private long lastInteractedVillagerEid;
|
||||
|
||||
@Setter
|
||||
private Vector3i lastInteractionPosition;
|
||||
|
||||
@Setter
|
||||
private ItemStack firstTradeSlot;
|
||||
|
||||
@Setter
|
||||
private ItemStack secondTradeSlot;
|
||||
|
||||
@Setter
|
||||
private boolean switchingDimension = false;
|
||||
private boolean manyDimPackets = false;
|
||||
|
|
|
@ -159,6 +159,7 @@ public class Translators {
|
|||
inventoryTranslators.put(WindowType.ANVIL, new AnvilInventoryTranslator());
|
||||
inventoryTranslators.put(WindowType.CRAFTING, new CraftingInventoryTranslator());
|
||||
inventoryTranslators.put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());
|
||||
inventoryTranslators.put(WindowType.MERCHANT, new MerchantInventoryTranslator());
|
||||
//inventoryTranslators.put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO
|
||||
|
||||
InventoryTranslator furnace = new FurnaceInventoryTranslator();
|
||||
|
|
|
@ -44,6 +44,7 @@ import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
|
|||
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
import org.geysermc.connector.entity.living.merchant.VillagerEntity;
|
||||
import org.geysermc.connector.inventory.Inventory;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
|
@ -187,6 +188,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
session.sendDownstreamPacket(interactAtPacket);
|
||||
|
||||
EntitySoundInteractionHandler.handleEntityInteraction(session, vector, entity);
|
||||
|
||||
if (entity instanceof VillagerEntity) {
|
||||
session.setLastInteractedVillagerEid(packet.getRuntimeEntityId());
|
||||
}
|
||||
break;
|
||||
case 1: //Attack
|
||||
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 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;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSelectTradePacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
|
||||
import com.nukkitx.protocol.bedrock.data.ContainerId;
|
||||
import com.nukkitx.protocol.bedrock.data.InventoryActionData;
|
||||
import org.geysermc.connector.entity.living.merchant.VillagerEntity;
|
||||
import org.geysermc.connector.inventory.Inventory;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.Translators;
|
||||
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
|
||||
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||
|
||||
private final InventoryUpdater updater;
|
||||
|
||||
public MerchantInventoryTranslator() {
|
||||
super(3);
|
||||
this.updater = new CursorInventoryUpdater();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int javaSlotToBedrock(int slot) {
|
||||
switch (slot) {
|
||||
case 0:
|
||||
return 4;
|
||||
case 1:
|
||||
return 5;
|
||||
case 2:
|
||||
return 50;
|
||||
}
|
||||
return super.javaSlotToBedrock(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bedrockSlotToJava(InventoryActionData action) {
|
||||
if (action.getSource().getContainerId() == ContainerId.CURSOR) {
|
||||
switch (action.getSlot()) {
|
||||
case 4:
|
||||
return 0;
|
||||
case 5:
|
||||
return 1;
|
||||
case 50:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return super.bedrockSlotToJava(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareInventory(GeyserSession session, Inventory inventory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(GeyserSession session, Inventory inventory) {
|
||||
session.setLastInteractedVillagerEid(-1);
|
||||
session.setFirstTradeSlot(null);
|
||||
session.setSecondTradeSlot(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateInventory(GeyserSession session, Inventory inventory) {
|
||||
updater.updateInventory(this, session, inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
|
||||
updater.updateSlot(this, session, inventory, slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
|
||||
InventoryActionData result = null;
|
||||
|
||||
VillagerEntity villager = (VillagerEntity) session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
|
||||
if (villager == null) {
|
||||
session.getConnector().getLogger().debug("Could not find villager with entity id: " + session.getLastInteractedVillagerEid());
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to store the trade slot data in the session itself as data
|
||||
// needs to persist beyond this translateActions method since the client
|
||||
// sends multiple packets for this
|
||||
for (InventoryActionData data : actions) {
|
||||
if (data.getSlot() == 4 && session.getFirstTradeSlot() == null && data.getSource().getContainerId() == ContainerId.CURSOR) {
|
||||
session.setFirstTradeSlot(Translators.getItemTranslator().translateToJava(session, data.getToItem()));
|
||||
}
|
||||
|
||||
if (data.getSlot() == 5 && session.getSecondTradeSlot() == null && data.getToItem() != null && data.getSource().getContainerId() == ContainerId.CURSOR) {
|
||||
session.setSecondTradeSlot(Translators.getItemTranslator().translateToJava(session, data.getToItem()));
|
||||
}
|
||||
if (data.getSlot() == 50 && result == null) {
|
||||
result = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == null || session.getFirstTradeSlot() == null) {
|
||||
super.translateActions(session, inventory, actions);
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack resultSlot = Translators.getItemTranslator().translateToJava(session, result.getToItem());
|
||||
for (int i = 0; i < villager.getVillagerTrades().length; i++) {
|
||||
VillagerTrade trade = villager.getVillagerTrades()[i];
|
||||
if (!Translators.getItemTranslator().equals(session.getFirstTradeSlot(), trade.getFirstInput(), true, true, false) || !Translators.getItemTranslator().equals(resultSlot, trade.getOutput(), true, false, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (session.getSecondTradeSlot() != null && trade.getSecondInput() != null && !Translators.getItemTranslator().equals(session.getSecondTradeSlot(), trade.getSecondInput(), true, false, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(i);
|
||||
session.sendDownstreamPacket(selectTradePacket);
|
||||
|
||||
ClientWindowActionPacket tradeAction = new ClientWindowActionPacket(
|
||||
inventory.getId(),
|
||||
inventory.getTransactionId().getAndIncrement(),
|
||||
this.bedrockSlotToJava(result),
|
||||
null,
|
||||
WindowAction.CLICK_ITEM,
|
||||
ClickItemParam.LEFT_CLICK
|
||||
);
|
||||
session.sendDownstreamPacket(tradeAction);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
super.translateActions(session, inventory, actions);
|
||||
}
|
||||
}
|
|
@ -160,6 +160,51 @@ public class ItemTranslator {
|
|||
.stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an {@link ItemStack} is equal to another item stack
|
||||
*
|
||||
* @param itemStack the item stack to check
|
||||
* @param equalsItemStack the item stack to check if equal to
|
||||
* @param checkAmount if the amount should be taken into account
|
||||
* @param trueIfAmountIsGreater if this should return true if the amount of the
|
||||
* first item stack is greater than that of the second
|
||||
* @param checkNbt if NBT data should be checked
|
||||
* @return if an item stack is equal to another item stack
|
||||
*/
|
||||
public boolean equals(ItemStack itemStack, ItemStack equalsItemStack, boolean checkAmount, boolean trueIfAmountIsGreater, boolean checkNbt) {
|
||||
if (itemStack.getId() != equalsItemStack.getId()) {
|
||||
return false;
|
||||
}
|
||||
if (checkAmount) {
|
||||
if (trueIfAmountIsGreater) {
|
||||
if (itemStack.getAmount() < equalsItemStack.getAmount()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (itemStack.getAmount() != equalsItemStack.getAmount()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkNbt) {
|
||||
return true;
|
||||
}
|
||||
if ((itemStack.getNbt() == null || itemStack.getNbt().isEmpty()) && (equalsItemStack.getNbt() != null && !equalsItemStack.getNbt().isEmpty())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((itemStack.getNbt() != null && !itemStack.getNbt().isEmpty() && (equalsItemStack.getNbt() == null || !equalsItemStack.getNbt().isEmpty()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemStack.getNbt() != null && equalsItemStack.getNbt() != null) {
|
||||
return itemStack.getNbt().equals(equalsItemStack.getNbt());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final ItemStackTranslator DEFAULT_TRANSLATOR = new ItemStackTranslator() {
|
||||
@Override
|
||||
public List<ItemEntry> getAppliedItems() {
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2020 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.java.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerTradeListPacket;
|
||||
import com.nukkitx.nbt.CompoundTagBuilder;
|
||||
import com.nukkitx.nbt.tag.CompoundTag;
|
||||
import com.nukkitx.protocol.bedrock.data.ContainerType;
|
||||
import com.nukkitx.protocol.bedrock.data.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateTradePacket;
|
||||
import org.geysermc.connector.entity.living.merchant.VillagerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.network.translators.Translators;
|
||||
import org.geysermc.connector.network.translators.item.ItemEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Translator(packet = ServerTradeListPacket.class)
|
||||
public class JavaTradeListTranslator extends PacketTranslator<ServerTradeListPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerTradeListPacket packet, GeyserSession session) {
|
||||
VillagerEntity villager = (VillagerEntity) session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
|
||||
if (villager == null) {
|
||||
session.getConnector().getLogger().debug("Could not find villager with entity id: " + session.getLastInteractedVillagerEid());
|
||||
return;
|
||||
}
|
||||
villager.setVillagerTrades(packet.getTrades());
|
||||
villager.getMetadata().put(EntityData.TRADE_XP, packet.getExperience());
|
||||
villager.getMetadata().put(EntityData.TRADE_TIER, packet.getVillagerLevel() - 1);
|
||||
villager.updateBedrockMetadata(session);
|
||||
|
||||
UpdateTradePacket updateTradePacket = new UpdateTradePacket();
|
||||
updateTradePacket.setTradeTier(packet.getVillagerLevel() + 1);
|
||||
updateTradePacket.setWindowId((short) packet.getWindowId());
|
||||
updateTradePacket.setWindowType((short) ContainerType.TRADING.id());
|
||||
updateTradePacket.setDisplayName("Villager");
|
||||
updateTradePacket.setUnknownInt(0);
|
||||
updateTradePacket.setScreen2(true);
|
||||
updateTradePacket.setWilling(true);
|
||||
updateTradePacket.setPlayerUniqueEntityId(session.getPlayerEntity().getGeyserId());
|
||||
updateTradePacket.setTraderUniqueEntityId(session.getLastInteractedVillagerEid());
|
||||
CompoundTagBuilder builder = CompoundTagBuilder.builder();
|
||||
List<CompoundTag> tags = new ArrayList<>();
|
||||
for (VillagerTrade trade : packet.getTrades()) {
|
||||
CompoundTagBuilder recipe = CompoundTagBuilder.builder();
|
||||
recipe.intTag("maxUses", trade.getMaxUses());
|
||||
recipe.intTag("traderExp", packet.getExperience());
|
||||
recipe.floatTag("priceMultiplierA", trade.getPriceMultiplier());
|
||||
recipe.tag(getItemTag(session, trade.getOutput(), "sell"));
|
||||
recipe.floatTag("priceMultiplierB", 0.0f);
|
||||
recipe.intTag("buyCountB", 0);
|
||||
recipe.intTag("buyCountA", trade.getOutput().getAmount());
|
||||
recipe.intTag("demand", trade.getDemand());
|
||||
recipe.intTag("tier", packet.getVillagerLevel() - 1);
|
||||
recipe.tag(getItemTag(session, trade.getFirstInput(), "buyA"));
|
||||
if (trade.getSecondInput() != null) {
|
||||
recipe.tag(getItemTag(session, trade.getSecondInput(), "buyB"));
|
||||
}
|
||||
recipe.intTag("uses", trade.getNumUses());
|
||||
recipe.byteTag("rewardExp", (byte) trade.getXp());
|
||||
tags.add(recipe.buildRootTag());
|
||||
}
|
||||
builder.listTag("Recipes", CompoundTag.class, tags);
|
||||
List<CompoundTag> expTags = new ArrayList<>();
|
||||
expTags.add(CompoundTagBuilder.builder().intTag("0", 0).buildRootTag());
|
||||
expTags.add(CompoundTagBuilder.builder().intTag("1", 10).buildRootTag());
|
||||
expTags.add(CompoundTagBuilder.builder().intTag("2", 60).buildRootTag());
|
||||
expTags.add(CompoundTagBuilder.builder().intTag("3", 160).buildRootTag());
|
||||
expTags.add(CompoundTagBuilder.builder().intTag("4", 310).buildRootTag());
|
||||
builder.listTag("TierExpRequirements", CompoundTag.class, expTags);
|
||||
updateTradePacket.setOffers(builder.buildRootTag());
|
||||
session.sendUpstreamPacket(updateTradePacket);
|
||||
}
|
||||
|
||||
private CompoundTag getItemTag(GeyserSession session, ItemStack stack, String name) {
|
||||
ItemData itemData = Translators.getItemTranslator().translateToBedrock(session, stack);
|
||||
ItemEntry itemEntry = Translators.getItemTranslator().getItem(stack);
|
||||
CompoundTagBuilder builder = CompoundTagBuilder.builder();
|
||||
builder.byteTag("Count", (byte) itemData.getCount());
|
||||
builder.shortTag("Damage", itemData.getDamage());
|
||||
builder.stringTag("Name", itemEntry.getJavaIdentifier());
|
||||
return builder.build(name);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue