Virtuelle Villager Trades

Hier könnt ihr anderen Leuten helfen, indem ihr Anleitungen oder praktische Codesegmente zur Verfügung stellt.

Virtuelle Villager Trades

Beitragvon Sep2703 » So 1. Mär 2015, 12:56

Hallo,

heute möchte ich euch zeigen, wie man virtuelle Tauschgeschäfte öffnen kann.


Inhalt
1. Vorwort
2. MerchantRecipe und MerchantRecipeList
3. Trades öffnen und IMerchant
4. Schlusswort




1 Vorwort

In dieser Anleitung wollen wir eigene Tauschgeschäfte erstellen und diese ohne Villager öffnen. Auf einigen Servern (vgl. gommehd.net vor etwa 3 Monaten) gibt es Inventare, in denen man sich durch klicken kann. Diese Server schaffen es, mittendrin ein Tausch-Inventar zu öffnen. Genau darum geht es auch hier in dieser Anleitung.
Vorkenntnisse solltet ihr durchaus mitbringen. Auf Java-Techniken werde ich hier nicht eingehen. Es ist vom Vorteil, sich einmal die EntityVillager.class angeschaut zu haben.
Das ist kein Tutorial für Anfänger. Für Fortgeschrittene aber auf jeden Fall zu bewältigen.



2 MerchantRecipe und MerchantRecipeList

Wenn man das Tausch-Menü eines Dorfbewohners vor sich hat, werden oft mehrere Tauschgeschäfte angeboten, die sich mit den beiden Pfeilen links und rechts auswählen lassen. In Java repräsentiert eine MerchantRecipeList all diese "Rezepte". Eine MerchantRecipeList erbt von einer ArrayList, sodass bekannte Methode (z.B. "add(Object)") zur Verfügung stehen. Davon werden wir auch noch Gebrauch machen.
Jedes einzelne Rezept ist ein MerchantRecipe. Diese Klasse ist glücklicherweise nicht allzu stark verschleiert. Hier ein Auszug aus der dekompilierten MerchantRecipe.class:
Code: Alles auswählen
  1.   private ItemStack buyingItem1;
  2.   private ItemStack buyingItem2;
  3.   private ItemStack sellingItem;
  4.   private int uses;
  5.   private int maxUses;
  6.   private boolean rewardExp;

Direkt unter der öffnenden geschweiften Klammer für den Classbody werden diese 6 Variablen deklariert. Dabei interessieren uns zunächst die ersten fünf.
Ich habe hier einmal eine Grafik zur Verdeutlichung:

Bild


1 - buyingItem1
2 - buyingItem2
3 - sellingItem


uses ist die Anzahl der Benutzungen und maxUses die Anzahl der maximalen Benutzungen. Beide Parameter spielen für uns eine eher untergeordnete Rolle, da bei jedem Öffnen des Inventars sowieso eine komplett neue MerchantRecipeList erzeugt wird. Dennoch sollten wir bei maxUses einen größeren Wert verwenden, damit der Spieler ungehindert tauschen kann. Ihr könnt diesen Parameter aber auch zur Beschränkung verwenden.

Ein MerchantRecipe erzeugen
Der Konstruktor von MerchantRecipe ist mehrfach überladen. Wir nutzen folgenden:
Code: Alles auswählen
  1.   public MerchantRecipe(ItemStack paramItemStack1, ItemStack paramItemStack2, ItemStack paramItemStack3, int paramInt1, int paramInt2) {
  2.     this.buyingItem1 = paramItemStack1;
  3.     this.buyingItem2 = paramItemStack2;
  4.     this.sellingItem = paramItemStack3;
  5.     this.uses = paramInt1;
  6.     this.maxUses = paramInt2;
  7.     this.rewardExp = true;
  8.   }


Zunächst sollten wir uns eine Klasse anlegen, die alle Tauschrezepte zu einer MerchantRecipeList zusammenfässt und eine Methode bereitstellt, um an die MerchantRecipeList zu gelangen. Ich habe das wie folgt gelöst:
Code: Alles auswählen
  1. public class MerchantTrade {
  2.    private MerchantRecipeList recipeList;
  3.    
  4.    public MerchantTrade() {
  5.       this.recipeList = new MerchantRecipeList();
  6.    }
  7.    
  8.    public void addTrade(ItemStack buy1, ItemStack sell) {
  9.       addTrade(buy1, null, sell);
  10.    }
  11.    
  12.    @SuppressWarnings("unchecked")
  13.    public void addTrade(ItemStack buy1, ItemStack buy2, ItemStack sell) {
  14.       MerchantRecipe recipe = new MerchantRecipe(CraftItemStack.asNMSCopy(buy1), CraftItemStack.asNMSCopy(buy2), CraftItemStack.asNMSCopy(sell), 0, 999999);
  15.       recipeList.add(recipe);
  16.    }
  17.    
  18.    protected MerchantRecipeList getRecipeList() {
  19.       return recipeList;
  20.    }
  21. }


Ich habe den org.bukkit.inventory.ItemStack importiert. Mit "CraftItemStack.asNMSCopy()" lassen sich Bukkit-ItemStacks in NMS-ItemStacks konvertieren. Dies ist hier notwendig, da der Konstruktor vom MerchantRecipe NMS-Itemstacks erwartet.

Jetzt sind wir bereit für den nächsten Schritt - die getRecipeList()-Methode habe ich "protected" gemacht, damit andere Entwickler außerhalb dieser kleinen "API" nicht mit NMS in Kontakt kommen - diese Arbeit erledigen wir ja jetzt und später soll es dann ganz einfach zu benutzen sein, ohne einen Blick auf NMS werfen zu müssen.



3 Trades öffnen und IMerchant

Hier sollten wir uns ein Beispiel am EntityVillager nehmen. Dieser implementiert das Interface "IMerchant" und erfüllt somit die sechs vorgeschriebenen Methoden. Ich habe mich mal drangesetzt und ein bisschen Detektiv gespielt und konnte wirklich die eine oder andere entschlüsseln. Fakt ist, dass wir im Prinzip nur drei von den sechs Methoden wirklich füllen müssen. Die anderen lassen wir unberührt.
Zunächst einmal ... wie öffnet man überhaupt ein Tausch-Inventar?
Ich lege uns hierzu eine statische Methode in einer Utility-Klasse an, das sieht so aus:
Code: Alles auswählen
  1. public class VillagerTrades {
  2.    public static void openTrade(Player p, MerchantTrade trade) {
  3.       openTrade(p, trade, "Händler");
  4.    }
  5.    
  6.    public static void openTrade(Player p, MerchantTrade trade, String displayName) {
  7.       
  8.    }
  9. }


Der "displayName" ist der Name, der oben im Inventar erscheint. In meiner Grafik ist das "Erze". Ich habe "Händler" als Standard definiert.
Nun müssen wir uns erstmal einen EntityPlayer beschaffen. Das geht über einen Cast zur Bukkit-Implementation ("CraftPlayer") und dann einen Aufruf der getHandle()-Methode. Praktisch schaut das so aus:
Code: Alles auswählen
  1. EntityPlayer nmsPlayer = ((CraftPlayer) p).getHandle();

Dieser EntityPlayer besitzt nun eine openTrade(IMerchant)-Methode. Für die IMerchant-Instanz haben wir nun zwei Möglichkeiten:
  • eine innere anonyme Klasse erzeugen oder
  • eine Klasse erstellen und IMerchant implementieren lassen
Ich werde jetzt letztere Lösung vorstellen, da sie etwas mehr Übersicht und Ordnung gewährt. Meine Klasse sieht wie folgt aus:
Code: Alles auswählen
  1. // Custom IMerchant
  2. public class VirtualMerchant implements IMerchant {
  3.    
  4.    private EntityHuman tradingPlayer;
  5.    private MerchantTrade trade;
  6.    private ChatComponentText displayName;
  7.    
  8.    public VirtualMerchant(EntityPlayer tradingPlayer, MerchantTrade trade, String displayName) {
  9.       this.tradingPlayer = tradingPlayer;
  10.       this.trade = trade;
  11.       this.displayName = new ChatComponentText(displayName);
  12.    }
  13.    @Override
  14.    public void a(MerchantRecipe recipe) {}
  15.    // Bei meinem ersten Test hat es auch funktioniert, als ich diesen Rumpf leer gelassen habe
  16.    // Ich fülle ihn aber, weil es so wahrscheinlich möglich ist, den tauschenden Spieler zu wechseln
  17.    @Override
  18.    public void a_(EntityHuman entityHuman) {
  19.       this.tradingPlayer = entityHuman;
  20.    }
  21.    @Override
  22.    public void a_(ItemStack is) {}
  23.    @Override
  24.    public MerchantRecipeList getOffers(EntityHuman entityHuman) {
  25.       return trade.getRecipeList();
  26.    }
  27.    @Override
  28.    public IChatBaseComponent getScoreboardDisplayName() {
  29.       return displayName;
  30.    }
  31.    @Override
  32.    public EntityHuman u_() {
  33.       return tradingPlayer;
  34.    }
  35. }


Nun müssen wir nur noch eine Instanz davon erzeugen und der openTrade()-Methode übergeben. Das schaut dann so aus:
Code: Alles auswählen
  1.    public static void openTrade(Player p, MerchantTrade trade, String displayName) {
  2.       EntityPlayer nmsPlayer = ((CraftPlayer) p).getHandle();
  3.       nmsPlayer.openTrade(new VirtualMerchant(nmsPlayer, trade, displayName));
  4.    }


Und schon sind wir fertig!

Verwendungsbeispiel

Code: Alles auswählen
  1.          MerchantTrade trade = new MerchantTrade();
  2.          trade.addTrade(new ItemStack(Material.DIAMOND), new ItemStack(Material.GOLD_INGOT, 4));
  3.          trade.addTrade(new ItemStack(Material.DIAMOND), new ItemStack(Material.GOLD_INGOT), new ItemStack(Material.DIAMOND, 2));
  4.          VillagerTrades.openTrade(p, trade, "§3§lVirtuelle Trades");


Das funktioniert jetzt, ohne nochmal explizit auf NMS zugreifen zu müssen.



4 Schlusswort


Dieses Tutorial ist für Minecraft 1.8 konzipiert. Ich arbeite dabei mit einer Spigot-Version.
In älteren Versionen (u.a. Minecraft 1.7) funktioniert diese Anleitung nicht. In neueren Versionen könnte es später ebenfalls zu Komplikationen kommen. Ich bemühe mich jedoch darum, dieses Tutorial immer auf dem neusten Stand zu halten.

Ich hoffe, der eine oder andere hat dabei noch etwas gelernt ;)


Sep2703 / Janhektor
Du möchtest programmieren lernen oder dein Bukkit-/Spigot-Wissen erweitern?
Hier habe ich für dich kostenlose Tutorials: https://youtube.com/janhektor
Benutzeravatar
Sep2703
 
Beiträge: 677
Registriert: Mi 8. Jan 2014, 15:13
Wohnort: 127.0.0.1

Zurück zu Anleitungen

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste

cron