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;
}
}