From a1355f6d3ce1940696fc612bcbcf7ad2b28423cb Mon Sep 17 00:00:00 2001 From: Ragnok123 Date: Fri, 2 Apr 2021 17:23:39 +0200 Subject: [PATCH 1/4] Basic villager trades --- .../nukkit/entity/passive/EntityVillager.java | 69 +++++++++++- .../cn/nukkit/inventory/InventoryType.java | 3 +- .../cn/nukkit/inventory/TradeInventory.java | 54 +++++++++ .../inventory/TradeInventoryRecipe.java | 104 ++++++++++++++++++ 4 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 src/main/java/cn/nukkit/inventory/TradeInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java index fca7bc48fe8..42305db8aec 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java @@ -1,13 +1,28 @@ package cn.nukkit.entity.passive; +import java.util.ArrayList; +import java.util.List; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityAgeable; import cn.nukkit.entity.EntityCreature; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.inventory.TradeInventory; +import cn.nukkit.inventory.TradeInventoryRecipe; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; -public class EntityVillager extends EntityCreature implements EntityNPC, EntityAgeable { +public class EntityVillager extends EntityCreature implements InventoryHolder, EntityNPC, EntityAgeable { public static final int NETWORK_ID = 115; + private TradeInventory inventory; + private List recipes = new ArrayList(); + + private int tradeTier = 0; public EntityVillager(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -43,7 +58,54 @@ public String getName() { public void initEntity() { super.initEntity(); this.setMaxHealth(20); + this.inventory = new TradeInventory(this); + } + + public void setTradeTier(int tier) { + this.tradeTier = tier; } + + public int getTradeTier() { + return this.tradeTier; + } + + @Override + public boolean onInteract(Player player, Item item) { + if(recipes.size() > 0) { + player.addWindow(this.getInventory()); + return true; + } + return false; + } + + public void addTradeRcipe(TradeInventoryRecipe recipe) { + this.recipes.add(recipe); + } + + public CompoundTag getOffers() { + CompoundTag nbt = new CompoundTag(); + nbt.putList(recipesToNbt()); + nbt.putList(getTierExpRequirements()); + return nbt; + } + + private ListTag recipesToNbt() { + ListTag tag = new ListTag("Recipes"); + for(TradeInventoryRecipe recipe : this.recipes) { + tag.add(recipe.toNBT()); + } + return tag; + } + + private ListTag getTierExpRequirements() { + ListTag tag = new ListTag("TierExpRequirements"); + tag.add(new CompoundTag().putInt("0", 0)); + tag.add(new CompoundTag().putInt("1", 10)); + tag.add(new CompoundTag().putInt("2", 60)); + tag.add(new CompoundTag().putInt("3", 160)); + tag.add(new CompoundTag().putInt("4", 310)); + return tag; + } public boolean isBaby() { return this.getDataFlag(DATA_FLAGS, DATA_FLAG_BABY); @@ -53,4 +115,9 @@ public void setBaby(boolean baby) { this.setDataFlag(DATA_FLAGS, DATA_FLAG_BABY, baby); this.setScale(baby ? 0.5f : 1); } + + @Override + public Inventory getInventory() { + return this.inventory; + } } diff --git a/src/main/java/cn/nukkit/inventory/InventoryType.java b/src/main/java/cn/nukkit/inventory/InventoryType.java index 1b16ae7b9a6..f33570a62e3 100644 --- a/src/main/java/cn/nukkit/inventory/InventoryType.java +++ b/src/main/java/cn/nukkit/inventory/InventoryType.java @@ -23,7 +23,8 @@ public enum InventoryType { BEACON(1, "Beacon", 13), OFFHAND(1, "Offhand", -1), MINECART_CHEST(27, "Minecart with Chest", 0), - MINECART_HOPPER(5, "Minecart with Hopper", 8); + MINECART_HOPPER(5, "Minecart with Hopper", 8), + TRADING(3, "Villager Trade", 15); private final int size; private final String title; diff --git a/src/main/java/cn/nukkit/inventory/TradeInventory.java b/src/main/java/cn/nukkit/inventory/TradeInventory.java new file mode 100644 index 00000000000..f56608aa059 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/TradeInventory.java @@ -0,0 +1,54 @@ +package cn.nukkit.inventory; + +import java.io.IOException; +import java.nio.ByteOrder; + +import cn.nukkit.Player; +import cn.nukkit.entity.passive.EntityVillager; +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.network.protocol.UpdateTradePacket; + +public class TradeInventory extends BaseInventory { + + public TradeInventory(InventoryHolder holder) { + super(holder, InventoryType.TRADING); + } + + public void onOpen(Player who) { + super.onOpen(who); + + UpdateTradePacket pk = new UpdateTradePacket(); + pk.windowId = (byte) who.getWindowId(this); + pk.windowType = (byte) InventoryType.TRADING.getNetworkType(); + pk.isWilling = true; + pk.screen2 = true; + pk.trader = this.getHolder().getId(); + pk.tradeTier = this.getHolder().getTradeTier(); + pk.player = who.getId(); + try { + pk.offers = NBTIO.write(this.getHolder().getOffers(),ByteOrder.LITTLE_ENDIAN); + } catch(IOException ex) {} + + who.dataPacket(pk); + } + + public void onClose(Player who) { + for(int i = 0; i <= 1; i++) { + Item item = getItem(i); + if(who.getInventory().canAddItem(item)) { + who.getInventory().addItem(item); + } else { + who.dropItem(item); + } + this.clear(i); + } + + super.onClose(who); + } + + public EntityVillager getHolder() { + return (EntityVillager) this.holder; + } + +} diff --git a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java new file mode 100644 index 00000000000..bac060cff63 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java @@ -0,0 +1,104 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import lombok.Getter; + +public class TradeInventoryRecipe { + + public static int A_ITEM = 0; + public static int B_ITEM = 1; + @Getter + private Item sellItem; + @Getter + private Item buyItem; + @Getter + private Item secondBuyItem; + + private int tier = -1; + private int maxUses = 999; + private int buyCountA = 0; + private int buyCountB = 0; + private int uses = 0; + private int demand = 0; + private int rewardsExp = 0; + private int traderExp = 0; + private float priceMultiplierA = 0F; + private float priceMultiplierB = 0F; + + + public TradeInventoryRecipe(Item sellItem, Item buyItem) { + this(sellItem, buyItem, Item.get(0)); + } + + public TradeInventoryRecipe(Item sellItem, Item buyItem, Item secondBuyItem) { + this.sellItem = sellItem; + this.buyItem = buyItem; + this.secondBuyItem = secondBuyItem; + } + + public TradeInventoryRecipe setTier(int tier) { + this.tier = tier; + return this; + } + + public TradeInventoryRecipe setMaxUses(int maxUses) { + this.maxUses = maxUses; + return this; + } + + public TradeInventoryRecipe setBuyCount(int count, int type) { + switch(type) { + case 0: + this.buyCountA = count; + break; + case 1: + this.buyCountB = count; + break; + } + this.buyCountA = count; + return this; + } + + public TradeInventoryRecipe setDemand(int demand) { + this.demand = demand; + return this; + } + + public TradeInventoryRecipe setMultiplier(float multiplier, int type) { + switch(type) { + case 0: + this.priceMultiplierA = multiplier; + break; + case 1: + this.priceMultiplierB = multiplier; + break; + } + return this; + } + + public TradeInventoryRecipe setRewardExp(int reward) { + this.rewardsExp = reward; + return this; + } + + public CompoundTag toNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.putCompound("buyA", NBTIO.putItemHelper(buyItem, -1)); + nbt.putCompound("buyB", NBTIO.putItemHelper(secondBuyItem,-1)); + nbt.putCompound("sell", NBTIO.putItemHelper(sellItem, -1)); + nbt.putInt("tier", tier); + nbt.putInt("buyCountA", buyCountA); + nbt.putInt("buyCountB", buyCountB); + nbt.putInt("uses", uses); + nbt.putInt("maxUses", maxUses); + nbt.putInt("rewardExp", rewardsExp); + nbt.putInt("demand", demand); + nbt.putInt("traderExp", traderExp); + nbt.putFloat("priceMultiplierA", priceMultiplierA); + nbt.putFloat("priceMultiplierB", priceMultiplierB); + return nbt; + } + +} \ No newline at end of file From 46a2a579db1acc74c4d38e939a3a1ab09a182092 Mon Sep 17 00:00:00 2001 From: Ragnok123 Date: Fri, 2 Apr 2021 20:51:28 +0200 Subject: [PATCH 2/4] clean tabulation --- .../nukkit/entity/passive/EntityVillager.java | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java index 42305db8aec..020f5e14712 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java @@ -19,10 +19,9 @@ public class EntityVillager extends EntityCreature implements InventoryHolder, EntityNPC, EntityAgeable { public static final int NETWORK_ID = 115; - private TradeInventory inventory; - private List recipes = new ArrayList(); - - private int tradeTier = 0; + private TradeInventory inventory; + private List recipes = new ArrayList(); + private int tradeTier = 0; public EntityVillager(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -62,49 +61,49 @@ public void initEntity() { } public void setTradeTier(int tier) { - this.tradeTier = tier; + this.tradeTier = tier; } public int getTradeTier() { - return this.tradeTier; + return this.tradeTier; } @Override public boolean onInteract(Player player, Item item) { - if(recipes.size() > 0) { - player.addWindow(this.getInventory()); - return true; - } - return false; + if(recipes.size() > 0) { + player.addWindow(this.getInventory()); + return true; + } + return false; } public void addTradeRcipe(TradeInventoryRecipe recipe) { - this.recipes.add(recipe); + this.recipes.add(recipe); } public CompoundTag getOffers() { - CompoundTag nbt = new CompoundTag(); - nbt.putList(recipesToNbt()); - nbt.putList(getTierExpRequirements()); - return nbt; + CompoundTag nbt = new CompoundTag(); + nbt.putList(recipesToNbt()); + nbt.putList(getTierExpRequirements()); + return nbt; } private ListTag recipesToNbt() { - ListTag tag = new ListTag("Recipes"); - for(TradeInventoryRecipe recipe : this.recipes) { - tag.add(recipe.toNBT()); - } - return tag; + ListTag tag = new ListTag("Recipes"); + for(TradeInventoryRecipe recipe : this.recipes) { + tag.add(recipe.toNBT()); + } + return tag; } private ListTag getTierExpRequirements() { - ListTag tag = new ListTag("TierExpRequirements"); - tag.add(new CompoundTag().putInt("0", 0)); - tag.add(new CompoundTag().putInt("1", 10)); - tag.add(new CompoundTag().putInt("2", 60)); - tag.add(new CompoundTag().putInt("3", 160)); - tag.add(new CompoundTag().putInt("4", 310)); - return tag; + ListTag tag = new ListTag("TierExpRequirements"); + tag.add(new CompoundTag().putInt("0", 0)); + tag.add(new CompoundTag().putInt("1", 10)); + tag.add(new CompoundTag().putInt("2", 60)); + tag.add(new CompoundTag().putInt("3", 160)); + tag.add(new CompoundTag().putInt("4", 310)); + return tag; } public boolean isBaby() { From c30fc23f5b8830a1d19d456bc775161954e79a08 Mon Sep 17 00:00:00 2001 From: Ragnok123 Date: Sun, 4 Apr 2021 19:07:49 +0200 Subject: [PATCH 3/4] trade recipes nbt --- .../nukkit/entity/passive/EntityVillager.java | 57 ++++++++++++------- .../cn/nukkit/inventory/TradeInventory.java | 2 +- .../inventory/TradeInventoryRecipe.java | 13 +++++ 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java index 020f5e14712..4c68d6c9697 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java @@ -21,7 +21,6 @@ public class EntityVillager extends EntityCreature implements InventoryHolder, E public static final int NETWORK_ID = 115; private TradeInventory inventory; private List recipes = new ArrayList(); - private int tradeTier = 0; public EntityVillager(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -58,19 +57,38 @@ public void initEntity() { super.initEntity(); this.setMaxHealth(20); this.inventory = new TradeInventory(this); + if(this.namedTag.getCompound("Offers") != null) { + ListTag nbtRecipes = this.namedTag.getCompound("Offers").getList("Recipes", CompoundTag.class); + for(CompoundTag nbt : nbtRecipes.getAll()) { + recipes.add(TradeInventoryRecipe.toNBT(nbt)); + } + } else { + CompoundTag nbt = new CompoundTag("Offers"); + nbt.putList(new ListTag("Recipes")); + nbt.putList(getDefaultTierExpRequirements()); + this.namedTag.putCompound("Offers", nbt); + } } public void setTradeTier(int tier) { - this.tradeTier = tier; + this.namedTag.putInt("TradeTier", tier); } public int getTradeTier() { - return this.tradeTier; + return this.namedTag.getInt("TradeTier"); + } + + public void setWilling(boolean value) { + this.namedTag.putBoolean("Willing", value); + } + + public boolean isWilling() { + return this.namedTag.getBoolean("Willing"); } @Override public boolean onInteract(Player player, Item item) { - if(recipes.size() > 0) { + if(this.namedTag.getCompound("Offers") != null) { player.addWindow(this.getInventory()); return true; } @@ -79,32 +97,27 @@ public boolean onInteract(Player player, Item item) { public void addTradeRcipe(TradeInventoryRecipe recipe) { this.recipes.add(recipe); + ListTag nbtRecipes = this.getOffers().getList("Recipes", CompoundTag.class); + nbtRecipes.add(recipe.toNBT()); } - public CompoundTag getOffers() { - CompoundTag nbt = new CompoundTag(); - nbt.putList(recipesToNbt()); - nbt.putList(getTierExpRequirements()); - return nbt; - } - - private ListTag recipesToNbt() { - ListTag tag = new ListTag("Recipes"); - for(TradeInventoryRecipe recipe : this.recipes) { - tag.add(recipe.toNBT()); - } - return tag; + public List getRecipes(){ + return this.recipes; + } + + public CompoundTag getOffers() { + return this.namedTag.getCompound("Offers"); } - private ListTag getTierExpRequirements() { + private ListTag getDefaultTierExpRequirements() { ListTag tag = new ListTag("TierExpRequirements"); tag.add(new CompoundTag().putInt("0", 0)); tag.add(new CompoundTag().putInt("1", 10)); - tag.add(new CompoundTag().putInt("2", 60)); - tag.add(new CompoundTag().putInt("3", 160)); - tag.add(new CompoundTag().putInt("4", 310)); + tag.add(new CompoundTag().putInt("2", 70)); + tag.add(new CompoundTag().putInt("3", 150)); + tag.add(new CompoundTag().putInt("4", 250)); return tag; - } + } public boolean isBaby() { return this.getDataFlag(DATA_FLAGS, DATA_FLAG_BABY); diff --git a/src/main/java/cn/nukkit/inventory/TradeInventory.java b/src/main/java/cn/nukkit/inventory/TradeInventory.java index f56608aa059..fef3cf408ff 100644 --- a/src/main/java/cn/nukkit/inventory/TradeInventory.java +++ b/src/main/java/cn/nukkit/inventory/TradeInventory.java @@ -21,7 +21,7 @@ public void onOpen(Player who) { UpdateTradePacket pk = new UpdateTradePacket(); pk.windowId = (byte) who.getWindowId(this); pk.windowType = (byte) InventoryType.TRADING.getNetworkType(); - pk.isWilling = true; + pk.isWilling = this.getHolder().isWilling(); pk.screen2 = true; pk.trader = this.getHolder().getId(); pk.tradeTier = this.getHolder().getTradeTier(); diff --git a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java index bac060cff63..ccc1af1962a 100644 --- a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java +++ b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java @@ -101,4 +101,17 @@ public CompoundTag toNBT() { return nbt; } + public static TradeInventoryRecipe toNBT(CompoundTag nbt) { + TradeInventoryRecipe recipe = new TradeInventoryRecipe(NBTIO.getItemHelper(nbt.getCompound("sell")), NBTIO.getItemHelper(nbt.getCompound("buyA")), NBTIO.getItemHelper(nbt.getCompound("buyB"))) + .setTier(nbt.getInt("tier")) + .setBuyCount(nbt.getInt("buyCountA"), A_ITEM) + .setBuyCount(nbt.getInt("buyCountB"), B_ITEM) + .setMaxUses(nbt.getInt("maxUses")) + .setMultiplier(nbt.getInt("priceMultiplierA"), A_ITEM) + .setMultiplier(nbt.getInt("priceMultiplierB"), B_ITEM) + .setDemand(nbt.getInt("demand")) + .setRewardExp(nbt.getInt("rewardExp")); + return recipe; + } + } \ No newline at end of file From d3af118ca9e40e5f551447ec0796cd92e2fa599b Mon Sep 17 00:00:00 2001 From: Ragnok123 Date: Tue, 6 Apr 2021 17:38:02 +0200 Subject: [PATCH 4/4] Some improvements Fix nbt Handle trade inventory transaction --- src/main/java/cn/nukkit/Player.java | 11 +++ .../nukkit/entity/passive/EntityVillager.java | 84 +++++++++++++++++-- .../cn/nukkit/inventory/TradeInventory.java | 19 ++++- .../inventory/TradeInventoryRecipe.java | 18 ++-- .../transaction/action/TradeAction.java | 32 +++++++ .../types/NetworkInventoryAction.java | 37 ++++++++ 6 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 src/main/java/cn/nukkit/inventory/transaction/action/TradeAction.java diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 971c3435444..bad53aa9949 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -143,6 +143,8 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected final Set permanentWindows = new IntOpenHashSet(); private boolean inventoryOpen; protected int closingWindowId = Integer.MIN_VALUE; + + public boolean isTrading = false; protected int messageCounter = 2; @@ -4690,6 +4692,15 @@ public PlayerCursorInventory getCursorInventory() { public CraftingGrid getCraftingGrid() { return this.craftingGrid; } + + public TradeInventory getTradeInventory() { + for(Inventory inv : this.windows.keySet()) { + if(inv instanceof TradeInventory) { + return (TradeInventory) inv; + } + } + return null; + } public void setCraftingGrid(CraftingGrid grid) { this.craftingGrid = grid; diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java index 4c68d6c9697..82faf541348 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java @@ -15,12 +15,13 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.ServerException; public class EntityVillager extends EntityCreature implements InventoryHolder, EntityNPC, EntityAgeable { public static final int NETWORK_ID = 115; private TradeInventory inventory; - private List recipes = new ArrayList(); + private List recipes; public EntityVillager(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -57,8 +58,10 @@ public void initEntity() { super.initEntity(); this.setMaxHealth(20); this.inventory = new TradeInventory(this); - if(this.namedTag.getCompound("Offers") != null) { - ListTag nbtRecipes = this.namedTag.getCompound("Offers").getList("Recipes", CompoundTag.class); + this.recipes = new ArrayList(); + this.dataProperties.putLong(DATA_TRADING_PLAYER_EID, 0L); + if(this.namedTag.contains("Offers")) { + ListTag nbtRecipes = getListRecipes(); for(CompoundTag nbt : nbtRecipes.getAll()) { recipes.add(TradeInventoryRecipe.toNBT(nbt)); } @@ -70,11 +73,52 @@ public void initEntity() { } } + public void setTraderName(String name) { + this.namedTag.putString("TraderName", name); + } + + public String getTraderName() { + if(!this.namedTag.contains("TraderName")) { + this.namedTag.putString("TraderName", getNameTag()); + } + return this.namedTag.getString("TraderName"); + } + public void setTradeTier(int tier) { + if(tier > 4){ + throw new ServerException("Maximal tier is 4, but your tier is higher than 4"); + } this.namedTag.putInt("TradeTier", tier); + this.dataProperties.putInt(DATA_TRADE_TIER, tier); + } + + public void setMaxTradeTier(int maxTier) { + if(maxTier > 4) maxTier = 4; + this.dataProperties.putInt(DATA_MAX_TRADE_TIER, maxTier); + } + + public int getMaxTradeTier() { + if(!this.dataProperties.exists(DATA_MAX_TRADE_TIER)) { + this.setMaxTradeTier(4); + } + return this.dataProperties.getInt(DATA_MAX_TRADE_TIER); + } + + public void setExperience(int experience) { + this.dataProperties.putInt(DATA_TRADE_EXPERIENCE, experience); + } + + public int getExperience() { + if(!this.dataProperties.exists(DATA_TRADE_EXPERIENCE)) { + setExperience(0); + } + return this.dataProperties.getInt(DATA_TRADE_EXPERIENCE); } public int getTradeTier() { + if(!this.namedTag.contains("TradeTier")) { + this.namedTag.putInt("TradeTier", 0); + } return this.namedTag.getInt("TradeTier"); } @@ -83,33 +127,57 @@ public void setWilling(boolean value) { } public boolean isWilling() { + if(!this.namedTag.contains("Willing")) { + this.namedTag.putBoolean("Willing", true); + } return this.namedTag.getBoolean("Willing"); } + public void cancelTradingWithPlayer() { + this.setTradingWith(0L); + } + + public void setTradingWith(long eid) { + this.dataProperties.putLong(DATA_TRADING_PLAYER_EID, eid); + } + + public boolean isTrading() { + return this.dataProperties.getLong(DATA_TRADING_PLAYER_EID) != 0L; + } + @Override public boolean onInteract(Player player, Item item) { - if(this.namedTag.getCompound("Offers") != null) { + if(this.namedTag.contains("Offers") && !isTrading()) { player.addWindow(this.getInventory()); return true; } return false; } - public void addTradeRcipe(TradeInventoryRecipe recipe) { + public void addTradeRecipe(TradeInventoryRecipe recipe) { this.recipes.add(recipe); - ListTag nbtRecipes = this.getOffers().getList("Recipes", CompoundTag.class); - nbtRecipes.add(recipe.toNBT()); + getListRecipes().add(recipe.toNBT()); } public List getRecipes(){ return this.recipes; } + public ListTag getListRecipes(){ + if(!this.getOffers().contains("Recipes")) { + this.getOffers().putList(new ListTag("Recipes")); + } + return this.getOffers().getList("Recipes", CompoundTag.class); + } + public CompoundTag getOffers() { + if(!this.namedTag.contains("Offers")) { + this.namedTag.putCompound("Offers", new CompoundTag("Offers")); + } return this.namedTag.getCompound("Offers"); } - private ListTag getDefaultTierExpRequirements() { + public ListTag getDefaultTierExpRequirements() { ListTag tag = new ListTag("TierExpRequirements"); tag.add(new CompoundTag().putInt("0", 0)); tag.add(new CompoundTag().putInt("1", 10)); diff --git a/src/main/java/cn/nukkit/inventory/TradeInventory.java b/src/main/java/cn/nukkit/inventory/TradeInventory.java index fef3cf408ff..980b1885d3d 100644 --- a/src/main/java/cn/nukkit/inventory/TradeInventory.java +++ b/src/main/java/cn/nukkit/inventory/TradeInventory.java @@ -7,9 +7,18 @@ import cn.nukkit.entity.passive.EntityVillager; import cn.nukkit.item.Item; import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.UpdateTradePacket; public class TradeInventory extends BaseInventory { + + public static final int TRADE_INPUT_A = 4; + public static final int TRADE_INPUT_B = 5; + public static final int TRADE_OUTPUT = 51; + + // mojang, what the heck?? + public static final int FAKE_TRADE_INPUT = -30; // moves from fake ui (villager inventory) to player inventory + public static final int FAKE_TRADE_OUTPUT = -31; // item is sent from player inventory to villager fake inv public TradeInventory(InventoryHolder holder) { super(holder, InventoryType.TRADING); @@ -25,11 +34,16 @@ public void onOpen(Player who) { pk.screen2 = true; pk.trader = this.getHolder().getId(); pk.tradeTier = this.getHolder().getTradeTier(); + pk.displayName = this.getHolder().getTraderName(); pk.player = who.getId(); + pk.unknownVarInt1 = 0; try { - pk.offers = NBTIO.write(this.getHolder().getOffers(),ByteOrder.LITTLE_ENDIAN); + pk.offers = NBTIO.write(this.getHolder().getOffers(),ByteOrder.LITTLE_ENDIAN, true); } catch(IOException ex) {} + this.getHolder().setTradingWith(who.getId()); + who.isTrading = true; + who.dataPacket(pk); } @@ -44,6 +58,9 @@ public void onClose(Player who) { this.clear(i); } + this.getHolder().cancelTradingWithPlayer(); + who.isTrading = false; + super.onClose(who); } diff --git a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java index ccc1af1962a..1757154457a 100644 --- a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java +++ b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java @@ -16,20 +16,20 @@ public class TradeInventoryRecipe { @Getter private Item secondBuyItem; - private int tier = -1; + private int tier = 0; private int maxUses = 999; private int buyCountA = 0; private int buyCountB = 0; private int uses = 0; private int demand = 0; - private int rewardsExp = 0; + private byte rewardsExp = (byte) 0; private int traderExp = 0; private float priceMultiplierA = 0F; private float priceMultiplierB = 0F; public TradeInventoryRecipe(Item sellItem, Item buyItem) { - this(sellItem, buyItem, Item.get(0)); + this(sellItem, buyItem, null); } public TradeInventoryRecipe(Item sellItem, Item buyItem, Item secondBuyItem) { @@ -78,7 +78,7 @@ public TradeInventoryRecipe setMultiplier(float multiplier, int type) { return this; } - public TradeInventoryRecipe setRewardExp(int reward) { + public TradeInventoryRecipe setRewardExp(byte reward) { this.rewardsExp = reward; return this; } @@ -86,14 +86,16 @@ public TradeInventoryRecipe setRewardExp(int reward) { public CompoundTag toNBT() { CompoundTag nbt = new CompoundTag(); nbt.putCompound("buyA", NBTIO.putItemHelper(buyItem, -1)); - nbt.putCompound("buyB", NBTIO.putItemHelper(secondBuyItem,-1)); + if(secondBuyItem != null) { + nbt.putCompound("buyB", NBTIO.putItemHelper(secondBuyItem,-1)); + } nbt.putCompound("sell", NBTIO.putItemHelper(sellItem, -1)); nbt.putInt("tier", tier); nbt.putInt("buyCountA", buyCountA); nbt.putInt("buyCountB", buyCountB); nbt.putInt("uses", uses); nbt.putInt("maxUses", maxUses); - nbt.putInt("rewardExp", rewardsExp); + nbt.putByte("rewardExp", rewardsExp); nbt.putInt("demand", demand); nbt.putInt("traderExp", traderExp); nbt.putFloat("priceMultiplierA", priceMultiplierA); @@ -102,7 +104,7 @@ public CompoundTag toNBT() { } public static TradeInventoryRecipe toNBT(CompoundTag nbt) { - TradeInventoryRecipe recipe = new TradeInventoryRecipe(NBTIO.getItemHelper(nbt.getCompound("sell")), NBTIO.getItemHelper(nbt.getCompound("buyA")), NBTIO.getItemHelper(nbt.getCompound("buyB"))) + TradeInventoryRecipe recipe = new TradeInventoryRecipe(NBTIO.getItemHelper(nbt.getCompound("sell")), NBTIO.getItemHelper(nbt.getCompound("buyA")), nbt.contains("buyB") ? NBTIO.getItemHelper(nbt.getCompound("buyB")) : null) .setTier(nbt.getInt("tier")) .setBuyCount(nbt.getInt("buyCountA"), A_ITEM) .setBuyCount(nbt.getInt("buyCountB"), B_ITEM) @@ -110,7 +112,7 @@ public static TradeInventoryRecipe toNBT(CompoundTag nbt) { .setMultiplier(nbt.getInt("priceMultiplierA"), A_ITEM) .setMultiplier(nbt.getInt("priceMultiplierB"), B_ITEM) .setDemand(nbt.getInt("demand")) - .setRewardExp(nbt.getInt("rewardExp")); + .setRewardExp((byte)nbt.getByte("rewardExp")); return recipe; } diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/TradeAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/TradeAction.java new file mode 100644 index 00000000000..614b84646da --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/action/TradeAction.java @@ -0,0 +1,32 @@ +package cn.nukkit.inventory.transaction.action; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; + +public class TradeAction extends InventoryAction { + + public TradeAction(Item sourceItem, Item targetItem) { + super(sourceItem, targetItem); + } + + @Override + public boolean isValid(Player source) { + return true; + } + + @Override + public boolean execute(Player source) { + return source.getInventory().contains(this.sourceItem); + } + + @Override + public void onExecuteSuccess(Player source) { + + } + + @Override + public void onExecuteFail(Player source) { + + } + +} diff --git a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java index 36b132c4af5..ecd795e8473 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java @@ -6,6 +6,7 @@ import cn.nukkit.inventory.EnchantInventory; import cn.nukkit.inventory.Inventory; import cn.nukkit.inventory.PlayerUIComponent; +import cn.nukkit.inventory.TradeInventory; import cn.nukkit.inventory.transaction.action.*; import cn.nukkit.item.Item; import cn.nukkit.network.protocol.InventoryTransactionPacket; @@ -190,6 +191,30 @@ public InventoryAction createInventoryAction(Player player) { this.windowId = Player.ANVIL_WINDOW_ID; this.inventorySlot = 1; break; + case TradeInventory.TRADE_INPUT_A: + if(player.getTradeInventory() == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have trade window open"); + return null; + } + this.windowId = player.getWindowId(player.getTradeInventory()); + this.inventorySlot = 0; + break; + case TradeInventory.TRADE_INPUT_B: + if(player.getTradeInventory() == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have trade window open"); + return null; + } + this.windowId = player.getWindowId(player.getTradeInventory()); + this.inventorySlot = 0; + break; + case TradeInventory.TRADE_OUTPUT: + if(player.getTradeInventory() == null) { + player.getServer().getLogger().error("Player " + player.getName() + " does not have trade window open"); + return null; + } + this.windowId = player.getWindowId(player.getTradeInventory()); + this.inventorySlot = 2; + break; } } @@ -241,6 +266,18 @@ public InventoryAction createInventoryAction(Player player) { return new CraftingTakeResultAction(this.oldItem, this.newItem); case SOURCE_TYPE_CRAFTING_USE_INGREDIENT: return new CraftingTransferMaterialAction(this.oldItem, this.newItem, this.inventorySlot); + case SOURCE_TYPE_TRADING_INPUT_1: + case SOURCE_TYPE_TRADING_INPUT_2: + Optional tradeInv = player.getTopWindow(); + if(tradeInv.get() instanceof TradeInventory) { + return new SlotChangeAction(tradeInv.get(), this.inventorySlot, this.oldItem, this.newItem); + } + return null; + case SOURCE_TYPE_TRADING_USE_INPUTS: + case SOURCE_TYPE_TRADING_OUTPUT: + case TradeInventory.FAKE_TRADE_INPUT: + case TradeInventory.FAKE_TRADE_OUTPUT: + return new TradeAction(this.oldItem, this.newItem); } if (this.windowId >= SOURCE_TYPE_ANVIL_OUTPUT && this.windowId <= SOURCE_TYPE_ANVIL_INPUT) { //anvil actions