Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic villager trades #1819

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/main/java/cn/nukkit/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde
protected final Set<Integer> permanentWindows = new IntOpenHashSet();
private boolean inventoryOpen;
protected int closingWindowId = Integer.MIN_VALUE;

public boolean isTrading = false;

protected int messageCounter = 2;

Expand Down Expand Up @@ -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;
Expand Down
149 changes: 148 additions & 1 deletion src/main/java/cn/nukkit/entity/passive/EntityVillager.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
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;
import cn.nukkit.utils.ServerException;

public class EntityVillager extends EntityCreature implements EntityNPC, EntityAgeable {
public class EntityVillager extends EntityCreature implements InventoryHolder, EntityNPC, EntityAgeable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about implementing InventoryHolder here, since it has no physical inventory

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or does it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Villager trading is inventory and since villager contains trade recipes, i find them as inventory holders.


public static final int NETWORK_ID = 115;
private TradeInventory inventory;
private List<TradeInventoryRecipe> recipes;

public EntityVillager(FullChunk chunk, CompoundTag nbt) {
super(chunk, nbt);
Expand Down Expand Up @@ -43,6 +57,134 @@ public String getName() {
public void initEntity() {
super.initEntity();
this.setMaxHealth(20);
this.inventory = new TradeInventory(this);
this.recipes = new ArrayList<TradeInventoryRecipe>();
this.dataProperties.putLong(DATA_TRADING_PLAYER_EID, 0L);
if(this.namedTag.contains("Offers")) {
ListTag<CompoundTag> nbtRecipes = getListRecipes();
for(CompoundTag nbt : nbtRecipes.getAll()) {
recipes.add(TradeInventoryRecipe.toNBT(nbt));
}
} else {
CompoundTag nbt = new CompoundTag("Offers");
nbt.putList(new ListTag<CompoundTag>("Recipes"));
nbt.putList(getDefaultTierExpRequirements());
this.namedTag.putCompound("Offers", nbt);
}
}

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");
}

public void setWilling(boolean value) {
this.namedTag.putBoolean("Willing", 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) {
Copy link
Contributor

@lt-name lt-name May 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Google Translate)
I think we also need to rewrite the onInteract(Player player, Item item, Vector3 clickedPos) method
like this:

    @Override
    public boolean onInteract(Player player, Item item, Vector3 clickedPos) {
        if (super.onInteract(player, item, clickedPos)) {
            return true;
        }
        return this.onInteract(player, item);
    }

if(this.namedTag.contains("Offers") && !isTrading()) {
player.addWindow(this.getInventory());
return true;
}
return false;
}

public void addTradeRecipe(TradeInventoryRecipe recipe) {
this.recipes.add(recipe);
getListRecipes().add(recipe.toNBT());
}

public List<TradeInventoryRecipe> getRecipes(){
return this.recipes;
}

public ListTag<CompoundTag> getListRecipes(){
if(!this.getOffers().contains("Recipes")) {
this.getOffers().putList(new ListTag<CompoundTag>("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");
}

public ListTag<CompoundTag> getDefaultTierExpRequirements() {
ListTag<CompoundTag> tag = new ListTag<CompoundTag>("TierExpRequirements");
tag.add(new CompoundTag().putInt("0", 0));
tag.add(new CompoundTag().putInt("1", 10));
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() {
Expand All @@ -53,4 +195,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;
}
}
3 changes: 2 additions & 1 deletion src/main/java/cn/nukkit/inventory/InventoryType.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/cn/nukkit/inventory/TradeInventory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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.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);
}

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 = this.getHolder().isWilling();
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, true);
} catch(IOException ex) {}

this.getHolder().setTradingWith(who.getId());
who.isTrading = true;

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);
}

this.getHolder().cancelTradingWithPlayer();
who.isTrading = false;

super.onClose(who);
}

public EntityVillager getHolder() {
return (EntityVillager) this.holder;
}

}
Loading