mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Implement unified achievements feature: data model, protocol handling, state management, ChatBot API, and /achievement command
Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/9db483a8-4a5f-47b1-a6f4-30b6e39075bd Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com>
This commit is contained in:
parent
6dc42d9bd1
commit
65ef3dde6b
8 changed files with 614 additions and 0 deletions
36
MinecraftClient/Achievement.cs
Normal file
36
MinecraftClient/Achievement.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace MinecraftClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type of an achievement or advancement.
|
||||||
|
/// </summary>
|
||||||
|
public enum AchievementType
|
||||||
|
{
|
||||||
|
Task,
|
||||||
|
Challenge,
|
||||||
|
Goal,
|
||||||
|
Legacy
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Minecraft achievement (pre-1.12) or advancement (1.12+).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Id">Resource identifier, e.g. "minecraft:story/root" or "achievement.openInventory"</param>
|
||||||
|
/// <param name="Title">Display title (null for legacy achievements without display info)</param>
|
||||||
|
/// <param name="Description">Display description (null for legacy achievements without display info)</param>
|
||||||
|
/// <param name="Type">The frame type / achievement category</param>
|
||||||
|
/// <param name="IsHidden">Whether this advancement is hidden in the UI</param>
|
||||||
|
/// <param name="IsCompleted">Whether all requirements have been met</param>
|
||||||
|
/// <param name="Requirements">OR-groups of criterion names; all groups must be satisfied</param>
|
||||||
|
/// <param name="CriteriaProgress">Per-criterion completion status</param>
|
||||||
|
public record Achievement(
|
||||||
|
string Id,
|
||||||
|
string? Title,
|
||||||
|
string? Description,
|
||||||
|
AchievementType Type,
|
||||||
|
bool IsHidden,
|
||||||
|
bool IsCompleted,
|
||||||
|
IReadOnlyList<IReadOnlyList<string>> Requirements,
|
||||||
|
IReadOnlyDictionary<string, bool> CriteriaProgress);
|
||||||
|
}
|
||||||
106
MinecraftClient/Commands/AchievementCommand.cs
Normal file
106
MinecraftClient/Commands/AchievementCommand.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Brigadier.NET;
|
||||||
|
using Brigadier.NET.Builder;
|
||||||
|
using MinecraftClient.CommandHandler;
|
||||||
|
|
||||||
|
namespace MinecraftClient.Commands
|
||||||
|
{
|
||||||
|
public class AchievementCommand : Command
|
||||||
|
{
|
||||||
|
public override string CmdName => "achievement";
|
||||||
|
public override string CmdUsage => "achievement <list|locked|unlocked>";
|
||||||
|
public override string CmdDesc => Translations.cmd_achievement_desc;
|
||||||
|
|
||||||
|
public override void RegisterCommand(CommandDispatcher<CmdResult> dispatcher)
|
||||||
|
{
|
||||||
|
dispatcher.Register(l => l.Literal("help")
|
||||||
|
.Then(l => l.Literal(CmdName)
|
||||||
|
.Executes(r => GetUsage(r.Source, string.Empty))
|
||||||
|
.Then(l => l.Literal("list")
|
||||||
|
.Executes(r => GetUsage(r.Source, "list")))
|
||||||
|
.Then(l => l.Literal("locked")
|
||||||
|
.Executes(r => GetUsage(r.Source, "locked")))
|
||||||
|
.Then(l => l.Literal("unlocked")
|
||||||
|
.Executes(r => GetUsage(r.Source, "unlocked")))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatcher.Register(l => l.Literal(CmdName)
|
||||||
|
.Executes(r => ListAchievements(r.Source, null))
|
||||||
|
.Then(l => l.Literal("list")
|
||||||
|
.Executes(r => ListAchievements(r.Source, null)))
|
||||||
|
.Then(l => l.Literal("locked")
|
||||||
|
.Executes(r => ListAchievements(r.Source, false)))
|
||||||
|
.Then(l => l.Literal("unlocked")
|
||||||
|
.Executes(r => ListAchievements(r.Source, true)))
|
||||||
|
.Then(l => l.Literal("_help")
|
||||||
|
.Executes(r => GetUsage(r.Source, string.Empty))
|
||||||
|
.Redirect(dispatcher.GetRoot().GetChild("help").GetChild(CmdName)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetUsage(CmdResult r, string? cmd)
|
||||||
|
{
|
||||||
|
return r.SetAndReturn(cmd switch
|
||||||
|
{
|
||||||
|
#pragma warning disable format
|
||||||
|
"list" => GetCmdDescTranslated(),
|
||||||
|
"locked" => GetCmdDescTranslated(),
|
||||||
|
"unlocked" => GetCmdDescTranslated(),
|
||||||
|
_ => GetCmdDescTranslated(),
|
||||||
|
#pragma warning restore format
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="completed">null = all, true = unlocked only, false = locked only</param>
|
||||||
|
private static int ListAchievements(CmdResult r, bool? completed)
|
||||||
|
{
|
||||||
|
McClient handler = CmdResult.currentHandler!;
|
||||||
|
|
||||||
|
Achievement[] items = completed switch
|
||||||
|
{
|
||||||
|
true => handler.GetUnlockedAchievements(),
|
||||||
|
false => handler.GetLockedAchievements(),
|
||||||
|
null => handler.GetAchievements()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (items.Length == 0)
|
||||||
|
{
|
||||||
|
string msg = completed switch
|
||||||
|
{
|
||||||
|
true => Translations.cmd_achievement_none_unlocked,
|
||||||
|
false => Translations.cmd_achievement_none_locked,
|
||||||
|
_ => Translations.cmd_achievement_none
|
||||||
|
};
|
||||||
|
return r.SetAndReturn(CmdResult.Status.Done, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
string header = completed switch
|
||||||
|
{
|
||||||
|
true => Translations.cmd_achievement_header_unlocked,
|
||||||
|
false => Translations.cmd_achievement_header_locked,
|
||||||
|
_ => Translations.cmd_achievement_header
|
||||||
|
};
|
||||||
|
|
||||||
|
StringBuilder sb = new();
|
||||||
|
sb.AppendLine(header);
|
||||||
|
|
||||||
|
foreach (Achievement a in items.OrderBy(static a => a.Id))
|
||||||
|
{
|
||||||
|
string status = a.IsCompleted
|
||||||
|
? Translations.cmd_achievement_done
|
||||||
|
: Translations.cmd_achievement_todo;
|
||||||
|
|
||||||
|
string display = a.Title is not null
|
||||||
|
? string.Format(Translations.cmd_achievement_entry_titled, status, a.Title, a.Id, a.Type)
|
||||||
|
: string.Format(Translations.cmd_achievement_entry, status, a.Id, a.Type);
|
||||||
|
|
||||||
|
sb.AppendLine(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Log.Info(sb.ToString().TrimEnd());
|
||||||
|
return r.SetAndReturn(CmdResult.Status.Done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,11 +45,14 @@ namespace MinecraftClient
|
||||||
private readonly Queue<Action> threadTasks = new();
|
private readonly Queue<Action> threadTasks = new();
|
||||||
private readonly Lock threadTasksLock = new();
|
private readonly Lock threadTasksLock = new();
|
||||||
private readonly Lock recipeBookLock = new();
|
private readonly Lock recipeBookLock = new();
|
||||||
|
private readonly Lock achievementsLock = new();
|
||||||
|
|
||||||
private readonly List<ChatBot> bots = new();
|
private readonly List<ChatBot> bots = new();
|
||||||
private static readonly List<ChatBot> botsOnHold = new();
|
private static readonly List<ChatBot> botsOnHold = new();
|
||||||
private static readonly Dictionary<int, Container> inventories = new();
|
private static readonly Dictionary<int, Container> inventories = new();
|
||||||
private readonly Dictionary<string, RecipeBookRecipeEntry> unlockedRecipes = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, RecipeBookRecipeEntry> unlockedRecipes = new(StringComparer.Ordinal);
|
||||||
|
private readonly Dictionary<string, Achievement> achievements = new(StringComparer.Ordinal);
|
||||||
|
private string? activeAdvancementTab;
|
||||||
|
|
||||||
private readonly Dictionary<string, List<ChatBot>> registeredBotPluginChannels = new();
|
private readonly Dictionary<string, List<ChatBot>> registeredBotPluginChannels = new();
|
||||||
private readonly List<string> registeredServerPluginChannels = new();
|
private readonly List<string> registeredServerPluginChannels = new();
|
||||||
|
|
@ -1353,6 +1356,42 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all achievements/advancements known to the client.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of all achievements</returns>
|
||||||
|
public Achievement[] GetAchievements()
|
||||||
|
{
|
||||||
|
lock (achievementsLock)
|
||||||
|
{
|
||||||
|
return [.. achievements.Values];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get only completed achievements/advancements.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of completed achievements</returns>
|
||||||
|
public Achievement[] GetUnlockedAchievements()
|
||||||
|
{
|
||||||
|
lock (achievementsLock)
|
||||||
|
{
|
||||||
|
return achievements.Values.Where(static a => a.IsCompleted).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get only incomplete achievements/advancements.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of locked achievements</returns>
|
||||||
|
public Achievement[] GetLockedAchievements()
|
||||||
|
{
|
||||||
|
lock (achievementsLock)
|
||||||
|
{
|
||||||
|
return achievements.Values.Where(static a => !a.IsCompleted).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all Entities
|
/// Get all Entities
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -4139,6 +4178,67 @@ namespace MinecraftClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnAchievementsUpdate(IReadOnlyList<Achievement> added, IReadOnlyList<string> removedIds, bool reset)
|
||||||
|
{
|
||||||
|
lock (achievementsLock)
|
||||||
|
{
|
||||||
|
if (reset)
|
||||||
|
achievements.Clear();
|
||||||
|
|
||||||
|
// Remove entries
|
||||||
|
foreach (string id in removedIds)
|
||||||
|
achievements.Remove(id);
|
||||||
|
|
||||||
|
// Add/update entries. For progress-only updates (no definition),
|
||||||
|
// merge with existing definition if available.
|
||||||
|
foreach (Achievement entry in added)
|
||||||
|
{
|
||||||
|
if (entry.Title is null && achievements.TryGetValue(entry.Id, out Achievement? existing))
|
||||||
|
{
|
||||||
|
// Progress-only update - merge with existing definition
|
||||||
|
bool isCompleted = ComputeAchievementCompleted(existing.Requirements, entry.CriteriaProgress);
|
||||||
|
achievements[entry.Id] = existing with { IsCompleted = isCompleted, CriteriaProgress = entry.CriteriaProgress };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
achievements[entry.Id] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchBotEvent(bot => bot.OnAchievementUpdate(added, removedIds, reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSelectAdvancementTab(string? tabId)
|
||||||
|
{
|
||||||
|
activeAdvancementTab = tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute whether an achievement is completed based on AND-of-ORs requirements.
|
||||||
|
/// </summary>
|
||||||
|
private static bool ComputeAchievementCompleted(IReadOnlyList<IReadOnlyList<string>> requirements, IReadOnlyDictionary<string, bool> criteria)
|
||||||
|
{
|
||||||
|
if (requirements.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (IReadOnlyList<string> group in requirements)
|
||||||
|
{
|
||||||
|
bool groupSatisfied = false;
|
||||||
|
foreach (string criterion in group)
|
||||||
|
{
|
||||||
|
if (criteria.TryGetValue(criterion, out bool done) && done)
|
||||||
|
{
|
||||||
|
groupSatisfied = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!groupSatisfied)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a click container button packet to the server.
|
/// Send a click container button packet to the server.
|
||||||
/// Used for Enchanting table, Lectern, stone cutter and loom
|
/// Used for Enchanting table, Lectern, stone cutter and loom
|
||||||
|
|
|
||||||
|
|
@ -3132,6 +3132,14 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
case PacketTypesIn.RecipeBookSettings:
|
case PacketTypesIn.RecipeBookSettings:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PacketTypesIn.Advancements:
|
||||||
|
HandleAdvancements(packetData);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PacketTypesIn.SelectAdvancementTab:
|
||||||
|
HandleSelectAdvancementTab(packetData);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false; //Ignored packet
|
return false; //Ignored packet
|
||||||
}
|
}
|
||||||
|
|
@ -3139,6 +3147,189 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
return true; //Packet processed
|
return true; //Packet processed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the Advancements packet (1.12+).
|
||||||
|
/// Also handles the Statistics packet for pre-1.12 legacy achievements.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleAdvancements(Queue<byte> packetData)
|
||||||
|
{
|
||||||
|
bool reset = dataTypes.ReadNextBool(packetData);
|
||||||
|
|
||||||
|
// --- Added advancements ---
|
||||||
|
int addedCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var added = new List<Achievement>(addedCount);
|
||||||
|
var addedDefinitions = new Dictionary<string, (string? title, string? description, AchievementType type, bool isHidden, List<List<string>> requirements)>(addedCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < addedCount; i++)
|
||||||
|
{
|
||||||
|
string id = dataTypes.ReadNextString(packetData);
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
bool hasParent = dataTypes.ReadNextBool(packetData);
|
||||||
|
if (hasParent)
|
||||||
|
dataTypes.ReadNextString(packetData); // parentId - read and discard
|
||||||
|
|
||||||
|
// Display
|
||||||
|
string? title = null;
|
||||||
|
string? description = null;
|
||||||
|
var type = AchievementType.Task;
|
||||||
|
bool isHidden = false;
|
||||||
|
|
||||||
|
bool hasDisplay = dataTypes.ReadNextBool(packetData);
|
||||||
|
if (hasDisplay)
|
||||||
|
{
|
||||||
|
title = dataTypes.ReadNextChat(packetData);
|
||||||
|
description = dataTypes.ReadNextChat(packetData);
|
||||||
|
dataTypes.ReadNextItemSlot(packetData, itemPalette); // icon - read and discard
|
||||||
|
|
||||||
|
int frameType = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
type = frameType switch
|
||||||
|
{
|
||||||
|
1 => AchievementType.Challenge,
|
||||||
|
2 => AchievementType.Goal,
|
||||||
|
_ => AchievementType.Task
|
||||||
|
};
|
||||||
|
|
||||||
|
int flags = dataTypes.ReadNextInt(packetData);
|
||||||
|
isHidden = (flags & 0x04) != 0;
|
||||||
|
if ((flags & 0x01) != 0)
|
||||||
|
dataTypes.ReadNextString(packetData); // background texture - read and discard
|
||||||
|
|
||||||
|
dataTypes.ReadNextFloat(packetData); // x
|
||||||
|
dataTypes.ReadNextFloat(packetData); // y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criteria and requirements differ by version
|
||||||
|
var requirements = new List<List<string>>();
|
||||||
|
|
||||||
|
if (protocolVersion < MC_1_20_6_Version)
|
||||||
|
{
|
||||||
|
// Builder-based: criteria names list, then requirements
|
||||||
|
int criteriaCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
for (int c = 0; c < criteriaCount; c++)
|
||||||
|
dataTypes.ReadNextString(packetData); // criterion name only, no trigger data
|
||||||
|
|
||||||
|
int reqGroupCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
for (int g = 0; g < reqGroupCount; g++)
|
||||||
|
{
|
||||||
|
int groupSize = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var group = new List<string>(groupSize);
|
||||||
|
for (int s = 0; s < groupSize; s++)
|
||||||
|
group.Add(dataTypes.ReadNextString(packetData));
|
||||||
|
requirements.Add(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// AdvancementHolder-based (1.20.6+): requirements only, then sendsTelemetryEvent
|
||||||
|
int reqGroupCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
for (int g = 0; g < reqGroupCount; g++)
|
||||||
|
{
|
||||||
|
int groupSize = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var group = new List<string>(groupSize);
|
||||||
|
for (int s = 0; s < groupSize; s++)
|
||||||
|
group.Add(dataTypes.ReadNextString(packetData));
|
||||||
|
requirements.Add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTypes.ReadNextBool(packetData); // sendsTelemetryEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
addedDefinitions[id] = (title, description, type, isHidden, requirements);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Removed advancement IDs ---
|
||||||
|
int removedCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var removedIds = new List<string>(removedCount);
|
||||||
|
for (int i = 0; i < removedCount; i++)
|
||||||
|
removedIds.Add(dataTypes.ReadNextString(packetData));
|
||||||
|
|
||||||
|
// --- Progress updates ---
|
||||||
|
int progressCount = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var progressMap = new Dictionary<string, Dictionary<string, bool>>(progressCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < progressCount; i++)
|
||||||
|
{
|
||||||
|
string id = dataTypes.ReadNextString(packetData);
|
||||||
|
int criteriaEntries = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
var criteria = new Dictionary<string, bool>(criteriaEntries);
|
||||||
|
|
||||||
|
for (int c = 0; c < criteriaEntries; c++)
|
||||||
|
{
|
||||||
|
string criterionName = dataTypes.ReadNextString(packetData);
|
||||||
|
bool isDone = dataTypes.ReadNextBool(packetData);
|
||||||
|
if (isDone)
|
||||||
|
dataTypes.ReadNextLong(packetData); // epochMs - read and discard
|
||||||
|
criteria[criterionName] = isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressMap[id] = criteria;
|
||||||
|
}
|
||||||
|
|
||||||
|
// showAdvancements boolean added in 1.21.11+
|
||||||
|
if (protocolVersion >= MC_1_21_11_Version)
|
||||||
|
dataTypes.ReadNextBool(packetData); // showAdvancements - read and discard
|
||||||
|
|
||||||
|
// Build Achievement records from definitions + progress
|
||||||
|
foreach (var (id, def) in addedDefinitions)
|
||||||
|
{
|
||||||
|
progressMap.TryGetValue(id, out var criteria);
|
||||||
|
criteria ??= new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
bool isCompleted = ComputeAdvancementCompleted(def.requirements, criteria);
|
||||||
|
|
||||||
|
var readOnlyReqs = def.requirements.ConvertAll<IReadOnlyList<string>>(static g => g.AsReadOnly());
|
||||||
|
added.Add(new Achievement(id, def.title, def.description, def.type, def.isHidden, isCompleted, readOnlyReqs.AsReadOnly(), criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also build Achievement records for progress-only updates (no definition change)
|
||||||
|
var progressOnly = new List<Achievement>();
|
||||||
|
foreach (var (id, criteria) in progressMap)
|
||||||
|
{
|
||||||
|
if (!addedDefinitions.ContainsKey(id))
|
||||||
|
progressOnly.Add(new Achievement(id, null, null, AchievementType.Task, false, false, [], criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.OnAchievementsUpdate([.. added, .. progressOnly], removedIds, reset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute whether an advancement is completed based on AND-of-ORs requirements.
|
||||||
|
/// </summary>
|
||||||
|
private static bool ComputeAdvancementCompleted(List<List<string>> requirements, Dictionary<string, bool> criteria)
|
||||||
|
{
|
||||||
|
// Zero requirements = automatically done
|
||||||
|
if (requirements.Count == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Each OR-group must have at least one satisfied criterion
|
||||||
|
foreach (var group in requirements)
|
||||||
|
{
|
||||||
|
bool groupSatisfied = false;
|
||||||
|
foreach (string criterion in group)
|
||||||
|
{
|
||||||
|
if (criteria.TryGetValue(criterion, out bool done) && done)
|
||||||
|
{
|
||||||
|
groupSatisfied = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!groupSatisfied)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle the SelectAdvancementTab packet.
|
||||||
|
/// </summary>
|
||||||
|
private void HandleSelectAdvancementTab(Queue<byte> packetData)
|
||||||
|
{
|
||||||
|
bool hasTab = dataTypes.ReadNextBool(packetData);
|
||||||
|
string? tabId = hasTab ? dataTypes.ReadNextString(packetData) : null;
|
||||||
|
handler.OnSelectAdvancementTab(tabId);
|
||||||
|
}
|
||||||
|
|
||||||
private void HandleUnlockRecipes(Queue<byte> packetData)
|
private void HandleUnlockRecipes(Queue<byte> packetData)
|
||||||
{
|
{
|
||||||
int action = dataTypes.ReadNextVarInt(packetData);
|
int action = dataTypes.ReadNextVarInt(packetData);
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,20 @@ namespace MinecraftClient.Protocol
|
||||||
/// <param name="recipeIds">Recipe identifiers to remove</param>
|
/// <param name="recipeIds">Recipe identifiers to remove</param>
|
||||||
public void OnRecipeBookRemove(string[] recipeIds);
|
public void OnRecipeBookRemove(string[] recipeIds);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when achievement/advancement data is received from the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="added">Achievements that were added or updated</param>
|
||||||
|
/// <param name="removedIds">IDs of achievements that were removed</param>
|
||||||
|
/// <param name="reset">True if all existing state should be cleared before applying</param>
|
||||||
|
public void OnAchievementsUpdate(IReadOnlyList<Achievement> added, IReadOnlyList<string> removedIds, bool reset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the server selects an advancement tab.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tabId">The tab identifier, or null if no tab is selected</param>
|
||||||
|
public void OnSelectAdvancementTab(string? tabId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a click container button packet to the server.
|
/// Send a click container button packet to the server.
|
||||||
/// Used for Enchanting table, Lectern, stone cutter and loom
|
/// Used for Enchanting table, Lectern, stone cutter and loom
|
||||||
|
|
|
||||||
|
|
@ -7174,5 +7174,104 @@ namespace MinecraftClient {
|
||||||
return ResourceManager.GetString("cmd.minimap.position_set", resourceCulture);
|
return ResourceManager.GetString("cmd.minimap.position_set", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to list achievements/advancements from the server..
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_desc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.desc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No achievements/advancements received yet..
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_none {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.none", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No completed achievements/advancements..
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_none_unlocked {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.none_unlocked", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to No incomplete achievements/advancements..
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_none_locked {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.none_locked", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Achievements/Advancements:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_header {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.header", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Completed achievements/advancements:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_header_unlocked {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.header_unlocked", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Incomplete achievements/advancements:.
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_header_locked {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.header_locked", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to [DONE].
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_done {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.done", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to [TODO].
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_todo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.todo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to {0} {1} ({2}) [{3}].
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_entry_titled {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.entry_titled", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to {0} {1} [{2}].
|
||||||
|
/// </summary>
|
||||||
|
internal static string cmd_achievement_entry {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("cmd.achievement.entry", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2527,4 +2527,37 @@ see item details.</value>
|
||||||
<data name="cmd.minimap.position_set" xml:space="preserve">
|
<data name="cmd.minimap.position_set" xml:space="preserve">
|
||||||
<value>Minimap position set to: {0}</value>
|
<value>Minimap position set to: {0}</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="cmd.achievement.desc" xml:space="preserve">
|
||||||
|
<value>list achievements/advancements from the server.</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.none" xml:space="preserve">
|
||||||
|
<value>No achievements/advancements received yet.</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.none_unlocked" xml:space="preserve">
|
||||||
|
<value>No completed achievements/advancements.</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.none_locked" xml:space="preserve">
|
||||||
|
<value>No incomplete achievements/advancements.</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.header" xml:space="preserve">
|
||||||
|
<value>Achievements/Advancements:</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.header_unlocked" xml:space="preserve">
|
||||||
|
<value>Completed achievements/advancements:</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.header_locked" xml:space="preserve">
|
||||||
|
<value>Incomplete achievements/advancements:</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.done" xml:space="preserve">
|
||||||
|
<value>[DONE]</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.todo" xml:space="preserve">
|
||||||
|
<value>[TODO]</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.entry_titled" xml:space="preserve">
|
||||||
|
<value>{0} {1} ({2}) [{3}]</value>
|
||||||
|
</data>
|
||||||
|
<data name="cmd.achievement.entry" xml:space="preserve">
|
||||||
|
<value>{0} {1} [{2}]</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,14 @@ namespace MinecraftClient.Scripting
|
||||||
/// <param name="block">The block</param>
|
/// <param name="block">The block</param>
|
||||||
public virtual void OnBlockChange(Location location, Block block) { }
|
public virtual void OnBlockChange(Location location, Block block) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when achievement/advancement data is updated.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="updated">Achievements that were added or updated</param>
|
||||||
|
/// <param name="removedIds">IDs of achievements that were removed</param>
|
||||||
|
/// <param name="reset">Whether the achievement state was fully reset before this update</param>
|
||||||
|
public virtual void OnAchievementUpdate(IReadOnlyList<Achievement> updated, IReadOnlyList<string> removedIds, bool reset) { }
|
||||||
|
|
||||||
/* =================================================================== */
|
/* =================================================================== */
|
||||||
/* ToolBox - Methods below might be useful while creating your bot. */
|
/* ToolBox - Methods below might be useful while creating your bot. */
|
||||||
/* You should not need to interact with other classes of the program. */
|
/* You should not need to interact with other classes of the program. */
|
||||||
|
|
@ -1120,6 +1128,33 @@ namespace MinecraftClient.Scripting
|
||||||
return Handler.GetEntities();
|
return Handler.GetEntities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all achievements/advancements.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of all achievements</returns>
|
||||||
|
protected Achievement[] GetAchievements()
|
||||||
|
{
|
||||||
|
return Handler.GetAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get only completed achievements/advancements.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of unlocked achievements</returns>
|
||||||
|
protected Achievement[] GetUnlockedAchievements()
|
||||||
|
{
|
||||||
|
return Handler.GetUnlockedAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get only incomplete achievements/advancements.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Snapshot of locked achievements</returns>
|
||||||
|
protected Achievement[] GetLockedAchievements()
|
||||||
|
{
|
||||||
|
return Handler.GetLockedAchievements();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all players Latency
|
/// Get all players Latency
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue