Merge branch 'master' of github.com:MCCTeam/Minecraft-Console-Client into fix/use-item

This commit is contained in:
Anon 2026-03-27 16:46:29 +01:00
commit 0a9948a92a
19 changed files with 845 additions and 128 deletions

View file

@ -0,0 +1,67 @@
using System.Linq;
using System.Text;
using Brigadier.NET;
using Brigadier.NET.Builder;
using MinecraftClient.CommandHandler;
namespace MinecraftClient.Commands
{
public class EffectsCommand : Command
{
public override string CmdName { get { return "effects"; } }
public override string CmdUsage { get { return "effects"; } }
public override string CmdDesc { get { return Translations.cmd_effects_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))
)
);
dispatcher.Register(l => l.Literal(CmdName)
.Executes(r => ShowEffects(r.Source))
.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 // @formatter:off
_ => GetCmdDescTranslated(),
#pragma warning restore format // @formatter:on
});
}
private int ShowEffects(CmdResult r)
{
McClient handler = CmdResult.currentHandler!;
if (!handler.GetEntityHandlingEnabled())
return r.SetAndReturn(CmdResult.Status.FailNeedEntity);
var effects = handler.GetPlayerEffects()
.Values
.Where(effectData => !effectData.IsExpired)
.OrderBy(effectData => effectData.Effect)
.ToArray();
if (effects.Length == 0)
return r.SetAndReturn(CmdResult.Status.Done, Translations.cmd_effects_none);
StringBuilder response = new();
response.AppendLine(Translations.cmd_effects_header);
foreach (var effectData in effects)
{
response.AppendLine(string.Format(Translations.cmd_effects_entry,
effectData.GetDisplayName(), effectData.GetRemainingDurationText()));
}
return r.SetAndReturn(CmdResult.Status.Done, response.ToString().TrimEnd());
}
}
}

View file

@ -0,0 +1,194 @@
namespace MinecraftClient.Inventory;
using System;
using System.Collections.Generic;
using System.Linq;
using MinecraftClient.Protocol;
using MinecraftClient.Protocol.Message;
/// <summary>
/// Represents an active status effect on an entity
/// </summary>
public class EffectData
{
/// <summary>
/// The type of effect
/// </summary>
public Effects Effect { get; set; }
/// <summary>
/// Effect amplifier (level - 1, e.g., 0 = level I, 1 = level II)
/// </summary>
public int Amplifier { get; set; }
/// <summary>
/// Duration in ticks (20 ticks = 1 second). -1 for infinite.
/// </summary>
public int Duration { get; set; }
/// <summary>
/// Effect flags (ambient, show particles, show icon)
/// </summary>
public byte Flags { get; set; }
/// <summary>
/// Time when the effect was applied
/// </summary>
public DateTime StartTime { get; set; }
public EffectData(Effects effect, int amplifier, int duration, byte flags)
{
Effect = effect;
Amplifier = amplifier;
Duration = duration;
Flags = flags;
StartTime = DateTime.UtcNow;
}
/// <summary>
/// Check if this is an infinite duration effect
/// </summary>
public bool IsInfinite => Duration == -1 || Duration == int.MaxValue;
/// <summary>
/// Check if the effect has expired
/// </summary>
public bool IsExpired
{
get
{
if (IsInfinite) return false;
return GetElapsedTicks() >= Duration;
}
}
/// <summary>
/// Get remaining duration in ticks
/// </summary>
public int RemainingTicks
{
get
{
if (IsInfinite) return -1;
return Math.Max(0, Duration - GetElapsedTicks());
}
}
/// <summary>
/// Get remaining duration in seconds
/// </summary>
public int RemainingSeconds
{
get
{
if (IsInfinite) return -1;
return (RemainingTicks + 19) / 20;
}
}
/// <summary>
/// Get the translated effect name from Minecraft translations
/// </summary>
public string GetTranslatedName()
{
var key = $"effect.minecraft.{Effect.ToString().ToUnderscoreCase()}";
var translated = ChatParser.TranslateString(key);
return string.IsNullOrEmpty(translated) ? Effect.ToString() : translated;
}
/// <summary>
/// Get the translated effect name with level when applicable
/// </summary>
public string GetDisplayName()
{
string translatedName = GetTranslatedName();
if (Amplifier <= 0)
return translatedName;
return string.Format(Translations.effect_name_with_amplifier, translatedName,
EnchantmentMapping.ConvertLevelToRomanNumbers(Amplifier + 1));
}
/// <summary>
/// Get the translated effect name prefixed with the best-fit indefinite article
/// </summary>
public string GetDisplayNameWithArticle()
{
string displayName = GetDisplayName();
char? firstLetter = displayName
.TrimStart()
.FirstOrDefault(char.IsLetter);
if (firstLetter is null)
return displayName;
string article = "AEIOUaeiou".Contains(firstLetter.Value)
? Translations.effect_article_an
: Translations.effect_article_a;
return $"{article} {displayName}";
}
/// <summary>
/// Get the configured short duration label for the remaining time
/// </summary>
public string GetRemainingDurationText()
{
return FormatShortDuration(RemainingSeconds);
}
/// <summary>
/// Get the configured short duration label for the initial effect duration
/// </summary>
public string GetInitialDurationText()
{
if (IsInfinite)
return Translations.effect_duration_unlimited;
int durationSeconds = (Duration + 19) / 20;
return FormatShortDuration(durationSeconds);
}
/// <summary>
/// Format a duration for compact UI output
/// </summary>
/// <param name="seconds">Duration in seconds, -1 for unlimited</param>
public static string FormatShortDuration(int seconds)
{
if (seconds < 0)
return Translations.effect_duration_short_unlimited;
if (seconds < 60)
return string.Format(Translations.effect_duration_short_seconds, seconds);
int minutes = seconds / 60;
int remainingSeconds = seconds % 60;
if (seconds < 3600)
{
return remainingSeconds == 0
? string.Format(Translations.effect_duration_short_minutes, minutes)
: string.Format(Translations.effect_duration_short_minutes_seconds, minutes, remainingSeconds);
}
int hours = seconds / 3600;
int remainingMinutes = (seconds % 3600) / 60;
return remainingMinutes == 0
? string.Format(Translations.effect_duration_short_hours, hours)
: string.Format(Translations.effect_duration_short_hours_minutes, hours, remainingMinutes);
}
private int GetElapsedTicks()
{
return (int)((DateTime.UtcNow - StartTime).TotalMilliseconds / 50);
}
}
/// <summary>
/// Extension method for converting PascalCase to snake_case
/// </summary>
public static class StringExtensions
{
public static string ToUnderscoreCase(this string str)
{
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
}
}

View file

@ -99,6 +99,11 @@ namespace MinecraftClient.Mapping
/// </summary>
public Dictionary<int, Item> Equipment;
/// <summary>
/// Active status effects on this entity
/// </summary>
public Dictionary<Effects, EffectData> ActiveEffects { get; private set; }
/// <summary>
/// Create a new entity based on Entity ID, Entity Type and location
/// </summary>
@ -112,6 +117,7 @@ namespace MinecraftClient.Mapping
Location = location;
Health = 1.0f;
Equipment = new Dictionary<int, Item>();
ActiveEffects = new Dictionary<Effects, EffectData>();
Item = new Item(ItemType.Air, 0, null);
}
@ -128,6 +134,7 @@ namespace MinecraftClient.Mapping
Location = location;
Health = 1.0f;
Equipment = new Dictionary<int, Item>();
ActiveEffects = new Dictionary<Effects, EffectData>();
Item = new Item(ItemType.Air, 0, null);
Yaw = yaw * (1F / 256) * 360; // to angle in 360 degree
Pitch = pitch * (1F / 256) * 360;
@ -151,6 +158,7 @@ namespace MinecraftClient.Mapping
Name = name;
Health = 1.0f;
Equipment = new Dictionary<int, Item>();
ActiveEffects = new Dictionary<Effects, EffectData>();
Item = new Item(ItemType.Air, 0, null);
Yaw = yaw * (1F / 256) * 360; // to angle in 360 degree
Pitch = pitch * (1F / 256) * 360;

View file

@ -102,6 +102,9 @@ namespace MinecraftClient
private int playerLevel;
private int playerTotalExperience;
private byte CurrentSlot = 0;
// player effects
private readonly Dictionary<Effects, EffectData> playerEffects = new();
// Sneaking
public bool IsSneaking { get; set; } = false;
@ -141,6 +144,16 @@ namespace MinecraftClient
public bool GetIsSupportPreviewsChat() { return isSupportPreviewsChat; }
public float GetHealth() { return playerHealth; }
public int GetSaturation() { return playerFoodSaturation; }
/// <summary>
/// Get the player's active effects
/// </summary>
/// <returns>Dictionary of active effects</returns>
public Dictionary<Effects, EffectData> GetPlayerEffects()
{
return new Dictionary<Effects, EffectData>(playerEffects);
}
public int GetLevel() { return playerLevel; }
public int GetTotalExperience() { return playerTotalExperience; }
public byte GetCurrentSlot() { return CurrentSlot; }
@ -616,6 +629,26 @@ namespace MinecraftClient
SendRespawnPacket();
}
// Check for expired effects
if (playerEffects.Count > 0)
{
var expiredEffects = playerEffects
.Where(e => e.Value.IsExpired)
.Select(e => e.Key)
.ToList();
foreach (var effect in expiredEffects)
{
if (!playerEffects.Remove(effect, out var effectData))
continue;
ConsoleIO.WriteLine(string.Format(Translations.bot_effect_expired, effectData.GetDisplayName()));
if (entities.TryGetValue(playerEntityID, out var playerEntity))
playerEntity.ActiveEffects.Remove(effect);
}
}
lock (threadTasksLock)
{
while (threadTasks.Count > 0)
@ -3394,8 +3427,61 @@ namespace MinecraftClient
/// </summary>
public void OnEntityEffect(int entityid, Effects effect, int amplifier, int duration, byte flags, bool hasFactorData, Dictionary<string, object>? factorCodec)
{
if (entities.ContainsKey(entityid))
DispatchBotEvent(bot => bot.OnEntityEffect(entities[entityid], effect, amplifier, duration, flags));
Entity? entity = null;
if (entities.TryGetValue(entityid, out var trackedEntity))
{
entity = trackedEntity;
}
var effectData = new EffectData(effect, amplifier, duration, flags);
entity?.ActiveEffects[effect] = effectData;
if (entityid == playerEntityID)
{
playerEffects.TryGetValue(effect, out var previousPlayerEffect);
playerEffects[effect] = effectData;
bool shouldAnnounceEffectGain = previousPlayerEffect is null
|| previousPlayerEffect.Amplifier != amplifier
|| (effectData.IsInfinite && !previousPlayerEffect.IsInfinite)
|| (!effectData.IsInfinite && duration > previousPlayerEffect.RemainingTicks + 20);
if (shouldAnnounceEffectGain)
{
ConsoleIO.WriteLine(string.Format(Translations.bot_effect_gained,
effectData.GetDisplayNameWithArticle(), effectData.GetInitialDurationText()));
}
}
if (entity is not null)
DispatchBotEvent(bot => bot.OnEntityEffect(entity, effect, amplifier, duration, flags));
}
/// <summary>
/// Called when an entity has an effect removed
/// </summary>
/// <param name="entityid">Entity ID</param>
/// <param name="effect">Effect that was removed</param>
public void OnRemoveEntityEffect(int entityid, Effects effect)
{
Entity? entity = null;
EffectData? removedEffectData = null;
if (entities.TryGetValue(entityid, out var trackedEntity))
{
entity = trackedEntity;
if (entity.ActiveEffects.Remove(effect, out var entityEffectData))
removedEffectData = entityEffectData;
}
if (entityid == playerEntityID && playerEffects.Remove(effect, out var playerEffectData))
removedEffectData ??= playerEffectData;
if (entityid == playerEntityID && removedEffectData is not null)
ConsoleIO.WriteLine(string.Format(Translations.bot_effect_expired, removedEffectData.GetDisplayName()));
if (entity is not null)
DispatchBotEvent(bot => bot.OnRemoveEntityEffect(entity, effect));
}
/// <summary>

View file

@ -2513,11 +2513,12 @@ namespace MinecraftClient.Protocol.Handlers
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var effectId = protocolVersion >= MC_1_18_2_Version
? dataTypes.ReadNextVarInt(packetData)
? dataTypes.ReadNextVarInt(packetData) + 1
: dataTypes.ReadNextByte(packetData);
if (Enum.TryParse(effectId.ToString(), out Effects effect))
if (Enum.IsDefined(typeof(Effects), effectId))
{
var effect = (Effects)effectId;
var amplifier = dataTypes.ReadNextByte(packetData);
var duration = dataTypes.ReadNextVarInt(packetData);
var flags = dataTypes.ReadNextByte(packetData);
@ -2536,6 +2537,22 @@ namespace MinecraftClient.Protocol.Handlers
}
}
break;
case PacketTypesIn.RemoveEntityEffect:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var effectId = protocolVersion >= MC_1_18_2_Version
? dataTypes.ReadNextVarInt(packetData) + 1
: dataTypes.ReadNextByte(packetData);
if (Enum.IsDefined(typeof(Effects), effectId))
{
var effect = (Effects)effectId;
handler.OnRemoveEntityEffect(entityId, effect);
}
}
break;
case PacketTypesIn.DestroyEntities:
if (handler.GetEntityHandlingEnabled())

View file

@ -434,6 +434,19 @@ namespace MinecraftClient.Protocol
/// <param name="factorCodec">factorCodec</param>
void OnEntityEffect(int entityid, Effects effect, int amplifier, int duration, byte flags, bool hasFactorData, Dictionary<String, object>? factorCodec);
/// <summary>
/// Called when an entity has an effect removed
/// </summary>
/// <param name="entityid">Entity ID</param>
/// <param name="effect">Effect that was removed</param>
void OnRemoveEntityEffect(int entityid, Effects effect);
/// <summary>
/// Get the player's active effects
/// </summary>
/// <returns>Dictionary of active effects</returns>
Dictionary<Effects, EffectData> GetPlayerEffects();
/// <summary>
/// Called when Soreboard Objective
/// </summary>

View file

@ -1843,16 +1843,25 @@ namespace MinecraftClient {
/// <summary>
/// Looks up a localized string similar to Show inventory layout as ASCII art in inventory command..
/// </summary>
internal static string Main_Advanced_show_inventory_layout {
get {
return ResourceManager.GetString("Main.Advanced.show_inventory_layout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to System messages for server ops..
/// </summary>
internal static string Main_Advanced_show_system_messages {
internal static string Main_Advanced_show_inventory_layout {
get {
return ResourceManager.GetString("Main.Advanced.show_inventory_layout", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show full effect names and levels in the TUI status bar instead of compact effect icons only..
/// </summary>
internal static string Main_Advanced_show_effect_names_in_tui {
get {
return ResourceManager.GetString("Main.Advanced.show_effect_names_in_tui", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to System messages for server ops..
/// </summary>
internal static string Main_Advanced_show_system_messages {
get {
return ResourceManager.GetString("Main.Advanced.show_system_messages", resourceCulture);
}

View file

@ -705,6 +705,9 @@ Usage examples: "/tell &lt;mybot&gt; connect Server1", "/connect Server2"</value
<data name="Main.Advanced.show_inventory_layout" xml:space="preserve">
<value>Show inventory layout as ASCII art in inventory command.</value>
</data>
<data name="Main.Advanced.show_effect_names_in_tui" xml:space="preserve">
<value>Show full effect names and levels in the TUI status bar instead of compact effect icons only.</value>
</data>
<data name="Main.Advanced.show_system_messages" xml:space="preserve">
<value>System messages for server ops.</value>
</data>

View file

@ -3522,6 +3522,42 @@ namespace MinecraftClient {
}
}
/// <summary>
/// Looks up a localized string similar to list your currently active effects..
/// </summary>
internal static string cmd_effects_desc {
get {
return ResourceManager.GetString("cmd.effects.desc", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to - {0} ({1}).
/// </summary>
internal static string cmd_effects_entry {
get {
return ResourceManager.GetString("cmd.effects.entry", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Active effects:.
/// </summary>
internal static string cmd_effects_header {
get {
return ResourceManager.GetString("cmd.effects.header", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No active effects..
/// </summary>
internal static string cmd_effects_none {
get {
return ResourceManager.GetString("cmd.effects.none", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Display Health and Food saturation..
/// </summary>
@ -6520,5 +6556,113 @@ namespace MinecraftClient {
return ResourceManager.GetString("tui.inventory.item_count", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You&apos;re now under {0} effect (Duration: {1})..
/// </summary>
internal static string bot_effect_gained {
get {
return ResourceManager.GetString("bot.effect.gained", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Effect {0} has expired.
/// </summary>
internal static string bot_effect_expired {
get {
return ResourceManager.GetString("bot.effect.expired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unlimited.
/// </summary>
internal static string effect_duration_unlimited {
get {
return ResourceManager.GetString("effect.duration.unlimited", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to a.
/// </summary>
internal static string effect_article_a {
get {
return ResourceManager.GetString("effect.article.a", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to an.
/// </summary>
internal static string effect_article_an {
get {
return ResourceManager.GetString("effect.article.an", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}h.
/// </summary>
internal static string effect_duration_short_hours {
get {
return ResourceManager.GetString("effect.duration.short.hours", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}h {1}m.
/// </summary>
internal static string effect_duration_short_hours_minutes {
get {
return ResourceManager.GetString("effect.duration.short.hours_minutes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}m.
/// </summary>
internal static string effect_duration_short_minutes {
get {
return ResourceManager.GetString("effect.duration.short.minutes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}m {1}s.
/// </summary>
internal static string effect_duration_short_minutes_seconds {
get {
return ResourceManager.GetString("effect.duration.short.minutes_seconds", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}s.
/// </summary>
internal static string effect_duration_short_seconds {
get {
return ResourceManager.GetString("effect.duration.short.seconds", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ∞.
/// </summary>
internal static string effect_duration_short_unlimited {
get {
return ResourceManager.GetString("effect.duration.short.unlimited", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} {1}.
/// </summary>
internal static string effect_name_with_amplifier {
get {
return ResourceManager.GetString("effect.name.with_amplifier", resourceCulture);
}
}
}
}

View file

@ -1237,6 +1237,18 @@ Change EnableEmoji=false in the settings if the display is confusing.</value>
<data name="cmd.follow.usage" xml:space="preserve">
<value>follow &lt;player name|stop&gt; [-f] (Use -f to enable un-safe walking)</value>
</data>
<data name="cmd.effects.desc" xml:space="preserve">
<value>list your currently active effects.</value>
</data>
<data name="cmd.effects.entry" xml:space="preserve">
<value>- {0} ({1})</value>
</data>
<data name="cmd.effects.header" xml:space="preserve">
<value>Active effects:</value>
</data>
<data name="cmd.effects.none" xml:space="preserve">
<value>No active effects.</value>
</data>
<data name="cmd.health.desc" xml:space="preserve">
<value>Display Health and Food saturation.</value>
</data>
@ -2299,4 +2311,40 @@ see item details.</value>
<data name="tui.inventory.item_count" xml:space="preserve">
<value>{0} items</value>
</data>
</root>
<data name="bot.effect.gained" xml:space="preserve">
<value>You're now under {0} effect (Duration: {1}).</value>
</data>
<data name="bot.effect.expired" xml:space="preserve">
<value>Effect {0} has expired</value>
</data>
<data name="effect.duration.unlimited" xml:space="preserve">
<value>Unlimited</value>
</data>
<data name="effect.article.a" xml:space="preserve">
<value>a</value>
</data>
<data name="effect.article.an" xml:space="preserve">
<value>an</value>
</data>
<data name="effect.duration.short.hours" xml:space="preserve">
<value>{0}h</value>
</data>
<data name="effect.duration.short.hours_minutes" xml:space="preserve">
<value>{0}h {1}m</value>
</data>
<data name="effect.duration.short.minutes" xml:space="preserve">
<value>{0}m</value>
</data>
<data name="effect.duration.short.minutes_seconds" xml:space="preserve">
<value>{0}m {1}s</value>
</data>
<data name="effect.duration.short.seconds" xml:space="preserve">
<value>{0}s</value>
</data>
<data name="effect.duration.short.unlimited" xml:space="preserve">
<value>∞</value>
</data>
<data name="effect.name.with_amplifier" xml:space="preserve">
<value>{0} {1}</value>
</data>
</root>

View file

@ -333,6 +333,13 @@ namespace MinecraftClient.Scripting
/// <param name="flags">effect flags</param>
public virtual void OnEntityEffect(Entity entity, Effects effect, int amplifier, int duration, byte flags) { }
/// <summary>
/// Called when an entity has an effect removed (expired or cleared)
/// </summary>
/// <param name="entity">Entity</param>
/// <param name="effect">Effect that was removed</param>
public virtual void OnRemoveEntityEffect(Entity entity, Effects effect) { }
/// <summary>
/// Called when a scoreboard objective updated
/// </summary>

View file

@ -817,6 +817,9 @@ namespace MinecraftClient
[TomlInlineComment("$Main.Advanced.show_inventory_layout$")]
public bool ShowInventoryLayout = true;
[TomlInlineComment("$Main.Advanced.show_effect_names_in_tui$")]
public bool ShowEffectNamesInTUI = false;
[TomlInlineComment("$Main.Advanced.terrain_and_movements$")]
public bool TerrainAndMovements = false;

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@ -9,6 +10,7 @@ using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Threading;
using MinecraftClient.Inventory;
namespace MinecraftClient.Tui
{
@ -866,6 +868,45 @@ namespace MinecraftClient.Tui
Foreground = new SolidColorBrush(Color.FromRgb(220, 190, 100)),
});
// Add effects display
var effects = client.GetPlayerEffects().Values
.Where(effectData => !effectData.IsExpired)
.OrderBy(effectData => effectData.Effect)
.ToArray();
if (effects.Length > 0)
{
bool showEffectNamesInTui = Settings.Config.Main.Advanced.ShowEffectNamesInTUI;
_statusBar.Inlines.Add(new Avalonia.Controls.Documents.Run(" | ")
{
Foreground = Brushes.Gray,
});
bool first = true;
foreach (var effectData in effects)
{
if (!first)
{
_statusBar.Inlines.Add(new Avalonia.Controls.Documents.Run(", ")
{
Foreground = Brushes.Gray,
});
}
first = false;
var color = GetEffectIconAndColor(effectData.Effect).Color;
var displayText = showEffectNamesInTui
? effectData.GetDisplayName()
: GetCompactEffectLabel(effectData);
displayText = $"{displayText} ({effectData.GetRemainingDurationText()})";
_statusBar.Inlines.Add(new Avalonia.Controls.Documents.Run(displayText)
{
Foreground = color,
});
}
}
_statusBar.IsVisible = true;
}
@ -884,6 +925,54 @@ namespace MinecraftClient.Tui
return sb.ToString();
}
private static string GetCompactEffectLabel(EffectData effectData)
{
var icon = GetEffectIconAndColor(effectData.Effect).Icon;
return effectData.Amplifier > 0
? $"{icon}{effectData.Amplifier + 1}"
: icon;
}
private static (string Icon, IBrush Color) GetEffectIconAndColor(Effects effect)
{
return effect switch
{
Effects.Speed => ("⚡", new SolidColorBrush(Color.FromRgb(135, 206, 235))),
Effects.Slowness => ("🐢", new SolidColorBrush(Color.FromRgb(139, 139, 139))),
Effects.Haste => ("⛏", new SolidColorBrush(Color.FromRgb(255, 215, 0))),
Effects.MiningFatigue => ("🔨", new SolidColorBrush(Color.FromRgb(64, 64, 64))),
Effects.Strength => ("⚔", new SolidColorBrush(Color.FromRgb(255, 99, 71))),
Effects.InstantHealth => ("❤", new SolidColorBrush(Color.FromRgb(255, 182, 193))),
Effects.InstantDamage => ("💀", new SolidColorBrush(Color.FromRgb(139, 0, 0))),
Effects.JumpBoost => ("🦘", new SolidColorBrush(Color.FromRgb(50, 205, 50))),
Effects.Nausea => ("💫", new SolidColorBrush(Color.FromRgb(85, 107, 47))),
Effects.Regeneration => ("✨", new SolidColorBrush(Color.FromRgb(255, 105, 180))),
Effects.Resistance => ("🛡", new SolidColorBrush(Color.FromRgb(112, 128, 144))),
Effects.FireResistance => ("🔥", new SolidColorBrush(Color.FromRgb(255, 140, 0))),
Effects.WaterBreathing => ("🐟", new SolidColorBrush(Color.FromRgb(0, 191, 255))),
Effects.Invisibility => ("👻", new SolidColorBrush(Color.FromRgb(200, 200, 200))),
Effects.Blindness => ("🕶", new SolidColorBrush(Color.FromRgb(50, 50, 50))),
Effects.NightVision => ("👁", new SolidColorBrush(Color.FromRgb(0, 255, 127))),
Effects.Hunger => ("🍔", new SolidColorBrush(Color.FromRgb(139, 69, 19))),
Effects.Weakness => ("💪", new SolidColorBrush(Color.FromRgb(128, 128, 128))),
Effects.Poison => ("☠", new SolidColorBrush(Color.FromRgb(75, 0, 130))),
Effects.Wither => ("🥀", new SolidColorBrush(Color.FromRgb(0, 0, 0))),
Effects.HealthBoost => ("💖", new SolidColorBrush(Color.FromRgb(255, 20, 147))),
Effects.Absorption => ("💛", new SolidColorBrush(Color.FromRgb(255, 215, 0))),
Effects.Saturation => ("🍖", new SolidColorBrush(Color.FromRgb(255, 165, 0))),
Effects.Glowing => ("💡", new SolidColorBrush(Color.FromRgb(255, 255, 150))),
Effects.Levitation => ("🎈", new SolidColorBrush(Color.FromRgb(147, 112, 219))),
Effects.Luck => ("🍀", new SolidColorBrush(Color.FromRgb(50, 205, 50))),
Effects.BadLuck => ("🐈‍⬛", new SolidColorBrush(Color.FromRgb(128, 0, 0))),
Effects.SlowFalling => ("🪶", new SolidColorBrush(Color.FromRgb(255, 182, 193))),
Effects.ConduitPower => ("🐡", new SolidColorBrush(Color.FromRgb(0, 255, 255))),
Effects.DolphinsGrace => ("🐬", new SolidColorBrush(Color.FromRgb(135, 206, 235))),
Effects.BadOmen => ("🏴", new SolidColorBrush(Color.FromRgb(0, 100, 0))),
Effects.HerooftheVillage => ("🎉", new SolidColorBrush(Color.FromRgb(255, 215, 0))),
_ => ("✦", new SolidColorBrush(Color.FromRgb(200, 200, 200))),
};
}
#endregion
#region Overlay