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

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