This commit is contained in:
Tourswen
2025-11-20 22:57:25 +08:00
parent 6dc949158a
commit 22f03e2e05
19 changed files with 491 additions and 158 deletions

View File

@@ -55,8 +55,6 @@
<writeCombatLog>True</writeCombatLog>
<showPsycastEffects>False</showPsycastEffects>
<aiCanUse>true</aiCanUse>
<ai_SearchAOEForTargets>true</ai_SearchAOEForTargets>
<ai_IsIncendiary>true</ai_IsIncendiary>
<cooldownTicksRange>600</cooldownTicksRange>
<verbProperties>
<verbClass>Verb_CastAbility</verbClass>
@@ -64,7 +62,7 @@
<warmupTime>1</warmupTime>
<soundCast>WarqueenWarUrchinsSpawned</soundCast>
<ai_IsWeapon>true</ai_IsWeapon>
<ai_IsWeapon>false</ai_IsWeapon>
<ai_ProjectileLaunchingIgnoresMeleeThreats>true</ai_ProjectileLaunchingIgnoresMeleeThreats>
<requireLineOfSight>false</requireLineOfSight>
@@ -86,15 +84,112 @@
<comps>
<li Class="WulaFallenEmpire.CompProperties_AbilityAreaDestruction">
<range>18</range>
<lineWidthEnd>12</lineWidthEnd>
<lineWidthEnd>18</lineWidthEnd>
<canHitFilledCells>true</canHitFilledCells>
<affectAllies>false</affectAllies>
<affectCaster>false</affectCaster>
<castEffecter>Fire_SpewShort</castEffecter>
<castEffecterMaintainTicks>60</castEffecterMaintainTicks>
<hitEffecter>Fire_Spew</hitEffecter>
<hitEffecterMaintainTicks>30</hitEffecterMaintainTicks>
<castEffecter>WULA_AreaDestruction_Shockwave</castEffecter>
<hitEffecter>WULA_AreaDestruction_Hit</hitEffecter>
</li>
</comps>
</AbilityDef>
<EffecterDef>
<defName>WULA_AreaDestruction_Hit</defName>
<children>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<moteDef>WULA_Mote_ChargeLanceShot</moteDef>
<burstCount>1~4</burstCount>
<scale>0.4~0.8</scale>
<speed>20~40</speed>
<angle>135~225</angle>
<positionRadius>0.01</positionRadius>
<spawnLocType>OnSource</spawnLocType>
</li>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<moteDef>WULA_Mote_ChargeLanceShot</moteDef>
<burstCount>2~3</burstCount>
<scale>0.4~0.8</scale>
<speed>10~20</speed>
<angle>135~225</angle>
<positionRadius>0.01</positionRadius>
<spawnLocType>OnSource</spawnLocType>
</li>
</children>
<offsetTowardsTarget>0.25~0.25</offsetTowardsTarget>
<positionRadius>0.1</positionRadius>
</EffecterDef>
<EffecterDef>
<defName>WULA_AreaDestruction_Shockwave</defName>
<children>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<fleckDef>Fleck_BlastMechBandShockwave</fleckDef>
<burstCount>1</burstCount>
<spawnLocType>OnSource</spawnLocType>
<absoluteAngle>true</absoluteAngle>
<rotation>0~0</rotation>
</li>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<moteDef>WULA_AreaDestruction_Mainwave</moteDef>
<burstCount>1</burstCount>
<scale>1</scale>
<speed>20</speed>
<angle>0</angle>
<positionRadius>0.01</positionRadius>
<spawnLocType>OnSource</spawnLocType>
</li>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<fleckDef>FlashMechBand</fleckDef>
<burstCount>1</burstCount>
<spawnLocType>OnSource</spawnLocType>
<absoluteAngle>true</absoluteAngle>
<rotation>0~0</rotation>
</li>
<li>
<subEffecterClass>SubEffecter_SprayerTriggered</subEffecterClass>
<moteDef>Mote_RedFlashStrong</moteDef>
<burstCount>1</burstCount>
<spawnLocType>OnSource</spawnLocType>
<scale>24</scale>
</li>
</children>
</EffecterDef>
<ThingDef ParentName="MoteBase">
<defName>WULA_Mote_ChargeLanceShot</defName>
<graphicData>
<texPath>Things/Projectile/ChargeLanceShot</texPath>
<drawSize>(0.75,1.5)</drawSize>
<shaderType>MoteGlow</shaderType>
<color>(0.6,0.1,0.6,1)</color>
</graphicData>
<altitudeLayer>Projectile</altitudeLayer>
<mote>
<fadeInTime>0.2</fadeInTime>
<solidTime>0.4</solidTime>
<fadeOutTime>0.2</fadeOutTime>
<growthRate>-0.8</growthRate>
<rotateTowardsMoveDirection>true</rotateTowardsMoveDirection>
</mote>
</ThingDef>
<ThingDef ParentName="MoteBase">
<defName>WULA_AreaDestruction_Mainwave</defName>
<graphicData>
<texPath>Wula/Mote/WULA_AreaDestruction_Mainwave</texPath>
<drawSize>(15,15)</drawSize>
<shaderType>MoteGlow</shaderType>
<color>(0.6,0.1,0.6,0.5)</color>
</graphicData>
<altitudeLayer>Floor</altitudeLayer>
<mote>
<fadeInTime>0.2</fadeInTime>
<solidTime>0.4</solidTime>
<fadeOutTime>0.2</fadeOutTime>
<growthRate>0.25</growthRate>
<rotateTowardsMoveDirection>true</rotateTowardsMoveDirection>
</mote>
</ThingDef>
</Defs>

View File

@@ -231,6 +231,44 @@
<techHediffsChance>1</techHediffsChance>
<techHediffsMoney>9999~9999</techHediffsMoney>
</PawnKindDef>
<PawnKindDef ParentName="HeavyMechanoidKind">
<defName>Wula_Psi_Titan</defName>
<label>PAt-52"灵能泰坦"</label>
<race>Wula_Psi_Titan</race>
<defaultFactionType>PlayerColony</defaultFactionType>
<allowInMechClusters>false</allowInMechClusters>
<canMeleeAttack>false</canMeleeAttack>
<isGoodBreacher>true</isGoodBreacher>
<combatPower>1000</combatPower>
<isBoss>true</isBoss>
<maxPerGroup>1</maxPerGroup>
<flyingAnimationFramePathPrefix>Wula/Things/Wula_Mech_Mobile_Factory/Flying/Wula_Mech_Mobile_Factory_Flying_</flyingAnimationFramePathPrefix>
<flyingAnimationDrawSize>1</flyingAnimationDrawSize>
<flyingAnimationFrameCount>1</flyingAnimationFrameCount>
<flyingAnimationTicksPerFrame>2</flyingAnimationTicksPerFrame>
<flyingAnimationInheritColors>false</flyingAnimationInheritColors>
<lifeStages>
<li>
<bodyGraphicData>
<texPath>Wula/Things/Wula_Psi_Titan/Bodies/Naked_Thin</texPath>
<maskPath>Wula/Things/WULA_Cat/AllegianceOverlays/None</maskPath>
<!-- <shaderType>TransparentPostLight</shaderType> -->
<graphicClass>Graphic_Multi</graphicClass>
<drawSize>10</drawSize>
</bodyGraphicData>
</li>
</lifeStages>
<weaponMoney>0</weaponMoney>
<controlGroupPortraitZoom>0.4</controlGroupPortraitZoom>
<abilities>
<li>WULA_PsiCrusher</li>
</abilities>
</PawnKindDef>
<!-- 战斗类乌拉 -->
<PawnKindDef>

View File

@@ -598,8 +598,8 @@
</apparel>
<comps>
<li Class="WulaFallenEmpire.CompProperties_AreaShield">
<radius>3</radius>
<baseHitPoints>50</baseHitPoints>
<radius>5</radius>
<baseHitPoints>100</baseHitPoints>
<rechargeDelay>2400</rechargeDelay>
<rechargeHitPointsIntervalTicks>30</rechargeHitPointsIntervalTicks>
@@ -609,7 +609,7 @@
<breakEffecter>Shield_Break</breakEffecter>
<reactivateEffecter>BulletShieldGenerator_Reactivate</reactivateEffecter>
<color>(0.9, 0.2, 0.2, 0.2)</color> <!-- 护盾气泡的颜色 (RGBA) -->
<color>(0.9, 0.2, 0.2, 0.5)</color> <!-- 护盾气泡的颜色 (RGBA) -->
<!-- 拦截设置 -->
<interceptGroundProjectiles>true</interceptGroundProjectiles>

View File

@@ -48,7 +48,7 @@
<graphicData>
<texPath>Wula/Projectile/WULA_Shrapnel</texPath>
<graphicClass>Graphic_Single</graphicClass>
<drawSize>(1,2)</drawSize>
<drawSize>(1.5,2)</drawSize>
</graphicData>
<modExtensions>
<li Class="WulaFallenEmpire.TrackingBulletDef">

View File

@@ -1445,6 +1445,70 @@
</li>
</comps>
</ThingDef>
<ThingDef ParentName="WULA_BaseMechanoid">
<defName>Wula_Psi_Titan</defName>
<label>PAt-6"灵能泰坦"</label>
<description>由乌拉帝国大教堂所开发的重型灵能机械体,以短距离折跃优雅地穿梭于炮火间,并用灵能盾抵挡敌方射弹侵袭。该机体不仅镌刻了破坏力强大的星光追猎术式用以发起远距离跟踪打击,还拥有一系列改变战局的灵能能力。\n\n但是在近身搏斗中灵能泰坦是一个可笑的对手并且它的秘文纹路很容易遭到外力破坏它的本体无法吸收太多伤害</description>
<uiIconPath>Wula/Things/Wula_Psi_Titan/Wula_Psi_Titan_Icon</uiIconPath>
<statBases>
<BandwidthCost>1</BandwidthCost>
<MoveSpeed>1</MoveSpeed>
<EnergyShieldEnergyMax>5</EnergyShieldEnergyMax>
<MaxFlightTime>9999</MaxFlightTime>
<FlightCooldown>0</FlightCooldown>
</statBases>
<race>
<body>Mech_Warqueen</body>
<baseBodySize>30</baseBodySize>
<lifeStageAges>
<li>
<def>MechanoidFullyFormed</def>
<minAge>0</minAge>
<soundWounded>Pawn_Mech_Warqueen_Wounded</soundWounded>
<soundDeath>Pawn_Mech_Warqueen_Death</soundDeath>
<soundCall>Pawn_Mech_Warqueen_Call</soundCall>
</li>
</lifeStageAges>
<baseHealthScale>25</baseHealthScale>
<flightStartChanceOnJobStart>1</flightStartChanceOnJobStart>
<!-- <thinkTreeConstant>WarUrchinConstant</thinkTreeConstant> -->
</race>
<tools>
<li>
<label>碾压</label>
<capacities>
<li>Blunt</li>
</capacities>
<power>360</power>
<cooldownTime>8</cooldownTime>
<linkedBodyPartsGroup>Torso</linkedBodyPartsGroup>
<ensureLinkedBodyPartsGroupAlwaysUsable>true</ensureLinkedBodyPartsGroupAlwaysUsable>
</li>
</tools>
<comps>
<li Class="CompProperties_Shield">
<startingTicksToReset>36000</startingTicksToReset><!-- 10 mins -->
<minDrawSize>8.2</minDrawSize>
<maxDrawSize>8.4</maxDrawSize>
<energyLossPerDamage>0.01</energyLossPerDamage>
<energyOnReset>4.0</energyOnReset>
<blocksRangedWeapons>false</blocksRangedWeapons>
</li>
<!-- 飞行组件 -->
<li Class="WulaFallenEmpire.CompProperties_PawnFlight">
<!-- 飞行触发条件:仅在征召时飞行 -->
<flightCondition>Drafted</flightCondition>
<!-- 链接到我们刚刚创建的 AnimationDef -->
<flyingAnimationNorth>WULA_Hover_FlyNorth</flyingAnimationNorth>
<flyingAnimationEast>WULA_Hover_FlyEast</flyingAnimationEast>
<flyingAnimationSouth>WULA_Hover_FlySouth</flyingAnimationSouth>
</li>
</comps>
</ThingDef>
<!-- 特殊单位 -->
<ThingDef ParentName="BaseMechanoidWalker">

View File

@@ -346,15 +346,5 @@
<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>
<ShieldOfflineByMoving>未激活-移动中</ShieldOfflineByMoving>
</LanguageData>

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 210 KiB

View File

@@ -21,8 +21,8 @@ namespace WulaFallenEmpire
Map map = parent.pawn.MapHeld;
if (map == null) return;
// 播放发射特效(在施法者位置)
PlayCastEffecter(map);
// 播放发射特效(在施法者位置)- 在释放瞬间播放
//PlayCastEffecter(target, map);
// 获取扇形区域内的所有单元格
List<IntVec3> affectedCells = AffectedCells(target);
@@ -61,26 +61,26 @@ namespace WulaFallenEmpire
}
}
private void PlayCastEffecter(Map map)
private void PlayCastEffecter(LocalTargetInfo target, Map map)
{
try
{
if (Props.castEffecter == null) return;
// 使用与 CompAbilityEffect_EffecterOnCaster 相同的方
Effecter effecter = Props.castEffecter.Spawn(Pawn, map);
// 在释放瞬间创建效果器,确保正确的方
Effecter effecter = Props.castEffecter.Spawn(Pawn.Position, target.Cell, map);
if (Props.castEffecterMaintainTicks > 0)
{
// 使用与参考代码相同的方法来维持效果器
map.effecterMaintainer.AddEffecterToMaintain(effecter, new TargetInfo(Pawn), Pawn, Props.castEffecterMaintainTicks);
parent.AddEffecterToMaintain(effecter, Pawn.Position, target.Cell, Props.castEffecterMaintainTicks, map);
}
else
{
effecter.Cleanup();
}
Log.Message($"[AreaDestruction] Played cast effecter on caster at {Pawn.Position}");
Log.Message($"[AreaDestruction] Played cast effecter from {Pawn.Position} to {target.Cell}");
}
catch (System.Exception ex)
{
@@ -95,28 +95,35 @@ namespace WulaFallenEmpire
if (Props.hitEffecter == null) return;
if (target == null || target.Destroyed) return;
// 使用与 CompAbilityEffect_EffecterOnTarget 相同的方法
Effecter effecter;
if (target is Pawn pawnTarget)
{
effecter = Props.hitEffecter.Spawn(pawnTarget, map);
}
else
{
effecter = Props.hitEffecter.Spawn(target.Position, map);
}
// 计算冲击波方向:从施法者到目标的向量
Vector3 directionFromCaster = (target.Position.ToVector3Shifted() - Pawn.Position.ToVector3Shifted()).normalized;
// 计算反向位置:目标位置 + 反向向量 * 距离
// 这样特效会从目标位置向施法者的反方向播放
IntVec3 reversePosition = target.Position + new IntVec3(
Mathf.RoundToInt(-directionFromCaster.x * 2f),
0,
Mathf.RoundToInt(-directionFromCaster.z * 2f)
);
// 确保反向位置在地图范围内
reversePosition = reversePosition.ClampInsideMap(map);
// 使用两个位置参数来设置效果器方向
// 从目标位置到反向位置,这样特效会向施法者反方向播放
Effecter effecter = Props.hitEffecter.Spawn(target.Position, reversePosition, map);
if (Props.hitEffecterMaintainTicks > 0)
{
// 使用与参考代码相同的方法来维持效果器
parent.AddEffecterToMaintain(effecter, target.Position, Props.hitEffecterMaintainTicks);
// 维持效果器
parent.AddEffecterToMaintain(effecter, target.Position, reversePosition, Props.hitEffecterMaintainTicks, map);
}
else
{
effecter.Cleanup();
}
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position}");
Log.Message($"[AreaDestruction] Played hit effecter on {target.Label} at {target.Position} with reverse direction to {reversePosition}");
}
catch (System.Exception ex)
{
@@ -124,6 +131,21 @@ namespace WulaFallenEmpire
}
}
public override IEnumerable<PreCastAction> GetPreCastActions()
{
if (Props.castEffecter != null)
{
yield return new PreCastAction
{
action = delegate (LocalTargetInfo a, LocalTargetInfo b)
{
parent.AddEffecterToMaintain(Props.castEffecter.Spawn(parent.pawn.Position, a.Cell, parent.pawn.Map), Pawn.Position, a.Cell, 17, Pawn.MapHeld);
},
ticksAwayFromCast = 17
};
}
}
private void ProcessTarget(Thing target)
{
if (target is Building building)
@@ -254,12 +276,6 @@ namespace WulaFallenEmpire
}
}
public override IEnumerable<PreCastAction> GetPreCastActions()
{
// 这里不再预先创建效果器改为在Apply中创建
yield break;
}
public override void DrawEffectPreview(LocalTargetInfo target)
{
GenDraw.DrawFieldEdges(AffectedCells(target), Color.red);

View File

@@ -14,46 +14,45 @@ namespace WulaFallenEmpire
public static IEnumerable<ThingComp_AreaShield> GetActiveShieldsForMap(Map map)
{
if (map == null)
yield break;
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)
if (shield?.parent != null && !shield.parent.Destroyed && shield?.Active == true)
yield return shield;
}
}
}
private static void UpdateShieldCache()
{
activeShieldsByMap.Clear();
foreach (var map in Find.Maps)
{
if (map == null) continue;
var shieldSet = new HashSet<ThingComp_AreaShield>();
foreach (var pawn in map.mapPawns.AllPawnsSpawned)
{
if (pawn.apparel != null)
if (pawn?.apparel == null || pawn.Destroyed)
continue;
foreach (var apparel in pawn.apparel.WornApparel)
{
foreach (var apparel in pawn.apparel.WornApparel)
if (apparel == null || apparel.Destroyed)
continue;
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
// 修改:只有立定且激活的护盾才加入缓存
if (shield != null && shield.Active && !shield.IsWearerMoving)
{
// 同时支持普通护盾和反弹护盾
var shield = apparel.TryGetComp<ThingComp_AreaShield>();
if (shield != null && shield.Active)
{
shieldSet.Add(shield);
}
shieldSet.Add(shield);
}
}
}
activeShieldsByMap[map] = shieldSet;
}
}

View File

@@ -4,13 +4,14 @@ 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;
// 新增:移动状态的颜色
private static readonly Texture2D MovingShieldBarTex = SolidColorMaterials.NewSolidColorMaterial(new Color(0.5f, 0.5f, 0.5f), ShaderDatabase.MetaOverlay).mainTexture as Texture2D;
public override float GetWidth(float maxWidth) => 140f;
@@ -28,14 +29,33 @@ namespace WulaFallenEmpire
Rect barRect = rect2;
barRect.yMin = rect2.y + rect2.height / 2f;
float fillPercent = (float)shield.currentHitPoints / shield.HitPointsMax;
Widgets.FillableBar(barRect, fillPercent, FullShieldBarTex, EmptyShieldBarTex, false);
// 修改:根据状态选择不同的状态条
Texture2D barTex;
TaggedString statusText;
if (shield.IsOnCooldown)
{
barTex = EmptyShieldBarTex;
statusText = "ShieldOnCooldown".Translate();
}
else if (shield.IsWearerMoving)
{
// 移动时显示灰色状态条和"移动中"文本
barTex = MovingShieldBarTex;
statusText = "ShieldOfflineByMoving".Translate(); // 你可以根据需要修改这个文本
}
else
{
barTex = FullShieldBarTex;
statusText = new TaggedString(shield.currentHitPoints + " / " + shield.HitPointsMax);
}
Widgets.FillableBar(barRect, fillPercent, barTex, 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

@@ -2,39 +2,118 @@ using HarmonyLib;
using RimWorld;
using Verse;
using UnityEngine;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public static class ReflectedProjectileManager
{
private static Dictionary<Projectile, int> projectilesToDestroy = new Dictionary<Projectile, int>();
private const int DESTROY_DELAY_TICKS = 1;
public static void MarkForDelayedDestroy(Projectile projectile)
{
if (projectile != null && !projectile.Destroyed)
{
projectilesToDestroy[projectile] = Find.TickManager.TicksGame + DESTROY_DELAY_TICKS;
}
}
public static void Tick()
{
var toRemove = new List<Projectile>();
foreach (var kvp in projectilesToDestroy)
{
if (kvp.Key == null || kvp.Key.Destroyed || Find.TickManager.TicksGame >= kvp.Value)
{
if (kvp.Key != null && !kvp.Key.Destroyed)
{
kvp.Key.Destroy(DestroyMode.Vanish);
}
toRemove.Add(kvp.Key);
}
}
foreach (var projectile in toRemove)
{
projectilesToDestroy.Remove(projectile);
}
}
// 在 ReflectedProjectileManager 类中添加这个方法
public static bool IsMarkedForDestroy(Projectile projectile)
{
return projectile != null && projectilesToDestroy.ContainsKey(projectile);
}
}
[HarmonyPatch(typeof(Projectile), "CheckForFreeInterceptBetween")]
public static class Projectile_CheckForFreeInterceptBetween_Patch
{
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos)
public static bool Prefix(Projectile __instance, Vector3 lastExactPos, Vector3 newExactPos, ref bool __result)
{
if (__instance.Map == null || __instance.Destroyed)
try
{
return true;
}
bool shouldDestroy = false;
// 使用缓存系统获取激活的护盾
foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map))
{
if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true)
// 安全检查
if (__instance == null || __instance.Map == null || __instance.Destroyed)
{
shouldDestroy = true;
break; // 只要有一个护盾吸收就销毁
return true; // 继续执行原方法
}
// 如果护盾反射了抛射体,继续检查其他护盾(允许多重反射)
}
if (shouldDestroy)
bool shouldDestroy = false;
bool wasReflected = false;
// 使用缓存系统获取激活的护盾
foreach (var shield in AreaShieldManager.GetActiveShieldsForMap(__instance.Map))
{
if (shield == null || shield.parent == null || shield.parent.Destroyed)
continue;
if (shield?.TryIntercept(__instance, lastExactPos, newExactPos) == true)
{
shouldDestroy = true;
break;
}
// 检查抛射体是否已经被反射(被标记为延迟销毁)
if (ReflectedProjectileManager.IsMarkedForDestroy(__instance))
{
wasReflected = true;
break;
}
}
if (shouldDestroy)
{
__instance.Destroy(DestroyMode.Vanish);
__result = true; // 设置结果为 true 表示已被拦截
return false; // 跳过原方法
}
if (wasReflected)
{
__result = false; // 设置结果为 false 表示未被拦截(因为被反射了)
return false; // 跳过原方法
}
return true; // 继续执行原方法
}
catch (System.Exception ex)
{
__instance.Destroy(DestroyMode.Vanish);
return false;
Log.Warning($"AreaShield interception error: {ex}");
return true; // 出错时继续执行原方法
}
}
}
return true;
// 添加Tick管理器
[HarmonyPatch(typeof(TickManager), "DoSingleTick")]
public static class TickManager_DoSingleTick_Patch
{
public static void Postfix()
{
ReflectedProjectileManager.Tick();
}
}

View File

@@ -25,15 +25,29 @@ namespace WulaFallenEmpire
public bool IsOnCooldown => ticksToReset > 0;
public int HitPointsMax => Props.baseHitPoints;
private StunHandler stunner;
private bool initialized = false;
private StunHandler stunner;
// 新增:移动状态检测
public bool IsWearerMoving
{
get
{
if (Wearer == null || !Wearer.Spawned) return false;
return Wearer.pather.Moving;
}
}
// 修改Active属性只有在立定时才激活
public bool Active
{
get
{
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown)
return false;
// 新增:只有在立定时才激活
if (IsWearerMoving)
return false;
return true;
}
}
@@ -110,38 +124,51 @@ namespace WulaFallenEmpire
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()))
{
// 增强安全检查
if (!Active || projectile == null || projectile.Destroyed || Wearer == null || Wearer.Map == null)
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))
if (currentHitPoints <= 0)
return false;
try
{
// 反射成功,播放反射特效
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
ApplyCosts(Props.reflectCost);
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; // 销毁抛射体
}
}
else
catch (System.Exception ex)
{
// 普通拦截,播放拦截特效
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
ApplyCosts();
return true; // 销毁抛射体
Log.Warning($"Error in TryIntercept: {ex}");
return false;
}
}
@@ -150,79 +177,87 @@ namespace WulaFallenEmpire
/// </summary>
private bool TryReflectProjectile(Projectile originalProjectile, Vector3 lastExactPos, Vector3 newExactPos)
{
if (!Props.canReflect) return false;
// 检查反射概率
if (Rand.Value > Props.reflectChance) return false;
if (!Props.canReflect || originalProjectile == null || originalProjectile.Destroyed)
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;
return CreateReflectedProjectile(originalProjectile, reflectDirection, newExactPos);
}
catch (System.Exception ex)
{
Log.Warning($"Error reflecting projectile: {ex}");
}
return false;
}
/// <summary>
/// 创建反射后的新抛射体
/// </summary>
private void CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint)
private bool CreateReflectedProjectile(Projectile originalProjectile, Vector3 reflectDirection, Vector3 collisionPoint)
{
try
{
if (originalProjectile == null || originalProjectile.Destroyed || Wearer == null || Wearer.Map == null)
return false;
// 计算新的发射位置(护盾位置附近)
Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint);
// 确保位置在地图内
IntVec3 spawnCell = spawnPosition.ToIntVec3();
if (!spawnCell.InBounds(Wearer.Map))
{
spawnCell = Wearer.Position;
}
// 计算新的目标位置
Vector3 targetPosition = spawnPosition + reflectDirection * 30f; // 足够远的距离
Vector3 targetPosition = spawnCell.ToVector3Shifted() + reflectDirection * 30f;
IntVec3 targetCell = targetPosition.ToIntVec3();
// 创建新的抛射体
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnPosition.ToIntVec3(), Wearer.Map);
// 设置发射者为原抛射体的发射者
Thing launcher = originalProjectile.Launcher ?? Wearer;
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Wearer.Map);
if (newProjectile == null)
{
Log.Warning("Failed to spawn reflected projectile");
return false;
}
// 设置发射者为装备穿戴者
Thing launcher = Wearer;
// 发射新抛射体
newProjectile.Launch(
launcher,
spawnPosition,
new LocalTargetInfo(targetPosition.ToIntVec3()),
new LocalTargetInfo(targetPosition.ToIntVec3()),
spawnCell.ToVector3Shifted(),
new LocalTargetInfo(targetCell),
new LocalTargetInfo(targetCell),
ProjectileHitFlags.All,
false
);
// 复制重要的属性
CopyProjectileProperties(originalProjectile, newProjectile);
// 销毁原抛射体
originalProjectile.Destroy(DestroyMode.Vanish);
Log.Message($"反射抛射体: 从 {spawnPosition} 向 {targetPosition} 发射");
// 使用延迟销毁而不是立即销毁
ReflectedProjectileManager.MarkForDelayedDestroy(originalProjectile);
Log.Message($"反射抛射体: 由 {Wearer?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
return true;
}
catch (System.Exception ex)
{
Log.Warning($"Error creating reflected projectile: {ex}");
return false;
}
}
@@ -231,21 +266,17 @@ namespace WulaFallenEmpire
/// </summary>
private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint)
{
if (Wearer == null)
return 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();
return spawnPosition;
}
/// <summary>
@@ -314,12 +345,13 @@ namespace WulaFallenEmpire
AreaShieldManager.NotifyShieldStateChanged(this);
}
// 护盾绘制方法
// 护盾绘制方法 - 只有在立定时才绘制
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
if (!Active || Wearer?.Map == null || !ShouldDisplay)
// 修改:移动时不绘制护盾
if (!Active || Wearer?.Map == null || !ShouldDisplay || IsWearerMoving)
return;
Vector3 drawPos = Wearer.Drawer?.DrawPos ?? Wearer.Position.ToVector3Shifted();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB