This commit is contained in:
2025-11-20 17:31:17 +08:00
parent 60a4c6de65
commit 6dc949158a
15 changed files with 789 additions and 805 deletions

View File

@@ -60,8 +60,8 @@
<cooldownTicksRange>600</cooldownTicksRange>
<verbProperties>
<verbClass>Verb_CastAbility</verbClass>
<range>24</range>
<warmupTime>0</warmupTime>
<range>18</range>
<warmupTime>1</warmupTime>
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
<ai_IsWeapon>true</ai_IsWeapon>
@@ -85,36 +85,16 @@
</verbProperties>
<comps>
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
<range>12</range>
<lineWidthEnd>5</lineWidthEnd>
<range>18</range>
<lineWidthEnd>12</lineWidthEnd>
<canHitFilledCells>true</canHitFilledCells>
<affectAllies>false</affectAllies>
<affectCaster>false</affectCaster>
<effecterDef>Fire_Spew</effecterDef>
<hitEffecter>Fire_SpewShort</hitEffecter>
<castEffecter>Fire_SpewShort</castEffecter>
<castEffecterMaintainTicks>60</castEffecterMaintainTicks>
<hitEffecter>Fire_Spew</hitEffecter>
<hitEffecterMaintainTicks>30</hitEffecterMaintainTicks>
</li>
<li Class="CompProperties_AbilityEffecterOnCaster">
<effecterDef>Fire_Spew</effecterDef>
<maintainTicks>20</maintainTicks> <!-- Long enough for the "2nd wave" to spawn -->
</li>
<!-- <li Class="CompProperties_AbilityFleckOnTarget">
<fleckDef>PsycastPsychicEffect</fleckDef>
</li> -->
<li Class="CompProperties_AbilityEffecterOnTarget">
<effecterDef>Fire_SpewShort</effecterDef>
</li>
<!-- <li Class="CompProperties_AbilityEffecterOnTarget">
<effecterDef>Skip_Exit</effecterDef>
<maintainForTicks>60</maintainForTicks>
</li>
<li Class="CompProperties_AbilityFleckOnTarget">
<fleckDefs>
<li>PsycastSkipInnerExit</li>
<li>PsycastSkipOuterRingExit</li>
</fleckDefs>
<sound>Psycast_Skip_Exit</sound>
<preCastTicks>5</preCastTicks>
</li> -->
</comps>
</AbilityDef>
</Defs>

View File

@@ -502,33 +502,6 @@
<wornGraphicPath>Wula/Apparel/WULA_Knight_PowerArmor</wornGraphicPath>
</apparel>
<comps>
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
<!-- 基础功能 -->
<radius>1</radius> <!-- 护盾半径,决定了拦截范围 -->
<hitPoints>50</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
<maxBounces>3</maxBounces>
<!-- 拦截类型 -->
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
<interceptAirProjectiles>true</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
<interceptNonHostileProjectiles>true</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
<!-- 视觉与音效 -->
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
<soundInterceptEffecter>Interceptor_BlockedProjectile</soundInterceptEffecter> <!-- 成功拦截时的音效 -->
<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>
<ThingDef ParentName="WULA_ApparelHelmetBase">
@@ -624,30 +597,31 @@
<wornGraphicPath>Wula/Apparel/WULA_Heavy_Infantry_PowerArmor</wornGraphicPath>
</apparel>
<comps>
<li Class="WulaFallenEmpire.CompProperties_ApparelInterceptor">
<!-- 基础功能 -->
<radius>1.5</radius> <!-- 护盾半径,决定了拦截范围 -->
<hitPoints>200</hitPoints> <!-- 护盾的生命值,每次拦截会消耗 -->
<rechargeDelay>2800</rechargeDelay> <!-- 护盾破裂后的冷却时间 (ticks) -->
<!-- 拦截类型 -->
<interceptGroundProjectiles>true</interceptGroundProjectiles> <!-- 是否拦截地面弹丸 (如子弹) -->
<interceptAirProjectiles>false</interceptAirProjectiles> <!-- 是否拦截空中弹丸 (如炮弹) -->
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles> <!-- 是否拦截非敌对弹丸 -->
<!-- 视觉与音效 -->
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
<soundInterceptEffecter>Interceptor_BlockedProjectile</soundInterceptEffecter> <!-- 成功拦截时的音效 -->
<soundBreakEffecter>Shield_Break</soundBreakEffecter> <!-- 护盾破裂时的音效 -->
<reactivateEffect>BulletShieldGenerator_Reactivate</reactivateEffect> <!-- 护盾冷却结束后恢复的特效 -->
<!-- EMP 效果 -->
<isImmuneToEMP>false</isImmuneToEMP> <!-- 是否免疫EMP伤害 -->
<disarmedByEmpForTicks>600</disarmedByEmpForTicks> <!-- 被EMP击中后额外的眩晕/瘫痪时间 (ticks) -->
<!-- 被动恢复 -->
<rechargeHitPointsIntervalTicks>60</rechargeHitPointsIntervalTicks> <!-- 未破盾时每隔多少ticks恢复1点生命值 -->
<li Class="WulaFallenEmpire.CompProperties_AreaShield">
<radius>3</radius>
<baseHitPoints>50</baseHitPoints>
<rechargeDelay>2400</rechargeDelay>
<rechargeHitPointsIntervalTicks>30</rechargeHitPointsIntervalTicks>
<!-- 效果器配置 -->
<absorbEffecter>Interceptor_BlockedProjectile</absorbEffecter>
<interceptEffecter>Interceptor_BlockedProjectile</interceptEffecter>
<breakEffecter>Shield_Break</breakEffecter>
<reactivateEffecter>BulletShieldGenerator_Reactivate</reactivateEffecter>
<color>(0.9, 0.2, 0.2, 0.2)</color> <!-- 护盾气泡的颜色 (RGBA) -->
<!-- 拦截设置 -->
<interceptGroundProjectiles>true</interceptGroundProjectiles>
<interceptNonHostileProjectiles>false</interceptNonHostileProjectiles>
<interceptAirProjectiles>true</interceptAirProjectiles>
<!-- 反射设置 -->
<canReflect>true</canReflect>
<reflectChance>1</reflectChance>
<reflectAngleRange>30</reflectAngleRange>
<reflectCost>1</reflectCost>
<reflectEffecter>Interceptor_BlockedProjectile</reflectEffecter>
</li>
<li Class="CompProperties_CauseHediff_Apparel">
<hediff>WULA_Heavy_Infantry_PowerArmor_Mortar_Hediff</hediff>

View File

@@ -344,4 +344,17 @@
<WULA_TaxCollection.Status>什一税征收:{0} {1} {2}</WULA_TaxCollection.Status>
<WULA_TaxCollection.Status.Succeeded>- 征收已完成</WULA_TaxCollection.Status.Succeeded>
<WULA_TaxCollection.Status.Failed>- 逾期</WULA_TaxCollection.Status.Failed>
<!-- 区域护盾Gizmo文本 -->
<AreaShield_Cooldown>冷却中</AreaShield_Cooldown>
<AreaShield_Inactive>未激活</AreaShield_Inactive>
<AreaShield_Yes></AreaShield_Yes>
<AreaShield_No></AreaShield_No>
<AreaShield_Tooltip_Basic>区域护盾状态:\n最大生命值{0}\n保护半径{1}格\n破碎冷却{3}秒</AreaShield_Tooltip_Basic>
<AreaShield_Tooltip_Intercept>拦截设置:\n- 地面投射物:{0}\n- 空中投射物:{1}\n- 非敌对投射物:{2}</AreaShield_Tooltip_Intercept>
<AreaShield_CooldownRemaining>冷却剩余时间:{0}秒</AreaShield_CooldownRemaining>
<AreaShield_InactiveReason>护盾未激活:\n- 装备未被穿戴\n- 穿戴者死亡或倒下\n- 护盾已破碎</AreaShield_InactiveReason>
</LanguageData>

View File

@@ -21,6 +21,9 @@ namespace WulaFallenEmpire
Map map = parent.pawn.MapHeld;
if (map == null) return;
// 播放发射特效(在施法者位置)
PlayCastEffecter(map);
// 获取扇形区域内的所有单元格
List<IntVec3> 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<PreCastAction> 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)

View File

@@ -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()

View File

@@ -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<Gizmo> 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<Texture2D>.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<Texture2D>.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<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)
@@ -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;
}
}
}

View File

@@ -1,87 +0,0 @@
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;
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;
}
}
}

View File

@@ -1,462 +0,0 @@
## 🤖 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. 空闲状态
### 优势:
- **协同攻击**:所有炮塔集中火力攻击同一目标
- **灵活控制**:可以随时启用/禁用齐射模式
- **兼容性**:不影响原有的单个炮塔开关功能
- **智能判断**:自动检查射程和攻击可行性
这样您就为两个炮塔类添加了完整的齐射目标功能,让多个炮塔可以协同攻击同一个目标!

View File

@@ -0,0 +1,84 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public static class AreaShieldManager
{
private static Dictionary<Map, HashSet<ThingComp_AreaShield>> activeShieldsByMap =
new Dictionary<Map, HashSet<ThingComp_AreaShield>>();
private static int lastUpdateTick = 0;
private const int UPDATE_INTERVAL_TICKS = 60;
public static IEnumerable<ThingComp_AreaShield> 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<ThingComp_AreaShield>();
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
{
if (pawn.apparel != null)
{
foreach (var apparel in pawn.apparel.WornApparel)
{
// 同时支持普通护盾和反弹护盾
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
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<Map>();
foreach (var map in activeShieldsByMap.Keys)
{
if (map == null || !Find.Maps.Contains(map))
mapsToRemove.Add(map);
}
foreach (var map in mapsToRemove)
{
activeShieldsByMap.Remove(map);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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; // 销毁抛射体
}
}
/// <summary>
/// 尝试反射抛射体 - 现在会创建新的抛射体
/// </summary>
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;
}
/// <summary>
/// 创建反射后的新抛射体
/// </summary>
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}");
}
}
/// <summary>
/// 获取反射抛射体的发射位置(护盾边界上)
/// </summary>
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();
}
/// <summary>
/// 复制抛射体重要属性
/// </summary>
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<ExtraDamage>(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<Gizmo> 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);
}
}
}

View File

@@ -166,7 +166,6 @@
<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" />
@@ -225,10 +224,15 @@
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.cs" />
<Compile Include="ThingComp\Comp_WeaponRenderDynamic.cs" />
<Compile Include="ThingComp\WULA_AreaShield\AreaShieldManager.cs" />
<Compile Include="ThingComp\WULA_AreaShield\CompProperties_AreaShield.cs" />
<Compile Include="ThingComp\WULA_AreaShield\Gizmo_AreaShieldStatus.cs" />
<Compile Include="ThingComp\WULA_AreaShield\Harmony_AreaShieldInterceptor.cs" />
<Compile Include="ThingComp\WULA_AreaShield\ThingComp_AreaShield.cs" />
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompCustomUniqueWeapon.cs" />
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompProperties_CustomUniqueWeapon.cs" />
<Compile Include="ThingComp\Wula_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
<Compile Include="ThingComp\Wula_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
<Compile Include="ThingComp\WULA_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
<Compile Include="ThingComp\WULA_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />