This commit is contained in:
Tourswen
2025-11-19 00:46:26 +08:00
parent 878657af47
commit 0d6fb3bf19
7 changed files with 473 additions and 245 deletions

View File

@@ -342,25 +342,22 @@ namespace WulaFallenEmpire
}
}
}
// 绘制单个选项
// 绘制单个选项 - 增强版本(修复版本)
private void DrawSingleOption(Rect rect, EventOption option)
{
string reason;
bool conditionsMet = AreConditionsMet(option.conditions, out reason);
// 水平居中选项
float optionWidth = Mathf.Min(rect.width, Config.optionSize.x * (rect.width / Config.windowSize.x));
float optionX = rect.x + (rect.width - optionWidth) / 2;
Rect optionRect = new Rect(optionX, rect.y, optionWidth, rect.height);
if (conditionsMet)
{
// 保存原始颜色状态
// 保存原始状态
Color originalColor = GUI.color;
GameFont originalFont = Text.Font;
Color originalTextColor = GUI.contentColor;
TextAnchor originalAnchor = Text.Anchor;
try
{
// 应用自定义颜色
@@ -368,6 +365,8 @@ namespace WulaFallenEmpire
{
ApplyOptionColors(option, optionRect);
}
// 设置文本居中
Text.Anchor = TextAnchor.MiddleCenter;
if (Widgets.ButtonText(optionRect, option.label))
{
@@ -376,44 +375,57 @@ namespace WulaFallenEmpire
}
finally
{
// 恢复原始颜色状态
// 恢复原始状态
GUI.color = originalColor;
Text.Font = originalFont;
GUI.contentColor = originalTextColor;
Text.Anchor = originalAnchor;
}
}
else
{
// 禁用状态的选项
// 禁用状态的选项 - 修复版本
Color originalColor = GUI.color;
GameFont originalFont = Text.Font;
Color originalTextColor = GUI.contentColor;
TextAnchor originalAnchor = Text.Anchor;
try
{
// 应用禁用状态的自定义颜色
if (option.useCustomColors && option.disabledColor.HasValue)
// 设置禁用状态的颜色
GUI.color = new Color(0.85f, 0.85f, 0.85f, 1f);
GUI.contentColor = new Color(0.85f, 0.85f, 0.85f, 1f);
Text.Font = GameFont.Small;
// 修复:使用正确的 DrawBoxSolid 方法
Widgets.DrawBoxSolid(optionRect, new Color(0.2f, 0.2f, 0.2f, 0.3f));
Widgets.DrawBox(optionRect);
// 设置文本居中
Text.Anchor = TextAnchor.MiddleCenter;
// 绘制居中的文本
Rect textRect = optionRect.ContractedBy(4f);
Widgets.Label(textRect, option.label);
// 可选:在文本上添加删除线效果
if (Config.drawBorders)
{
GUI.color = option.disabledColor.Value;
// 修复:使用正确的 DrawLine 方法
Widgets.DrawLine(
new Vector2(textRect.x, textRect.center.y),
new Vector2(textRect.xMax, textRect.center.y),
Color.gray,
1f
);
}
if (option.useCustomColors && option.textDisabledColor.HasValue)
{
GUI.contentColor = option.textDisabledColor.Value;
}
Widgets.ButtonText(optionRect, option.label, false, true, false);
TooltipHandler.TipRegion(optionRect, GetDisabledReason(option, reason));
}
finally
{
// 恢复原始颜色状态
// 恢复原始状态
GUI.color = originalColor;
Text.Font = originalFont;
GUI.contentColor = originalTextColor;
Text.Anchor = originalAnchor;
}
}
}
// 应用选项颜色
private void ApplyOptionColors(EventOption option, Rect rect)
{

View File

@@ -17,6 +17,11 @@ namespace WulaFallenEmpire
// 存储每个伴飞的缩放和遮罩数据
private Dictionary<FlyOver, EscortVisualData> escortVisualData = new Dictionary<FlyOver, EscortVisualData>();
// 新增伴飞ID映射用于解决存档加载时的引用问题
private Dictionary<int, EscortVisualData> escortDataByID = new Dictionary<int, EscortVisualData>();
private List<int> escortIDs = new List<int>();
private List<EscortVisualData> escortDataList = new List<EscortVisualData>();
public override void Initialize(CompProperties props)
{
base.Initialize(props);
@@ -76,7 +81,18 @@ namespace WulaFallenEmpire
{
FlyOver removedEscort = activeEscorts[i];
activeEscorts.RemoveAt(i);
escortVisualData.Remove(removedEscort);
// 从两个字典中都移除
if (escortVisualData.ContainsKey(removedEscort))
{
escortVisualData.Remove(removedEscort);
}
int escortID = GetEscortID(removedEscort);
if (escortDataByID.ContainsKey(escortID))
{
escortDataByID.Remove(escortID);
}
}
}
}
@@ -114,6 +130,11 @@ namespace WulaFallenEmpire
{
activeEscorts.Add(escort);
escortVisualData[escort] = visualData;
// 添加到ID映射
int escortID = GetEscortID(escort);
escortDataByID[escortID] = visualData;
successfulSpawns++;
Log.Message($"Spawned escort #{successfulSpawns} for FlyOver at {mainFlyOver.DrawPos}, scale: {visualData.scale:F2}, maskAlpha: {visualData.heightMaskAlpha:F2}");
@@ -452,40 +473,109 @@ namespace WulaFallenEmpire
}
activeEscorts.Clear();
escortVisualData.Clear();
escortDataByID.Clear();
}
}
// 新增获取伴飞的唯一ID
private int GetEscortID(FlyOver escort)
{
return escort?.thingIDNumber ?? 0;
}
// 修复的序列化方法
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref ticksUntilNextSpawn, "ticksUntilNextSpawn", 0f);
Scribe_Collections.Look(ref activeEscorts, "activeEscorts", LookMode.Reference);
Scribe_Values.Look(ref hasInitialized, "hasInitialized", false);
// 保存视觉数据(如果需要)
// 修复使用ID映射来保存和恢复视觉数据
if (Scribe.mode == LoadSaveMode.Saving)
{
List<FlyOver> keys = new List<FlyOver>(escortVisualData.Keys);
List<EscortVisualData> values = new List<EscortVisualData>(escortVisualData.Values);
Scribe_Collections.Look(ref keys, "escortKeys", LookMode.Reference);
Scribe_Collections.Look(ref values, "escortValues", LookMode.Deep);
// 保存时将视觉数据转换为ID映射
escortIDs.Clear();
escortDataList.Clear();
escortDataByID.Clear();
foreach (var kvp in escortVisualData)
{
if (kvp.Key != null && !kvp.Key.Destroyed)
{
int escortID = GetEscortID(kvp.Key);
escortIDs.Add(escortID);
escortDataList.Add(kvp.Value);
escortDataByID[escortID] = kvp.Value;
}
}
Scribe_Collections.Look(ref escortIDs, "escortIDs", LookMode.Value);
Scribe_Collections.Look(ref escortDataList, "escortDataList", LookMode.Deep);
}
else if (Scribe.mode == LoadSaveMode.LoadingVars)
{
List<FlyOver> keys = new List<FlyOver>();
List<EscortVisualData> values = new List<EscortVisualData>();
Scribe_Collections.Look(ref keys, "escortKeys", LookMode.Reference);
Scribe_Collections.Look(ref values, "escortValues", LookMode.Deep);
// 加载时,先清空现有数据
escortVisualData.Clear();
escortDataByID.Clear();
if (keys != null && values != null && keys.Count == values.Count)
// 加载ID和视觉数据列表
Scribe_Collections.Look(ref escortIDs, "escortIDs", LookMode.Value);
Scribe_Collections.Look(ref escortDataList, "escortDataList", LookMode.Deep);
// 重建ID映射
if (escortIDs != null && escortDataList != null && escortIDs.Count == escortDataList.Count)
{
escortVisualData.Clear();
for (int i = 0; i < keys.Count; i++)
for (int i = 0; i < escortIDs.Count; i++)
{
escortVisualData[keys[i]] = values[i];
escortDataByID[escortIDs[i]] = escortDataList[i];
}
}
Log.Message($"Loaded escort visual data: {escortDataByID.Count} entries");
}
else if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
// 在PostLoadInit阶段重建视觉数据字典
RebuildEscortVisualData();
}
}
// 新增:重建伴飞视觉数据字典
private void RebuildEscortVisualData()
{
escortVisualData.Clear();
foreach (var escort in activeEscorts)
{
if (escort != null && !escort.Destroyed)
{
int escortID = GetEscortID(escort);
if (escortDataByID.TryGetValue(escortID, out var visualData))
{
// 恢复保存的视觉数据
escortVisualData[escort] = visualData;
// 重新设置伴飞的缩放
escort.escortScale = visualData.scale;
Log.Message($"Rebuilt visual data for escort {escortID}: scale={visualData.scale:F2}");
}
else
{
// 如果没有保存的数据,重新生成
var newVisualData = GenerateEscortVisualData();
escortVisualData[escort] = newVisualData;
escort.escortScale = newVisualData.scale;
Log.Message($"Regenerated visual data for escort {escortID}: scale={newVisualData.scale:F2}");
}
}
}
Log.Message($"Rebuilt escort visual data: {escortVisualData.Count} escorts");
}
// 公共方法:强制生成伴飞

View File

@@ -1,7 +1,7 @@
using HarmonyLib;
using RimWorld;
using System.Linq;
using System.Reflection;
using System;
using System.Collections.Generic;
using UnityEngine;
using Verse;
@@ -10,35 +10,71 @@ namespace WulaFallenEmpire.HarmonyPatches
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
public static class Projectile_CheckForFreeInterceptBetween_Patch
{
private static readonly MethodInfo ImpactMethod = AccessTools.Method(typeof(Projectile), "Impact");
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
{
if (__instance.Map == null || __instance.Destroyed) return true;
foreach (Pawn pawn in __instance.Map.mapPawns.AllPawnsSpawned)
try
{
if (pawn.apparel != null)
if (__instance == null || __instance.Map == null || __instance.Destroyed || !__instance.Spawned)
return true;
var map = __instance.Map;
var pawns = map.mapPawns?.AllPawnsSpawned;
if (pawns == null) return true;
foreach (Pawn pawn in pawns)
{
if (pawn == null || !pawn.Spawned || pawn.Dead || pawn.Downed || pawn.apparel == null)
continue;
foreach (Apparel apparel in pawn.apparel.WornApparel)
{
if (apparel.TryGetComp<CompApparelInterceptor>(out var interceptor))
if (apparel?.TryGetComp<CompApparelInterceptor>() is CompApparelInterceptor interceptor)
{
if (interceptor.TryIntercept(__instance, lastExactPos, newExactPos))
try
{
// Directly destroy the projectile instead of calling Impact via reflection.
// This is cleaner and avoids the NRE that happens when the game engine
// continues to process a projectile that was destroyed mid-tick.
__instance.Destroy(DestroyMode.Vanish);
return false; // Prevent original method from running.
if (interceptor.TryIntercept(__instance, lastExactPos, newExactPos))
{
// 简单直接:立即销毁子弹
if (!__instance.Destroyed && __instance.Spawned)
{
__instance.Destroy(DestroyMode.Vanish);
}
return false;
}
}
catch (Exception ex)
{
Log.Warning($"[Interceptor] Error: {ex.Message}");
}
}
}
}
}
return true;
return true;
}
catch (Exception ex)
{
Log.Error($"[Interceptor] Critical error: {ex}");
return true;
}
}
}
}
[HarmonyPatch(typeof(Projectile), "Tick")]
public static class Projectile_Tick_Patch
{
public static bool Prefix(Projectile __instance)
{
return __instance != null && !__instance.Destroyed && __instance.Spawned;
}
}
[HarmonyPatch(typeof(Projectile), "TickInterval")]
public static class Projectile_TickInterval_Patch
{
public static bool Prefix(Projectile __instance, int delta)
{
return __instance != null && !__instance.Destroyed && __instance.Spawned;
}
}
}

View File

@@ -61,36 +61,54 @@ namespace WulaFallenEmpire
public int currentHitPoints = -1;
private int ticksToReset;
private int activatedTick = -999999;
// 视觉效果变量
private bool initialized = false; // 添加初始化标志
// 视觉效果变量
private float lastInterceptAngle;
private bool drawInterceptCone;
// 静态资源
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
private static readonly Color InactiveColor = new Color(0.2f, 0.2f, 0.2f);
// 属性
public CompProperties_ApparelInterceptor Props => (CompProperties_ApparelInterceptor)props;
private Pawn PawnOwner => (parent as Apparel)?.Wearer;
// 修复:确保组件完全初始化的方法
private void EnsureInitialized()
{
if (initialized) return;
if (stunner == null)
stunner = new StunHandler(parent);
if (currentHitPoints == -1)
currentHitPoints = Props.startupDelay > 0 ? 0 : HitPointsMax;
initialized = true;
}
// 修复:安全的 Active 属性
public bool Active
{
get
{
EnsureInitialized();
if (PawnOwner == null || !PawnOwner.Spawned) return false;
if (OnCooldown || Charging || stunner.Stunned || shutDown || currentHitPoints <= 0) return false;
if (Props.activated && Find.TickManager.TicksGame > activatedTick + Props.activeDuration) return false;
// 修复:添加额外的 null 检查
if (stunner == null || OnCooldown || Charging || (stunner != null && stunner.Stunned) || shutDown || currentHitPoints <= 0)
return false;
if (Props.activated && Find.TickManager.TicksGame > activatedTick + Props.activeDuration)
return false;
return true;
}
}
protected bool ShouldDisplay
{
get
{
EnsureInitialized();
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || !Active)
{
return false;
@@ -106,29 +124,22 @@ namespace WulaFallenEmpire
return false;
}
}
public bool OnCooldown => ticksToReset > 0;
public bool Charging => startedChargingTick >= 0 && Find.TickManager.TicksGame < startedChargingTick + Props.startupDelay;
public int CooldownTicksLeft => ticksToReset;
public int ChargingTicksLeft => (startedChargingTick < 0) ? 0 : Mathf.Max(startedChargingTick + Props.startupDelay - Find.TickManager.TicksGame, 0);
public int HitPointsMax => Props.hitPoints;
protected virtual int HitPointsPerInterval => 1;
public override void PostPostMake()
{
base.PostPostMake();
stunner = new StunHandler(parent);
EnsureInitialized();
if (Props.startupDelay > 0)
{
startedChargingTick = Find.TickManager.TicksGame;
currentHitPoints = 0;
}
else
{
currentHitPoints = HitPointsMax;
}
}
public override void PostExposeData()
{
base.PostExposeData();
@@ -138,186 +149,228 @@ namespace WulaFallenEmpire
Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", -1);
Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0);
Scribe_Values.Look(ref activatedTick, "activatedTick", -999999);
Scribe_Values.Look(ref initialized, "initialized", false);
Scribe_Deep.Look(ref stunner, "stunner", parent);
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
if (stunner == null) stunner = new StunHandler(parent);
if (currentHitPoints == -1) currentHitPoints = HitPointsMax;
// 强制重新初始化
initialized = false;
EnsureInitialized();
}
}
// 在 CompApparelInterceptor 类中修改 TryIntercept 方法
public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos)
{
if (PawnOwner == null || !PawnOwner.Spawned || !Active)
try
{
return false;
}
if (!GenGeo.IntersectLineCircleOutline(PawnOwner.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
{
return false;
}
if (!InterceptsProjectile(Props, projectile))
{
return false;
}
bool isHostile = (projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner)) || (projectile.Launcher == null && Props.interceptNonHostileProjectiles);
if (!isHostile)
{
return false;
}
// --- Interception Success ---
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(PawnOwner.TrueCenter());
lastInterceptTicks = Find.TickManager.TicksGame;
drawInterceptCone = true;
if (Props.soundInterceptEffecter != null) Props.soundInterceptEffecter.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
if (projectile.DamageDef == DamageDefOf.EMP && !Props.isImmuneToEMP)
{
BreakShieldEmp(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
}
else if (HitPointsMax > 0)
{
currentHitPoints -= projectile.DamageAmount;
if (currentHitPoints <= 0)
EnsureInitialized();
// 更严格的防御性检查
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed)
return false;
if (projectile == null || projectile.Destroyed || !projectile.Spawned)
return false;
if (!Active)
return false;
// 检查地图匹配
if (PawnOwner.Map == null || projectile.Map == null || PawnOwner.Map != projectile.Map)
return false;
// 检查位置有效性
Vector2 pawnPos = PawnOwner.Position.ToVector2();
Vector2 lastPos = lastExactPos.ToVector2();
Vector2 newPos = newExactPos.ToVector2();
if (!GenGeo.IntersectLineCircleOutline(pawnPos, Props.radius, lastPos, newPos))
return false;
if (!InterceptsProjectile(Props, projectile))
return false;
bool isHostile = (projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner)) ||
(projectile.Launcher == null && Props.interceptNonHostileProjectiles);
if (!isHostile)
return false;
// --- Interception Success ---
try
{
BreakShieldHitpoints(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(PawnOwner.TrueCenter());
lastInterceptTicks = Find.TickManager.TicksGame;
drawInterceptCone = true;
if (Props.soundInterceptEffecter != null && PawnOwner.Map != null)
{
var effecter = Props.soundInterceptEffecter.Spawn(PawnOwner.Position, PawnOwner.Map);
if (effecter != null)
effecter.Cleanup();
}
// 处理伤害
if (projectile.DamageDef == DamageDefOf.EMP && !Props.isImmuneToEMP)
{
BreakShieldEmp(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
}
else if (HitPointsMax > 0)
{
currentHitPoints = Mathf.Max(0, currentHitPoints - projectile.DamageAmount);
if (currentHitPoints <= 0)
{
BreakShieldHitpoints(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
}
}
return true;
}
catch (Exception ex)
{
Log.Warning($"[CompApparelInterceptor] Error during interception effects: {ex.Message}");
return true; // 即使效果出错,仍然返回拦截成功
}
}
return true;
catch (Exception ex)
{
Log.Warning($"[CompApparelInterceptor] Critical error in TryIntercept: {ex.Message}");
return false;
}
}
public override void CompTick()
{
base.CompTick();
if (PawnOwner == null || !PawnOwner.Spawned) return;
try
{
base.CompTick();
EnsureInitialized();
stunner.StunHandlerTick();
if (OnCooldown)
{
ticksToReset--;
if (ticksToReset <= 0) Reset();
if (PawnOwner == null || !PawnOwner.Spawned) return;
// 防御性检查
if (stunner != null)
stunner.StunHandlerTick();
if (OnCooldown)
{
ticksToReset--;
if (ticksToReset <= 0) Reset();
}
else if (Charging)
{
// Charging logic handled by property
}
else if (currentHitPoints < HitPointsMax && parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks))
{
currentHitPoints = Mathf.Clamp(currentHitPoints + HitPointsPerInterval, 0, HitPointsMax);
}
if (Props.activeSound != null)
{
if (Active && (sustainer == null || sustainer.Ended))
sustainer = Props.activeSound.TrySpawnSustainer(SoundInfo.InMap(parent));
sustainer?.Maintain();
if (!Active && sustainer != null && !sustainer.Ended)
sustainer.End();
}
}
else if (Charging)
catch (System.Exception ex)
{
// Charging logic handled by property
}
else if (currentHitPoints < HitPointsMax && parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks))
{
currentHitPoints = Mathf.Clamp(currentHitPoints + HitPointsPerInterval, 0, HitPointsMax);
}
if (Props.activeSound != null)
{
if (Active && (sustainer == null || sustainer.Ended)) sustainer = Props.activeSound.TrySpawnSustainer(SoundInfo.InMap(parent));
sustainer?.Maintain();
if (!Active && sustainer != null && !sustainer.Ended) sustainer.End();
Log.Warning($"[CompApparelInterceptor] Error in CompTick: {ex.Message}");
}
}
public void Reset()
{
if (PawnOwner.Spawned) Props.reactivateEffect?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.Map != null)
Props.reactivateEffect?.Spawn(PawnOwner.Position, PawnOwner.Map).Cleanup();
currentHitPoints = HitPointsMax;
ticksToReset = 0;
}
private void BreakShieldHitpoints(DamageInfo dinfo)
{
if (PawnOwner.Spawned)
if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.MapHeld != null)
{
if (Props.soundBreakEffecter != null) Props.soundBreakEffecter.SpawnAttached(PawnOwner, PawnOwner.MapHeld, Props.radius).Cleanup();
if (Props.soundBreakEffecter != null)
Props.soundBreakEffecter.SpawnAttached(PawnOwner, PawnOwner.MapHeld, Props.radius).Cleanup();
}
currentHitPoints = 0;
ticksToReset = Props.rechargeDelay;
}
private void BreakShieldEmp(DamageInfo dinfo)
{
BreakShieldHitpoints(dinfo);
if (Props.disarmedByEmpForTicks > 0) stunner.Notify_DamageApplied(new DamageInfo(DamageDefOf.EMP, (float)Props.disarmedByEmpForTicks / 30f));
if (Props.disarmedByEmpForTicks > 0 && stunner != null)
stunner.Notify_DamageApplied(new DamageInfo(DamageDefOf.EMP, (float)Props.disarmedByEmpForTicks / 30f));
}
public static bool InterceptsProjectile(CompProperties_ApparelInterceptor props, Projectile projectile)
{
if (projectile.def.projectile.flyOverhead) return props.interceptAirProjectiles;
if (projectile == null || projectile.def == null || projectile.def.projectile == null)
return false;
if (projectile.def.projectile.flyOverhead)
return props.interceptAirProjectiles;
return props.interceptGroundProjectiles;
}
// --- DRAWING LOGIC ---
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
if (PawnOwner == null || !PawnOwner.Spawned || !ShouldDisplay) return;
Vector3 drawPos = PawnOwner.Drawer.DrawPos;
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
float alpha = GetCurrentAlpha();
if (alpha > 0f)
try
{
Color color = Props.color;
color.a *= alpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(Props.radius * 2f * 1.1601562f, 1f, Props.radius * 2f * 1.1601562f));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
base.CompDrawWornExtras();
EnsureInitialized();
if (PawnOwner == null || !PawnOwner.Spawned || !ShouldDisplay) return;
Vector3 drawPos = PawnOwner.Drawer?.DrawPos ?? PawnOwner.Position.ToVector3Shifted();
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
float alpha = GetCurrentAlpha();
if (alpha > 0f)
{
Color color = Props.color;
color.a *= alpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(Props.radius * 2f * 1.1601562f, 1f, Props.radius * 2f * 1.1601562f));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
}
float coneAlpha = GetCurrentConeAlpha_RecentlyIntercepted();
if (coneAlpha > 0f)
{
Color color = Props.color;
color.a *= coneAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(Props.radius * 2f, 1f, Props.radius * 2f));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
}
}
float coneAlpha = GetCurrentConeAlpha_RecentlyIntercepted();
if (coneAlpha > 0f)
catch (System.Exception ex)
{
Color color = Props.color;
color.a *= coneAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(Props.radius * 2f, 1f, Props.radius * 2f));
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock);
Log.Warning($"[CompApparelInterceptor] Error in CompDrawWornExtras: {ex.Message}");
}
}
private float GetCurrentAlpha()
{
float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
return Mathf.Max(idleAlpha, interceptAlpha);
}
private float GetCurrentConeAlpha_RecentlyIntercepted()
{
if (!drawInterceptCone) return 0f;
return Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f) * 0.82f;
}
// --- GIZMO ---
public override IEnumerable<Gizmo> CompGetWornGizmosExtra()
{
EnsureInitialized();
if (PawnOwner != null && Find.Selector.SingleSelectedThing == PawnOwner)
{
yield return new Gizmo_EnergyShieldStatus { shield = this };
}
}
public override string CompInspectStringExtra()
{
EnsureInitialized();
StringBuilder sb = new StringBuilder();
if (OnCooldown)
{
sb.Append("Cooldown: " + CooldownTicksLeft.ToStringTicksToPeriod());
}
else if (stunner.Stunned)
else if (stunner != null && stunner.Stunned)
{
sb.Append("EMP Shutdown: " + stunner.StunTicksLeft.ToStringTicksToPeriod());
}
return sb.ToString();
}
}
[StaticConstructorOnStartup]
public class Gizmo_EnergyShieldStatus : Gizmo
{

View File

@@ -157,8 +157,7 @@ namespace WulaFallenEmpire
Messages.Message("WULA_DataPackCompMissing".Translate(), parent, MessageTypeDefOf.RejectInput);
}
}
// 吸收数据包方法 - 现在会吸收所有附近的数据包并处理溢出
// 吸收数据包方法 - 修复版本
private void AbsorbDataPack()
{
if (Props.dataPackDef == null)
@@ -166,7 +165,6 @@ namespace WulaFallenEmpire
Messages.Message("WULA_NoDataPackDef".Translate(), parent, MessageTypeDefOf.RejectInput);
return;
}
// 查找附近的所有数据包
List<Thing> dataPacks = FindNearbyDataPacks();
if (dataPacks.Count == 0)
@@ -174,96 +172,134 @@ namespace WulaFallenEmpire
Messages.Message("WULA_NoDataPackNearby".Translate(), parent, MessageTypeDefOf.RejectInput);
return;
}
// 计算总可吸收经验
float totalExperienceToAbsorb = 0f;
float totalExperienceAvailable = 0f;
foreach (Thing dataPack in dataPacks)
{
CompExperienceDataPack dataPackComp = dataPack.TryGetComp<CompExperienceDataPack>();
if (dataPackComp != null && dataPackComp.storedExperience > 0)
{
totalExperienceToAbsorb += dataPackComp.storedExperience;
totalExperienceAvailable += dataPackComp.storedExperience;
}
}
if (totalExperienceToAbsorb <= 0)
if (totalExperienceAvailable <= 0)
{
Messages.Message("WULA_DataPackEmpty".Translate(), parent, MessageTypeDefOf.RejectInput);
return;
}
// 计算实际可吸收的经验(不超过最大阈值)
float actualExperienceToAbsorb;
float overflowFromAbsorption = 0f;
if (HasReachedMaxQuality)
// 计算实际需要的经验(如果未达到最大品质)
float experienceNeeded = 0f;
if (!HasReachedMaxQuality)
{
// 如果已经达到最大品质,所有吸收的经验都算作溢出
actualExperienceToAbsorb = 0f;
overflowFromAbsorption = totalExperienceToAbsorb;
}
else
{
float remainingToMax = MaxExperienceThreshold - currentExperience;
if (totalExperienceToAbsorb <= remainingToMax)
{
// 可以完全吸收
actualExperienceToAbsorb = totalExperienceToAbsorb;
overflowFromAbsorption = 0f;
}
else
{
// 只能吸收部分,其余溢出
actualExperienceToAbsorb = remainingToMax;
overflowFromAbsorption = totalExperienceToAbsorb - remainingToMax;
}
experienceNeeded = MaxExperienceThreshold - currentExperience;
}
// 实际吸收的经验量(不超过需要的经验)
float actualExperienceToAbsorb = Mathf.Min(totalExperienceAvailable, experienceNeeded);
// 应用吸收
currentExperience += actualExperienceToAbsorb;
overflowExperience += overflowFromAbsorption;
// 销毁所有被吸收的数据包
foreach (Thing dataPack in dataPacks)
{
dataPack.Destroy();
}
// 检查升级
// 剩余的经验(留在数据包中)
float remainingExperience = totalExperienceAvailable - actualExperienceToAbsorb;
// 应用吸收的经验
if (actualExperienceToAbsorb > 0)
{
CheckForQualityUpgrade();
currentExperience += actualExperienceToAbsorb;
CheckForQualityUpgrade(); // 检查升级
}
// 处理剩余的经验 - 更新数据包而不是创建新的
ProcessRemainingExperience(dataPacks, remainingExperience);
// 发送消息 - 修复阵营检查
SendAbsorptionMessage(actualExperienceToAbsorb, remainingExperience);
// 处理溢出经验 - 如果吸收后有溢出,自动创建一个新的数据包
if (overflowFromAbsorption > 0)
Log.Message($"[ExperienceCore] {parent.Label} absorbed {actualExperienceToAbsorb} experience, remaining: {remainingExperience}, current: {currentExperience}");
}
// 处理剩余的经验 - 更新现有数据包
private void ProcessRemainingExperience(List<Thing> dataPacks, float remainingExperience)
{
if (remainingExperience <= 0)
{
CreateOverflowDataPack(overflowFromAbsorption);
// 没有剩余经验,销毁所有数据包
foreach (Thing dataPack in dataPacks)
{
dataPack.Destroy();
}
return;
}
// 有剩余经验,找到第一个数据包来存储剩余经验
bool foundDataPackForRemaining = false;
// 发送消息
foreach (Thing dataPack in dataPacks)
{
CompExperienceDataPack dataPackComp = dataPack.TryGetComp<CompExperienceDataPack>();
if (dataPackComp != null)
{
if (!foundDataPackForRemaining)
{
// 使用第一个数据包存储剩余经验
dataPackComp.storedExperience = remainingExperience;
dataPackComp.sourceWeaponLabel = parent.Label + " (Remaining)";
foundDataPackForRemaining = true;
}
else
{
// 销毁其他数据包
dataPack.Destroy();
}
}
}
}
// 发送吸收消息 - 修复阵营检查
private void SendAbsorptionMessage(float absorbedExperience, float remainingExperience)
{
// 检查是否应该显示消息给玩家
bool shouldShowMessage = ShouldShowMessageToPlayer();
if (!shouldShowMessage) return;
string messageText;
if (actualExperienceToAbsorb > 0 && overflowFromAbsorption > 0)
if (absorbedExperience > 0 && remainingExperience > 0)
{
messageText = "WULA_DataPackPartiallyAbsorbed".Translate(
actualExperienceToAbsorb.ToString("F0"),
overflowFromAbsorption.ToString("F0")
absorbedExperience.ToString("F0"),
remainingExperience.ToString("F0")
);
}
else if (actualExperienceToAbsorb > 0)
else if (absorbedExperience > 0)
{
messageText = "WULA_DataPackAbsorbed".Translate(actualExperienceToAbsorb.ToString("F0"));
messageText = "WULA_DataPackAbsorbed".Translate(absorbedExperience.ToString("F0"));
}
else
{
messageText = "WULA_DataPackOverflowOnly".Translate(overflowFromAbsorption.ToString("F0"));
messageText = "WULA_NoExperienceAbsorbed".Translate(remainingExperience.ToString("F0"));
}
Messages.Message(messageText, parent, MessageTypeDefOf.PositiveEvent);
Log.Message($"[ExperienceCore] {parent.Label} absorbed {actualExperienceToAbsorb} experience, overflow: {overflowFromAbsorption}, total: {currentExperience}, overflow total: {overflowExperience}");
}
Messages.Message(messageText, parent, MessageTypeDefOf.PositiveEvent);
}
// 检查是否应该显示消息给玩家
private bool ShouldShowMessageToPlayer()
{
// 如果武器被装备,检查装备者的阵营
if (equippedPawn != null)
{
// 只有玩家阵营或者与玩家结盟的阵营才显示消息
return equippedPawn.Faction == Faction.OfPlayer ||
(equippedPawn.Faction != null && equippedPawn.Faction.PlayerRelationKind == FactionRelationKind.Ally);
}
// 如果武器没有被装备,检查武器的持有者(比如在库存中)
Pawn holder = parent.ParentHolder as Pawn;
if (holder != null)
{
return holder.Faction == Faction.OfPlayer ||
(holder.Faction != null && holder.Faction.PlayerRelationKind == FactionRelationKind.Ally);
}
// 如果武器在地上,检查地图是否为玩家地图
if (parent.Map != null)
{
return parent.Map.IsPlayerHome;
}
// 默认不显示
return false;
}
// 创建溢出数据包
private void CreateOverflowDataPack(float overflowAmount)
{
@@ -443,13 +479,12 @@ namespace WulaFallenEmpire
nextThreshold = NextThreshold; // 获取下一个阈值
}
}
private void UpgradeQuality(ExperienceThreshold threshold)
{
var oldQuality = currentQuality;
currentQuality = threshold.quality;
currentThresholdIndex++; // 移动到下一个阈值
// 更新武器的品质组件
var qualityComp = parent.TryGetComp<CompQuality>();
if (qualityComp != null)
@@ -462,23 +497,25 @@ namespace WulaFallenEmpire
Log.Error($"[ExperienceCore] ERROR: {parent.Label} has no CompQuality component!");
return;
}
// 发送升级消息
string messageText;
if (!threshold.messageKey.NullOrEmpty())
// 发送升级消息 - 添加阵营检查
if (ShouldShowMessageToPlayer())
{
messageText = threshold.messageKey.Translate(parent.Label, threshold.quality.GetLabel());
string messageText;
if (!threshold.messageKey.NullOrEmpty())
{
messageText = threshold.messageKey.Translate(parent.Label, threshold.quality.GetLabel());
}
else
{
messageText = "WULA_WeaponUpgraded".Translate(parent.Label, threshold.quality.GetLabel());
}
Messages.Message(messageText, parent, MessageTypeDefOf.PositiveEvent);
}
else
{
messageText = "WULA_WeaponUpgraded".Translate(parent.Label, threshold.quality.GetLabel());
}
Messages.Message(messageText, parent, MessageTypeDefOf.PositiveEvent);
Log.Message($"[ExperienceCore] {parent.Label} upgraded from {oldQuality} to {threshold.quality} at {currentExperience} experience");
}
public override string CompInspectStringExtra()
{
if (!Props.showExperienceInfo)