diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 00da6fd7..849f6821 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/ThingDefs_Misc/Apperals/WULA_Apparel.xml b/1.6/1.6/Defs/ThingDefs_Misc/Apperals/WULA_Apparel.xml index b35b1e63..39abdd7b 100644 --- a/1.6/1.6/Defs/ThingDefs_Misc/Apperals/WULA_Apparel.xml +++ b/1.6/1.6/Defs/ThingDefs_Misc/Apperals/WULA_Apparel.xml @@ -503,16 +503,16 @@
  • - - 1.25 - 150 + 1 + 50 2800 + 3 true - false - false + true + true (0.9, 0.2, 0.2, 0.5) @@ -520,13 +520,14 @@ Shield_Break BulletShieldGenerator_Reactivate + false + false 600 60 -
  • diff --git a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs index 8722f5f9..bad0bcd3 100644 --- a/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs +++ b/Source/WulaFallenEmpire/HarmonyPatches/Projectile_Launch_Patch.cs @@ -7,45 +7,42 @@ using Verse; namespace WulaFallenEmpire.HarmonyPatches { - [HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")] - public static class Projectile_CheckForFreeInterceptBetween_Patch + [HarmonyPatch(typeof(Projectile), "Impact")] + public static class Projectile_Impact_Patch { - public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos) + private static Dictionary bounceCount = new Dictionary(); + + [HarmonyPrefix] + public static bool Prefix(Projectile __instance, Thing hitThing) { try { - if (__instance == null || __instance.Map == null || __instance.Destroyed || !__instance.Spawned) + if (__instance.Destroyed || !__instance.Spawned || hitThing == null) return true; - var map = __instance.Map; - var pawns = map.mapPawns?.AllPawnsSpawned; - if (pawns == null) return true; - - foreach (Pawn pawn in pawns) + // 检查抛射体是否击中了穿戴护盾的 pawn + if (hitThing is Pawn hitPawn) { - if (pawn == null || !pawn.Spawned || pawn.Dead || pawn.Downed || pawn.apparel == null) - continue; - - foreach (Apparel apparel in pawn.apparel.WornApparel) + // 获取 pawn 身上的所有拦截护盾 + var interceptors = GetInterceptorsOnPawn(hitPawn); + + foreach (var interceptor in interceptors) { - if (apparel?.TryGetComp() is CompApparelInterceptor interceptor) + if (interceptor.TryInterceptProjectile(__instance, hitThing)) { - try + // 记录反弹次数 + int currentBounces = bounceCount.TryGetValue(__instance, 0) + 1; + bounceCount[__instance] = currentBounces; + + // 检查最大反弹次数 + if (currentBounces >= interceptor.Props.maxBounces) { - 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}"); + __instance.Destroy(); + return false; } + + // 拦截成功,阻止原版 Impact 逻辑 + return false; } } } @@ -54,27 +51,49 @@ namespace WulaFallenEmpire.HarmonyPatches } catch (Exception ex) { - Log.Error($"[Interceptor] Critical error: {ex}"); + Log.Error($"Error in Projectile_Impact_Patch: {ex}"); return true; } } - } - [HarmonyPatch(typeof(Projectile), "Tick")] - public static class Projectile_Tick_Patch - { - public static bool Prefix(Projectile __instance) + private static List GetInterceptorsOnPawn(Pawn pawn) { - return __instance != null && !__instance.Destroyed && __instance.Spawned; + var result = new List(); + + if (pawn == null || pawn.apparel == null) + return result; + + try + { + foreach (var apparel in pawn.apparel.WornApparel) + { + var interceptor = apparel.GetComp(); + if (interceptor != null && interceptor.Active) + { + result.Add(interceptor); + } + } + } + catch (Exception ex) + { + Log.Error($"Error in GetInterceptorsOnPawn: {ex}"); + } + + return result; } - } - [HarmonyPatch(typeof(Projectile), "TickInterval")] - public static class Projectile_TickInterval_Patch - { - public static bool Prefix(Projectile __instance, int delta) + // 清理反弹计数 - 修复:使用 Thing 的 Destroy 方法 + [HarmonyPatch(typeof(Thing), "Destroy")] + public static class Thing_Destroy_Patch { - return __instance != null && !__instance.Destroyed && __instance.Spawned; + [HarmonyPostfix] + public static void Postfix(Thing __instance) + { + if (__instance is Projectile projectile) + { + bounceCount.Remove(projectile); + } + } } } } diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs index 37b2de23..d96cb358 100644 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs @@ -15,10 +15,9 @@ namespace WulaFallenEmpire } public ThingDef turretDef; - public float angleOffset; - public bool autoAttack = true; + public bool defaultEnabled = true; // 新增:默认启用状态 } [StaticConstructorOnStartup] @@ -88,10 +87,200 @@ namespace WulaFallenEmpire } } + private bool ShouldAttackVolleyTarget + { + get + { + if (!VolleyTargetManager.IsVolleyEnabled(Pawn)) + return false; + + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (!volleyTarget.IsValid) + return false; + + // 检查目标是否在射程内 + float distance = Pawn.Position.DistanceTo(volleyTarget.Cell); + if (distance > AttackVerb.verbProps.range) + return false; + + // 检查是否可以命中目标 + return AttackVerb.CanHitTarget(volleyTarget); + } + } + public override void CompPostTick(ref float severityAdjustment) + { + base.CompPostTick(ref severityAdjustment); + + if (!TurretEnabled) + { + ResetCurrentTarget(); + return; + } + if (!this.CanShoot) + { + return; + } + // 新增:优先处理齐射目标 + if (ShouldAttackVolleyTarget) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + this.currentTarget = volleyTarget; + this.curRotation = (volleyTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + else if (this.currentTarget.IsValid) + { + this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + + this.AttackVerb.VerbTick(); + if (this.AttackVerb.state != VerbState.Bursting) + { + if (this.WarmingUp) + { + this.burstWarmupTicksLeft--; + if (this.burstWarmupTicksLeft == 0) + { + this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true); + this.lastAttackTargetTick = Find.TickManager.TicksGame; + this.lastAttackedTarget = this.currentTarget; + return; + } + } + else + { + if (this.burstCooldownTicksLeft > 0) + { + this.burstCooldownTicksLeft--; + } + if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10)) + { + // 只有在没有齐射目标时才寻找新目标 + if (!ShouldAttackVolleyTarget) + { + this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); + if (this.currentTarget.IsValid) + { + this.burstWarmupTicksLeft = 1; + return; + } + } + this.ResetCurrentTarget(); + } + } + } + } + // 新增:齐射Gizmos + public override IEnumerable CompGetGizmos() + { + // 只有 pawn 被选中且是玩家派系时才显示按钮 + if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn)) + { + // 原有开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleTurret".Translate(), + defaultDesc = "CommandToggleTurretDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ToggleTurret"), + isActive = () => TurretEnabled, + toggleAction = () => TurretEnabled = !TurretEnabled, + hotKey = KeyBindingDefOf.Misc1 + }; + // 新增:齐射开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleVolley".Translate(), + defaultDesc = "CommandToggleVolleyDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/VolleyFire"), + isActive = () => VolleyTargetManager.IsVolleyEnabled(Pawn), + toggleAction = () => VolleyTargetManager.ToggleVolley(Pawn), + hotKey = KeyBindingDefOf.Misc2 + }; + // 新增:设置齐射目标按钮(只在齐射启用时显示) + if (VolleyTargetManager.IsVolleyEnabled(Pawn)) + { + yield return new Command_Action + { + defaultLabel = "CommandSetVolleyTarget".Translate(), + defaultDesc = "CommandSetVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/SetTarget"), + action = () => Find.Targeter.BeginTargeting(TargetingParameters.ForAttack(), + delegate (LocalTargetInfo target) + { + VolleyTargetManager.SetVolleyTarget(Pawn, target); + }, + null, + null, + "SetVolleyTarget".Translate()), + hotKey = KeyBindingDefOf.Misc3 + }; + // 新增:清除齐射目标按钮 + LocalTargetInfo currentVolleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (currentVolleyTarget.IsValid) + { + yield return new Command_Action + { + defaultLabel = "CommandClearVolleyTarget".Translate(), + defaultDesc = "CommandClearVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ClearTarget"), + action = () => VolleyTargetManager.ClearVolleyTarget(Pawn), + hotKey = KeyBindingDefOf.Misc4 + }; + } + } + } + } + // 新增:在提示中显示齐射状态 + public override string CompTipStringExtra + { + get + { + string baseString = base.CompTipStringExtra; + string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; + + string volleyStatus = "Volley: "; + if (VolleyTargetManager.IsVolleyEnabled(Pawn)) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (volleyTarget.IsValid) + { + volleyStatus += $"Targeting {volleyTarget.Thing?.LabelCap ?? volleyTarget.Cell.ToString()}"; + } + else + { + volleyStatus += "Enabled (No Target)"; + } + } + else + { + volleyStatus += "Disabled"; + } + string result = turretStatus + "\n" + volleyStatus; + return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result; + } + } + + // 新增:炮塔启用状态 + public bool TurretEnabled + { + get { return turretEnabled; } + set + { + turretEnabled = value; + if (!turretEnabled) + { + ResetCurrentTarget(); // 禁用时重置目标 + } + } + } + private bool CanShoot { get { + // 新增:检查炮塔是否启用 + if (!TurretEnabled) + return false; + Pawn pawn; if ((pawn = (this.Pawn)) != null) { @@ -150,6 +339,8 @@ namespace WulaFallenEmpire { base.CompPostMake(); this.MakeGun(); + // 新增:设置默认启用状态 + TurretEnabled = Props.defaultEnabled; } private void MakeGun() @@ -171,9 +362,18 @@ namespace WulaFallenEmpire }; } } + public override void CompPostTick(ref float severityAdjustment) { base.CompPostTick(ref severityAdjustment); + + // 新增:只在启用状态下执行攻击逻辑 + if (!TurretEnabled) + { + ResetCurrentTarget(); + return; + } + if (!this.CanShoot) { return; @@ -230,6 +430,9 @@ namespace WulaFallenEmpire Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget"); Scribe_Deep.Look(ref this.gun, "gun", Array.Empty()); Scribe_Values.Look(ref this.fireAtWill, "fireAtWill", true, false); + // 新增:保存启用状态 + Scribe_Values.Look(ref this.turretEnabled, "turretEnabled", Props.defaultEnabled, false); + if (Scribe.mode == LoadSaveMode.PostLoadInit) { if (this.gun == null) @@ -242,27 +445,52 @@ namespace WulaFallenEmpire } } + // 新增:实现 Gizmo 接口 + public override IEnumerable CompGetGizmos() + { + // 只有 pawn 被选中且是玩家派系时才显示按钮 + if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn)) + { + yield return new Command_Toggle + { + defaultLabel = "CommandToggleTurret".Translate(), + defaultDesc = "CommandToggleTurretDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ToggleTurret"), + isActive = () => TurretEnabled, + toggleAction = () => TurretEnabled = !TurretEnabled, + hotKey = KeyBindingDefOf.Misc1 + }; + } + } + + // 新增:在绘制时显示状态 + public override string CompTipStringExtra + { + get + { + string baseString = base.CompTipStringExtra; + string status = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; + return string.IsNullOrEmpty(baseString) ? status : baseString + "\n" + status; + } + } + private const int StartShootIntervalTicks = 10; private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret"); public Thing gun; - protected int burstCooldownTicksLeft; - protected int burstWarmupTicksLeft; - protected LocalTargetInfo currentTarget = LocalTargetInfo.Invalid; - private bool fireAtWill = true; - private LocalTargetInfo lastAttackedTarget = LocalTargetInfo.Invalid; - private int lastAttackTargetTick; - private float curRotation; + + // 新增:炮塔启用状态字段 + private bool turretEnabled = true; [Unsaved(false)] public Material turretMat; } -} \ No newline at end of file +} diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs new file mode 100644 index 00000000..de05cc74 --- /dev/null +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Verse; +using RimWorld; + +namespace WulaFallenEmpire +{ + public static class VolleyTargetManager + { + private static Dictionary volleyTargets = new Dictionary(); + private static Dictionary volleyEnabled = new Dictionary(); + + public static void SetVolleyTarget(Pawn pawn, LocalTargetInfo target) + { + if (pawn == null) return; + + volleyTargets[pawn] = target; + volleyEnabled[pawn] = target.IsValid; + } + + public static void ClearVolleyTarget(Pawn pawn) + { + if (pawn == null) return; + + volleyTargets.Remove(pawn); + volleyEnabled.Remove(pawn); + } + + public static LocalTargetInfo GetVolleyTarget(Pawn pawn) + { + if (pawn == null || !volleyTargets.ContainsKey(pawn)) + return LocalTargetInfo.Invalid; + + return volleyTargets[pawn]; + } + + public static bool IsVolleyEnabled(Pawn pawn) + { + if (pawn == null || !volleyEnabled.ContainsKey(pawn)) + return false; + + return volleyEnabled[pawn]; + } + + public static void ToggleVolley(Pawn pawn) + { + if (pawn == null) return; + + bool current = IsVolleyEnabled(pawn); + volleyEnabled[pawn] = !current; + + // 如果禁用齐射,清除目标 + if (!volleyEnabled[pawn]) + { + ClearVolleyTarget(pawn); + } + } + } +} diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md new file mode 100644 index 00000000..414ac63c --- /dev/null +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md @@ -0,0 +1,462 @@ +## 🤖 Assistant + +我来帮您为这两个炮塔类添加齐射目标功能。我们需要创建一个共享的目标系统,让所有炮塔可以协同攻击同一个目标。 + +## 1. 首先创建共享目标管理器 + +```csharp +using System.Collections.Generic; +using Verse; +using RimWorld; + +namespace WulaFallenEmpire +{ + public static class VolleyTargetManager + { + private static Dictionary volleyTargets = new Dictionary(); + private static Dictionary volleyEnabled = new Dictionary(); + + public static void SetVolleyTarget(Pawn pawn, LocalTargetInfo target) + { + if (pawn == null) return; + + volleyTargets[pawn] = target; + volleyEnabled[pawn] = target.IsValid; + } + + public static void ClearVolleyTarget(Pawn pawn) + { + if (pawn == null) return; + + volleyTargets.Remove(pawn); + volleyEnabled.Remove(pawn); + } + + public static LocalTargetInfo GetVolleyTarget(Pawn pawn) + { + if (pawn == null || !volleyTargets.ContainsKey(pawn)) + return LocalTargetInfo.Invalid; + + return volleyTargets[pawn]; + } + + public static bool IsVolleyEnabled(Pawn pawn) + { + if (pawn == null || !volleyEnabled.ContainsKey(pawn)) + return false; + + return volleyEnabled[pawn]; + } + + public static void ToggleVolley(Pawn pawn) + { + if (pawn == null) return; + + bool current = IsVolleyEnabled(pawn); + volleyEnabled[pawn] = !current; + + // 如果禁用齐射,清除目标 + if (!volleyEnabled[pawn]) + { + ClearVolleyTarget(pawn); + } + } + } +} +``` + +## 2. 修改 HediffComp_TopTurret 添加齐射功能 + +```csharp +using System; +using System.Collections.Generic; +using UnityEngine; +using Verse; +using Verse.AI; +using RimWorld; + +namespace WulaFallenEmpire +{ + [StaticConstructorOnStartup] + public class HediffComp_TopTurret : HediffComp, IAttackTargetSearcher + { + // ... 保留原有属性和字段 ... + + // 新增:检查是否应该攻击齐射目标 + private bool ShouldAttackVolleyTarget + { + get + { + if (!VolleyTargetManager.IsVolleyEnabled(Pawn)) + return false; + + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (!volleyTarget.IsValid) + return false; + + // 检查目标是否在射程内 + float distance = Pawn.Position.DistanceTo(volleyTarget.Cell); + if (distance > AttackVerb.verbProps.range) + return false; + + // 检查是否可以命中目标 + return AttackVerb.CanHitTarget(volleyTarget); + } + } + + public override void CompPostTick(ref float severityAdjustment) + { + base.CompPostTick(ref severityAdjustment); + + if (!TurretEnabled) + { + ResetCurrentTarget(); + return; + } + + if (!this.CanShoot) + { + return; + } + + // 新增:优先处理齐射目标 + if (ShouldAttackVolleyTarget) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + this.currentTarget = volleyTarget; + this.curRotation = (volleyTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + else if (this.currentTarget.IsValid) + { + this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; + } + + this.AttackVerb.VerbTick(); + if (this.AttackVerb.state != VerbState.Bursting) + { + if (this.WarmingUp) + { + this.burstWarmupTicksLeft--; + if (this.burstWarmupTicksLeft == 0) + { + this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true); + this.lastAttackTargetTick = Find.TickManager.TicksGame; + this.lastAttackedTarget = this.currentTarget; + return; + } + } + else + { + if (this.burstCooldownTicksLeft > 0) + { + this.burstCooldownTicksLeft--; + } + if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10)) + { + // 只有在没有齐射目标时才寻找新目标 + if (!ShouldAttackVolleyTarget) + { + this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); + if (this.currentTarget.IsValid) + { + this.burstWarmupTicksLeft = 1; + return; + } + } + this.ResetCurrentTarget(); + } + } + } + } + + // 新增:齐射Gizmos + public override IEnumerable CompGetGizmos() + { + // 只有 pawn 被选中且是玩家派系时才显示按钮 + if (this.Pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(this.Pawn)) + { + // 原有开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleTurret".Translate(), + defaultDesc = "CommandToggleTurretDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ToggleTurret"), + isActive = () => TurretEnabled, + toggleAction = () => TurretEnabled = !TurretEnabled, + hotKey = KeyBindingDefOf.Misc1 + }; + + // 新增:齐射开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleVolley".Translate(), + defaultDesc = "CommandToggleVolleyDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/VolleyFire"), + isActive = () => VolleyTargetManager.IsVolleyEnabled(Pawn), + toggleAction = () => VolleyTargetManager.ToggleVolley(Pawn), + hotKey = KeyBindingDefOf.Misc2 + }; + + // 新增:设置齐射目标按钮(只在齐射启用时显示) + if (VolleyTargetManager.IsVolleyEnabled(Pawn)) + { + yield return new Command_Action + { + defaultLabel = "CommandSetVolleyTarget".Translate(), + defaultDesc = "CommandSetVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/SetTarget"), + action = () => Find.Targeter.BeginTargeting(TargetingParameters.ForAttack(), + delegate(LocalTargetInfo target) + { + VolleyTargetManager.SetVolleyTarget(Pawn, target); + }, + null, + null, + "SetVolleyTarget".Translate()), + hotKey = KeyBindingDefOf.Misc3 + }; + + // 新增:清除齐射目标按钮 + LocalTargetInfo currentVolleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (currentVolleyTarget.IsValid) + { + yield return new Command_Action + { + defaultLabel = "CommandClearVolleyTarget".Translate(), + defaultDesc = "CommandClearVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ClearTarget"), + action = () => VolleyTargetManager.ClearVolleyTarget(Pawn), + hotKey = KeyBindingDefOf.Misc4 + }; + } + } + } + } + + // 新增:在提示中显示齐射状态 + public override string CompTipStringExtra + { + get + { + string baseString = base.CompTipStringExtra; + string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; + + string volleyStatus = "Volley: "; + if (VolleyTargetManager.IsVolleyEnabled(Pawn)) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn); + if (volleyTarget.IsValid) + { + volleyStatus += $"Targeting {volleyTarget.Thing?.LabelCap ?? volleyTarget.Cell.ToString()}"; + } + else + { + volleyStatus += "Enabled (No Target)"; + } + } + else + { + volleyStatus += "Disabled"; + } + + string result = turretStatus + "\n" + volleyStatus; + return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result; + } + } + + // ... 保留其他原有代码 ... + } +} +``` + +## 3. 修改 Comp_MultiTurretGun 添加齐射功能 + +```csharp +using System; +using System.Collections.Generic; +using Verse; +using Verse.AI; +using RimWorld; +using UnityEngine; + +namespace WulaFallenEmpire +{ + public class Comp_MultiTurretGun : CompTurretGun + { + // ... 保留原有属性和字段 ... + + // 新增:检查是否应该攻击齐射目标 + private bool ShouldAttackVolleyTarget + { + get + { + if (parent is not Pawn pawn) + return false; + + if (!VolleyTargetManager.IsVolleyEnabled(pawn)) + return false; + + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn); + if (!volleyTarget.IsValid) + return false; + + // 检查目标是否在射程内 + float distance = parent.Position.DistanceTo(volleyTarget.Cell); + if (distance > AttackVerb.verbProps.range) + return false; + + // 检查是否可以命中目标 + return AttackVerb.CanHitTarget(volleyTarget); + } + } + + public override void CompTick() + { + base.CompTick(); + + // 新增:优先处理齐射目标 + if (ShouldAttackVolleyTarget && parent is Pawn pawn) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn); + currentTarget = volleyTarget; + curRotation = (volleyTarget.Cell.ToVector3Shifted() - parent.DrawPos).AngleFlat() + Props.angleOffset; + } + else if (!currentTarget.IsValid && burstCooldownTicksLeft <= 0) + { + // 在其他情况下没有目标且冷却结束时也回正 + curRotation = parent.Rotation.AsAngle + Props.angleOffset; + } + } + + // 新增:重写目标选择逻辑以支持齐射 + protected override void TryStartShootSomething(bool canBeginBurstImmediately) + { + // 优先检查齐射目标 + if (ShouldAttackVolleyTarget && parent is Pawn pawn) + { + LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(pawn); + if (volleyTarget.IsValid) + { + currentTarget = volleyTarget; + if (canBeginBurstImmediately) + { + BeginBurst(); + } + else + { + burstWarmupTicksLeft = AttackVerb.verbProps.warmupTime.SecondsToTicks(); + } + return; + } + } + + // 如果没有齐射目标,使用原逻辑 + base.TryStartShootSomething(canBeginBurstImmediately); + } + + // 新增:Gizmos + public override IEnumerable CompGetGizmos() + { + foreach (Gizmo gizmo in base.CompGetGizmos()) + { + yield return gizmo; + } + + // 只有 parent 是 pawn 且是玩家派系时才显示齐射按钮 + if (parent is Pawn pawn && pawn.Faction == Faction.OfPlayer && Find.Selector.IsSelected(parent)) + { + // 齐射开关按钮 + yield return new Command_Toggle + { + defaultLabel = "CommandToggleVolley".Translate(), + defaultDesc = "CommandToggleVolleyDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/VolleyFire"), + isActive = () => VolleyTargetManager.IsVolleyEnabled(pawn), + toggleAction = () => VolleyTargetManager.ToggleVolley(pawn), + hotKey = KeyBindingDefOf.Misc2 + }; + + // 设置齐射目标按钮(只在齐射启用时显示) + if (VolleyTargetManager.IsVolleyEnabled(pawn)) + { + yield return new Command_Action + { + defaultLabel = "CommandSetVolleyTarget".Translate(), + defaultDesc = "CommandSetVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/SetTarget"), + action = () => Find.Targeter.BeginTargeting(TargetingParameters.ForAttack(), + delegate(LocalTargetInfo target) + { + VolleyTargetManager.SetVolleyTarget(pawn, target); + }, + null, + null, + "SetVolleyTarget".Translate()), + hotKey = KeyBindingDefOf.Misc3 + }; + + // 清除齐射目标按钮 + LocalTargetInfo currentVolleyTarget = VolleyTargetManager.GetVolleyTarget(pawn); + if (currentVolleyTarget.IsValid) + { + yield return new Command_Action + { + defaultLabel = "CommandClearVolleyTarget".Translate(), + defaultDesc = "CommandClearVolleyTargetDesc".Translate(), + icon = ContentFinder.Get("UI/Gizmos/ClearTarget"), + action = () => VolleyTargetManager.ClearVolleyTarget(pawn), + hotKey = KeyBindingDefOf.Misc4 + }; + } + } + } + } + + // ... 保留其他原有代码 ... + } +} +``` + +## 4. 添加翻译字符串 + +在您的语言文件中添加: + +```xml + +Volley Fire +Enable or disable coordinated volley fire for all turrets. +Set Volley Target +Set a coordinated target for all turrets to focus fire. +Clear Volley Target +Clear the coordinated volley target. +Set volley target +``` + +## 5. 需要准备的图标 + +您需要在 `Textures/UI/Gizmos/` 文件夹中准备以下图标: +- `VolleyFire.png` - 齐射开关图标 +- `SetTarget.png` - 设置目标图标 +- `ClearTarget.png` - 清除目标图标 + +## 功能说明 + +### 工作流程: +1. **启用齐射模式**:点击齐射开关按钮启用协同攻击 +2. **设置目标**:在齐射模式下点击设置目标按钮,选择要攻击的目标 +3. **自动协同**:所有炮塔会优先攻击齐射目标,如果目标在射程内且可攻击 +4. **智能回退**:如果齐射目标无效或不在射程内,炮塔会恢复原来自动攻击逻辑 +5. **独立控制**:每个 pawn 有独立的齐射目标和开关状态 + +### 优先级系统: +1. 齐射目标(如果启用且有效) +2. 原有自动攻击目标 +3. 空闲状态 + +### 优势: +- **协同攻击**:所有炮塔集中火力攻击同一目标 +- **灵活控制**:可以随时启用/禁用齐射模式 +- **兼容性**:不影响原有的单个炮塔开关功能 +- **智能判断**:自动检查射程和攻击可行性 + +这样您就为两个炮塔类添加了完整的齐射目标功能,让多个炮塔可以协同攻击同一个目标! diff --git a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs index 7076a760..6b243f44 100644 --- a/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs +++ b/Source/WulaFallenEmpire/ThingComp/CompApparelInterceptor.cs @@ -11,10 +11,11 @@ namespace WulaFallenEmpire { public class CompProperties_ApparelInterceptor : CompProperties { - public float radius = 3f; + public float radius = 3f; // 仅用于视觉效果 public int startupDelay = 0; public int rechargeDelay = 3200; public int hitPoints = 100; + public int maxBounces = 3; public bool interceptGroundProjectiles = false; public bool interceptNonHostileProjectiles = false; @@ -61,19 +62,180 @@ namespace WulaFallenEmpire public int currentHitPoints = -1; private int ticksToReset; private int activatedTick = -999999; - private bool initialized = false; // 添加初始化标志 - // 视觉效果变量 + 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; - // 修复:确保组件完全初始化的方法 + public Pawn PawnOwner => (parent as Apparel)?.Wearer; + + // 主要拦截方法 + public bool TryInterceptProjectile(Projectile projectile, Thing hitThing) + { + try + { + 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; + + // 关键检查:抛射体是否要击中这个护盾的穿戴者? + if (hitThing != PawnOwner) + 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; + + // --- 拦截成功 --- + InterceptSuccess(projectile); + return true; + } + catch (Exception ex) + { + Log.Warning($"[CompApparelInterceptor] Error in TryInterceptProjectile: {ex.Message}"); + return false; + } + } + + private void InterceptSuccess(Projectile projectile) + { + try + { + // 记录拦截角度用于视觉效果 + 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 + { + // 固定减少1点护盾量 + currentHitPoints = Mathf.Max(0, currentHitPoints - 1); + if (currentHitPoints <= 0) + { + BreakShieldHitpoints(new DamageInfo(projectile.DamageDef, 1, instigator: projectile.Launcher)); + } + } + + // 反弹抛射体 + BounceProjectile(projectile); + } + catch (Exception ex) + { + Log.Warning($"[CompApparelInterceptor] Error during interception effects: {ex.Message}"); + } + } + + private void BounceProjectile(Projectile projectile) + { + try + { + if (projectile == null || projectile.Destroyed) + return; + + // 计算反弹方向 + Vector3 bounceDirection = CalculateBounceDirection(projectile); + + // 使用 Traverse 修改抛射体方向 + var traverse = Traverse.Create(projectile); + + // 修改目的地 - 随机弹射 + Vector3 newDestination = projectile.ExactPosition + bounceDirection * 30f; + traverse.Field("destination").SetValue(newDestination); + + // 重置起点为当前位置 + traverse.Field("origin").SetValue(projectile.ExactPosition); + + // 重新计算飞行时间 + float distance = (newDestination - projectile.ExactPosition).MagnitudeHorizontal(); + int newTicks = Mathf.CeilToInt(distance / projectile.def.projectile.SpeedTilesPerTick); + traverse.Field("ticksToImpact").SetValue(newTicks); + + // 播放反弹效果 + PlayBounceEffect(projectile); + + Log.Message($"[Interceptor] Projectile bounced towards {bounceDirection}"); + } + catch (Exception ex) + { + Log.Error($"Error in BounceProjectile: {ex}"); + } + } + + private Vector3 CalculateBounceDirection(Projectile projectile) + { + // 如果有发射者,尝试弹向发射者 + if (projectile.Launcher != null && projectile.Launcher.Spawned) + { + try + { + Vector3 toLauncher = (projectile.Launcher.Position.ToVector3() - projectile.ExactPosition).normalized; + return toLauncher; + } + catch + { + // 如果计算失败,使用随机方向 + } + } + + // 随机弹射方向 + float angle = Rand.Range(0f, 360f); + return Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward; + } + + private void PlayBounceEffect(Projectile projectile) + { + // 播放自定义反弹效果 + if (Props.soundInterceptEffecter != null) + { + var effecter = Props.soundInterceptEffecter.Spawn(projectile.Position, projectile.Map); + effecter?.Cleanup(); + } + + // 添加视觉特效 + FleckMaker.Static(projectile.ExactPosition, projectile.Map, FleckDefOf.ShotFlash, 1.5f); + } + + // 其余现有方法保持不变... private void EnsureInitialized() { if (initialized) return; @@ -86,7 +248,7 @@ namespace WulaFallenEmpire initialized = true; } - // 修复:安全的 Active 属性 + public bool Active { get @@ -94,7 +256,6 @@ namespace WulaFallenEmpire EnsureInitialized(); if (PawnOwner == null || !PawnOwner.Spawned) return false; - // 修复:添加额外的 null 检查 if (stunner == null || OnCooldown || Charging || (stunner != null && stunner.Stunned) || shutDown || currentHitPoints <= 0) return false; @@ -104,32 +265,15 @@ namespace WulaFallenEmpire return true; } } - protected bool ShouldDisplay - { - get - { - EnsureInitialized(); - if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || !Active) - { - return false; - } - if (PawnOwner.Drafted || PawnOwner.InAggroMentalState || (PawnOwner.Faction != null && PawnOwner.Faction.HostileTo(Faction.OfPlayer) && !PawnOwner.IsPrisoner)) - { - return true; - } - if (Find.Selector.IsSelected(PawnOwner)) - { - return true; - } - 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(); @@ -140,6 +284,7 @@ namespace WulaFallenEmpire startedChargingTick = Find.TickManager.TicksGame; } } + public override void PostExposeData() { base.PostExposeData(); @@ -153,79 +298,11 @@ namespace WulaFallenEmpire Scribe_Deep.Look(ref stunner, "stunner", parent); if (Scribe.mode == LoadSaveMode.PostLoadInit) { - // 强制重新初始化 initialized = false; EnsureInitialized(); } } - // 在 CompApparelInterceptor 类中修改 TryIntercept 方法 - public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos) - { - try - { - 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 - { - 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; // 即使效果出错,仍然返回拦截成功 - } - } - catch (Exception ex) - { - Log.Warning($"[CompApparelInterceptor] Critical error in TryIntercept: {ex.Message}"); - return false; - } - } public override void CompTick() { try @@ -234,9 +311,10 @@ namespace WulaFallenEmpire EnsureInitialized(); if (PawnOwner == null || !PawnOwner.Spawned) return; - // 防御性检查 + if (stunner != null) stunner.StunHandlerTick(); + if (OnCooldown) { ticksToReset--; @@ -250,6 +328,7 @@ namespace WulaFallenEmpire { currentHitPoints = Mathf.Clamp(currentHitPoints + HitPointsPerInterval, 0, HitPointsMax); } + if (Props.activeSound != null) { if (Active && (sustainer == null || sustainer.Ended)) @@ -264,6 +343,7 @@ namespace WulaFallenEmpire Log.Warning($"[CompApparelInterceptor] Error in CompTick: {ex.Message}"); } } + public void Reset() { if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.Map != null) @@ -272,6 +352,7 @@ namespace WulaFallenEmpire currentHitPoints = HitPointsMax; ticksToReset = 0; } + private void BreakShieldHitpoints(DamageInfo dinfo) { if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.MapHeld != null) @@ -282,12 +363,14 @@ namespace WulaFallenEmpire currentHitPoints = 0; ticksToReset = Props.rechargeDelay; } + private void BreakShieldEmp(DamageInfo dinfo) { BreakShieldHitpoints(dinfo); 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 == null || projectile.def == null || projectile.def.projectile == null) @@ -297,7 +380,27 @@ namespace WulaFallenEmpire return props.interceptAirProjectiles; return props.interceptGroundProjectiles; } - // --- DRAWING LOGIC --- + + // 视觉效果相关方法保持不变... + protected bool ShouldDisplay + { + get + { + EnsureInitialized(); + if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || !Active) + return false; + + if (PawnOwner.Drafted || PawnOwner.InAggroMentalState || + (PawnOwner.Faction != null && PawnOwner.Faction.HostileTo(Faction.OfPlayer) && !PawnOwner.IsPrisoner)) + return true; + + if (Find.Selector.IsSelected(PawnOwner)) + return true; + + return false; + } + } + public override void CompDrawWornExtras() { try @@ -306,8 +409,10 @@ namespace WulaFallenEmpire 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) { @@ -318,6 +423,7 @@ namespace WulaFallenEmpire 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) { @@ -334,18 +440,21 @@ namespace WulaFallenEmpire 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 --- + + // Gizmo 相关保持不变... public override IEnumerable CompGetWornGizmosExtra() { EnsureInitialized(); @@ -355,6 +464,7 @@ namespace WulaFallenEmpire yield return new Gizmo_EnergyShieldStatus { shield = this }; } } + public override string CompInspectStringExtra() { EnsureInitialized(); @@ -371,6 +481,8 @@ namespace WulaFallenEmpire return sb.ToString(); } } + + // Gizmo 类保持不变... [StaticConstructorOnStartup] public class Gizmo_EnergyShieldStatus : Gizmo { diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj index 0f3f434f..107a03fc 100644 --- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj +++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj @@ -164,6 +164,7 @@ +