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 @@
+
+
+
+
+
-
-
+
+