namespace MinecraftClient.Inventory; using System; using System.Collections.Generic; using System.Linq; using MinecraftClient.Protocol; using MinecraftClient.Protocol.Message; /// /// Represents an active status effect on an entity /// public class EffectData { /// /// The type of effect /// public Effects Effect { get; set; } /// /// Effect amplifier (level - 1, e.g., 0 = level I, 1 = level II) /// public int Amplifier { get; set; } /// /// Duration in ticks (20 ticks = 1 second). -1 for infinite. /// public int Duration { get; set; } /// /// Effect flags (ambient, show particles, show icon) /// public byte Flags { get; set; } /// /// Time when the effect was applied /// 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; } /// /// Check if this is an infinite duration effect /// public bool IsInfinite => Duration == -1 || Duration == int.MaxValue; /// /// Check if the effect has expired /// public bool IsExpired { get { if (IsInfinite) return false; return GetElapsedTicks() >= Duration; } } /// /// Get remaining duration in ticks /// public int RemainingTicks { get { if (IsInfinite) return -1; return Math.Max(0, Duration - GetElapsedTicks()); } } /// /// Get remaining duration in seconds /// public int RemainingSeconds { get { if (IsInfinite) return -1; return (RemainingTicks + 19) / 20; } } /// /// Get the translated effect name from Minecraft translations /// public string GetTranslatedName() { var key = $"effect.minecraft.{Effect.ToString().ToUnderscoreCase()}"; var translated = ChatParser.TranslateString(key); return string.IsNullOrEmpty(translated) ? Effect.ToString() : translated; } /// /// Get the translated effect name with level when applicable /// public string GetDisplayName() { string translatedName = GetTranslatedName(); if (Amplifier <= 0) return translatedName; return string.Format(Translations.effect_name_with_amplifier, translatedName, EnchantmentMapping.ConvertLevelToRomanNumbers(Amplifier + 1)); } /// /// Get the translated effect name prefixed with the best-fit indefinite article /// 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}"; } /// /// Get the configured short duration label for the remaining time /// public string GetRemainingDurationText() { return FormatShortDuration(RemainingSeconds); } /// /// Get the configured short duration label for the initial effect duration /// public string GetInitialDurationText() { if (IsInfinite) return Translations.effect_duration_unlimited; int durationSeconds = (Duration + 19) / 20; return FormatShortDuration(durationSeconds); } /// /// Format a duration for compact UI output /// /// Duration in seconds, -1 for unlimited 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); } } /// /// Extension method for converting PascalCase to snake_case /// 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(); } }