Modernize data carriers to records and add primary constructors

Convert 14 data carrier classes to records:
- VillagerInfo, MapIcon, EnchantmentData: non-positional records (mutable properties)
- ForgeMod, SkinInfo, VillagerTrade, Node, Response: positional records
- CommandNode: sealed positional record
- CommandArgumentDescriptor: readonly record struct
- ColorRGBA: record struct (multiple constructors preserved)
- RecipeConfig, Recipe, BannerLayer: non-positional records

Add primary constructors to 6 classes:
- DataTypes, Protocol18Terrain, Protocol18Forge, ItemMovingHelper,
  LastSeenMessageList, Acknowledgment, SuggestionTooltip

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-24 01:31:17 +00:00
parent 81b756292e
commit 654a16907b
18 changed files with 68 additions and 231 deletions

View file

@ -123,7 +123,7 @@ namespace MinecraftClient.ChatBots
public enum OnFailConfig { abort, wait }
public class RecipeConfig
public record RecipeConfig
{
public string Name = "Recipe Name";
@ -241,7 +241,7 @@ namespace MinecraftClient.ChatBots
/// <summary>
/// Represent a crafting recipe
/// </summary>
private class Recipe
private record Recipe
{
/// <summary>
/// The results item of this recipe

View file

@ -163,7 +163,7 @@ namespace MinecraftClient
}
}
public class ColorRGBA
public record struct ColorRGBA
{
public byte R { get; set; }
public byte G { get; set; }

View file

@ -2,13 +2,8 @@
namespace MinecraftClient.CommandHandler
{
internal class SuggestionTooltip : IMessage
internal class SuggestionTooltip(string tooltip) : IMessage
{
public SuggestionTooltip(string tooltip)
{
String = tooltip;
}
public string String { get; set; }
public string String { get; set; } = tooltip;
}
}

View file

@ -1,6 +1,6 @@
namespace MinecraftClient.Inventory
{
public class EnchantmentData
public record EnchantmentData
{
public Enchantments TopEnchantment { get; set; }
public Enchantments MiddleEnchantment { get; set; }

View file

@ -7,24 +7,10 @@ namespace MinecraftClient.Inventory
/// <summary>
/// Class that contains useful methods to move item around in a container
/// </summary>
public class ItemMovingHelper
public class ItemMovingHelper(Container c, McClient mc)
{
private readonly Container c;
private readonly McClient mc;
/// <summary>
/// Create a helper that contains useful methods to move item around in container
/// </summary>
/// <param name="c">Source container to use. All method will use this container for handling first slot parameter</param>
/// <param name="mc">McClient handler. Needed for sending WindowAction packet to the server</param>
/// <remarks>
/// If you are using ChatBot API and cannot have direct access to McClient handler, use <see cref="ChatBot.WindowAction(int, int, WindowActionType)"/> as second parameter
/// </remarks>
public ItemMovingHelper(Container c, McClient mc)
{
this.c = c;
this.mc = mc;
}
private readonly Container c = c;
private readonly McClient mc = mc;
/// <summary>
/// Move an item fron source to dest. Source should contain an item and dest slot should be empty

View file

@ -3,7 +3,7 @@
/// <summary>
/// Properties of a villager
/// </summary>
public class VillagerInfo
public record VillagerInfo
{
public int Level { get; set; }
public int Experience { get; set; }

View file

@ -3,31 +3,15 @@
/// <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)
{
InputItem1 = inputItem1;
OutputItem = outputItem;
InputItem2 = inputItem2;
TradeDisabled = tradeDisabled;
NumberOfTradeUses = numberOfTradeUses;
MaximumNumberOfTradeUses = maximumNumberOfTradeUses;
Xp = xp;
SpecialPrice = specialPrice;
PriceMultiplier = priceMultiplier;
Demand = demand;
}
}
public record VillagerTrade(
Item InputItem1,
Item OutputItem,
Item? InputItem2,
bool TradeDisabled,
int NumberOfTradeUses,
int MaximumNumberOfTradeUses,
int Xp,
int SpecialPrice,
float PriceMultiplier,
int Demand);
}

View file

@ -1,11 +1,11 @@
namespace MinecraftClient.Mapping
{
public class MapIcon
public record MapIcon
{
public MapIconType Type { set; get; }
public byte X { set; get; }
public byte Z { set; get; }
public byte Direction { set; get; }
public string? DisplayName { set; get; } = null;
public MapIconType Type { get; set; }
public byte X { get; set; }
public byte Z { get; set; }
public byte Direction { get; set; }
public string? DisplayName { get; set; } = null;
}
}

View file

@ -306,27 +306,9 @@ namespace MinecraftClient.Mapping
/// <summary>
/// Represents a location and its attributes
/// </summary>
public class Node
public record Node(int GScore, int HScore, Location Location)
{
// Distance to start
public int GScore;
// Distance to Goal
public int HScore;
public int FScore
{
get { return HScore + GScore; }
}
public Location Location;
public Node(int gScore, int hScore, Location loc)
{
this.GScore = gScore;
this.HScore = hScore;
Location = loc;
}
public int FScore => HScore + GScore;
}
// List which contains all nodes in form of a Binary Heap

View file

@ -15,21 +15,12 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Handle data types encoding / decoding
/// </summary>
public class DataTypes
public class DataTypes(int protocol)
{
/// <summary>
/// Protocol version for adjusting data types
/// </summary>
private readonly int protocolversion;
/// <summary>
/// Initialize a new DataTypes instance
/// </summary>
/// <param name="protocol">Protocol version</param>
public DataTypes(int protocol)
{
protocolversion = protocol;
}
private readonly int protocolversion = protocol;
/// <summary>
/// Protocol version used to adjust wire encodings.

View file

@ -11,17 +11,8 @@ namespace MinecraftClient.Protocol.Handlers.Forge
/// <summary>
/// Represents an individual forge mod.
/// </summary>
public class ForgeMod
public record ForgeMod(string ModID, string Version)
{
public ForgeMod(String ModID, String Version)
{
this.ModID = ModID;
this.Version = Version;
}
public readonly String ModID;
public readonly String Version;
public override string ToString()
{
return ModID + " v" + Version;

View file

@ -729,54 +729,25 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
ForgeEnum
}
private sealed class CommandNode
private sealed record CommandNode(
byte Flags,
int[] Children,
int RedirectNode = -1,
string? Name = null,
CommandArgumentDescriptor? Argument = null,
string? SuggestionsType = null,
int ParserId = -1)
{
public byte Flags { get; }
public int[] Children { get; }
public int RedirectNode { get; }
public string? Name { get; }
public CommandArgumentDescriptor? Argument { get; }
public string? SuggestionsType { get; }
public int ParserId { get; }
public CommandNodeKind Kind => (CommandNodeKind)(Flags & NodeTypeMask);
public bool IsExecutable => (Flags & NodeExecutableFlag) != 0;
public bool IsRestricted => (Flags & NodeRestrictedFlag) != 0;
public CommandNode(
byte flags,
int[] children,
int redirectNode = -1,
string? name = null,
CommandArgumentDescriptor? argument = null,
string? suggestionsType = null,
int parserId = -1)
{
Flags = flags;
Children = children;
RedirectNode = redirectNode;
Name = name;
Argument = argument;
SuggestionsType = suggestionsType;
ParserId = parserId;
}
}
private readonly struct CommandArgumentDescriptor
{
public string Name { get; }
public ArgumentConsumption Consumption { get; }
public int TokenCount { get; }
public bool IsSigned { get; }
public CommandArgumentDescriptor(string name, ArgumentConsumption consumption, int tokenCount = 1, bool isSigned = false)
{
Name = name;
Consumption = consumption;
TokenCount = tokenCount;
IsSigned = isSigned;
}
}
private readonly record struct CommandArgumentDescriptor(
string Name,
ArgumentConsumption Consumption,
int TokenCount = 1,
bool IsSigned = false);
private readonly struct ArgumentTypeLayout
{

View file

@ -12,32 +12,17 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Handler for the Minecraft Forge protocol
/// </summary>
class Protocol18Forge
class Protocol18Forge(ForgeInfo? forgeInfo, int protocolVersion, DataTypes dataTypes, Protocol18Handler protocol18, IMinecraftComHandler mcHandler)
{
private readonly int protocolversion;
private readonly DataTypes dataTypes;
private readonly Protocol18Handler protocol18;
private readonly IMinecraftComHandler mcHandler;
private readonly int protocolversion = protocolVersion;
private readonly DataTypes dataTypes = dataTypes;
private readonly Protocol18Handler protocol18 = protocol18;
private readonly IMinecraftComHandler mcHandler = mcHandler;
private readonly ForgeInfo? forgeInfo;
private readonly ForgeInfo? forgeInfo = forgeInfo;
private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState.START;
private bool ForgeEnabled() { return forgeInfo is not null; }
/// <summary>
/// Initialize a new Forge protocol handler
/// </summary>
/// <param name="forgeInfo">Forge Server Information</param>
/// <param name="protocolVersion">Minecraft protocol version</param>
/// <param name="dataTypes">Minecraft data types handler</param>
public Protocol18Forge(ForgeInfo? forgeInfo, int protocolVersion, DataTypes dataTypes, Protocol18Handler protocol18, IMinecraftComHandler mcHandler)
{
this.forgeInfo = forgeInfo;
protocolversion = protocolVersion;
this.dataTypes = dataTypes;
this.protocol18 = protocol18;
this.mcHandler = mcHandler;
}
/// <summary>
/// Get Forge-Tagged server address
/// </summary>

View file

@ -12,23 +12,11 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Terrain Decoding handler for Protocol18
/// </summary>
class Protocol18Terrain
class Protocol18Terrain(int protocolVersion, DataTypes dataTypes, IMinecraftComHandler handler)
{
private readonly int protocolversion;
private readonly DataTypes dataTypes;
private readonly IMinecraftComHandler handler;
/// <summary>
/// Initialize a new Terrain Decoder
/// </summary>
/// <param name="protocolVersion">Minecraft Protocol Version</param>
/// <param name="dataTypes">Minecraft Protocol Data Types</param>
public Protocol18Terrain(int protocolVersion, DataTypes dataTypes, IMinecraftComHandler handler)
{
protocolversion = protocolVersion;
this.dataTypes = dataTypes;
this.handler = handler;
}
private readonly int protocolversion = protocolVersion;
private readonly DataTypes dataTypes = dataTypes;
private readonly IMinecraftComHandler handler = handler;
/// <summary>
/// Reading the "Block states" field: consists of 4096 entries, representing all the blocks in the chunk section.

View file

@ -59,7 +59,7 @@ public class BannerPatternsComponent(DataTypes dataTypes, ItemPalette itemPalett
}
}
public class BannerLayer
public record BannerLayer
{
public int PatternType { get; set; }
public string? AssetId { get; set; } = null!;

View file

@ -8,17 +8,12 @@ namespace MinecraftClient.Protocol.Message
/// <summary>
/// A list of messages a client has seen.
/// </summary>
public class LastSeenMessageList
public class LastSeenMessageList(AcknowledgedMessage[] list)
{
public static readonly LastSeenMessageList EMPTY = new(Array.Empty<AcknowledgedMessage>());
public static readonly int MAX_ENTRIES = 5;
public AcknowledgedMessage[] entries;
public LastSeenMessageList(AcknowledgedMessage[] list)
{
entries = list;
}
public AcknowledgedMessage[] entries = list;
public void WriteForSign(List<byte> data)
{
@ -56,16 +51,10 @@ namespace MinecraftClient.Protocol.Message
/// A record of messages acknowledged by a client.
/// This holds the messages the client has recently seen, as well as the last message they received, if any.
/// </summary>
public class Acknowledgment
public class Acknowledgment(LastSeenMessageList lastSeenMessageList, AcknowledgedMessage? lastReceivedMessage)
{
public LastSeenMessageList lastSeen;
public AcknowledgedMessage? lastReceived;
public Acknowledgment(LastSeenMessageList lastSeenMessageList, AcknowledgedMessage? lastReceivedMessage)
{
lastSeen = lastSeenMessageList;
lastReceived = lastReceivedMessage;
}
public LastSeenMessageList lastSeen = lastSeenMessageList;
public AcknowledgedMessage? lastReceived = lastReceivedMessage;
}
}

View file

@ -19,19 +19,7 @@ namespace MinecraftClient.Protocol
/// Information about a players Skin.
/// Empty string if not available.
/// </summary>
public class SkinInfo
{
public readonly string SkinUrl;
public readonly string CapeUrl;
public readonly string SkinModel;
public SkinInfo(string skinUrl = "", string capeUrl = "", string skinModel = "")
{
SkinUrl = skinUrl;
CapeUrl = capeUrl;
SkinModel = skinModel;
}
}
public record SkinInfo(string SkinUrl = "", string CapeUrl = "", string SkinModel = "");
/// <summary>
/// Status of the single Mojang services
@ -254,14 +242,14 @@ namespace MinecraftClient.Protocol
// Can apparently be missing, if no custom skin is set.
if (textureObj.ContainsKey("SKIN"))
{
return new SkinInfo(skinUrl: textureObj["SKIN"]!["url"] is not null ? textureObj["SKIN"]!["url"]!.GetStringValue() : string.Empty,
capeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty,
skinModel: textureObj["SKIN"]!["metadata"] is not null ? "Alex" : "Steve");
return new SkinInfo(SkinUrl: textureObj["SKIN"]!["url"] is not null ? textureObj["SKIN"]!["url"]!.GetStringValue() : string.Empty,
CapeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty,
SkinModel: textureObj["SKIN"]!["metadata"] is not null ? "Alex" : "Steve");
}
else
{
return new SkinInfo(capeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty,
skinModel: DefaultModelAlex(uuid) ? "Alex" : "Steve");
return new SkinInfo(CapeUrl: textureObj.ContainsKey("CAPE") ? textureObj["CAPE"]!["url"]!.GetStringValue() : string.Empty,
SkinModel: DefaultModelAlex(uuid) ? "Alex" : "Steve");
}
}

View file

@ -202,21 +202,8 @@ namespace MinecraftClient.Protocol
/// <summary>
/// Basic HTTP response object.
/// </summary>
public class Response
public record Response(int StatusCode, string Body, NameValueCollection Headers, NameValueCollection Cookies)
{
public int StatusCode;
public string Body;
public NameValueCollection Headers;
public NameValueCollection Cookies;
public Response(int statusCode, string body, NameValueCollection headers, NameValueCollection cookies)
{
StatusCode = statusCode;
Body = body;
Headers = headers;
Cookies = cookies;
}
public static Response Empty() =>
new(204, "", new NameValueCollection(), new NameValueCollection());