diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index e184d87c..00da6fd7 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs b/Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs index a1663173..227971bf 100644 --- a/Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs +++ b/Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs @@ -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) { diff --git a/Source/WulaFallenEmpire/Flyover/WULA_FlyOverEscort/CompFlyOverEscort.cs b/Source/WulaFallenEmpire/Flyover/WULA_FlyOverEscort/CompFlyOverEscort.cs index 909f7389..d8877cf8 100644 --- a/Source/WulaFallenEmpire/Flyover/WULA_FlyOverEscort/CompFlyOverEscort.cs +++ b/Source/WulaFallenEmpire/Flyover/WULA_FlyOverEscort/CompFlyOverEscort.cs @@ -17,6 +17,11 @@ namespace WulaFallenEmpire // 存储每个伴飞的缩放和遮罩数据 private Dictionary escortVisualData = new Dictionary(); + // 新增:伴飞ID映射,用于解决存档加载时的引用问题 + private Dictionary escortDataByID = new Dictionary(); + private List escortIDs = new List(); + private List escortDataList = new List(); + 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 keys = new List(escortVisualData.Keys); - List values = new List(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 keys = new List(); - List values = new List(); - 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"); } // 公共方法:强制生成伴飞 diff --git a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs index aed6001e..8722f5f9 100644 --- a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs +++ b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs @@ -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(out var interceptor)) + if (apparel?.TryGetComp() 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; + } } } -} \ No newline at end of file + + [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; + } + } +} diff --git a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs index 8de2e862..7076a760 100644 --- a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs +++ b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs @@ -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 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 { diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_PersonaCore/CompExperienceCore.cs b/Source/WulaFallenEmpire/ThingComp/WULA_PersonaCore/CompExperienceCore.cs index 7de207e7..bdf1eff2 100644 --- a/Source/WulaFallenEmpire/ThingComp/WULA_PersonaCore/CompExperienceCore.cs +++ b/Source/WulaFallenEmpire/ThingComp/WULA_PersonaCore/CompExperienceCore.cs @@ -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 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(); 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 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(); + 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(); 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) diff --git a/美术与文本源文件/Wula/Things/Wula_AI_Heavy_Panzer/Bodies/Naked_Thin_east.sai2 b/美术与文本源文件/Wula/Things/Wula_AI_Heavy_Panzer/Bodies/Naked_Thin_east.sai2 index 6f10f2aa..740f2b2c 100644 Binary files a/美术与文本源文件/Wula/Things/Wula_AI_Heavy_Panzer/Bodies/Naked_Thin_east.sai2 and b/美术与文本源文件/Wula/Things/Wula_AI_Heavy_Panzer/Bodies/Naked_Thin_east.sai2 differ