mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Implemented effects support, as a command, chat notification and added in TUI mode
This commit is contained in:
parent
a7a3566756
commit
f6446b198d
15 changed files with 728 additions and 15 deletions
67
MinecraftClient/Commands/EffectsCommand.cs
Normal file
67
MinecraftClient/Commands/EffectsCommand.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
194
MinecraftClient/Inventory/EffectData.cs
Normal file
194
MinecraftClient/Inventory/EffectData.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -705,6 +705,9 @@ Usage examples: "/tell <mybot> 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>
|
||||
|
|
|
|||
|
|
@ -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'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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <player name|stop> [-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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue