diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index ec778dc..a9797aa 100644 Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ diff --git a/Source/ArachnaeSwarm/Abilities/ARA_PsychicLoadCost/CompAbilityEffect_PsychicLoadCost.cs b/Source/ArachnaeSwarm/Abilities/ARA_PsychicLoadCost/CompAbilityEffect_PsychicLoadCost.cs index 2dc5b8f..a997257 100644 --- a/Source/ArachnaeSwarm/Abilities/ARA_PsychicLoadCost/CompAbilityEffect_PsychicLoadCost.cs +++ b/Source/ArachnaeSwarm/Abilities/ARA_PsychicLoadCost/CompAbilityEffect_PsychicLoadCost.cs @@ -4,166 +4,171 @@ using System.Linq; using System.Text; using UnityEngine; using Verse; +using Verse.AI; namespace ArachnaeSwarm { - /// - /// 灵能负载消耗技能效果 - /// public class CompAbilityEffect_PsychicLoadCost : CompAbilityEffect { - #region 属性 public new CompProperties_AbilityPsychicLoadCost Props => (CompProperties_AbilityPsychicLoadCost)props; - - /// - /// 获取实际负载消耗(随机或固定) - /// - public float ActualLoadCost + + public float ActualLoadCost => Props.useFixedCost ? Props.fixedLoadCost : Props.loadCostRange.RandomInRange; + + public float GetPredictedLoadCost(bool useMaxCost = false) { - get + if (Props.useFixedCost) { - if (Props.useFixedCost) + return Props.fixedLoadCost; + } + + return useMaxCost ? Props.loadCostRange.max : Props.loadCostRange.Average; + } + + public static float CalculateAbilityLoadCost(Ability ability, bool useMaxCost = false) + { + if (ability == null || ability.comps.NullOrEmpty()) + { + return 0f; + } + + float totalCost = 0f; + foreach (CompAbilityEffect_PsychicLoadCost loadComp in ability.CompsOfType()) + { + totalCost += loadComp.GetPredictedLoadCost(useMaxCost); + } + + return totalCost; + } + + public static float TotalQueuedLoadCost(Pawn pawn, bool useMaxCost = false) + { + if (pawn?.jobs == null) + { + return 0f; + } + + float queuedCost = 0f; + + if (pawn.jobs.curJob?.verbToUse is Verb_CastAbility currentVerb) + { + queuedCost += CalculateAbilityLoadCost(currentVerb.ability, useMaxCost); + } + + for (int i = 0; i < pawn.jobs.jobQueue.Count; i++) + { + QueuedJob queuedJob = pawn.jobs.jobQueue[i]; + if (queuedJob.job?.verbToUse is Verb_CastAbility queuedVerb) { - return Props.fixedLoadCost; - } - else - { - return Props.loadCostRange.RandomInRange; + queuedCost += CalculateAbilityLoadCost(queuedVerb.ability, useMaxCost); } } - } - #endregion - #region 技能效果应用 + return queuedCost; + } + public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) { base.Apply(target, dest); - + Pawn caster = parent.pawn; if (caster == null || caster.Dead || caster.Downed) + { return; - - // 获取术法持有组件 + } + Comp_SwarmSpellHolder spellHolder = caster.TryGetComp(); if (spellHolder == null || !spellHolder.IsSystemInitialized) { Log.Warning($"[虫群术法] {caster.LabelCap} 没有有效的术法组件"); return; } - - // 计算实际负载消耗 + float loadCost = ActualLoadCost; bool wasOverloaded = spellHolder.IsOverloaded; - - // 添加灵能负载(不检查是否会超载) - spellHolder.AddPsychicLoad(loadCost, $"ARA_UsePsychicLoadSkill".Translate(parent.def.label)); - - // 检查当前是否超载 + + spellHolder.AddPsychicLoad(loadCost, "ARA_UsePsychicLoadSkill".Translate(parent.def.label)); + bool isNowOverloaded = spellHolder.IsOverloaded; - - // 如果从非超载状态变为超载状态,显示警告 if (!wasOverloaded && isNowOverloaded) { - Messages.Message("ARA_SwarmSpell_Load_OverloadWarning".Translate(caster.LabelShortCap), - caster, MessageTypeDefOf.NegativeEvent); + Messages.Message("ARA_SwarmSpell_Load_OverloadWarning".Translate(caster.LabelShortCap), caster, MessageTypeDefOf.NegativeEvent); } - - // 如果已经在超载状态时施放技能,应用惩罚 + if (wasOverloaded && Props.applyOverloadPenalty) { ApplyOverloadPenalty(caster, target); } } - #endregion - #region 超载惩罚 - /// - /// 应用超载惩罚 - /// private void ApplyOverloadPenalty(Pawn caster, LocalTargetInfo target) { try { - // 应用超载惩罚Hediff if (Props.overloadPenaltyHediff != null) { Hediff hediff = HediffMaker.MakeHediff(Props.overloadPenaltyHediff, caster); caster.health.AddHediff(hediff); } - - // 摧毁意识来源部位 + if (Props.destroyConsciousnessSource) { DestroyConsciousnessSourceParts(caster); } - - // 显示警告信息 - Messages.Message("ARA_SwarmSpell_Overload_Penalty".Translate(caster.LabelShortCap), - caster, MessageTypeDefOf.NegativeHealthEvent); + + Messages.Message("ARA_SwarmSpell_Overload_Penalty".Translate(caster.LabelShortCap), caster, MessageTypeDefOf.NegativeHealthEvent); } catch (Exception ex) { Log.Error($"[虫群术法] 应用超载惩罚时出错: {ex.Message}"); } } - - /// - /// 摧毁意识来源部位 - /// + private void DestroyConsciousnessSourceParts(Pawn pawn) { try { - // 获取所有意识来源部位 var consciousnessParts = pawn.health.hediffSet.GetNotMissingParts() .Where(part => part.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true) .ToList(); - + if (consciousnessParts.Any()) { - foreach (var part in consciousnessParts) + foreach (BodyPartRecord part in consciousnessParts) { - // 计算伤害 - 基于部位最大生命值和倍数 float partMaxHealth = part.def.GetMaxHealth(pawn); float damageAmount = partMaxHealth * Props.consciousnessSourceDamageMult; - - // 施加伤害 + DamageInfo damage = new DamageInfo( DamageDefOf.Burn, damageAmount, armorPenetration: 999f, instigator: pawn, hitPart: part, - category: DamageInfo.SourceCategory.ThingOrUnknown - ); - + category: DamageInfo.SourceCategory.ThingOrUnknown); + pawn.TakeDamage(damage); } - - // 如果还有剩余健康,尝试直接摧毁 - foreach (var part in consciousnessParts) + + foreach (BodyPartRecord part in consciousnessParts) { if (!pawn.health.hediffSet.PartIsMissing(part)) { - // 尝试施加额外伤害确保摧毁 DamageInfo finalDamage = new DamageInfo( DamageDefOf.Burn, 99999f, armorPenetration: 999f, instigator: pawn, - hitPart: part - ); + hitPart: part); pawn.TakeDamage(finalDamage); } } } else { - // 如果没有找到意识来源部位,对头部或胸腔造成伤害作为后备 BodyPartRecord fallbackPart = pawn.RaceProps.body.AllParts .FirstOrDefault(p => p.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true) ?? pawn.RaceProps.body.corePart; - + if (fallbackPart != null) { float damageAmount = fallbackPart.def.GetMaxHealth(pawn) * 2f; @@ -172,8 +177,7 @@ namespace ArachnaeSwarm damageAmount, armorPenetration: 999f, instigator: pawn, - hitPart: fallbackPart - ); + hitPart: fallbackPart); pawn.TakeDamage(damage); } } @@ -183,91 +187,119 @@ namespace ArachnaeSwarm Log.Error($"[虫群术法] 摧毁意识来源部位时出错: {ex.Message}"); } } - #endregion - #region Gizmo 相关 public override bool GizmoDisabled(out string reason) { - // 总是允许施放(忽略超载检查) + Pawn caster = parent.pawn; + if (caster == null || caster.Dead || caster.Downed) + { + reason = null; + return false; + } + + Comp_SwarmSpellHolder spellHolder = caster.TryGetComp(); + if (spellHolder == null || !spellHolder.IsSystemInitialized) + { + reason = null; + return false; + } + + if (spellHolder.LimitPsychicLoadAmount || !Props.ignoreOverloadCheck) + { + float thisCost = GetPredictedLoadCost(useMaxCost: true); + float queuedCost = TotalQueuedLoadCost(caster, useMaxCost: true); + float predictedLoad = spellHolder.PsychicLoad + thisCost + queuedCost; + + if (spellHolder.PsychicLoadCapacity > 0f && predictedLoad > spellHolder.PsychicLoadCapacity) + { + reason = "ARA_SwarmSpell_Load_OverloadWarning".Translate().Resolve(); + return true; + } + } + reason = null; return false; } - + public override string ExtraTooltipPart() { if (!Props.showCostInGizmo) + { return null; - + } + Pawn caster = parent.pawn; if (caster == null) + { return null; - + } + Comp_SwarmSpellHolder spellHolder = caster.TryGetComp(); if (spellHolder == null) + { return null; - + } + StringBuilder sb = new StringBuilder(); - - // 标题 string costLabel = Props.customLabel ?? "ARA_SwarmSpell_LoadCost".Translate(); sb.AppendLine(costLabel.Colorize(ColoredText.TipSectionTitleColor)); - - // 消耗范围或固定值 + if (Props.useFixedCost) { sb.AppendLine("ARA_SwarmSpell_LoadCost_Fixed".Translate(Props.fixedLoadCost.ToString("F1"))); } else { - sb.AppendLine("ARA_SwarmSpell_LoadCost_Range".Translate( - Props.loadCostRange.min.ToString("F1"), - Props.loadCostRange.max.ToString("F1"))); + sb.AppendLine("ARA_SwarmSpell_LoadCost_Range".Translate(Props.loadCostRange.min.ToString("F1"), Props.loadCostRange.max.ToString("F1"))); } - - // 超载警告 + if (spellHolder.IsOverloaded) { sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red)); sb.AppendLine("ARA_SwarmSpell_Overload_Penalty_Description".Translate()); } - + return sb.ToString().TrimEndNewlines(); } - + public override void DrawEffectPreview(LocalTargetInfo target) { base.DrawEffectPreview(target); - - // 可以在预览时显示一些效果,但这不是必须的 } - #endregion - #region AI 行为 public override bool AICanTargetNow(LocalTargetInfo target) { Pawn caster = parent.pawn; if (caster == null) + { return false; - + } + Comp_SwarmSpellHolder spellHolder = caster.TryGetComp(); if (spellHolder == null) + { return false; - - // AI 不会在超载状态下使用此技能(避免自我伤害) + } + if (spellHolder.IsOverloaded) + { return false; - - // 检查施放后的负载预测 - float predictedLoad = spellHolder.PsychicLoad + (Props.useFixedCost ? Props.fixedLoadCost : Props.loadCostRange.Average); - float predictedPercent = spellHolder.PsychicLoadCapacity > 0 ? predictedLoad / spellHolder.PsychicLoadCapacity : 0f; - - // AI 会避免可能导致严重超载的技能 + } + + float predictedLoad = spellHolder.PsychicLoad + GetPredictedLoadCost(); + if (spellHolder.LimitPsychicLoadAmount && spellHolder.PsychicLoadCapacity > 0f && predictedLoad > spellHolder.PsychicLoadCapacity) + { + return false; + } + + float predictedPercent = spellHolder.PsychicLoadCapacity > 0f ? predictedLoad / spellHolder.PsychicLoadCapacity : 0f; if (predictedPercent > 0.9f) + { return false; - + } + return base.AICanTargetNow(target); } - #endregion } } diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Comp_SwarmSpellHolder.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Comp_SwarmSpellHolder.cs index 015d38c..bbf4ca1 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Comp_SwarmSpellHolder.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Comp_SwarmSpellHolder.cs @@ -30,6 +30,7 @@ namespace ArachnaeSwarm // 超载系统 private bool isOverloaded = false; // 是否超载 + private bool limitPsychicLoadAmount = true; // 负载限制器(开启时不允许超过容量) private int overloadCheckTick = -1; // 上次检查超载的时间 private const int OVERLOAD_CHECK_INTERVAL = 30; // 检查超载间隔(ticks) @@ -202,6 +203,15 @@ namespace ArachnaeSwarm /// 是否冷却中 /// public bool IsOnCooldown => isCooldownActive && cooldownTicksRemaining > 0; + + /// + /// 是否启用负载限制器(类似原版心灵熵限制器) + /// + public bool LimitPsychicLoadAmount + { + get => limitPsychicLoadAmount; + set => limitPsychicLoadAmount = value; + } #endregion #region 生命周期方法 @@ -254,6 +264,7 @@ namespace ArachnaeSwarm // 超载系统 Scribe_Values.Look(ref isOverloaded, "isOverloaded", false); Scribe_Values.Look(ref overloadCheckTick, "overloadCheckTick", -1); + Scribe_Values.Look(ref limitPsychicLoadAmount, "limitPsychicLoadAmount", true); if (Scribe.mode == LoadSaveMode.LoadingVars) { diff --git a/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Gizmo_SwarmSpellStatus.cs b/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Gizmo_SwarmSpellStatus.cs index 72ddd85..ed3543d 100644 --- a/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Gizmo_SwarmSpellStatus.cs +++ b/Source/ArachnaeSwarm/Pawn_Comps/ARA_SwarmSpellHolder/Gizmo_SwarmSpellStatus.cs @@ -1,178 +1,259 @@ using RimWorld; +using System.Text; using UnityEngine; using Verse; -using System.Text; +using Verse.Sound; namespace ArachnaeSwarm { [StaticConstructorOnStartup] public class Gizmo_SwarmSpellStatus : Gizmo { - private Comp_SwarmSpellHolder spellHolder; - - // 材质定义 - private static readonly Texture2D FullResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.8f, 0.4f, 0.9f)); // 绿色 - 科研点 - private static readonly Texture2D EmptyResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.3f, 0.2f, 0.8f)); // 深绿 - 科研点背景 - private static readonly Texture2D FullLoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.8f, 0.2f, 0.4f, 0.9f)); // 红色 - 正常负载 - private static readonly Texture2D OverloadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 0.2f, 0.2f, 0.9f)); // 亮红色 - 超载 - private static readonly Texture2D EmptyLoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.3f, 0.1f, 0.2f, 0.8f)); // 深红 - 负载背景 - private static readonly Texture2D WarningTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.9f, 0.7f, 0.2f, 0.9f)); // 黄色 - 警告 - private static readonly Texture2D CooldownBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.4f, 0.4f, 0.8f, 0.7f)); // 蓝色 - 冷却条 - - // 布局常量 - private const float GizmoWidth = 160f; - private const float GizmoHeight = 75f; // 增加高度以显示冷却条 - private const float Padding = 4f; - private const float TitleHeight = 18f; - private const float BarHeight = 19f; - private const float BarSpacing = 4f; - private const float InfoHeight = 14f; - + private readonly Comp_SwarmSpellHolder spellHolder; + + private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.15f, 0.15f, 0.15f)); + private static readonly Texture2D LoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.46f, 0.34f, 0.35f)); + private static readonly Texture2D OverLimitBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.72f, 0.25f, 0.25f)); + private static readonly Texture2D LoadAddPreviewTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.78f, 0.72f, 0.66f)); + private static readonly Texture2D ResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.32f, 0.47f, 0.7f)); + private static readonly Texture2D ThresholdTex = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 1f, 1f, 0.7f)); + private static readonly Texture2D LimitedTex = ContentFinder.Get("UI/Icons/EntropyLimit/Limited", false); + private static readonly Texture2D UnlimitedTex = ContentFinder.Get("UI/Icons/EntropyLimit/Unlimited", false); + + private const float GizmoWidth = 212f; + private const float GizmoHeight = 75f; + private const float Padding = 6f; + public Gizmo_SwarmSpellStatus(Comp_SwarmSpellHolder holder) { - this.spellHolder = holder; - Order = -95f; // 在护盾Gizmo之前显示 + spellHolder = holder; + Order = -95f; } - + public override float GetWidth(float maxWidth) { - return Mathf.Min(GizmoWidth, maxWidth); + return GizmoWidth; } - + public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) { if (spellHolder == null || !spellHolder.IsSystemInitialized) { return new GizmoResult(GizmoState.Clear); } - + Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), GizmoHeight); Rect contentRect = rect.ContractedBy(Padding); - - // 绘制窗口背景 Widgets.DrawWindowBackground(rect); - - // 标题区域 - Rect titleRect = new Rect(contentRect.x, contentRect.y, contentRect.width, TitleHeight); - Text.Font = GameFont.Tiny; - Text.Anchor = TextAnchor.UpperCenter; - GUI.color = new Color(0.9f, 0.5f, 0.9f); // 紫色,虫群主题色 - - // 如果有超载,标题显示警告 - if (spellHolder.IsOverloaded) - { - GUI.color = Color.red; - Widgets.Label(titleRect, "ARA_SwarmSpell_Title".Translate(spellHolder.SpellLevel.ToString("F0"))); - } - else - { - Widgets.Label(titleRect, "ARA_SwarmSpell_Title".Translate(spellHolder.SpellLevel.ToString("F0"))); - } - - Text.Anchor = TextAnchor.UpperLeft; - GUI.color = Color.white; - - float currentY = titleRect.yMax + BarSpacing; - - // === 灵能科研点条 === - Rect researchBarRect = new Rect(contentRect.x, currentY, contentRect.width, BarHeight); - - // 绘制科研点条 - float researchPercent = spellHolder.PsychicResearchPercent; - Texture2D researchBarTex = researchPercent > 0.8f ? WarningTex : FullResearchBarTex; - Widgets.FillableBar(researchBarRect, researchPercent, researchBarTex, EmptyResearchBarTex, doBorder: true); - - // 绘制科研点文本 + + Command_Ability hoveredAbility = MapGizmoUtility.LastMouseOverGizmo as Command_Ability; + FloatRange hoveredLoadRange = default; + bool hasHoveredLoad = TryGetHoveredAbilityLoadRange(hoveredAbility, out hoveredLoadRange); + + DrawLabels(contentRect); + Rect loadBarRect = new Rect(contentRect.x + 63f, contentRect.y + 6f, 100f, 22f); + Rect researchBarRect = new Rect(contentRect.x + 63f, contentRect.y + 38f, 100f, 22f); + + DrawLoadBar(loadBarRect, hasHoveredLoad ? hoveredLoadRange : default, hasHoveredLoad); + DrawResearchBar(researchBarRect); + DrawLimiterToggle(contentRect); + + TooltipHandler.TipRegion(loadBarRect, new TipSignal(GenerateLoadTooltip(hasHoveredLoad ? hoveredLoadRange : default, hasHoveredLoad), 43502)); + TooltipHandler.TipRegion(researchBarRect, new TipSignal(GenerateResearchTooltip(), 43501)); + TooltipHandler.TipRegion(rect, new TipSignal(GenerateBasicTooltip(), 43500)); + + return new GizmoResult(GizmoState.Clear); + } + + private void DrawLabels(Rect contentRect) + { Text.Font = GameFont.Small; - Text.Anchor = TextAnchor.MiddleCenter; - string researchText = $"ARA_SwarmSpell_Research".Translate(spellHolder.PsychicResearchPoints.ToString("F0"), spellHolder.MaxPsychicResearchPoints.ToString("F0")); - GUI.color = Color.white; - Widgets.Label(researchBarRect, researchText); Text.Anchor = TextAnchor.UpperLeft; - - // 科研点悬浮提示 - string researchTooltip = GenerateResearchTooltip(); - TooltipHandler.TipRegion(researchBarRect, new TipSignal(researchTooltip, 43501)); - - currentY = researchBarRect.yMax + BarSpacing; - - // === 灵能负载条 === - Rect loadBarRect = new Rect(contentRect.x, currentY, contentRect.width, BarHeight); - - // 绘制负载条 - 超载时使用红色条,但长度限制为100% - float loadPercent = spellHolder.PsychicLoadPercentForDisplay; - Texture2D loadBarTex = spellHolder.IsOverloaded ? OverloadBarTex : (loadPercent > 0.7f ? WarningTex : FullLoadBarTex); - Widgets.FillableBar(loadBarRect, loadPercent, loadBarTex, EmptyLoadBarTex, doBorder: true); - - // 绘制负载文本 - 超载时显示额外信息 - Text.Font = GameFont.Small; - Text.Anchor = TextAnchor.MiddleCenter; + + Rect loadLabelRect = new Rect(contentRect.x, contentRect.y + 6f, 62f, Text.LineHeight); + Rect researchLabelRect = new Rect(contentRect.x, contentRect.y + 38f, 62f, Text.LineHeight); + + Widgets.Label(loadLabelRect, "ARA_SwarmSpell_Load_Title".Translate()); + Widgets.Label(researchLabelRect, "ARA_SwarmSpell_Research_Title".Translate()); + } + + private void DrawLoadBar(Rect loadBarRect, FloatRange hoveredLoadRange, bool hasHoveredLoad) + { + float loadRelative = LoadToRelativeValue(spellHolder.PsychicLoad, spellHolder.PsychicLoadCapacity); + Widgets.FillableBar(loadBarRect, Mathf.Min(loadRelative, 1f), LoadBarTex, EmptyBarTex, doBorder: true); + + if (loadRelative > 1f) + { + Widgets.FillableBar(loadBarRect, Mathf.Min(loadRelative - 1f, 1f), OverLimitBarTex, LoadBarTex, doBorder: true); + } + + if (hasHoveredLoad && hoveredLoadRange.max > float.Epsilon) + { + DrawLoadPreview(loadBarRect, loadRelative, hoveredLoadRange.max); + } + + if (loadRelative > 1f) + { + float overloadRelative = loadRelative - 1f; + DrawThreshold(loadBarRect, 0.33f, overloadRelative); + DrawThreshold(loadBarRect, 0.66f, overloadRelative); + } + string loadText; if (spellHolder.IsOverloaded) { - loadText = $"ARA_SwarmSpell_Load_Overload".Translate( - spellHolder.PsychicLoad.ToString("F1"), + loadText = "ARA_SwarmSpell_Load_Overload".Translate( + spellHolder.PsychicLoad.ToString("F1"), spellHolder.PsychicLoadCapacity.ToString("F1"), spellHolder.OverloadPercent.ToString("P0")); } else { - loadText = $"ARA_SwarmSpell_Load".Translate( - spellHolder.PsychicLoad.ToString("F1"), + loadText = "ARA_SwarmSpell_Load".Translate( + spellHolder.PsychicLoad.ToString("F1"), spellHolder.PsychicLoadCapacity.ToString("F1")); } - GUI.color = Color.white; + + Text.Anchor = TextAnchor.MiddleCenter; Widgets.Label(loadBarRect, loadText); Text.Anchor = TextAnchor.UpperLeft; - - // 负载悬浮提示 - string loadTooltip = GenerateLoadTooltip(); - TooltipHandler.TipRegion(loadBarRect, new TipSignal(loadTooltip, 43502)); - - // 整个Gizmo的基础工具提示 - string basicTooltip = GenerateBasicTooltip(); - TooltipHandler.TipRegion(rect, new TipSignal(basicTooltip, 43500)); - - return new GizmoResult(GizmoState.Clear); } - - /// - /// 生成灵能科研点的详细悬浮提示 - /// + + private void DrawLoadPreview(Rect loadBarRect, float currentRelative, float addedLoad) + { + float predictedRelative = LoadToRelativeValue(spellHolder.PsychicLoad + addedLoad, spellHolder.PsychicLoadCapacity); + Rect previewRect = loadBarRect.ContractedBy(3f); + float width = previewRect.width; + + float from = currentRelative; + float to = predictedRelative; + + if (from > 1f) + { + from -= 1f; + to -= 1f; + } + + previewRect.xMin = previewRect.xMin + Mathf.Clamp01(from) * width; + previewRect.width = Mathf.Max(Mathf.Min(to, 1f) - Mathf.Clamp01(from), 0f) * width; + + if (previewRect.width > 0f) + { + GUI.color = new Color(1f, 1f, 1f, PulsingAlpha() * 0.7f); + GenUI.DrawTextureWithMaterial(previewRect, LoadAddPreviewTex, null); + GUI.color = Color.white; + } + } + + private void DrawThreshold(Rect rect, float percent, float currentOverloadRelative) + { + if (currentOverloadRelative < percent) + { + return; + } + + Rect thresholdRect = new Rect(rect.x + rect.width * percent - 1f, rect.y + 2f, 2f, rect.height - 4f); + GUI.DrawTexture(thresholdRect, ThresholdTex); + } + + private void DrawResearchBar(Rect researchBarRect) + { + float researchPercent = Mathf.Clamp01(spellHolder.PsychicResearchPercent); + Widgets.FillableBar(researchBarRect, researchPercent, ResearchBarTex, EmptyBarTex, doBorder: true); + + Text.Anchor = TextAnchor.MiddleCenter; + Widgets.Label(researchBarRect, "ARA_SwarmSpell_Research".Translate( + spellHolder.PsychicResearchPoints.ToString("F0"), + spellHolder.MaxPsychicResearchPoints.ToString("F0"))); + Text.Anchor = TextAnchor.UpperLeft; + } + + private void DrawLimiterToggle(Rect contentRect) + { + float buttonSize = 24f; + Rect buttonRect = new Rect(contentRect.x + contentRect.width - buttonSize, contentRect.y + contentRect.height / 2f - buttonSize / 2f, buttonSize, buttonSize); + + Texture2D buttonTexture = spellHolder.LimitPsychicLoadAmount ? LimitedTex : UnlimitedTex; + if (buttonTexture == null) + { + buttonTexture = BaseContent.BadTex; + } + + if (Widgets.ButtonImage(buttonRect, buttonTexture)) + { + spellHolder.LimitPsychicLoadAmount = !spellHolder.LimitPsychicLoadAmount; + if (spellHolder.LimitPsychicLoadAmount) + { + SoundDefOf.Tick_Low.PlayOneShotOnCamera(); + } + else + { + SoundDefOf.Tick_High.PlayOneShotOnCamera(); + } + } + + TooltipHandler.TipRegion(buttonRect, "PawnTooltipPsychicEntropyLimit".Translate()); + } + + private bool TryGetHoveredAbilityLoadRange(Command_Ability command, out FloatRange loadRange) + { + loadRange = default; + if (command?.Ability == null) + { + return false; + } + + if (command.Ability.pawn != spellHolder.parent) + { + return false; + } + + float min = 0f; + float max = 0f; + bool foundAny = false; + + foreach (CompAbilityEffect_PsychicLoadCost loadComp in command.Ability.CompsOfType()) + { + foundAny = true; + if (loadComp.Props.useFixedCost) + { + min += loadComp.Props.fixedLoadCost; + max += loadComp.Props.fixedLoadCost; + } + else + { + min += loadComp.Props.loadCostRange.min; + max += loadComp.Props.loadCostRange.max; + } + } + + if (!foundAny) + { + return false; + } + + loadRange = new FloatRange(min, max); + return true; + } + private string GenerateResearchTooltip() { StringBuilder sb = new StringBuilder(); - - // 标题 sb.AppendLine("ARA_SwarmSpell_Research_Title".Translate().Colorize(ColoredText.TipSectionTitleColor)); sb.AppendLine(); - - // 当前数值 sb.AppendLine("ARA_SwarmSpell_Research_Current".Translate( - spellHolder.PsychicResearchPoints.ToString("F0"), + spellHolder.PsychicResearchPoints.ToString("F0"), spellHolder.MaxPsychicResearchPoints.ToString("F0"))); - - // 容量信息 - sb.AppendLine(); - - float remainingCapacity = spellHolder.GetRemainingResearchCapacity(); - - // 作用说明 sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Research_Description".Translate().Colorize(ColoredText.TipSectionTitleColor)); sb.AppendLine("ARA_SwarmSpell_Research_DescriptionText".Translate()); - return sb.ToString(); } - - /// - /// 生成灵能负载的详细悬浮提示 - /// - private string GenerateLoadTooltip() + + private string GenerateLoadTooltip(FloatRange hoveredLoadRange, bool hasHoveredLoad) { StringBuilder sb = new StringBuilder(); - - // 标题 + if (spellHolder.IsOverloaded) { sb.AppendLine("ARA_SwarmSpell_Load_Title".Translate().Colorize(Color.red)); @@ -181,90 +262,96 @@ namespace ArachnaeSwarm { sb.AppendLine("ARA_SwarmSpell_Load_Title".Translate().Colorize(ColoredText.TipSectionTitleColor)); } + sb.AppendLine(); - - // 当前数值 sb.AppendLine("ARA_SwarmSpell_Load_Current".Translate( - spellHolder.PsychicLoad.ToString("F1"), + spellHolder.PsychicLoad.ToString("F1"), spellHolder.PsychicLoadCapacity.ToString("F1"))); - - // 超载信息 + + if (hasHoveredLoad && hoveredLoadRange.max > float.Epsilon) + { + sb.AppendLine(); + sb.AppendLine("ARA_SwarmSpell_LoadCost".Translate().Colorize(ColoredText.TipSectionTitleColor)); + if (Mathf.Abs(hoveredLoadRange.max - hoveredLoadRange.min) < 0.01f) + { + sb.AppendLine("ARA_SwarmSpell_LoadCost_Fixed".Translate(hoveredLoadRange.max.ToString("F1"))); + } + else + { + sb.AppendLine("ARA_SwarmSpell_LoadCost_Range".Translate( + hoveredLoadRange.min.ToString("F1"), + hoveredLoadRange.max.ToString("F1"))); + } + } + if (spellHolder.IsOverloaded) { + sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red)); } - - // 冷却信息 + if (spellHolder.IsOnCooldown) { sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Cooldown_Status".Translate().Colorize(ColoredText.TipSectionTitleColor)); sb.AppendLine("ARA_SwarmSpell_Cooldown_Remaining".Translate(spellHolder.CooldownSecondsRemaining.ToString("F1"))); - sb.AppendLine("ARA_SwarmSpell_Cooldown_Behavior".Translate()); } - else if (!spellHolder.IsOverloaded) - { - sb.AppendLine(); - sb.AppendLine("ARA_SwarmSpell_Load_Recovery".Translate().Colorize(Color.green)); - } - - // 术法等级影响 + sb.AppendLine(); - sb.AppendLine("ARA_SwarmSpell_Level_Title".Translate().Colorize(ColoredText.TipSectionTitleColor)); - sb.AppendLine("ARA_SwarmSpell_Level_Current".Translate(spellHolder.SpellLevel.ToString("F0"))); - sb.AppendLine("ARA_SwarmSpell_Load_CapacityFormula".Translate(spellHolder.SpellLevel.ToString("F0"))); - - // 作用说明 - sb.AppendLine(); - sb.AppendLine("ARA_SwarmSpell_Load_Description".Translate().Colorize(ColoredText.TipSectionTitleColor)); - sb.AppendLine("ARA_SwarmSpell_Load_DescriptionText".Translate()); - + string limiterState = spellHolder.LimitPsychicLoadAmount ? "On".Translate() : "Off".Translate(); + sb.AppendLine($"负载限制器: {limiterState}".Colorize(ColoredText.TipSectionTitleColor)); + sb.AppendLine("PawnTooltipPsychicEntropyLimit".Translate()); + return sb.ToString(); } - - /// - /// 生成基础工具提示 - /// + private string GenerateBasicTooltip() { StringBuilder sb = new StringBuilder(); - sb.AppendLine("ARA_SwarmSpell_Gizmo_Title".Translate().Colorize(ColoredText.TipSectionTitleColor)); sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Gizmo_Desc".Translate()); - - // 超载警告 - if (spellHolder.IsOverloaded) - { - sb.AppendLine(); - sb.AppendLine("ARA_SwarmSpell_Gizmo_OverloadWarning".Translate()); - } - sb.AppendLine(); sb.AppendLine("ARA_SwarmSpell_Gizmo_Hint".Translate()); - - if (DebugSettings.godMode) - { - sb.AppendLine(); - sb.AppendLine("Debug".Translate().Colorize(Color.gray)); - sb.AppendLine($"超载状态: {spellHolder.IsOverloaded}"); - sb.AppendLine($"冷却状态: {spellHolder.IsOnCooldown}"); - sb.AppendLine($"冷却剩余: {spellHolder.CooldownSecondsRemaining:F1}秒"); - sb.AppendLine($"系统初始化: {spellHolder.IsSystemInitialized}"); - sb.AppendLine($"单位: {spellHolder.parent.LabelCap}"); - } - return sb.ToString(); } - - /// - /// 检查是否应该显示此Gizmo - /// + + private static float LoadToRelativeValue(float load, float capacity) + { + if (load <= float.Epsilon || capacity <= float.Epsilon) + { + return 0f; + } + + if (load <= capacity) + { + return load / capacity; + } + + return 1f + (load - capacity) / capacity; + } + + private static float PulsingAlpha() + { + float cycle = Mathf.Repeat(Time.time, 0.85f); + if (cycle < 0.1f) + { + return cycle / 0.1f; + } + + if (cycle >= 0.25f) + { + return 1f - (cycle - 0.25f) / 0.6f; + } + + return 1f; + } + public bool ShouldDisplay() { - return spellHolder != null && - spellHolder.IsSystemInitialized && - spellHolder.parent is Pawn && + return spellHolder != null && + spellHolder.IsSystemInitialized && + spellHolder.parent is Pawn && spellHolder.parent.Faction == Faction.OfPlayer; } }