Adds support for villager trading (#1316)

* adds villager trading support
Adds handler for tradeList packet and selectTrade packet
* added extra line at end
* removed tab; removed size and hasSecondItem
removed a mistakenly added tab instead of 4 spaces. SelectTrade was already added in 1.13. Removed unnecessary size and hasSecondItem from trade dataType.
* Added VillagerInfo class and capitalized vars in Trade class
* Update VillagerInfo.cs
* Small formatting/naming adjustments
Co-authored-by: ORelio <ORelio@users.noreply.github.com>
This commit is contained in:
mexiscool 2020-11-08 23:39:07 +01:00 committed by GitHub
parent 2a7f0c7f16
commit 28f47cc532
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 186 additions and 1 deletions

View file

@ -220,7 +220,15 @@ namespace MinecraftClient
/// <param name="locked"></param>
/// <param name="iconcount"></param>
public virtual void OnMapData(int mapid, byte scale, bool trackingposition, bool locked, int iconcount) { }
/// <summary>
/// Called when tradeList is received from server
/// </summary>
/// <param name="windowID">Window ID</param>
/// <param name="trades">List of trades.</param>
/// <param name="villagerInfo">Contains Level, Experience, IsRegularVillager and CanRestock .</param>
public virtual void OnTradeList(int windowID, List<VillagerTrade> trades, VillagerInfo villagerInfo) { }
/// <summary>
/// Called when received a title from the server
/// <param name="action"> 0 = set title, 1 = set subtitle, 3 = set action bar, 4 = set times and display, 4 = hide, 5 = reset</param>
@ -1225,6 +1233,15 @@ namespace MinecraftClient
{
return Handler.UpdateSign(location, line1, line2, line3, line4);
}
/// <summary>
/// Selects villager trade
/// </summary>
/// <param name="selectedSlot">Trade slot to select, starts at 0.</param>
protected bool SelectTrade(int selectedSlot)
{
return Handler.SelectTrade(selectedSlot);
}
/// <summary>
/// Update command block

View file

@ -0,0 +1,13 @@
namespace MinecraftClient.Mapping
{
/// <summary>
/// Properties of a villager
/// </summary>
public class VillagerInfo
{
public int Level { get; set; }
public int Experience { get; set; }
public bool IsRegularVillager { get; set; }
public bool CanRestock { get; set; }
}
}

View file

@ -0,0 +1,33 @@
namespace MinecraftClient.Inventory
{
/// <summary>
/// Represents a trade of a villager
/// </summary>
public class VillagerTrade
{
public Item InputItem1;
public Item OutputItem;
public Item InputItem2;
public bool TradeDisabled;
public int NumberOfTradeUses;
public int MaximumNumberOfTradeUses;
public int Xp;
public int SpecialPrice;
public float PriceMultiplier;
public int Demand;
public VillagerTrade(Item inputItem1, Item outputItem, Item inputItem2, bool tradeDisabled, int numberOfTradeUses, int maximumNumberOfTradeUses, int xp, int specialPrice, float priceMultiplier, int demand)
{
this.InputItem1 = inputItem1;
this.OutputItem = outputItem;
this.InputItem2 = inputItem2;
this.TradeDisabled = tradeDisabled;
this.NumberOfTradeUses = numberOfTradeUses;
this.MaximumNumberOfTradeUses = maximumNumberOfTradeUses;
this.Xp = xp;
this.SpecialPrice = specialPrice;
this.PriceMultiplier = priceMultiplier;
this.Demand = demand;
}
}
}

View file

@ -0,0 +1,13 @@
namespace MinecraftClient
{
/// <summary>
/// Information of a villager
/// </summary>
public class VillagerInfo
{
public int Level { get; set; }
public int Experience { get; set; }
public bool IsRegularVillager { get; set; }
public bool CanRestock { get; set; }
}
}

View file

@ -1449,6 +1449,15 @@ namespace MinecraftClient
// TODO Open sign editor first https://wiki.vg/Protocol#Open_Sign_Editor
return handler.SendUpdateSign(location, line1, line2, line3, line4);
}
/// <summary>
/// Select villager trade
/// </summary>
/// <param name="selectedSlot">The slot of the trade, starts at 0.</param>
public bool SelectTrade(int selectedSlot)
{
return handler.SelectTrade(selectedSlot);
}
/// <summary>
/// Update command block
@ -2246,6 +2255,18 @@ namespace MinecraftClient
DispatchBotEvent(bot => bot.OnEntityMetadata(entity, metadata));
}
}
/// <summary>
/// Called when tradeList is recieved after interacting with villager
/// </summary>
/// <param name="windowID">Window ID</param>
/// <param name="trades">List of trades.</param>
/// <param name="villagerInfo">Contains Level, Experience, IsRegularVillager and CanRestock .</param>
public void OnTradeList(int windowID, List<VillagerTrade> trades, VillagerInfo villagerInfo)
{
DispatchBotEvent(bot => bot.OnTradeList(windowID, trades, villagerInfo));
}
#endregion
}
}

View file

@ -212,8 +212,10 @@
<Compile Include="Protocol\Handlers\SocketWrapper.cs" />
<Compile Include="Protocol\DataTypeGenerator.cs" />
<Compile Include="FileMonitor.cs" />
<Compile Include="Inventory\VillagerTrade.cs" />
<Compile Include="Protocol\ReplayHandler.cs" />
<Compile Include="Translations.cs" />
<Compile Include="Inventory\VillagerInfo.cs" />
<Compile Include="WinAPI\ConsoleIcon.cs" />
<Compile Include="ConsoleIO.cs" />
<Compile Include="Crypto\Streams\BouncyAes\AesFastEngine.cs" />

View file

@ -610,6 +610,29 @@ namespace MinecraftClient.Protocol.Handlers
return data;
}
/// <summary>
/// Read a single villager trade from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The item that was read or NULL for an empty slot</returns>
public VillagerTrade ReadNextTrade(Queue<byte> cache, ItemPalette itemPalette)
{
Item inputItem1 = ReadNextItemSlot(cache, itemPalette);
Item outputItem = ReadNextItemSlot(cache, itemPalette);
Item inputItem2 = null;
if (ReadNextBool(cache)) //check if villager has second item
{
inputItem2 = ReadNextItemSlot(cache, itemPalette);
}
bool tradeDisabled = ReadNextBool(cache);
int numberOfTradeUses = ReadNextInt(cache);
int maximumNumberOfTradeUses = ReadNextInt(cache);
int xp = ReadNextInt(cache);
int specialPrice = ReadNextInt(cache);
float priceMultiplier = ReadNextFloat(cache);
int demand = ReadNextInt(cache);
return new VillagerTrade(inputItem1, outputItem, inputItem2, tradeDisabled, numberOfTradeUses, maximumNumberOfTradeUses, xp, specialPrice, priceMultiplier, demand);
}
/// <summary>
/// Build an uncompressed Named Binary Tag blob for sending over the network
/// </summary>

View file

@ -845,5 +845,10 @@ namespace MinecraftClient.Protocol.Handlers
}
catch { return false; }
}
public bool SelectTrade(int selectedSlot)
{
return false; //MC 1.13+
}
}
}

View file

@ -432,6 +432,28 @@ namespace MinecraftClient.Protocol.Handlers
int iconcount = dataTypes.ReadNextVarInt(packetData);
handler.OnMapData(mapid, scale, trackingposition, locked, iconcount);
break;
case PacketTypesIn.TradeList:
if ((protocolversion >= MC114Version) && (handler.GetInventoryEnabled()))
{
// MC 1.14 or greater
int windowID = dataTypes.ReadNextVarInt(packetData);
int size = dataTypes.ReadNextByte(packetData);
List<VillagerTrade> trades = new List<VillagerTrade>();
for (int tradeId = 0; tradeId < size; tradeId++)
{
VillagerTrade trade = dataTypes.ReadNextTrade(packetData, itemPalette);
trades.Add(trade);
}
VillagerInfo villagerInfo = new VillagerInfo()
{
Level = dataTypes.ReadNextVarInt(packetData),
Experience = dataTypes.ReadNextVarInt(packetData),
IsRegularVillager = dataTypes.ReadNextBool(packetData),
CanRestock = dataTypes.ReadNextBool(packetData)
};
handler.OnTradeList(windowID, trades, villagerInfo);
}
break;
case PacketTypesIn.Title:
if (protocolversion >= MC18Version)
{
@ -1889,5 +1911,24 @@ namespace MinecraftClient.Protocol.Handlers
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
public bool SelectTrade(int selectedSlot)
{
// MC 1.13 or greater
if (protocolversion >= MC113Version)
{
try
{
List<byte> packet = new List<byte>();
packet.AddRange(dataTypes.GetVarInt(selectedSlot));
SendPacket(PacketTypesOut.SelectTrade, packet);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
else { return false; }
}
}
}

View file

@ -229,5 +229,11 @@ namespace MinecraftClient.Protocol
/// <param name="mode">command block mode</param>
/// <param name="flags">command block flags</param>
bool UpdateCommandBlock(Location location, string command, CommandBlockMode mode, CommandBlockFlags flags);
/// <summary>
/// Select villager trade
/// </summary>
/// <param name="selectedSlot">The slot of the trade, starts at 0.</param>
bool SelectTrade(int selectedSlot);
}
}

View file

@ -330,5 +330,16 @@ namespace MinecraftClient.Protocol
/// <param name="objectivename">The name of the objective the score belongs to</param>
/// <param name="value">he score to be displayed next to the entry. Only sent when Action does not equal 1.</param>
void OnUpdateScore(string entityname, byte action, string objectivename, int value);
/// <summary>
/// Called when tradeList is received from server
/// </summary>
/// <param name="windowID">Window ID</param>
/// <param name="trades">List of trades.</param>
/// <param name="villagerLevel">The level the villager is.</param>
/// <param name="experience">The amount of experience the villager has.</param>
/// <param name="isRegularVillager">True if regular villagers and false if the wandering trader.</param>
/// <param name="canRestock">If the villager can restock his trades at a workstation, True for regular villagers and false for the wandering trader.</param>
void OnTradeList(int windowID, List<VillagerTrade> trades, VillagerInfo villagerInfo);
}
}