- 重构 Gizmo_SwarmSpellStatus 为原版样式布局(212x75 双条 + 左侧标签) - 增加悬停技能负荷增量预览(闪烁叠加条)与超载阈值刻度线 - 增加负荷限制器按钮(限幅开关)与对应提示信息 - 在 Comp_SwarmSpellHolder 中新增并序列化 LimitPsychicLoadAmount 状态 - 在 CompAbilityEffect_PsychicLoadCost 中加入队列负荷预测,并在限制器开启时禁用超载施法 - AI 施法判定同步遵守限制器超载检查 - 通过项目编译验证(0 warning / 0 error)
359 lines
14 KiB
C#
359 lines
14 KiB
C#
using RimWorld;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
using Verse;
|
|
using Verse.Sound;
|
|
|
|
namespace ArachnaeSwarm
|
|
{
|
|
[StaticConstructorOnStartup]
|
|
public class Gizmo_SwarmSpellStatus : Gizmo
|
|
{
|
|
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<Texture2D>.Get("UI/Icons/EntropyLimit/Limited", false);
|
|
private static readonly Texture2D UnlimitedTex = ContentFinder<Texture2D>.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)
|
|
{
|
|
spellHolder = holder;
|
|
Order = -95f;
|
|
}
|
|
|
|
public override float GetWidth(float 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);
|
|
|
|
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.UpperLeft;
|
|
|
|
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"),
|
|
spellHolder.PsychicLoadCapacity.ToString("F1"),
|
|
spellHolder.OverloadPercent.ToString("P0"));
|
|
}
|
|
else
|
|
{
|
|
loadText = "ARA_SwarmSpell_Load".Translate(
|
|
spellHolder.PsychicLoad.ToString("F1"),
|
|
spellHolder.PsychicLoadCapacity.ToString("F1"));
|
|
}
|
|
|
|
Text.Anchor = TextAnchor.MiddleCenter;
|
|
Widgets.Label(loadBarRect, loadText);
|
|
Text.Anchor = TextAnchor.UpperLeft;
|
|
}
|
|
|
|
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<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()
|
|
{
|
|
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.MaxPsychicResearchPoints.ToString("F0")));
|
|
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(FloatRange hoveredLoadRange, bool hasHoveredLoad)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
if (spellHolder.IsOverloaded)
|
|
{
|
|
sb.AppendLine("ARA_SwarmSpell_Load_Title".Translate().Colorize(Color.red));
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine("ARA_SwarmSpell_Load_Title".Translate().Colorize(ColoredText.TipSectionTitleColor));
|
|
}
|
|
|
|
sb.AppendLine();
|
|
sb.AppendLine("ARA_SwarmSpell_Load_Current".Translate(
|
|
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();
|
|
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());
|
|
sb.AppendLine();
|
|
sb.AppendLine("ARA_SwarmSpell_Gizmo_Hint".Translate());
|
|
return sb.ToString();
|
|
}
|
|
|
|
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 &&
|
|
spellHolder.parent.Faction == Faction.OfPlayer;
|
|
}
|
|
}
|
|
}
|