feat(swarmspell-ui): 将神经束负荷面板改为原版心灵熵风格并接入限制器联动
- 重构 Gizmo_SwarmSpellStatus 为原版样式布局(212x75 双条 + 左侧标签) - 增加悬停技能负荷增量预览(闪烁叠加条)与超载阈值刻度线 - 增加负荷限制器按钮(限幅开关)与对应提示信息 - 在 Comp_SwarmSpellHolder 中新增并序列化 LimitPsychicLoadAmount 状态 - 在 CompAbilityEffect_PsychicLoadCost 中加入队列负荷预测,并在限制器开启时禁用超载施法 - AI 施法判定同步遵守限制器超载检查 - 通过项目编译验证(0 warning / 0 error)
This commit is contained in:
Binary file not shown.
@@ -4,166 +4,171 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Verse;
|
using Verse;
|
||||||
|
using Verse.AI;
|
||||||
|
|
||||||
namespace ArachnaeSwarm
|
namespace ArachnaeSwarm
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 灵能负载消耗技能效果
|
|
||||||
/// </summary>
|
|
||||||
public class CompAbilityEffect_PsychicLoadCost : CompAbilityEffect
|
public class CompAbilityEffect_PsychicLoadCost : CompAbilityEffect
|
||||||
{
|
{
|
||||||
#region 属性
|
|
||||||
public new CompProperties_AbilityPsychicLoadCost Props => (CompProperties_AbilityPsychicLoadCost)props;
|
public new CompProperties_AbilityPsychicLoadCost Props => (CompProperties_AbilityPsychicLoadCost)props;
|
||||||
|
|
||||||
/// <summary>
|
public float ActualLoadCost => Props.useFixedCost ? Props.fixedLoadCost : Props.loadCostRange.RandomInRange;
|
||||||
/// 获取实际负载消耗(随机或固定)
|
|
||||||
/// </summary>
|
public float GetPredictedLoadCost(bool useMaxCost = false)
|
||||||
public float ActualLoadCost
|
|
||||||
{
|
{
|
||||||
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<CompAbilityEffect_PsychicLoadCost>())
|
||||||
|
{
|
||||||
|
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;
|
queuedCost += CalculateAbilityLoadCost(queuedVerb.ability, useMaxCost);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Props.loadCostRange.RandomInRange;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 技能效果应用
|
return queuedCost;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||||||
{
|
{
|
||||||
base.Apply(target, dest);
|
base.Apply(target, dest);
|
||||||
|
|
||||||
Pawn caster = parent.pawn;
|
Pawn caster = parent.pawn;
|
||||||
if (caster == null || caster.Dead || caster.Downed)
|
if (caster == null || caster.Dead || caster.Downed)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
// 获取术法持有组件
|
|
||||||
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
||||||
if (spellHolder == null || !spellHolder.IsSystemInitialized)
|
if (spellHolder == null || !spellHolder.IsSystemInitialized)
|
||||||
{
|
{
|
||||||
Log.Warning($"[虫群术法] {caster.LabelCap} 没有有效的术法组件");
|
Log.Warning($"[虫群术法] {caster.LabelCap} 没有有效的术法组件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算实际负载消耗
|
|
||||||
float loadCost = ActualLoadCost;
|
float loadCost = ActualLoadCost;
|
||||||
bool wasOverloaded = spellHolder.IsOverloaded;
|
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;
|
bool isNowOverloaded = spellHolder.IsOverloaded;
|
||||||
|
|
||||||
// 如果从非超载状态变为超载状态,显示警告
|
|
||||||
if (!wasOverloaded && isNowOverloaded)
|
if (!wasOverloaded && isNowOverloaded)
|
||||||
{
|
{
|
||||||
Messages.Message("ARA_SwarmSpell_Load_OverloadWarning".Translate(caster.LabelShortCap),
|
Messages.Message("ARA_SwarmSpell_Load_OverloadWarning".Translate(caster.LabelShortCap), caster, MessageTypeDefOf.NegativeEvent);
|
||||||
caster, MessageTypeDefOf.NegativeEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果已经在超载状态时施放技能,应用惩罚
|
|
||||||
if (wasOverloaded && Props.applyOverloadPenalty)
|
if (wasOverloaded && Props.applyOverloadPenalty)
|
||||||
{
|
{
|
||||||
ApplyOverloadPenalty(caster, target);
|
ApplyOverloadPenalty(caster, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 超载惩罚
|
|
||||||
/// <summary>
|
|
||||||
/// 应用超载惩罚
|
|
||||||
/// </summary>
|
|
||||||
private void ApplyOverloadPenalty(Pawn caster, LocalTargetInfo target)
|
private void ApplyOverloadPenalty(Pawn caster, LocalTargetInfo target)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 应用超载惩罚Hediff
|
|
||||||
if (Props.overloadPenaltyHediff != null)
|
if (Props.overloadPenaltyHediff != null)
|
||||||
{
|
{
|
||||||
Hediff hediff = HediffMaker.MakeHediff(Props.overloadPenaltyHediff, caster);
|
Hediff hediff = HediffMaker.MakeHediff(Props.overloadPenaltyHediff, caster);
|
||||||
caster.health.AddHediff(hediff);
|
caster.health.AddHediff(hediff);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 摧毁意识来源部位
|
|
||||||
if (Props.destroyConsciousnessSource)
|
if (Props.destroyConsciousnessSource)
|
||||||
{
|
{
|
||||||
DestroyConsciousnessSourceParts(caster);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error($"[虫群术法] 应用超载惩罚时出错: {ex.Message}");
|
Log.Error($"[虫群术法] 应用超载惩罚时出错: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 摧毁意识来源部位
|
|
||||||
/// </summary>
|
|
||||||
private void DestroyConsciousnessSourceParts(Pawn pawn)
|
private void DestroyConsciousnessSourceParts(Pawn pawn)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获取所有意识来源部位
|
|
||||||
var consciousnessParts = pawn.health.hediffSet.GetNotMissingParts()
|
var consciousnessParts = pawn.health.hediffSet.GetNotMissingParts()
|
||||||
.Where(part => part.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true)
|
.Where(part => part.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (consciousnessParts.Any())
|
if (consciousnessParts.Any())
|
||||||
{
|
{
|
||||||
foreach (var part in consciousnessParts)
|
foreach (BodyPartRecord part in consciousnessParts)
|
||||||
{
|
{
|
||||||
// 计算伤害 - 基于部位最大生命值和倍数
|
|
||||||
float partMaxHealth = part.def.GetMaxHealth(pawn);
|
float partMaxHealth = part.def.GetMaxHealth(pawn);
|
||||||
float damageAmount = partMaxHealth * Props.consciousnessSourceDamageMult;
|
float damageAmount = partMaxHealth * Props.consciousnessSourceDamageMult;
|
||||||
|
|
||||||
// 施加伤害
|
|
||||||
DamageInfo damage = new DamageInfo(
|
DamageInfo damage = new DamageInfo(
|
||||||
DamageDefOf.Burn,
|
DamageDefOf.Burn,
|
||||||
damageAmount,
|
damageAmount,
|
||||||
armorPenetration: 999f,
|
armorPenetration: 999f,
|
||||||
instigator: pawn,
|
instigator: pawn,
|
||||||
hitPart: part,
|
hitPart: part,
|
||||||
category: DamageInfo.SourceCategory.ThingOrUnknown
|
category: DamageInfo.SourceCategory.ThingOrUnknown);
|
||||||
);
|
|
||||||
|
|
||||||
pawn.TakeDamage(damage);
|
pawn.TakeDamage(damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果还有剩余健康,尝试直接摧毁
|
foreach (BodyPartRecord part in consciousnessParts)
|
||||||
foreach (var part in consciousnessParts)
|
|
||||||
{
|
{
|
||||||
if (!pawn.health.hediffSet.PartIsMissing(part))
|
if (!pawn.health.hediffSet.PartIsMissing(part))
|
||||||
{
|
{
|
||||||
// 尝试施加额外伤害确保摧毁
|
|
||||||
DamageInfo finalDamage = new DamageInfo(
|
DamageInfo finalDamage = new DamageInfo(
|
||||||
DamageDefOf.Burn,
|
DamageDefOf.Burn,
|
||||||
99999f,
|
99999f,
|
||||||
armorPenetration: 999f,
|
armorPenetration: 999f,
|
||||||
instigator: pawn,
|
instigator: pawn,
|
||||||
hitPart: part
|
hitPart: part);
|
||||||
);
|
|
||||||
pawn.TakeDamage(finalDamage);
|
pawn.TakeDamage(finalDamage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 如果没有找到意识来源部位,对头部或胸腔造成伤害作为后备
|
|
||||||
BodyPartRecord fallbackPart = pawn.RaceProps.body.AllParts
|
BodyPartRecord fallbackPart = pawn.RaceProps.body.AllParts
|
||||||
.FirstOrDefault(p => p.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true)
|
.FirstOrDefault(p => p.def.tags?.Contains(BodyPartTagDefOf.ConsciousnessSource) == true)
|
||||||
?? pawn.RaceProps.body.corePart;
|
?? pawn.RaceProps.body.corePart;
|
||||||
|
|
||||||
if (fallbackPart != null)
|
if (fallbackPart != null)
|
||||||
{
|
{
|
||||||
float damageAmount = fallbackPart.def.GetMaxHealth(pawn) * 2f;
|
float damageAmount = fallbackPart.def.GetMaxHealth(pawn) * 2f;
|
||||||
@@ -172,8 +177,7 @@ namespace ArachnaeSwarm
|
|||||||
damageAmount,
|
damageAmount,
|
||||||
armorPenetration: 999f,
|
armorPenetration: 999f,
|
||||||
instigator: pawn,
|
instigator: pawn,
|
||||||
hitPart: fallbackPart
|
hitPart: fallbackPart);
|
||||||
);
|
|
||||||
pawn.TakeDamage(damage);
|
pawn.TakeDamage(damage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,91 +187,119 @@ namespace ArachnaeSwarm
|
|||||||
Log.Error($"[虫群术法] 摧毁意识来源部位时出错: {ex.Message}");
|
Log.Error($"[虫群术法] 摧毁意识来源部位时出错: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Gizmo 相关
|
|
||||||
public override bool GizmoDisabled(out string reason)
|
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<Comp_SwarmSpellHolder>();
|
||||||
|
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;
|
reason = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ExtraTooltipPart()
|
public override string ExtraTooltipPart()
|
||||||
{
|
{
|
||||||
if (!Props.showCostInGizmo)
|
if (!Props.showCostInGizmo)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Pawn caster = parent.pawn;
|
Pawn caster = parent.pawn;
|
||||||
if (caster == null)
|
if (caster == null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
||||||
if (spellHolder == null)
|
if (spellHolder == null)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
// 标题
|
|
||||||
string costLabel = Props.customLabel ?? "ARA_SwarmSpell_LoadCost".Translate();
|
string costLabel = Props.customLabel ?? "ARA_SwarmSpell_LoadCost".Translate();
|
||||||
sb.AppendLine(costLabel.Colorize(ColoredText.TipSectionTitleColor));
|
sb.AppendLine(costLabel.Colorize(ColoredText.TipSectionTitleColor));
|
||||||
|
|
||||||
// 消耗范围或固定值
|
|
||||||
if (Props.useFixedCost)
|
if (Props.useFixedCost)
|
||||||
{
|
{
|
||||||
sb.AppendLine("ARA_SwarmSpell_LoadCost_Fixed".Translate(Props.fixedLoadCost.ToString("F1")));
|
sb.AppendLine("ARA_SwarmSpell_LoadCost_Fixed".Translate(Props.fixedLoadCost.ToString("F1")));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.AppendLine("ARA_SwarmSpell_LoadCost_Range".Translate(
|
sb.AppendLine("ARA_SwarmSpell_LoadCost_Range".Translate(Props.loadCostRange.min.ToString("F1"), Props.loadCostRange.max.ToString("F1")));
|
||||||
Props.loadCostRange.min.ToString("F1"),
|
|
||||||
Props.loadCostRange.max.ToString("F1")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 超载警告
|
|
||||||
if (spellHolder.IsOverloaded)
|
if (spellHolder.IsOverloaded)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red));
|
sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red));
|
||||||
sb.AppendLine("ARA_SwarmSpell_Overload_Penalty_Description".Translate());
|
sb.AppendLine("ARA_SwarmSpell_Overload_Penalty_Description".Translate());
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString().TrimEndNewlines();
|
return sb.ToString().TrimEndNewlines();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DrawEffectPreview(LocalTargetInfo target)
|
public override void DrawEffectPreview(LocalTargetInfo target)
|
||||||
{
|
{
|
||||||
base.DrawEffectPreview(target);
|
base.DrawEffectPreview(target);
|
||||||
|
|
||||||
// 可以在预览时显示一些效果,但这不是必须的
|
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region AI 行为
|
|
||||||
public override bool AICanTargetNow(LocalTargetInfo target)
|
public override bool AICanTargetNow(LocalTargetInfo target)
|
||||||
{
|
{
|
||||||
Pawn caster = parent.pawn;
|
Pawn caster = parent.pawn;
|
||||||
if (caster == null)
|
if (caster == null)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
Comp_SwarmSpellHolder spellHolder = caster.TryGetComp<Comp_SwarmSpellHolder>();
|
||||||
if (spellHolder == null)
|
if (spellHolder == null)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// AI 不会在超载状态下使用此技能(避免自我伤害)
|
|
||||||
if (spellHolder.IsOverloaded)
|
if (spellHolder.IsOverloaded)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// 检查施放后的负载预测
|
|
||||||
float predictedLoad = spellHolder.PsychicLoad + (Props.useFixedCost ? Props.fixedLoadCost : Props.loadCostRange.Average);
|
float predictedLoad = spellHolder.PsychicLoad + GetPredictedLoadCost();
|
||||||
float predictedPercent = spellHolder.PsychicLoadCapacity > 0 ? predictedLoad / spellHolder.PsychicLoadCapacity : 0f;
|
if (spellHolder.LimitPsychicLoadAmount && spellHolder.PsychicLoadCapacity > 0f && predictedLoad > spellHolder.PsychicLoadCapacity)
|
||||||
|
{
|
||||||
// AI 会避免可能导致严重超载的技能
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float predictedPercent = spellHolder.PsychicLoadCapacity > 0f ? predictedLoad / spellHolder.PsychicLoadCapacity : 0f;
|
||||||
if (predictedPercent > 0.9f)
|
if (predictedPercent > 0.9f)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return base.AICanTargetNow(target);
|
return base.AICanTargetNow(target);
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace ArachnaeSwarm
|
|||||||
|
|
||||||
// 超载系统
|
// 超载系统
|
||||||
private bool isOverloaded = false; // 是否超载
|
private bool isOverloaded = false; // 是否超载
|
||||||
|
private bool limitPsychicLoadAmount = true; // 负载限制器(开启时不允许超过容量)
|
||||||
private int overloadCheckTick = -1; // 上次检查超载的时间
|
private int overloadCheckTick = -1; // 上次检查超载的时间
|
||||||
private const int OVERLOAD_CHECK_INTERVAL = 30; // 检查超载间隔(ticks)
|
private const int OVERLOAD_CHECK_INTERVAL = 30; // 检查超载间隔(ticks)
|
||||||
|
|
||||||
@@ -202,6 +203,15 @@ namespace ArachnaeSwarm
|
|||||||
/// 是否冷却中
|
/// 是否冷却中
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsOnCooldown => isCooldownActive && cooldownTicksRemaining > 0;
|
public bool IsOnCooldown => isCooldownActive && cooldownTicksRemaining > 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用负载限制器(类似原版心灵熵限制器)
|
||||||
|
/// </summary>
|
||||||
|
public bool LimitPsychicLoadAmount
|
||||||
|
{
|
||||||
|
get => limitPsychicLoadAmount;
|
||||||
|
set => limitPsychicLoadAmount = value;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 生命周期方法
|
#region 生命周期方法
|
||||||
@@ -254,6 +264,7 @@ namespace ArachnaeSwarm
|
|||||||
// 超载系统
|
// 超载系统
|
||||||
Scribe_Values.Look(ref isOverloaded, "isOverloaded", false);
|
Scribe_Values.Look(ref isOverloaded, "isOverloaded", false);
|
||||||
Scribe_Values.Look(ref overloadCheckTick, "overloadCheckTick", -1);
|
Scribe_Values.Look(ref overloadCheckTick, "overloadCheckTick", -1);
|
||||||
|
Scribe_Values.Look(ref limitPsychicLoadAmount, "limitPsychicLoadAmount", true);
|
||||||
|
|
||||||
if (Scribe.mode == LoadSaveMode.LoadingVars)
|
if (Scribe.mode == LoadSaveMode.LoadingVars)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,178 +1,259 @@
|
|||||||
using RimWorld;
|
using RimWorld;
|
||||||
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Verse;
|
using Verse;
|
||||||
using System.Text;
|
using Verse.Sound;
|
||||||
|
|
||||||
namespace ArachnaeSwarm
|
namespace ArachnaeSwarm
|
||||||
{
|
{
|
||||||
[StaticConstructorOnStartup]
|
[StaticConstructorOnStartup]
|
||||||
public class Gizmo_SwarmSpellStatus : Gizmo
|
public class Gizmo_SwarmSpellStatus : Gizmo
|
||||||
{
|
{
|
||||||
private Comp_SwarmSpellHolder spellHolder;
|
private readonly Comp_SwarmSpellHolder spellHolder;
|
||||||
|
|
||||||
// 材质定义
|
private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.15f, 0.15f, 0.15f));
|
||||||
private static readonly Texture2D FullResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.8f, 0.4f, 0.9f)); // 绿色 - 科研点
|
private static readonly Texture2D LoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.46f, 0.34f, 0.35f));
|
||||||
private static readonly Texture2D EmptyResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.3f, 0.2f, 0.8f)); // 深绿 - 科研点背景
|
private static readonly Texture2D OverLimitBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.72f, 0.25f, 0.25f));
|
||||||
private static readonly Texture2D FullLoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.8f, 0.2f, 0.4f, 0.9f)); // 红色 - 正常负载
|
private static readonly Texture2D LoadAddPreviewTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.78f, 0.72f, 0.66f));
|
||||||
private static readonly Texture2D OverloadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 0.2f, 0.2f, 0.9f)); // 亮红色 - 超载
|
private static readonly Texture2D ResearchBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.32f, 0.47f, 0.7f));
|
||||||
private static readonly Texture2D EmptyLoadBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.3f, 0.1f, 0.2f, 0.8f)); // 深红 - 负载背景
|
private static readonly Texture2D ThresholdTex = SolidColorMaterials.NewSolidColorTexture(new Color(1f, 1f, 1f, 0.7f));
|
||||||
private static readonly Texture2D WarningTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.9f, 0.7f, 0.2f, 0.9f)); // 黄色 - 警告
|
private static readonly Texture2D LimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/EntropyLimit/Limited", false);
|
||||||
private static readonly Texture2D CooldownBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.4f, 0.4f, 0.8f, 0.7f)); // 蓝色 - 冷却条
|
private static readonly Texture2D UnlimitedTex = ContentFinder<Texture2D>.Get("UI/Icons/EntropyLimit/Unlimited", false);
|
||||||
|
|
||||||
// 布局常量
|
private const float GizmoWidth = 212f;
|
||||||
private const float GizmoWidth = 160f;
|
private const float GizmoHeight = 75f;
|
||||||
private const float GizmoHeight = 75f; // 增加高度以显示冷却条
|
private const float Padding = 6f;
|
||||||
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;
|
|
||||||
|
|
||||||
public Gizmo_SwarmSpellStatus(Comp_SwarmSpellHolder holder)
|
public Gizmo_SwarmSpellStatus(Comp_SwarmSpellHolder holder)
|
||||||
{
|
{
|
||||||
this.spellHolder = holder;
|
spellHolder = holder;
|
||||||
Order = -95f; // 在护盾Gizmo之前显示
|
Order = -95f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetWidth(float maxWidth)
|
public override float GetWidth(float maxWidth)
|
||||||
{
|
{
|
||||||
return Mathf.Min(GizmoWidth, maxWidth);
|
return GizmoWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
|
||||||
{
|
{
|
||||||
if (spellHolder == null || !spellHolder.IsSystemInitialized)
|
if (spellHolder == null || !spellHolder.IsSystemInitialized)
|
||||||
{
|
{
|
||||||
return new GizmoResult(GizmoState.Clear);
|
return new GizmoResult(GizmoState.Clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), GizmoHeight);
|
Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), GizmoHeight);
|
||||||
Rect contentRect = rect.ContractedBy(Padding);
|
Rect contentRect = rect.ContractedBy(Padding);
|
||||||
|
|
||||||
// 绘制窗口背景
|
|
||||||
Widgets.DrawWindowBackground(rect);
|
Widgets.DrawWindowBackground(rect);
|
||||||
|
|
||||||
// 标题区域
|
Command_Ability hoveredAbility = MapGizmoUtility.LastMouseOverGizmo as Command_Ability;
|
||||||
Rect titleRect = new Rect(contentRect.x, contentRect.y, contentRect.width, TitleHeight);
|
FloatRange hoveredLoadRange = default;
|
||||||
Text.Font = GameFont.Tiny;
|
bool hasHoveredLoad = TryGetHoveredAbilityLoadRange(hoveredAbility, out hoveredLoadRange);
|
||||||
Text.Anchor = TextAnchor.UpperCenter;
|
|
||||||
GUI.color = new Color(0.9f, 0.5f, 0.9f); // 紫色,虫群主题色
|
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);
|
||||||
if (spellHolder.IsOverloaded)
|
|
||||||
{
|
DrawLoadBar(loadBarRect, hasHoveredLoad ? hoveredLoadRange : default, hasHoveredLoad);
|
||||||
GUI.color = Color.red;
|
DrawResearchBar(researchBarRect);
|
||||||
Widgets.Label(titleRect, "ARA_SwarmSpell_Title".Translate(spellHolder.SpellLevel.ToString("F0")));
|
DrawLimiterToggle(contentRect);
|
||||||
}
|
|
||||||
else
|
TooltipHandler.TipRegion(loadBarRect, new TipSignal(GenerateLoadTooltip(hasHoveredLoad ? hoveredLoadRange : default, hasHoveredLoad), 43502));
|
||||||
{
|
TooltipHandler.TipRegion(researchBarRect, new TipSignal(GenerateResearchTooltip(), 43501));
|
||||||
Widgets.Label(titleRect, "ARA_SwarmSpell_Title".Translate(spellHolder.SpellLevel.ToString("F0")));
|
TooltipHandler.TipRegion(rect, new TipSignal(GenerateBasicTooltip(), 43500));
|
||||||
}
|
|
||||||
|
return new GizmoResult(GizmoState.Clear);
|
||||||
Text.Anchor = TextAnchor.UpperLeft;
|
}
|
||||||
GUI.color = Color.white;
|
|
||||||
|
private void DrawLabels(Rect contentRect)
|
||||||
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);
|
|
||||||
|
|
||||||
// 绘制科研点文本
|
|
||||||
Text.Font = GameFont.Small;
|
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;
|
Text.Anchor = TextAnchor.UpperLeft;
|
||||||
|
|
||||||
// 科研点悬浮提示
|
Rect loadLabelRect = new Rect(contentRect.x, contentRect.y + 6f, 62f, Text.LineHeight);
|
||||||
string researchTooltip = GenerateResearchTooltip();
|
Rect researchLabelRect = new Rect(contentRect.x, contentRect.y + 38f, 62f, Text.LineHeight);
|
||||||
TooltipHandler.TipRegion(researchBarRect, new TipSignal(researchTooltip, 43501));
|
|
||||||
|
Widgets.Label(loadLabelRect, "ARA_SwarmSpell_Load_Title".Translate());
|
||||||
currentY = researchBarRect.yMax + BarSpacing;
|
Widgets.Label(researchLabelRect, "ARA_SwarmSpell_Research_Title".Translate());
|
||||||
|
}
|
||||||
// === 灵能负载条 ===
|
|
||||||
Rect loadBarRect = new Rect(contentRect.x, currentY, contentRect.width, BarHeight);
|
private void DrawLoadBar(Rect loadBarRect, FloatRange hoveredLoadRange, bool hasHoveredLoad)
|
||||||
|
{
|
||||||
// 绘制负载条 - 超载时使用红色条,但长度限制为100%
|
float loadRelative = LoadToRelativeValue(spellHolder.PsychicLoad, spellHolder.PsychicLoadCapacity);
|
||||||
float loadPercent = spellHolder.PsychicLoadPercentForDisplay;
|
Widgets.FillableBar(loadBarRect, Mathf.Min(loadRelative, 1f), LoadBarTex, EmptyBarTex, doBorder: true);
|
||||||
Texture2D loadBarTex = spellHolder.IsOverloaded ? OverloadBarTex : (loadPercent > 0.7f ? WarningTex : FullLoadBarTex);
|
|
||||||
Widgets.FillableBar(loadBarRect, loadPercent, loadBarTex, EmptyLoadBarTex, doBorder: true);
|
if (loadRelative > 1f)
|
||||||
|
{
|
||||||
// 绘制负载文本 - 超载时显示额外信息
|
Widgets.FillableBar(loadBarRect, Mathf.Min(loadRelative - 1f, 1f), OverLimitBarTex, LoadBarTex, doBorder: true);
|
||||||
Text.Font = GameFont.Small;
|
}
|
||||||
Text.Anchor = TextAnchor.MiddleCenter;
|
|
||||||
|
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;
|
string loadText;
|
||||||
if (spellHolder.IsOverloaded)
|
if (spellHolder.IsOverloaded)
|
||||||
{
|
{
|
||||||
loadText = $"<color=red>ARA_SwarmSpell_Load_Overload</color>".Translate(
|
loadText = "ARA_SwarmSpell_Load_Overload".Translate(
|
||||||
spellHolder.PsychicLoad.ToString("F1"),
|
spellHolder.PsychicLoad.ToString("F1"),
|
||||||
spellHolder.PsychicLoadCapacity.ToString("F1"),
|
spellHolder.PsychicLoadCapacity.ToString("F1"),
|
||||||
spellHolder.OverloadPercent.ToString("P0"));
|
spellHolder.OverloadPercent.ToString("P0"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
loadText = $"ARA_SwarmSpell_Load".Translate(
|
loadText = "ARA_SwarmSpell_Load".Translate(
|
||||||
spellHolder.PsychicLoad.ToString("F1"),
|
spellHolder.PsychicLoad.ToString("F1"),
|
||||||
spellHolder.PsychicLoadCapacity.ToString("F1"));
|
spellHolder.PsychicLoadCapacity.ToString("F1"));
|
||||||
}
|
}
|
||||||
GUI.color = Color.white;
|
|
||||||
|
Text.Anchor = TextAnchor.MiddleCenter;
|
||||||
Widgets.Label(loadBarRect, loadText);
|
Widgets.Label(loadBarRect, loadText);
|
||||||
Text.Anchor = TextAnchor.UpperLeft;
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void DrawLoadPreview(Rect loadBarRect, float currentRelative, float addedLoad)
|
||||||
/// 生成灵能科研点的详细悬浮提示
|
{
|
||||||
/// </summary>
|
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<CompAbilityEffect_PsychicLoadCost>())
|
||||||
|
{
|
||||||
|
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()
|
private string GenerateResearchTooltip()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
// 标题
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Research_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
sb.AppendLine("ARA_SwarmSpell_Research_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
// 当前数值
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Research_Current".Translate(
|
sb.AppendLine("ARA_SwarmSpell_Research_Current".Translate(
|
||||||
spellHolder.PsychicResearchPoints.ToString("F0"),
|
spellHolder.PsychicResearchPoints.ToString("F0"),
|
||||||
spellHolder.MaxPsychicResearchPoints.ToString("F0")));
|
spellHolder.MaxPsychicResearchPoints.ToString("F0")));
|
||||||
|
|
||||||
// 容量信息
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
float remainingCapacity = spellHolder.GetRemainingResearchCapacity();
|
|
||||||
|
|
||||||
// 作用说明
|
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Research_Description".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
sb.AppendLine("ARA_SwarmSpell_Research_Description".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
||||||
sb.AppendLine("ARA_SwarmSpell_Research_DescriptionText".Translate());
|
sb.AppendLine("ARA_SwarmSpell_Research_DescriptionText".Translate());
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private string GenerateLoadTooltip(FloatRange hoveredLoadRange, bool hasHoveredLoad)
|
||||||
/// 生成灵能负载的详细悬浮提示
|
|
||||||
/// </summary>
|
|
||||||
private string GenerateLoadTooltip()
|
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
// 标题
|
|
||||||
if (spellHolder.IsOverloaded)
|
if (spellHolder.IsOverloaded)
|
||||||
{
|
{
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_Title".Translate().Colorize(Color.red));
|
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("ARA_SwarmSpell_Load_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
||||||
// 当前数值
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_Current".Translate(
|
sb.AppendLine("ARA_SwarmSpell_Load_Current".Translate(
|
||||||
spellHolder.PsychicLoad.ToString("F1"),
|
spellHolder.PsychicLoad.ToString("F1"),
|
||||||
spellHolder.PsychicLoadCapacity.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)
|
if (spellHolder.IsOverloaded)
|
||||||
{
|
{
|
||||||
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red));
|
sb.AppendLine("ARA_SwarmSpell_Load_OverloadWarning".Translate().Colorize(Color.red));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 冷却信息
|
|
||||||
if (spellHolder.IsOnCooldown)
|
if (spellHolder.IsOnCooldown)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Cooldown_Status".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
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_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();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Level_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
string limiterState = spellHolder.LimitPsychicLoadAmount ? "On".Translate() : "Off".Translate();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Level_Current".Translate(spellHolder.SpellLevel.ToString("F0")));
|
sb.AppendLine($"负载限制器: {limiterState}".Colorize(ColoredText.TipSectionTitleColor));
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_CapacityFormula".Translate(spellHolder.SpellLevel.ToString("F0")));
|
sb.AppendLine("PawnTooltipPsychicEntropyLimit".Translate());
|
||||||
|
|
||||||
// 作用说明
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_Description".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Load_DescriptionText".Translate());
|
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 生成基础工具提示
|
|
||||||
/// </summary>
|
|
||||||
private string GenerateBasicTooltip()
|
private string GenerateBasicTooltip()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.AppendLine("ARA_SwarmSpell_Gizmo_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
sb.AppendLine("ARA_SwarmSpell_Gizmo_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Gizmo_Desc".Translate());
|
sb.AppendLine("ARA_SwarmSpell_Gizmo_Desc".Translate());
|
||||||
|
|
||||||
// 超载警告
|
|
||||||
if (spellHolder.IsOverloaded)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("<color=red>ARA_SwarmSpell_Gizmo_OverloadWarning</color>".Translate());
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("ARA_SwarmSpell_Gizmo_Hint".Translate());
|
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();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static float LoadToRelativeValue(float load, float capacity)
|
||||||
/// 检查是否应该显示此Gizmo
|
{
|
||||||
/// </summary>
|
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()
|
public bool ShouldDisplay()
|
||||||
{
|
{
|
||||||
return spellHolder != null &&
|
return spellHolder != null &&
|
||||||
spellHolder.IsSystemInitialized &&
|
spellHolder.IsSystemInitialized &&
|
||||||
spellHolder.parent is Pawn &&
|
spellHolder.parent is Pawn &&
|
||||||
spellHolder.parent.Faction == Faction.OfPlayer;
|
spellHolder.parent.Faction == Faction.OfPlayer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user