1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user