diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 3eaef9a8..0e1e3c11 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/AbilityDefs/WULA_Misc_Ability.xml b/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml index 6e90b062..507bc130 100644 --- a/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml +++ b/1.6/1.6/Defs/AbilityDefs/WULA_Misc_Ability.xml @@ -60,8 +60,8 @@ 600 Verb_CastAbility - 24 - 0 + 18 + 1 WarqueenWarUrchinsSpawned true @@ -85,36 +85,16 @@
  • - 12 - 5 + 18 + 12 true false false - Fire_Spew - Fire_SpewShort + Fire_SpewShort + 60 + Fire_Spew + 30
  • -
  • - Fire_Spew - 20 -
  • - -
  • - Fire_SpewShort -
  • -
    \ No newline at end of file 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 39abdd7b..77bfc54f 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 @@ -502,33 +502,6 @@ Wula/Apparel/WULA_Knight_PowerArmor -
  • - - 1 - 50 - 2800 - 3 - - - true - true - true - - - (0.9, 0.2, 0.2, 0.5) - Interceptor_BlockedProjectile - Shield_Break - BulletShieldGenerator_Reactivate - - false - - - false - 600 - - - 60 -
  • @@ -624,30 +597,31 @@ Wula/Apparel/WULA_Heavy_Infantry_PowerArmor -
  • - - 1.5 - 200 - 2800 - - - true - false - false - - - (0.9, 0.2, 0.2, 0.5) - Interceptor_BlockedProjectile - Shield_Break - BulletShieldGenerator_Reactivate - - - false - 600 - - - 60 - +
  • + 3 + 50 + 2400 + 30 + + + Interceptor_BlockedProjectile + Interceptor_BlockedProjectile + Shield_Break + BulletShieldGenerator_Reactivate + + (0.9, 0.2, 0.2, 0.2) + + + true + false + true + + + true + 1 + 30 + 1 + Interceptor_BlockedProjectile
  • WULA_Heavy_Infantry_PowerArmor_Mortar_Hediff diff --git a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml index 0d3a8370..e8141d5c 100644 --- a/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml +++ b/1.6/1.6/Languages/ChineseSimplified (简体中文)/Keyed/Misc_Gameplay.xml @@ -344,4 +344,17 @@ 什一税征收:{0} {1} {2} - 征收已完成 - 逾期 + + + 冷却中 + 未激活 + + + + 区域护盾状态:\n最大生命值:{0}\n保护半径:{1}格\n破碎冷却:{3}秒 + + 拦截设置:\n- 地面投射物:{0}\n- 空中投射物:{1}\n- 非敌对投射物:{2} + + 冷却剩余时间:{0}秒 + 护盾未激活:\n- 装备未被穿戴\n- 穿戴者死亡或倒下\n- 护盾已破碎 \ No newline at end of file diff --git a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs index be21297e..55cc81bc 100644 --- a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs +++ b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompAbilityEffect_AreaDestruction.cs @@ -21,6 +21,9 @@ namespace WulaFallenEmpire Map map = parent.pawn.MapHeld; if (map == null) return; + // 播放发射特效(在施法者位置) + PlayCastEffecter(map); + // 获取扇形区域内的所有单元格 List affectedCells = AffectedCells(target); @@ -47,29 +50,41 @@ namespace WulaFallenEmpire { affectedTargets.Add(thing); } - - // 根据事物类型进行处理 - if (thing is Building building) - { - DestroyBuilding(building); - } - else if (thing is Pawn targetPawn) - { - DestroyAllBodyParts(targetPawn); - } } } - // 为每个受影响的目标播放命中效果器 + // 为每个受影响的目标播放命中效果器并处理效果 foreach (Thing affectedThing in affectedTargets) { PlayHitEffecter(affectedThing, map); + ProcessTarget(affectedThing); } - - // 播放主要效果 - if (Props.effecterDef != null) + } + + private void PlayCastEffecter(Map map) + { + try { - Props.effecterDef.Spawn(target.Cell, map).Cleanup(); + if (Props.castEffecter == null) return; + + // 使用与 CompAbilityEffect_EffecterOnCaster 相同的方法 + Effecter effecter = Props.castEffecter.Spawn(Pawn, map); + + if (Props.castEffecterMaintainTicks > 0) + { + // 使用与参考代码相同的方法来维持效果器 + map.effecterMaintainer.AddEffecterToMaintain(effecter, new TargetInfo(Pawn), Pawn, Props.castEffecterMaintainTicks); + } + else + { + effecter.Cleanup(); + } + + Log.Message($"[AreaDestruction] Played cast effecter on caster at {Pawn.Position}"); + } + catch (System.Exception ex) + { + Log.Warning($"[AreaDestruction] Error playing cast effecter: {ex.Message}"); } } @@ -80,28 +95,25 @@ namespace WulaFallenEmpire if (Props.hitEffecter == null) return; if (target == null || target.Destroyed) return; - // 创建效果器 - Effecter effecter = Props.hitEffecter.Spawn(); - - // 计算效果器方向(从目标指向施法者) - TargetInfo targetInfo = new TargetInfo(target.Position, map, false); - TargetInfo casterInfo = new TargetInfo(Pawn.Position, map, false); - - // 触发效果器,方向朝向施法者 - effecter.Trigger(targetInfo, casterInfo); - - // 如果效果器需要持续维护,添加到维护列表 - if (Props.hitEffecter.maintainTicks > 0) + // 使用与 CompAbilityEffect_EffecterOnTarget 相同的方法 + Effecter effecter; + if (target is Pawn pawnTarget) { - map.effecterMaintainer.AddEffecterToMaintain(effecter, target, Props.hitEffecter.maintainTicks); + effecter = Props.hitEffecter.Spawn(pawnTarget, map); } else { - // 否则在适当时间后清理 - LongEventHandler.ExecuteWhenFinished(delegate - { - effecter.Cleanup(); - }); + effecter = Props.hitEffecter.Spawn(target.Position, map); + } + + if (Props.hitEffecterMaintainTicks > 0) + { + // 使用与参考代码相同的方法来维持效果器 + parent.AddEffecterToMaintain(effecter, target.Position, Props.hitEffecterMaintainTicks); + } + else + { + effecter.Cleanup(); } Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}"); @@ -112,6 +124,18 @@ namespace WulaFallenEmpire } } + private void ProcessTarget(Thing target) + { + if (target is Building building) + { + DestroyBuilding(building); + } + else if (target is Pawn targetPawn) + { + DestroyAllBodyParts(targetPawn); + } + } + private bool ShouldAffectThing(Thing thing) { // 检查是否影响施法者自己 @@ -232,18 +256,8 @@ namespace WulaFallenEmpire public override IEnumerable GetPreCastActions() { - if (Props.effecterDef != null) - { - yield return new PreCastAction - { - action = delegate(LocalTargetInfo a, LocalTargetInfo b) - { - parent.AddEffecterToMaintain(Props.effecterDef.Spawn(parent.pawn.Position, a.Cell, parent.pawn.Map), - Pawn.Position, a.Cell, 17, Pawn.MapHeld); - }, - ticksAwayFromCast = 17 - }; - } + // 这里不再预先创建效果器,改为在Apply中创建 + yield break; } public override void DrawEffectPreview(LocalTargetInfo target) diff --git a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs index f30deb0e..561a3b19 100644 --- a/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs +++ b/Source/WulaFallenEmpire/Ability/WULA_AbilityAreaDestruction/CompProperties_AbilityAreaDestruction.cs @@ -7,16 +7,17 @@ namespace WulaFallenEmpire { public float range; public float lineWidthEnd; - public EffecterDef effecterDef; - public bool canHitFilledCells; - // 新增:命中效果器 + // 发射特效(在施法者位置) + public EffecterDef castEffecter; + public int castEffecterMaintainTicks = 60; + + // 命中特效(在目标位置) public EffecterDef hitEffecter; + public int hitEffecterMaintainTicks = 30; - // 新增:是否影响友方单位 + public bool canHitFilledCells; public bool affectAllies = false; - - // 新增:是否影响施法者自己 public bool affectCaster = false; public CompProperties_AbilityAreaDestruction() diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs index 3bbabbea..9660b1bd 100644 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs +++ b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/HediffComp_TopTurret.cs @@ -17,7 +17,7 @@ namespace WulaFallenEmpire public ThingDef turretDef; public float angleOffset; public bool autoAttack = true; - public bool defaultEnabled = true; // 新增:默认启用状态 + public bool defaultEnabled = true; } [StaticConstructorOnStartup] @@ -87,31 +87,28 @@ namespace WulaFallenEmpire } } - public override void CompPostTick(ref float severityAdjustment) { base.CompPostTick(ref severityAdjustment); + if (!TurretEnabled) { ResetCurrentTarget(); return; } + if (!this.CanShoot) { return; } - // 优先处理手动目标 - if (HasManualTarget && CanAttackManualTarget) - { - LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); - this.currentTarget = manualTarget; - this.curRotation = (manualTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset; - } - else if (this.currentTarget.IsValid) + + 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) @@ -125,14 +122,6 @@ namespace WulaFallenEmpire this.lastAttackTargetTick = Find.TickManager.TicksGame; this.lastAttackedTarget = this.currentTarget; } - else - { - // 如果手动攻击失败且目标无效,清除手动目标 - if (HasManualTarget && !CanAttackManualTarget) - { - VolleyTargetManager.ClearVolleyTarget(Pawn); - } - } return; } } @@ -142,68 +131,30 @@ namespace WulaFallenEmpire { this.burstCooldownTicksLeft--; } + if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10)) { - // 如果手动目标无效,清除它 - if (HasManualTarget && !CanAttackManualTarget) + // 自动寻找目标 + this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); + + if (this.currentTarget.IsValid) { - VolleyTargetManager.ClearVolleyTarget(Pawn); - } - // 只有在没有有效的手动目标时才寻找新目标 - if (!HasManualTarget || !CanAttackManualTarget) - { - this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f); - if (this.currentTarget.IsValid) - { - this.burstWarmupTicksLeft = 1; - return; - } + this.burstWarmupTicksLeft = 1; + return; } + this.ResetCurrentTarget(); } } } } - // 检查是否有手动目标 - private bool HasManualTarget - { - get - { - LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); - return manualTarget.IsValid; - } - } - // 检查是否可以攻击手动目标 - private bool CanAttackManualTarget - { - get - { - LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); - if (!manualTarget.IsValid) - return false; - // 检查目标是否在射程内 - float distance = Pawn.Position.DistanceTo(manualTarget.Cell); - if (distance > AttackVerb.verbProps.range) - return false; - // 检查是否可以命中目标 - if (!AttackVerb.CanHitTarget(manualTarget)) - return false; - // 检查目标是否还活着(如果是生物) - if (manualTarget.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed)) - return false; - // 检查目标是否被摧毁(如果是建筑) - if (manualTarget.Thing != null && manualTarget.Thing.Destroyed) - return false; - return true; - } - } - // 简化的Gizmos - 只有设置目标和清除目标按钮 + + // 简化的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(), @@ -213,75 +164,33 @@ namespace WulaFallenEmpire toggleAction = () => TurretEnabled = !TurretEnabled, hotKey = KeyBindingDefOf.Misc1 }; - // 设置目标按钮 - yield return new Command_Action - { - defaultLabel = "CommandSetTarget".Translate(), - defaultDesc = "CommandSetTargetDesc".Translate(), - icon = ContentFinder.Get("UI/Gizmos/SetTarget"), - action = () => - { - Find.Targeter.BeginTargeting( - CreateTargetingParameters(), - delegate (LocalTargetInfo target) - { - VolleyTargetManager.SetVolleyTarget(Pawn, target); - }, - Pawn, // caster 参数 - null, // actionWhenFinished - null, // mouseAttachment - true // requiresCastedSelected - ); - }, - hotKey = KeyBindingDefOf.Misc2 - }; - // 清除目标按钮(只在有手动目标时显示) - LocalTargetInfo currentTarget = VolleyTargetManager.GetVolleyTarget(Pawn); - if (currentTarget.IsValid) - { - yield return new Command_Action - { - defaultLabel = "CommandClearTarget".Translate(), - defaultDesc = "CommandClearTargetDesc".Translate(), - icon = ContentFinder.Get("UI/Gizmos/ClearTarget"), - action = () => VolleyTargetManager.ClearVolleyTarget(Pawn), - hotKey = KeyBindingDefOf.Misc3 - }; - } } } - // 创建目标参数 - private TargetingParameters CreateTargetingParameters() - { - return TargetingParameters.ForThing(); - } - // 在提示中显示目标状态 + + // 简化的提示信息 public override string CompTipStringExtra { get { string baseString = base.CompTipStringExtra; string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive"; - string targetStatus = "Manual Target: "; - LocalTargetInfo manualTarget = VolleyTargetManager.GetVolleyTarget(Pawn); - if (manualTarget.IsValid) + string targetStatus = "Target: "; + + if (this.currentTarget.IsValid) { - targetStatus += $"{manualTarget.Thing?.LabelCap ?? manualTarget.Cell.ToString()}"; - if (!CanAttackManualTarget) - { - targetStatus += " (Unreachable)"; - } + targetStatus += $"{this.currentTarget.Thing?.LabelCap ?? this.currentTarget.Cell.ToString()}"; } else { targetStatus += "None"; } + string result = turretStatus + "\n" + targetStatus; return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result; } } - // 新增:炮塔启用状态 + // 炮塔启用状态 public bool TurretEnabled { get { return turretEnabled; } @@ -299,7 +208,7 @@ namespace WulaFallenEmpire { get { - // 新增:检查炮塔是否启用 + // 检查炮塔是否启用 if (!TurretEnabled) return false; @@ -361,7 +270,7 @@ namespace WulaFallenEmpire { base.CompPostMake(); this.MakeGun(); - // 新增:设置默认启用状态 + // 设置默认启用状态 TurretEnabled = Props.defaultEnabled; } @@ -384,6 +293,7 @@ namespace WulaFallenEmpire }; } } + private void ResetCurrentTarget() { this.currentTarget = LocalTargetInfo.Invalid; @@ -398,7 +308,7 @@ 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) @@ -415,8 +325,6 @@ namespace WulaFallenEmpire private const int StartShootIntervalTicks = 10; - private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret"); - public Thing gun; protected int burstCooldownTicksLeft; protected int burstWarmupTicksLeft; @@ -426,10 +334,10 @@ namespace WulaFallenEmpire 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 deleted file mode 100644 index 61adfccc..00000000 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/VolleyTargetManager.cs +++ /dev/null @@ -1,87 +0,0 @@ -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; - - Log.Message($"Set volley target for {pawn.Label}: {target.Thing?.Label ?? target.Cell.ToString()}"); - } - - public static void ClearVolleyTarget(Pawn pawn) - { - if (pawn == null) return; - - volleyTargets.Remove(pawn); - volleyEnabled.Remove(pawn); - - Log.Message($"Cleared volley target for {pawn.Label}"); - } - - 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; - - Log.Message($"Toggled volley for {pawn.Label}: {!current}"); - - // 如果禁用齐射,清除目标 - if (!volleyEnabled[pawn]) - { - ClearVolleyTarget(pawn); - } - } - - // 新增:检查齐射目标是否仍然有效 - public static bool IsVolleyTargetValid(Pawn pawn) - { - if (!IsVolleyEnabled(pawn)) - return false; - - LocalTargetInfo target = GetVolleyTarget(pawn); - if (!target.IsValid) - return false; - - // 检查目标是否还活着/存在 - if (target.Thing != null) - { - if (target.Thing.Destroyed) - return false; - - if (target.Thing is Pawn targetPawn && (targetPawn.Dead || targetPawn.Downed)) - return false; - } - - return true; - } - } -} diff --git a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md b/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md deleted file mode 100644 index 414ac63c..00000000 --- a/Source/WulaFallenEmpire/HediffComp/WULA_HediffComp_TopTurret/我来帮您为这两个炮塔类添加齐射目标功能.md +++ /dev/null @@ -1,462 +0,0 @@ -## 🤖 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/WULA_AreaShield/AreaShieldManager.cs b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/AreaShieldManager.cs new file mode 100644 index 00000000..6916d4ba --- /dev/null +++ b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/AreaShieldManager.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using RimWorld; +using Verse; + +namespace WulaFallenEmpire +{ + public static class AreaShieldManager + { + private static Dictionary> activeShieldsByMap = + new Dictionary>(); + + private static int lastUpdateTick = 0; + private const int UPDATE_INTERVAL_TICKS = 60; + + public static IEnumerable GetActiveShieldsForMap(Map map) + { + if (Find.TickManager.TicksGame - lastUpdateTick > UPDATE_INTERVAL_TICKS) + { + UpdateShieldCache(); + lastUpdateTick = Find.TickManager.TicksGame; + } + + if (activeShieldsByMap.TryGetValue(map, out var shields)) + { + foreach (var shield in shields) + { + if (shield?.Active == true) + yield return shield; + } + } + } + + private static void UpdateShieldCache() + { + activeShieldsByMap.Clear(); + + foreach (var map in Find.Maps) + { + var shieldSet = new HashSet(); + + foreach (var pawn in map.mapPawns.AllPawnsSpawned) + { + if (pawn.apparel != null) + { + foreach (var apparel in pawn.apparel.WornApparel) + { + // 同时支持普通护盾和反弹护盾 + var shield = apparel.TryGetComp(); + if (shield != null && shield.Active) + { + shieldSet.Add(shield); + } + } + } + } + + activeShieldsByMap[map] = shieldSet; + } + } + + public static void NotifyShieldStateChanged(ThingComp_AreaShield shield) + { + if (shield?.Wearer?.Map != null) + { + lastUpdateTick = 0; + } + } + + public static void Cleanup() + { + var mapsToRemove = new List(); + foreach (var map in activeShieldsByMap.Keys) + { + if (map == null || !Find.Maps.Contains(map)) + mapsToRemove.Add(map); + } + + foreach (var map in mapsToRemove) + { + activeShieldsByMap.Remove(map); + } + } + } +} diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/CompProperties_AreaShield.cs b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/CompProperties_AreaShield.cs new file mode 100644 index 00000000..de4626a9 --- /dev/null +++ b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/CompProperties_AreaShield.cs @@ -0,0 +1,39 @@ +using RimWorld; +using Verse; +using UnityEngine; + +namespace WulaFallenEmpire +{ + public class CompProperties_AreaShield : CompProperties + { + public float radius = 5.9f; + public int baseHitPoints = 100; + public int rechargeDelay = 3200; + public int rechargeHitPointsIntervalTicks = 60; + + public EffecterDef absorbEffecter; + public EffecterDef interceptEffecter; + public EffecterDef breakEffecter; + public EffecterDef reactivateEffecter; + + public Color color = Color.cyan; + public int startupDelay = 0; + + // 拦截设置 + public bool interceptGroundProjectiles = true; + public bool interceptNonHostileProjectiles = false; + public bool interceptAirProjectiles = true; + + // 反射设置 + public bool canReflect = false; + public float reflectChance = 0.5f; + public float reflectAngleRange = 30f; + public int reflectCost = 1; + public EffecterDef reflectEffecter; + + public CompProperties_AreaShield() + { + compClass = typeof(ThingComp_AreaShield); + } + } +} diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Gizmo_AreaShieldStatus.cs b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Gizmo_AreaShieldStatus.cs new file mode 100644 index 00000000..47a89caa --- /dev/null +++ b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Gizmo_AreaShieldStatus.cs @@ -0,0 +1,44 @@ +using RimWorld; +using UnityEngine; +using Verse; + +namespace WulaFallenEmpire +{ + // Gizmo 类保持不变... + [StaticConstructorOnStartup] + public class Gizmo_AreaShieldStatus : Gizmo + { + public ThingComp_AreaShield shield; + private static readonly Texture2D FullShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.8f, 0.85f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D; + private static readonly Texture2D EmptyShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.2f, 0.2f, 0.24f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D; + + public override float GetWidth(float maxWidth) => 140f; + + public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms) + { + Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f); + Rect rect2 = rect.ContractedBy(6f); + Widgets.DrawWindowBackground(rect); + + Rect labelRect = rect2; + labelRect.height = rect.height / 2f; + Text.Font = GameFont.Tiny; + Widgets.Label(labelRect, shield.parent.LabelCap); + + Rect barRect = rect2; + barRect.yMin = rect2.y + rect2.height / 2f; + float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax; + Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false); + + Text.Font = GameFont.Small; + Text.Anchor = TextAnchor.MiddleCenter; + + TaggedString statusText = shield.IsOnCooldown ? "ShieldOnCooldown".Translate() : new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax); + Widgets.Label(barRect, statusText); + + Text.Anchor = TextAnchor.UpperLeft; + + return new GizmoResult(GizmoState.Clear); + } + } +} diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Harmony_AreaShieldInterceptor.cs b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Harmony_AreaShieldInterceptor.cs new file mode 100644 index 00000000..81acf643 --- /dev/null +++ b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/Harmony_AreaShieldInterceptor.cs @@ -0,0 +1,50 @@ +using HarmonyLib; +using RimWorld; +using Verse; +using UnityEngine; + +namespace WulaFallenEmpire +{ + [HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")] + public static class Projectile_CheckForFreeInterceptBetween_Patch + { + public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos) + { + if (__instance.Map == null || __instance.Destroyed) + { + return true; + } + + bool shouldDestroy = false; + + // 使用缓存系统获取激活的护盾 + foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map)) + { + if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true) + { + shouldDestroy = true; + break; // 只要有一个护盾吸收就销毁 + } + // 如果护盾反射了抛射体,继续检查其他护盾(允许多重反射) + } + + if (shouldDestroy) + { + __instance.Destroy(DestroyMode.Vanish); + return false; + } + + return true; + } + } + + // 额外的清理补丁 + [HarmonyPatch(typeof(Map), "FinalizeInit")] + public static class Map_FinalizeInit_Patch + { + public static void Postfix() + { + AreaShieldManager.Cleanup(); + } + } +} diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/ThingComp_AreaShield.cs b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/ThingComp_AreaShield.cs new file mode 100644 index 00000000..8c342477 --- /dev/null +++ b/Source/WulaFallenEmpire/ThingComp/WULA_AreaShield/ThingComp_AreaShield.cs @@ -0,0 +1,422 @@ +using RimWorld; +using Verse; +using UnityEngine; +using Verse.Sound; +using System.Collections.Generic; +using HarmonyLib; + +namespace WulaFallenEmpire +{ + [StaticConstructorOnStartup] + public class ThingComp_AreaShield : ThingComp + { + private int lastInterceptTicks = -999999; + public int ticksToReset = 0; + public int currentHitPoints; + private bool wasNotAtFullHp = false; + private bool wasActiveLastCheck = false; + + // 视觉效果变量 + private float lastInterceptAngle; + private bool drawInterceptCone; + + public CompProperties_AreaShield Props => (CompProperties_AreaShield)props; + public Pawn Wearer => (parent as Apparel)?.Wearer; + public bool IsOnCooldown => ticksToReset > 0; + public int HitPointsMax => Props.baseHitPoints; + + private StunHandler stunner; + private bool initialized = false; + + public bool Active + { + get + { + if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown) + return false; + return true; + } + } + + // 材质定义 + 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(); + + public override void PostPostMake() + { + base.PostPostMake(); + currentHitPoints = HitPointsMax; + } + + public override void PostExposeData() + { + base.PostExposeData(); + Scribe_Values.Look(ref lastInterceptTicks, "lastInterceptTicks", -999999); + Scribe_Values.Look(ref ticksToReset, "ticksToReset", 0); + Scribe_Values.Look(ref currentHitPoints, "currentHitPoints", 0); + } + + public override void CompTick() + { + base.CompTick(); + + bool isActive = Active; + + // 检查状态变化并通知管理器 + if (isActive != wasActiveLastCheck) + { + AreaShieldManager.NotifyShieldStateChanged(this); + wasActiveLastCheck = isActive; + } + + if (Wearer == null) return; + + if (IsOnCooldown) + { + ticksToReset--; + if (ticksToReset <= 0) + { + Reset(); + } + } + else if (isActive && currentHitPoints < HitPointsMax) + { + wasNotAtFullHp = true; + if (parent.IsHashIntervalTick(Props.rechargeHitPointsIntervalTicks)) + { + currentHitPoints += 1; + if (currentHitPoints > HitPointsMax) + currentHitPoints = HitPointsMax; + } + } + else if (wasNotAtFullHp && currentHitPoints >= HitPointsMax) + { + wasNotAtFullHp = false; + } + } + + private void ApplyCosts(int cost = 1) + { + currentHitPoints -= cost; + if (currentHitPoints <= 0) + { + Break(); + } + + // 护盾值变化时通知管理器 + AreaShieldManager.NotifyShieldStateChanged(this); + } + + public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos) + { + if (!Active) return false; + if (currentHitPoints <= 0) return false; + + if (!GenGeo.IntersectLineCircleOutline(Wearer.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2())) + { + return false; + } + + if (projectile.def.projectile.flyOverhead && !Props.interceptAirProjectiles) return false; + if (!projectile.def.projectile.flyOverhead && !Props.interceptGroundProjectiles) return false; + if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Wearer.Faction) && !Props.interceptNonHostileProjectiles) return false; + + lastInterceptTicks = Find.TickManager.TicksGame; + + // 记录拦截角度用于视觉效果 + lastInterceptAngle = projectile.ExactPosition.AngleToFlat(Wearer.TrueCenter()); + drawInterceptCone = true; + + // 尝试反射 + if (Props.canReflect && TryReflectProjectile(projectile, lastExactPos, newExactPos)) + { + // 反射成功,播放反射特效 + Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup(); + ApplyCosts(Props.reflectCost); + return false; // 不销毁原抛射体,让它继续飞行(我们会在反射中销毁它) + } + else + { + // 普通拦截,播放拦截特效 + Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup(); + ApplyCosts(); + return true; // 销毁抛射体 + } + } + + /// + /// 尝试反射抛射体 - 现在会创建新的抛射体 + /// + private bool TryReflectProjectile(Projectile originalProjectile, Vector3 lastExactPos, Vector3 newExactPos) + { + if (!Props.canReflect) return false; + + // 检查反射概率 + if (Rand.Value > Props.reflectChance) return false; + + try + { + // 计算入射方向 + Vector3 incomingDirection = (newExactPos - lastExactPos).normalized; + + // 计算法线方向(从护盾中心到碰撞点) + Vector3 normal = (newExactPos - Wearer.DrawPos).normalized; + + // 计算反射方向(镜面反射) + Vector3 reflectDirection = Vector3.Reflect(incomingDirection, normal); + + // 添加随机角度偏移 + float randomAngle = Rand.Range(-Props.reflectAngleRange, Props.reflectAngleRange); + reflectDirection = Quaternion.Euler(0, randomAngle, 0) * reflectDirection; + + // 创建新的反射抛射体 + CreateReflectedProjectile(originalProjectile, reflectDirection, newExactPos); + + return true; + } + catch (System.Exception ex) + { + Log.Warning($"Error reflecting projectile: {ex}"); + } + + return false; + } + + /// + /// 创建反射后的新抛射体 + /// + private void CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint) + { + try + { + // 计算新的发射位置(护盾位置附近) + Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint); + + // 计算新的目标位置 + Vector3 targetPosition = spawnPosition + reflectDirection * 30f; // 足够远的距离 + + // 创建新的抛射体 + Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnPosition.ToIntVec3(), Wearer.Map); + + // 设置发射者为原抛射体的发射者 + Thing launcher = originalProjectile.Launcher ?? Wearer; + + // 发射新抛射体 + newProjectile.Launch( + launcher, + spawnPosition, + new LocalTargetInfo(targetPosition.ToIntVec3()), + new LocalTargetInfo(targetPosition.ToIntVec3()), + ProjectileHitFlags.All, + false + ); + + // 复制重要的属性 + CopyProjectileProperties(originalProjectile, newProjectile); + + // 销毁原抛射体 + originalProjectile.Destroy(DestroyMode.Vanish); + + Log.Message($"反射抛射体: 从 {spawnPosition} 向 {targetPosition} 发射"); + } + catch (System.Exception ex) + { + Log.Warning($"Error creating reflected projectile: {ex}"); + } + } + + /// + /// 获取反射抛射体的发射位置(护盾边界上) + /// + private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint) + { + // 计算从护盾中心到碰撞点的方向 + Vector3 directionFromCenter = (collisionPoint - Wearer.DrawPos).normalized; + + // 在护盾边界上生成(稍微向内一点避免立即再次碰撞) + float spawnDistance = Props.radius * 0.9f; + Vector3 spawnPosition = Wearer.DrawPos + directionFromCenter * spawnDistance; + + // 确保位置在地图内 + IntVec3 spawnCell = spawnPosition.ToIntVec3(); + if (!spawnCell.InBounds(Wearer.Map)) + { + spawnCell = Wearer.Position; + } + + return spawnCell.ToVector3Shifted(); + } + + /// + /// 复制抛射体重要属性 + /// + private void CopyProjectileProperties(Projectile source, Projectile destination) + { + try + { + var sourceTraverse = Traverse.Create(source); + var destTraverse = Traverse.Create(destination); + + // 复制伤害属性 + destTraverse.Field("damageDefOverride").SetValue(source.damageDefOverride); + + // 复制额外伤害 + if (source.extraDamages != null) + { + destTraverse.Field("extraDamages").SetValue(new List(source.extraDamages)); + } + + // 复制停止力 + destTraverse.Field("stoppingPower").SetValue(source.stoppingPower); + } + catch (System.Exception ex) + { + Log.Warning($"Error copying projectile properties: {ex}"); + } + } + + public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed) + { + absorbed = false; + if (!Active || Wearer == null) return; + + if (dinfo.Def.isRanged) return; + + if (dinfo.Instigator != null) + { + float distance = Wearer.Position.DistanceTo(dinfo.Instigator.Position); + if (distance > Props.radius) return; + } + + if (currentHitPoints <= 0) return; + + Props.absorbEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup(); + ApplyCosts(); + absorbed = true; + } + + private void Break() + { + Props.breakEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup(); + ticksToReset = Props.rechargeDelay; + currentHitPoints = 0; + AreaShieldManager.NotifyShieldStateChanged(this); + } + + private void Reset() + { + if (Wearer != null && Wearer.Spawned) + { + Props.reactivateEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup(); + } + currentHitPoints = HitPointsMax; + AreaShieldManager.NotifyShieldStateChanged(this); + } + + // 护盾绘制方法 + public override void CompDrawWornExtras() + { + base.CompDrawWornExtras(); + + if (!Active || Wearer?.Map == null || !ShouldDisplay) + return; + + Vector3 drawPos = Wearer.Drawer?.DrawPos ?? Wearer.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; + + float scale = Props.radius * 2f * 1.1601562f; + matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(scale, 1f, scale)); + Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock); + } + + // 添加拦截锥形效果 + float coneAlpha = GetCurrentConeAlpha(); + if (coneAlpha > 0f) + { + Color color = Props.color; + color.a *= coneAlpha; + MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color); + Matrix4x4 matrix = default; + float scale = Props.radius * 2f; + matrix.SetTRS(drawPos, Quaternion.Euler(0f, lastInterceptAngle - 90f, 0f), new Vector3(scale, 1f, scale)); + Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldConeMat, 0, null, 0, MatPropertyBlock); + } + } + + // 显示条件 + protected bool ShouldDisplay + { + get + { + if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || !Active) + return false; + + if (Wearer.Drafted || Wearer.InAggroMentalState || + (Wearer.Faction != null && Wearer.Faction.HostileTo(Faction.OfPlayer) && !Wearer.IsPrisoner)) + return true; + + if (Find.Selector.IsSelected(Wearer)) + return true; + + return false; + } + } + + 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() + { + if (!drawInterceptCone) return 0f; + return Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f) * 0.82f; + } + + private void EnsureInitialized() + { + if (initialized) return; + + if (stunner == null) + stunner = new StunHandler(parent); + + if (currentHitPoints == -1) + currentHitPoints = Props.startupDelay > 0 ? 0 : HitPointsMax; + + initialized = true; + } + + public override IEnumerable CompGetWornGizmosExtra() + { + EnsureInitialized(); + + if (Wearer != null && Find.Selector.SingleSelectedThing == Wearer) + { + yield return new Gizmo_AreaShieldStatus { shield = this }; + } + } + + public override void Notify_Equipped(Pawn pawn) + { + base.Notify_Equipped(pawn); + AreaShieldManager.NotifyShieldStateChanged(this); + } + + public override void Notify_Unequipped(Pawn pawn) + { + base.Notify_Unequipped(pawn); + AreaShieldManager.NotifyShieldStateChanged(this); + } + } +} diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj index 259d1d09..60fe80bc 100644 --- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj +++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj @@ -166,7 +166,6 @@ - @@ -225,10 +224,15 @@ + + + + + - - + +