1
This commit is contained in:
Binary file not shown.
@@ -503,16 +503,16 @@
|
||||
</apparel>
|
||||
<comps>
|
||||
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
|
||||
|
||||
<!-- 基础功能 -->
|
||||
<radius>1.25</radius> <!-- 护盾半径,决定了拦截范围 -->
|
||||
<hitPoints>150</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
|
||||
<radius>1</radius> <!-- 护盾半径,决定了拦截范围 -->
|
||||
<hitPoints>50</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
|
||||
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
|
||||
<maxBounces>3</maxBounces>
|
||||
|
||||
<!-- 拦截类型 -->
|
||||
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
|
||||
<interceptAirProjectiles>false</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
|
||||
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
|
||||
<interceptAirProjectiles>true</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
|
||||
<interceptNonHostileProjectiles>true</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
|
||||
|
||||
<!-- 视觉与音效 -->
|
||||
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
|
||||
@@ -520,13 +520,14 @@
|
||||
<soundBreakEffecter>Shield_Break</soundBreakEffecter> <!-- 护盾破裂时的音效 -->
|
||||
<reactivateEffect>BulletShieldGenerator_Reactivate</reactivateEffect> <!-- 护盾冷却结束后恢复的特效 -->
|
||||
|
||||
<drawWithNoSelection>false</drawWithNoSelection>
|
||||
|
||||
<!-- EMP 效果 -->
|
||||
<isImmuneToEMP>false</isImmuneToEMP> <!-- 是否免疫EMP伤害 -->
|
||||
<disarmedByEmpForTicks>600</disarmedByEmpForTicks> <!-- 被EMP击中后,额外的眩晕/瘫痪时间 (ticks) -->
|
||||
|
||||
<!-- 被动恢复 -->
|
||||
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks> <!-- 未破盾时,每隔多少ticks恢复1点生命值 -->
|
||||
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
|
||||
@@ -7,45 +7,42 @@ using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.HarmonyPatches
|
||||
{
|
||||
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
|
||||
public static class Projectile_CheckForFreeInterceptBetween_Patch
|
||||
[HarmonyPatch(typeof(Projectile), "Impact")]
|
||||
public static class Projectile_Impact_Patch
|
||||
{
|
||||
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
|
||||
private static Dictionary<Projectile, int> bounceCount = new Dictionary<Projectile, int>();
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix(Projectile __instance, Thing hitThing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (__instance == null || __instance.Map == null || __instance.Destroyed || !__instance.Spawned)
|
||||
if (__instance.Destroyed || !__instance.Spawned || hitThing == null)
|
||||
return true;
|
||||
|
||||
var map = __instance.Map;
|
||||
var pawns = map.mapPawns?.AllPawnsSpawned;
|
||||
if (pawns == null) return true;
|
||||
|
||||
foreach (Pawn pawn in pawns)
|
||||
// 检查抛射体是否击中了穿戴护盾的 pawn
|
||||
if (hitThing is Pawn hitPawn)
|
||||
{
|
||||
if (pawn == null || !pawn.Spawned || pawn.Dead || pawn.Downed || pawn.apparel == null)
|
||||
continue;
|
||||
|
||||
foreach (Apparel apparel in pawn.apparel.WornApparel)
|
||||
// 获取 pawn 身上的所有拦截护盾
|
||||
var interceptors = GetInterceptorsOnPawn(hitPawn);
|
||||
|
||||
foreach (var interceptor in interceptors)
|
||||
{
|
||||
if (apparel?.TryGetComp<CompApparelInterceptor>() is CompApparelInterceptor interceptor)
|
||||
if (interceptor.TryInterceptProjectile(__instance, hitThing))
|
||||
{
|
||||
try
|
||||
// 记录反弹次数
|
||||
int currentBounces = bounceCount.TryGetValue(__instance, 0) + 1;
|
||||
bounceCount[__instance] = currentBounces;
|
||||
|
||||
// 检查最大反弹次数
|
||||
if (currentBounces >= interceptor.Props.maxBounces)
|
||||
{
|
||||
if (interceptor.TryIntercept(__instance, lastExactPos, newExactPos))
|
||||
{
|
||||
// 简单直接:立即销毁子弹
|
||||
if (!__instance.Destroyed && __instance.Spawned)
|
||||
{
|
||||
__instance.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"[Interceptor] Error: {ex.Message}");
|
||||
__instance.Destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 拦截成功,阻止原版 Impact 逻辑
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,27 +51,49 @@ namespace WulaFallenEmpire.HarmonyPatches
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[Interceptor] Critical error: {ex}");
|
||||
Log.Error($"Error in Projectile_Impact_Patch: {ex}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Projectile), "Tick")]
|
||||
public static class Projectile_Tick_Patch
|
||||
{
|
||||
public static bool Prefix(Projectile __instance)
|
||||
private static List<CompApparelInterceptor> GetInterceptorsOnPawn(Pawn pawn)
|
||||
{
|
||||
return __instance != null && !__instance.Destroyed && __instance.Spawned;
|
||||
var result = new List<CompApparelInterceptor>();
|
||||
|
||||
if (pawn == null || pawn.apparel == null)
|
||||
return result;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var apparel in pawn.apparel.WornApparel)
|
||||
{
|
||||
var interceptor = apparel.GetComp<CompApparelInterceptor>();
|
||||
if (interceptor != null && interceptor.Active)
|
||||
{
|
||||
result.Add(interceptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error in GetInterceptorsOnPawn: {ex}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Projectile), "TickInterval")]
|
||||
public static class Projectile_TickInterval_Patch
|
||||
{
|
||||
public static bool Prefix(Projectile __instance, int delta)
|
||||
// 清理反弹计数 - 修复:使用 Thing 的 Destroy 方法
|
||||
[HarmonyPatch(typeof(Thing), "Destroy")]
|
||||
public static class Thing_Destroy_Patch
|
||||
{
|
||||
return __instance != null && !__instance.Destroyed && __instance.Spawned;
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix(Thing __instance)
|
||||
{
|
||||
if (__instance is Projectile projectile)
|
||||
{
|
||||
bounceCount.Remove(projectile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
|
||||
public ThingDef turretDef;
|
||||
|
||||
public float angleOffset;
|
||||
|
||||
public bool autoAttack = true;
|
||||
public bool defaultEnabled = true; // 新增:默认启用状态
|
||||
}
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
@@ -88,10 +87,200 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldAttackVolleyTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!VolleyTargetManager.IsVolleyEnabled(Pawn))
|
||||
return false;
|
||||
|
||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
||||
if (!volleyTarget.IsValid)
|
||||
return false;
|
||||
|
||||
// 检查目标是否在射程内
|
||||
float distance = Pawn.Position.DistanceTo(volleyTarget.Cell);
|
||||
if (distance > AttackVerb.verbProps.range)
|
||||
return false;
|
||||
|
||||
// 检查是否可以命中目标
|
||||
return AttackVerb.CanHitTarget(volleyTarget);
|
||||
}
|
||||
}
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
if (!TurretEnabled)
|
||||
{
|
||||
ResetCurrentTarget();
|
||||
return;
|
||||
}
|
||||
if (!this.CanShoot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 新增:优先处理齐射目标
|
||||
if (ShouldAttackVolleyTarget)
|
||||
{
|
||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
||||
this.currentTarget = volleyTarget;
|
||||
this.curRotation = (volleyTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
||||
}
|
||||
else if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.curRotation = (this.currentTarget.Cell.ToVector3Shifted() - this.Pawn.DrawPos).AngleFlat() + this.Props.angleOffset;
|
||||
}
|
||||
|
||||
this.AttackVerb.VerbTick();
|
||||
if (this.AttackVerb.state != VerbState.Bursting)
|
||||
{
|
||||
if (this.WarmingUp)
|
||||
{
|
||||
this.burstWarmupTicksLeft--;
|
||||
if (this.burstWarmupTicksLeft == 0)
|
||||
{
|
||||
this.AttackVerb.TryStartCastOn(this.currentTarget, false, true, false, true);
|
||||
this.lastAttackTargetTick = Find.TickManager.TicksGame;
|
||||
this.lastAttackedTarget = this.currentTarget;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.burstCooldownTicksLeft > 0)
|
||||
{
|
||||
this.burstCooldownTicksLeft--;
|
||||
}
|
||||
if (this.burstCooldownTicksLeft <= 0 && this.Pawn.IsHashIntervalTick(10))
|
||||
{
|
||||
// 只有在没有齐射目标时才寻找新目标
|
||||
if (!ShouldAttackVolleyTarget)
|
||||
{
|
||||
this.currentTarget = (Thing)AttackTargetFinder.BestShootTargetFromCurrentPosition(this, TargetScanFlags.NeedThreat | TargetScanFlags.NeedAutoTargetable, null, 0f, 9999f);
|
||||
if (this.currentTarget.IsValid)
|
||||
{
|
||||
this.burstWarmupTicksLeft = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.ResetCurrentTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 新增:齐射Gizmos
|
||||
public override IEnumerable<Gizmo> 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<Texture2D>.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<Texture2D>.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<Texture2D>.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<Texture2D>.Get("UI/Gizmos/ClearTarget"),
|
||||
action = () => VolleyTargetManager.ClearVolleyTarget(Pawn),
|
||||
hotKey = KeyBindingDefOf.Misc4
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 新增:在提示中显示齐射状态
|
||||
public override string CompTipStringExtra
|
||||
{
|
||||
get
|
||||
{
|
||||
string baseString = base.CompTipStringExtra;
|
||||
string turretStatus = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
||||
|
||||
string volleyStatus = "Volley: ";
|
||||
if (VolleyTargetManager.IsVolleyEnabled(Pawn))
|
||||
{
|
||||
LocalTargetInfo volleyTarget = VolleyTargetManager.GetVolleyTarget(Pawn);
|
||||
if (volleyTarget.IsValid)
|
||||
{
|
||||
volleyStatus += $"Targeting {volleyTarget.Thing?.LabelCap ?? volleyTarget.Cell.ToString()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
volleyStatus += "Enabled (No Target)";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
volleyStatus += "Disabled";
|
||||
}
|
||||
string result = turretStatus + "\n" + volleyStatus;
|
||||
return string.IsNullOrEmpty(baseString) ? result : baseString + "\n" + result;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:炮塔启用状态
|
||||
public bool TurretEnabled
|
||||
{
|
||||
get { return turretEnabled; }
|
||||
set
|
||||
{
|
||||
turretEnabled = value;
|
||||
if (!turretEnabled)
|
||||
{
|
||||
ResetCurrentTarget(); // 禁用时重置目标
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanShoot
|
||||
{
|
||||
get
|
||||
{
|
||||
// 新增:检查炮塔是否启用
|
||||
if (!TurretEnabled)
|
||||
return false;
|
||||
|
||||
Pawn pawn;
|
||||
if ((pawn = (this.Pawn)) != null)
|
||||
{
|
||||
@@ -150,6 +339,8 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
base.CompPostMake();
|
||||
this.MakeGun();
|
||||
// 新增:设置默认启用状态
|
||||
TurretEnabled = Props.defaultEnabled;
|
||||
}
|
||||
|
||||
private void MakeGun()
|
||||
@@ -171,9 +362,18 @@ namespace WulaFallenEmpire
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
// 新增:只在启用状态下执行攻击逻辑
|
||||
if (!TurretEnabled)
|
||||
{
|
||||
ResetCurrentTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.CanShoot)
|
||||
{
|
||||
return;
|
||||
@@ -230,6 +430,9 @@ namespace WulaFallenEmpire
|
||||
Scribe_TargetInfo.Look(ref this.currentTarget, "currentTarget");
|
||||
Scribe_Deep.Look<Thing>(ref this.gun, "gun", Array.Empty<object>());
|
||||
Scribe_Values.Look<bool>(ref this.fireAtWill, "fireAtWill", true, false);
|
||||
// 新增:保存启用状态
|
||||
Scribe_Values.Look<bool>(ref this.turretEnabled, "turretEnabled", Props.defaultEnabled, false);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
if (this.gun == null)
|
||||
@@ -242,27 +445,52 @@ namespace WulaFallenEmpire
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:实现 Gizmo 接口
|
||||
public override IEnumerable<Gizmo> 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<Texture2D>.Get("UI/Gizmos/ToggleTurret"),
|
||||
isActive = () => TurretEnabled,
|
||||
toggleAction = () => TurretEnabled = !TurretEnabled,
|
||||
hotKey = KeyBindingDefOf.Misc1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:在绘制时显示状态
|
||||
public override string CompTipStringExtra
|
||||
{
|
||||
get
|
||||
{
|
||||
string baseString = base.CompTipStringExtra;
|
||||
string status = TurretEnabled ? "Turret: Active" : "Turret: Inactive";
|
||||
return string.IsNullOrEmpty(baseString) ? status : baseString + "\n" + status;
|
||||
}
|
||||
}
|
||||
|
||||
private const int StartShootIntervalTicks = 10;
|
||||
|
||||
private static readonly CachedTexture ToggleTurretIcon = new CachedTexture("UI/Gizmos/ToggleTurret");
|
||||
|
||||
public Thing gun;
|
||||
|
||||
protected int burstCooldownTicksLeft;
|
||||
|
||||
protected int burstWarmupTicksLeft;
|
||||
|
||||
protected LocalTargetInfo currentTarget = LocalTargetInfo.Invalid;
|
||||
|
||||
private bool fireAtWill = true;
|
||||
|
||||
private LocalTargetInfo lastAttackedTarget = LocalTargetInfo.Invalid;
|
||||
|
||||
private int lastAttackTargetTick;
|
||||
|
||||
private float curRotation;
|
||||
|
||||
// 新增:炮塔启用状态字段
|
||||
private bool turretEnabled = true;
|
||||
|
||||
[Unsaved(false)]
|
||||
public Material turretMat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public static class VolleyTargetManager
|
||||
{
|
||||
private static Dictionary<Pawn, LocalTargetInfo> volleyTargets = new Dictionary<Pawn, LocalTargetInfo>();
|
||||
private static Dictionary<Pawn, bool> volleyEnabled = new Dictionary<Pawn, bool>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
## 🤖 Assistant
|
||||
|
||||
我来帮您为这两个炮塔类添加齐射目标功能。我们需要创建一个共享的目标系统,让所有炮塔可以协同攻击同一个目标。
|
||||
|
||||
## 1. 首先创建共享目标管理器
|
||||
|
||||
```csharp
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public static class VolleyTargetManager
|
||||
{
|
||||
private static Dictionary<Pawn, LocalTargetInfo> volleyTargets = new Dictionary<Pawn, LocalTargetInfo>();
|
||||
private static Dictionary<Pawn, bool> volleyEnabled = new Dictionary<Pawn, bool>();
|
||||
|
||||
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<Gizmo> 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<Texture2D>.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<Texture2D>.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<Texture2D>.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<Texture2D>.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<Gizmo> 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<Texture2D>.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<Texture2D>.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<Texture2D>.Get("UI/Gizmos/ClearTarget"),
|
||||
action = () => VolleyTargetManager.ClearVolleyTarget(pawn),
|
||||
hotKey = KeyBindingDefOf.Misc4
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... 保留其他原有代码 ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 添加翻译字符串
|
||||
|
||||
在您的语言文件中添加:
|
||||
|
||||
```xml
|
||||
<!-- 在 Keyed 文件夹的 XML 文件中 -->
|
||||
<CommandToggleVolley>Volley Fire</CommandToggleVolley>
|
||||
<CommandToggleVolleyDesc>Enable or disable coordinated volley fire for all turrets.</CommandToggleVolleyDesc>
|
||||
<CommandSetVolleyTarget>Set Volley Target</CommandSetVolleyTarget>
|
||||
<CommandSetVolleyTargetDesc>Set a coordinated target for all turrets to focus fire.</CommandSetVolleyTargetDesc>
|
||||
<CommandClearVolleyTarget>Clear Volley Target</CommandClearVolleyTarget>
|
||||
<CommandClearVolleyTargetDesc>Clear the coordinated volley target.</CommandClearVolleyTargetDesc>
|
||||
<SetVolleyTarget>Set volley target</SetVolleyTarget>
|
||||
```
|
||||
|
||||
## 5. 需要准备的图标
|
||||
|
||||
您需要在 `Textures/UI/Gizmos/` 文件夹中准备以下图标:
|
||||
- `VolleyFire.png` - 齐射开关图标
|
||||
- `SetTarget.png` - 设置目标图标
|
||||
- `ClearTarget.png` - 清除目标图标
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 工作流程:
|
||||
1. **启用齐射模式**:点击齐射开关按钮启用协同攻击
|
||||
2. **设置目标**:在齐射模式下点击设置目标按钮,选择要攻击的目标
|
||||
3. **自动协同**:所有炮塔会优先攻击齐射目标,如果目标在射程内且可攻击
|
||||
4. **智能回退**:如果齐射目标无效或不在射程内,炮塔会恢复原来自动攻击逻辑
|
||||
5. **独立控制**:每个 pawn 有独立的齐射目标和开关状态
|
||||
|
||||
### 优先级系统:
|
||||
1. 齐射目标(如果启用且有效)
|
||||
2. 原有自动攻击目标
|
||||
3. 空闲状态
|
||||
|
||||
### 优势:
|
||||
- **协同攻击**:所有炮塔集中火力攻击同一目标
|
||||
- **灵活控制**:可以随时启用/禁用齐射模式
|
||||
- **兼容性**:不影响原有的单个炮塔开关功能
|
||||
- **智能判断**:自动检查射程和攻击可行性
|
||||
|
||||
这样您就为两个炮塔类添加了完整的齐射目标功能,让多个炮塔可以协同攻击同一个目标!
|
||||
@@ -11,10 +11,11 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_ApparelInterceptor : CompProperties
|
||||
{
|
||||
public float radius = 3f;
|
||||
public float radius = 3f; // 仅用于视觉效果
|
||||
public int startupDelay = 0;
|
||||
public int rechargeDelay = 3200;
|
||||
public int hitPoints = 100;
|
||||
public int maxBounces = 3;
|
||||
|
||||
public bool interceptGroundProjectiles = false;
|
||||
public bool interceptNonHostileProjectiles = false;
|
||||
@@ -61,19 +62,180 @@ namespace WulaFallenEmpire
|
||||
public int currentHitPoints = -1;
|
||||
private int ticksToReset;
|
||||
private int activatedTick = -999999;
|
||||
private bool initialized = false; // 添加初始化标志
|
||||
// 视觉效果变量
|
||||
private bool initialized = false;
|
||||
|
||||
// 视觉效果变量
|
||||
private float lastInterceptAngle;
|
||||
private bool drawInterceptCone;
|
||||
|
||||
// 静态资源
|
||||
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
|
||||
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
|
||||
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
|
||||
private static readonly Color InactiveColor = new Color(0.2f, 0.2f, 0.2f);
|
||||
|
||||
// 属性
|
||||
public CompProperties_ApparelInterceptor Props => (CompProperties_ApparelInterceptor)props;
|
||||
private Pawn PawnOwner => (parent as Apparel)?.Wearer;
|
||||
// 修复:确保组件完全初始化的方法
|
||||
public Pawn PawnOwner => (parent as Apparel)?.Wearer;
|
||||
|
||||
// 主要拦截方法
|
||||
public bool TryInterceptProjectile(Projectile projectile, Thing hitThing)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
// 基础检查
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed)
|
||||
return false;
|
||||
|
||||
if (projectile == null || projectile.Destroyed || !projectile.Spawned)
|
||||
return false;
|
||||
|
||||
if (!Active)
|
||||
return false;
|
||||
|
||||
// 检查地图匹配
|
||||
if (PawnOwner.Map == null || projectile.Map == null || PawnOwner.Map != projectile.Map)
|
||||
return false;
|
||||
|
||||
// 关键检查:抛射体是否要击中这个护盾的穿戴者?
|
||||
if (hitThing != PawnOwner)
|
||||
return false;
|
||||
|
||||
// 检查抛射体类型
|
||||
if (!InterceptsProjectile(Props, projectile))
|
||||
return false;
|
||||
|
||||
// 检查敌对关系
|
||||
bool isHostile = (projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner)) ||
|
||||
(projectile.Launcher == null && Props.interceptNonHostileProjectiles);
|
||||
if (!isHostile)
|
||||
return false;
|
||||
|
||||
// --- 拦截成功 ---
|
||||
InterceptSuccess(projectile);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"[CompApparelInterceptor] Error in TryInterceptProjectile: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void InterceptSuccess(Projectile projectile)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录拦截角度用于视觉效果
|
||||
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(PawnOwner.TrueCenter());
|
||||
lastInterceptTicks = Find.TickManager.TicksGame;
|
||||
drawInterceptCone = true;
|
||||
|
||||
// 播放拦截效果
|
||||
if (Props.soundInterceptEffecter != null && PawnOwner.Map != null)
|
||||
{
|
||||
var effecter = Props.soundInterceptEffecter.Spawn(PawnOwner.Position, PawnOwner.Map);
|
||||
if (effecter != null)
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
// 处理伤害类型
|
||||
if (projectile.DamageDef == DamageDefOf.EMP && !Props.isImmuneToEMP)
|
||||
{
|
||||
BreakShieldEmp(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 固定减少1点护盾量
|
||||
currentHitPoints = Mathf.Max(0, currentHitPoints - 1);
|
||||
if (currentHitPoints <= 0)
|
||||
{
|
||||
BreakShieldHitpoints(new DamageInfo(projectile.DamageDef, 1, instigator: projectile.Launcher));
|
||||
}
|
||||
}
|
||||
|
||||
// 反弹抛射体
|
||||
BounceProjectile(projectile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"[CompApparelInterceptor] Error during interception effects: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void BounceProjectile(Projectile projectile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (projectile == null || projectile.Destroyed)
|
||||
return;
|
||||
|
||||
// 计算反弹方向
|
||||
Vector3 bounceDirection = CalculateBounceDirection(projectile);
|
||||
|
||||
// 使用 Traverse 修改抛射体方向
|
||||
var traverse = Traverse.Create(projectile);
|
||||
|
||||
// 修改目的地 - 随机弹射
|
||||
Vector3 newDestination = projectile.ExactPosition + bounceDirection * 30f;
|
||||
traverse.Field("destination").SetValue(newDestination);
|
||||
|
||||
// 重置起点为当前位置
|
||||
traverse.Field("origin").SetValue(projectile.ExactPosition);
|
||||
|
||||
// 重新计算飞行时间
|
||||
float distance = (newDestination - projectile.ExactPosition).MagnitudeHorizontal();
|
||||
int newTicks = Mathf.CeilToInt(distance / projectile.def.projectile.SpeedTilesPerTick);
|
||||
traverse.Field("ticksToImpact").SetValue(newTicks);
|
||||
|
||||
// 播放反弹效果
|
||||
PlayBounceEffect(projectile);
|
||||
|
||||
Log.Message($"[Interceptor] Projectile bounced towards {bounceDirection}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Error in BounceProjectile: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 CalculateBounceDirection(Projectile projectile)
|
||||
{
|
||||
// 如果有发射者,尝试弹向发射者
|
||||
if (projectile.Launcher != null && projectile.Launcher.Spawned)
|
||||
{
|
||||
try
|
||||
{
|
||||
Vector3 toLauncher = (projectile.Launcher.Position.ToVector3() - projectile.ExactPosition).normalized;
|
||||
return toLauncher;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果计算失败,使用随机方向
|
||||
}
|
||||
}
|
||||
|
||||
// 随机弹射方向
|
||||
float angle = Rand.Range(0f, 360f);
|
||||
return Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward;
|
||||
}
|
||||
|
||||
private void PlayBounceEffect(Projectile projectile)
|
||||
{
|
||||
// 播放自定义反弹效果
|
||||
if (Props.soundInterceptEffecter != null)
|
||||
{
|
||||
var effecter = Props.soundInterceptEffecter.Spawn(projectile.Position, projectile.Map);
|
||||
effecter?.Cleanup();
|
||||
}
|
||||
|
||||
// 添加视觉特效
|
||||
FleckMaker.Static(projectile.ExactPosition, projectile.Map, FleckDefOf.ShotFlash, 1.5f);
|
||||
}
|
||||
|
||||
// 其余现有方法保持不变...
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (initialized) return;
|
||||
@@ -86,7 +248,7 @@ namespace WulaFallenEmpire
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
// 修复:安全的 Active 属性
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get
|
||||
@@ -94,7 +256,6 @@ namespace WulaFallenEmpire
|
||||
EnsureInitialized();
|
||||
if (PawnOwner == null || !PawnOwner.Spawned) return false;
|
||||
|
||||
// 修复:添加额外的 null 检查
|
||||
if (stunner == null || OnCooldown || Charging || (stunner != null && stunner.Stunned) || shutDown || currentHitPoints <= 0)
|
||||
return false;
|
||||
|
||||
@@ -104,32 +265,15 @@ namespace WulaFallenEmpire
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected bool ShouldDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || !Active)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (PawnOwner.Drafted || PawnOwner.InAggroMentalState || (PawnOwner.Faction != null && PawnOwner.Faction.HostileTo(Faction.OfPlayer) && !PawnOwner.IsPrisoner))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Find.Selector.IsSelected(PawnOwner))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ... 其余现有属性和方法保持不变
|
||||
public bool OnCooldown => ticksToReset > 0;
|
||||
public bool Charging => startedChargingTick >= 0 && Find.TickManager.TicksGame < startedChargingTick + Props.startupDelay;
|
||||
public int CooldownTicksLeft => ticksToReset;
|
||||
public int ChargingTicksLeft => (startedChargingTick < 0) ? 0 : Mathf.Max(startedChargingTick + Props.startupDelay - Find.TickManager.TicksGame, 0);
|
||||
public int HitPointsMax => Props.hitPoints;
|
||||
protected virtual int HitPointsPerInterval => 1;
|
||||
|
||||
public override void PostPostMake()
|
||||
{
|
||||
base.PostPostMake();
|
||||
@@ -140,6 +284,7 @@ namespace WulaFallenEmpire
|
||||
startedChargingTick = Find.TickManager.TicksGame;
|
||||
}
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
@@ -153,79 +298,11 @@ namespace WulaFallenEmpire
|
||||
Scribe_Deep.Look(ref stunner, "stunner", parent);
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
// 强制重新初始化
|
||||
initialized = false;
|
||||
EnsureInitialized();
|
||||
}
|
||||
}
|
||||
// 在 CompApparelInterceptor 类中修改 TryIntercept 方法
|
||||
public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureInitialized();
|
||||
// 更严格的防御性检查
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed)
|
||||
return false;
|
||||
if (projectile == null || projectile.Destroyed || !projectile.Spawned)
|
||||
return false;
|
||||
if (!Active)
|
||||
return false;
|
||||
// 检查地图匹配
|
||||
if (PawnOwner.Map == null || projectile.Map == null || PawnOwner.Map != projectile.Map)
|
||||
return false;
|
||||
// 检查位置有效性
|
||||
Vector2 pawnPos = PawnOwner.Position.ToVector2();
|
||||
Vector2 lastPos = lastExactPos.ToVector2();
|
||||
Vector2 newPos = newExactPos.ToVector2();
|
||||
if (!GenGeo.IntersectLineCircleOutline(pawnPos, Props.radius, lastPos, newPos))
|
||||
return false;
|
||||
if (!InterceptsProjectile(Props, projectile))
|
||||
return false;
|
||||
bool isHostile = (projectile.Launcher != null && projectile.Launcher.HostileTo(PawnOwner)) ||
|
||||
(projectile.Launcher == null && Props.interceptNonHostileProjectiles);
|
||||
if (!isHostile)
|
||||
return false;
|
||||
// --- Interception Success ---
|
||||
try
|
||||
{
|
||||
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(PawnOwner.TrueCenter());
|
||||
lastInterceptTicks = Find.TickManager.TicksGame;
|
||||
drawInterceptCone = true;
|
||||
if (Props.soundInterceptEffecter != null && PawnOwner.Map != null)
|
||||
{
|
||||
var effecter = Props.soundInterceptEffecter.Spawn(PawnOwner.Position, PawnOwner.Map);
|
||||
if (effecter != null)
|
||||
effecter.Cleanup();
|
||||
}
|
||||
// 处理伤害
|
||||
if (projectile.DamageDef == DamageDefOf.EMP && !Props.isImmuneToEMP)
|
||||
{
|
||||
BreakShieldEmp(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
|
||||
}
|
||||
else if (HitPointsMax > 0)
|
||||
{
|
||||
currentHitPoints = Mathf.Max(0, currentHitPoints - projectile.DamageAmount);
|
||||
if (currentHitPoints <= 0)
|
||||
{
|
||||
BreakShieldHitpoints(new DamageInfo(projectile.DamageDef, projectile.DamageAmount, instigator: projectile.Launcher));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"[CompApparelInterceptor] Error during interception effects: {ex.Message}");
|
||||
return true; // 即使效果出错,仍然返回拦截成功
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"[CompApparelInterceptor] Critical error in TryIntercept: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public override void CompTick()
|
||||
{
|
||||
try
|
||||
@@ -234,9 +311,10 @@ namespace WulaFallenEmpire
|
||||
EnsureInitialized();
|
||||
|
||||
if (PawnOwner == null || !PawnOwner.Spawned) return;
|
||||
// 防御性检查
|
||||
|
||||
if (stunner != null)
|
||||
stunner.StunHandlerTick();
|
||||
|
||||
if (OnCooldown)
|
||||
{
|
||||
ticksToReset--;
|
||||
@@ -250,6 +328,7 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
currentHitPoints = Mathf.Clamp(currentHitPoints + HitPointsPerInterval, 0, HitPointsMax);
|
||||
}
|
||||
|
||||
if (Props.activeSound != null)
|
||||
{
|
||||
if (Active && (sustainer == null || sustainer.Ended))
|
||||
@@ -264,6 +343,7 @@ namespace WulaFallenEmpire
|
||||
Log.Warning($"[CompApparelInterceptor] Error in CompTick: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.Map != null)
|
||||
@@ -272,6 +352,7 @@ namespace WulaFallenEmpire
|
||||
currentHitPoints = HitPointsMax;
|
||||
ticksToReset = 0;
|
||||
}
|
||||
|
||||
private void BreakShieldHitpoints(DamageInfo dinfo)
|
||||
{
|
||||
if (PawnOwner != null && PawnOwner.Spawned && PawnOwner.MapHeld != null)
|
||||
@@ -282,12 +363,14 @@ namespace WulaFallenEmpire
|
||||
currentHitPoints = 0;
|
||||
ticksToReset = Props.rechargeDelay;
|
||||
}
|
||||
|
||||
private void BreakShieldEmp(DamageInfo dinfo)
|
||||
{
|
||||
BreakShieldHitpoints(dinfo);
|
||||
if (Props.disarmedByEmpForTicks > 0 && stunner != null)
|
||||
stunner.Notify_DamageApplied(new DamageInfo(DamageDefOf.EMP, (float)Props.disarmedByEmpForTicks / 30f));
|
||||
}
|
||||
|
||||
public static bool InterceptsProjectile(CompProperties_ApparelInterceptor props, Projectile projectile)
|
||||
{
|
||||
if (projectile == null || projectile.def == null || projectile.def.projectile == null)
|
||||
@@ -297,7 +380,27 @@ namespace WulaFallenEmpire
|
||||
return props.interceptAirProjectiles;
|
||||
return props.interceptGroundProjectiles;
|
||||
}
|
||||
// --- DRAWING LOGIC ---
|
||||
|
||||
// 视觉效果相关方法保持不变...
|
||||
protected bool ShouldDisplay
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || PawnOwner.Dead || PawnOwner.Downed || !Active)
|
||||
return false;
|
||||
|
||||
if (PawnOwner.Drafted || PawnOwner.InAggroMentalState ||
|
||||
(PawnOwner.Faction != null && PawnOwner.Faction.HostileTo(Faction.OfPlayer) && !PawnOwner.IsPrisoner))
|
||||
return true;
|
||||
|
||||
if (Find.Selector.IsSelected(PawnOwner))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompDrawWornExtras()
|
||||
{
|
||||
try
|
||||
@@ -306,8 +409,10 @@ namespace WulaFallenEmpire
|
||||
EnsureInitialized();
|
||||
|
||||
if (PawnOwner == null || !PawnOwner.Spawned || !ShouldDisplay) return;
|
||||
|
||||
Vector3 drawPos = PawnOwner.Drawer?.DrawPos ?? PawnOwner.Position.ToVector3Shifted();
|
||||
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
|
||||
|
||||
float alpha = GetCurrentAlpha();
|
||||
if (alpha > 0f)
|
||||
{
|
||||
@@ -318,6 +423,7 @@ namespace WulaFallenEmpire
|
||||
matrix.SetTRS(drawPos, Quaternion.identity, new Vector3(Props.radius * 2f * 1.1601562f, 1f, Props.radius * 2f * 1.1601562f));
|
||||
Graphics.DrawMesh(MeshPool.plane10, matrix, ForceFieldMat, 0, null, 0, MatPropertyBlock);
|
||||
}
|
||||
|
||||
float coneAlpha = GetCurrentConeAlpha_RecentlyIntercepted();
|
||||
if (coneAlpha > 0f)
|
||||
{
|
||||
@@ -334,18 +440,21 @@ namespace WulaFallenEmpire
|
||||
Log.Warning($"[CompApparelInterceptor] Error in CompDrawWornExtras: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private float GetCurrentAlpha()
|
||||
{
|
||||
float idleAlpha = Mathf.Lerp(0.3f, 0.6f, (Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
|
||||
float interceptAlpha = Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f);
|
||||
return Mathf.Max(idleAlpha, interceptAlpha);
|
||||
}
|
||||
|
||||
private float GetCurrentConeAlpha_RecentlyIntercepted()
|
||||
{
|
||||
if (!drawInterceptCone) return 0f;
|
||||
return Mathf.Clamp01(1f - (float)(Find.TickManager.TicksGame - lastInterceptTicks) / 40f) * 0.82f;
|
||||
}
|
||||
// --- GIZMO ---
|
||||
|
||||
// Gizmo 相关保持不变...
|
||||
public override IEnumerable<Gizmo> CompGetWornGizmosExtra()
|
||||
{
|
||||
EnsureInitialized();
|
||||
@@ -355,6 +464,7 @@ namespace WulaFallenEmpire
|
||||
yield return new Gizmo_EnergyShieldStatus { shield = this };
|
||||
}
|
||||
}
|
||||
|
||||
public override string CompInspectStringExtra()
|
||||
{
|
||||
EnsureInitialized();
|
||||
@@ -371,6 +481,8 @@ namespace WulaFallenEmpire
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Gizmo 类保持不变...
|
||||
[StaticConstructorOnStartup]
|
||||
public class Gizmo_EnergyShieldStatus : Gizmo
|
||||
{
|
||||
|
||||
@@ -164,6 +164,7 @@
|
||||
<Compile Include="HediffComp\HediffCompProperties_DisappearWithEffect.cs" />
|
||||
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
|
||||
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffComp_TopTurret\VolleyTargetManager.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\DRMDamageShield.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
|
||||
<Compile Include="Pawn\Comp_MultiTurretGun.cs" />
|
||||
|
||||
Reference in New Issue
Block a user