This commit is contained in:
2025-11-25 12:01:49 +08:00
parent f73f4f55e3
commit e833245d0a
10 changed files with 498 additions and 108 deletions

View File

@@ -21,42 +21,205 @@ namespace WulaFallenEmpire
private bool drawInterceptCone;
public CompProperties_AreaShield Props => (CompProperties_AreaShield)props;
// 回退机制:支持装备和普通物品
public Pawn Wearer => (parent as Apparel)?.Wearer;
public bool IsEquipment => parent is Apparel;
public bool IsStandalone => !IsEquipment;
// 获取护盾持有者(回退机制)
public Thing Holder => IsEquipment ? (Thing)Wearer : parent;
public bool IsOnCooldown => ticksToReset > 0;
public int HitPointsMax => Props.baseHitPoints;
private bool initialized = false;
private StunHandler stunner;
// 新增:移动状态检测
public bool IsWearerMoving
// 材质定义
private static readonly Material ForceFieldMat = MaterialPool.MatFrom("Other/ForceField", ShaderDatabase.MoteGlow);
private static readonly Material ForceFieldConeMat = MaterialPool.MatFrom("Other/ForceFieldCone", ShaderDatabase.MoteGlow);
private static readonly MaterialPropertyBlock MatPropertyBlock = new MaterialPropertyBlock();
private const float TextureActualRingSizeFactor = 1.1601562f;
private static readonly Color InactiveColor = new Color(0.2f, 0.2f, 0.2f);
// 护盾绘制方法 - 参考原版实现
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
if (!IsEquipment) return; // 只有装备使用这个方法
DrawShield();
}
public override void PostDraw()
{
base.PostDraw();
if (IsEquipment) return; // 装备使用 CompDrawWornExtras
DrawShield();
}
/// <summary>
/// 统一的护盾绘制方法 - 参考原版实现
/// </summary>
private void DrawShield()
{
if (!Active || Holder?.Map == null || Holder.Destroyed)
return;
Vector3 drawPos = GetHolderDrawPos();
drawPos.y = AltitudeLayer.MoteOverhead.AltitudeFor();
float currentAlpha = GetCurrentAlpha();
if (currentAlpha > 0f)
{
// 参考原版:未激活但被选中时使用灰色
Color color = (!Active && Find.Selector.IsSelected(parent)) ? InactiveColor : Props.color;
color.a *= currentAlpha;
MatPropertyBlock.SetColor(ShaderPropertyIDs.Color, color);
Matrix4x4 matrix = default;
float scale = Props.radius * 2f * TextureActualRingSizeFactor;
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);
}
}
/// <summary>
/// 获取当前透明度 - 参考原版的多状态叠加
/// </summary>
private float GetCurrentAlpha()
{
// 多个透明度来源叠加,取最大值
return Mathf.Max(
Mathf.Max(
Mathf.Max(
GetCurrentAlpha_Idle(),
GetCurrentAlpha_Selected()
),
GetCurrentAlpha_RecentlyIntercepted()
),
0.1f // 最小透明度
);
}
/// <summary>
/// 空闲状态透明度
/// </summary>
private float GetCurrentAlpha_Idle()
{
if (!Active) return 0f;
// 固定物品:始终显示空闲状态
if (IsStandalone)
{
// 脉冲效果
return Mathf.Lerp(0.3f, 0.6f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
}
// 装备:只在特定条件下显示
else if (IsEquipment)
{
// 装备护盾只在以下情况显示空闲状态:
if (Holder is Pawn pawn)
{
if (pawn.Drafted || pawn.InAggroMentalState ||
(pawn.Faction != null && pawn.Faction.HostileTo(Faction.OfPlayer) && !pawn.IsPrisoner))
{
return Mathf.Lerp(0.3f, 0.6f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 35990913) + Time.realtimeSinceStartup * 2f) + 1f) / 2f);
}
}
}
return 0f;
}
/// <summary>
/// 被选中状态透明度 - 参考原版实现
/// </summary>
private float GetCurrentAlpha_Selected()
{
// 如果被选中,显示更高的透明度
if (Find.Selector.IsSelected(parent) && Active)
{
return Mathf.Lerp(0.4f, 0.8f,
(Mathf.Sin((float)Gen.HashCombineInt(parent.thingIDNumber, 96804938) + Time.realtimeSinceStartup * 2.5f) + 1f) / 2f);
}
return 0f;
}
/// <summary>
/// 最近拦截状态透明度
/// </summary>
private float GetCurrentAlpha_RecentlyIntercepted()
{
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.3f;
}
/// <summary>
/// 拦截锥形透明度
/// </summary>
private float GetCurrentConeAlpha()
{
if (!drawInterceptCone) return 0f;
int ticksSinceIntercept = Find.TickManager.TicksGame - lastInterceptTicks;
return Mathf.Clamp01(1f - (float)ticksSinceIntercept / 40f) * 0.82f;
}
/// <summary>
/// 获取持有者绘制位置(回退机制)
/// </summary>
private Vector3 GetHolderDrawPos()
{
if (Holder is Pawn pawn)
return pawn.Drawer?.DrawPos ?? pawn.Position.ToVector3Shifted();
else
return Holder.DrawPos;
}
// 移动状态检测(仅对装备有效)
public bool IsHolderMoving
{
get
{
if (IsStandalone) return false; // 固定物品不会移动
if (Wearer == null || !Wearer.Spawned) return false;
return Wearer.pather.Moving;
}
}
// 修改Active属性只有在立定时才激活
// 修改Active属性装备只有在立定时才激活,固定物品始终激活
public bool Active
{
get
{
if (Wearer == null || !Wearer.Spawned || Wearer.Dead || Wearer.Downed || IsOnCooldown)
if (Holder == null || !Holder.Spawned || Holder.Destroyed)
return false;
// 新增:只有在立定时才激活
if (IsWearerMoving)
if (Holder is Pawn pawn && (pawn.Dead || pawn.Downed))
return false;
if (IsOnCooldown)
return false;
// 装备:只有在立定时才激活
if (IsEquipment && IsHolderMoving)
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();
@@ -84,7 +247,7 @@ namespace WulaFallenEmpire
wasActiveLastCheck = isActive;
}
if (Wearer == null) return;
if (Holder == null) return;
if (IsOnCooldown)
{
@@ -125,14 +288,15 @@ namespace WulaFallenEmpire
public bool TryIntercept(Projectile projectile, Vector3 lastExactPos, Vector3 newExactPos)
{
// 增强安全检查
if (!Active || projectile == null || projectile.Destroyed || Wearer == null || Wearer.Map == null)
if (!Active || projectile == null || projectile.Destroyed || Holder == null || Holder.Map == null)
return false;
if (currentHitPoints <= 0)
return false;
try
{
if (!GenGeo.IntersectLineCircleOutline(Wearer.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
if (!GenGeo.IntersectLineCircleOutline(Holder.Position.ToVector2(), Props.radius, lastExactPos.ToVector2(), newExactPos.ToVector2()))
{
return false;
}
@@ -140,27 +304,27 @@ namespace WulaFallenEmpire
return false;
if (!projectile.def.projectile.flyOverhead && !Props.interceptGroundProjectiles)
return false;
if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Wearer.Faction) && !Props.interceptNonHostileProjectiles)
if (projectile.Launcher != null && !projectile.Launcher.HostileTo(Holder.Faction) && !Props.interceptNonHostileProjectiles)
return false;
lastInterceptTicks = Find.TickManager.TicksGame;
// 记录拦截角度用于视觉效果
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(Wearer.TrueCenter());
lastInterceptAngle = projectile.ExactPosition.AngleToFlat(GetHolderCenter());
drawInterceptCone = true;
// 尝试反射
if (Props.canReflect && TryReflectProjectile(projectile, lastExactPos, newExactPos))
{
// 反射成功,播放反射特效
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
Props.reflectEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Holder.Map).Cleanup();
ApplyCosts(Props.reflectCost);
return false; // 不销毁原抛射体,让它继续飞行(我们会在反射中销毁它)
}
else
{
// 普通拦截,播放拦截特效
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Wearer.Map).Cleanup();
Props.interceptEffecter?.Spawn(projectile.ExactPosition.ToIntVec3(), Holder.Map).Cleanup();
ApplyCosts();
return true; // 销毁抛射体
}
@@ -172,6 +336,17 @@ namespace WulaFallenEmpire
}
}
/// <summary>
/// 获取持有者中心位置(回退机制)
/// </summary>
private Vector3 GetHolderCenter()
{
if (Holder is Pawn pawn)
return pawn.TrueCenter();
else
return Holder.DrawPos;
}
/// <summary>
/// 尝试反射抛射体 - 现在会创建新的抛射体
/// </summary>
@@ -183,13 +358,14 @@ namespace WulaFallenEmpire
// 检查反射概率
if (Rand.Value > Props.reflectChance)
return false;
try
{
// 计算入射方向
Vector3 incomingDirection = (newExactPos - lastExactPos).normalized;
// 计算法线方向(从护盾中心到碰撞点)
Vector3 normal = (newExactPos - Wearer.DrawPos).normalized;
Vector3 normal = (newExactPos - GetHolderCenter()).normalized;
// 计算反射方向(镜面反射)
Vector3 reflectDirection = Vector3.Reflect(incomingDirection, normal);
@@ -216,28 +392,34 @@ namespace WulaFallenEmpire
{
try
{
if (originalProjectile == null || originalProjectile.Destroyed || Wearer == null || Wearer.Map == null)
if (originalProjectile == null || originalProjectile.Destroyed || Holder == null || Holder.Map == null)
return false;
// 计算新的发射位置(护盾位置附近)
Vector3 spawnPosition = GetReflectSpawnPosition(collisionPoint);
// 确保位置在地图内
IntVec3 spawnCell = spawnPosition.ToIntVec3();
if (!spawnCell.InBounds(Wearer.Map))
if (!spawnCell.InBounds(Holder.Map))
{
spawnCell = Wearer.Position;
spawnCell = Holder.Position;
}
// 计算新的目标位置
Vector3 targetPosition = spawnCell.ToVector3Shifted() + reflectDirection * 30f;
IntVec3 targetCell = targetPosition.ToIntVec3();
// 创建新的抛射体
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Wearer.Map);
Projectile newProjectile = (Projectile)GenSpawn.Spawn(originalProjectile.def, spawnCell, Holder.Map);
if (newProjectile == null)
{
Log.Warning("Failed to spawn reflected projectile");
return false;
}
// 设置发射者为装备穿戴者
Thing launcher = Wearer;
// 设置发射者为护盾持有者
Thing launcher = Holder;
// 发射新抛射体
newProjectile.Launch(
launcher,
@@ -247,11 +429,14 @@ namespace WulaFallenEmpire
ProjectileHitFlags.All,
false
);
// 复制重要的属性
CopyProjectileProperties(originalProjectile, newProjectile);
// 使用延迟销毁而不是立即销毁
ReflectedProjectileManager.MarkForDelayedDestroy(originalProjectile);
Log.Message($"反射抛射体: 由 {Wearer?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
Log.Message($"反射抛射体: 由 {Holder?.LabelShort} 从 {spawnCell} 向 {targetCell} 发射");
return true;
}
catch (System.Exception ex)
@@ -266,15 +451,15 @@ namespace WulaFallenEmpire
/// </summary>
private Vector3 GetReflectSpawnPosition(Vector3 collisionPoint)
{
if (Wearer == null)
if (Holder == null)
return collisionPoint;
// 计算从护盾中心到碰撞点的方向
Vector3 directionFromCenter = (collisionPoint - Wearer.DrawPos).normalized;
Vector3 directionFromCenter = (collisionPoint - GetHolderCenter()).normalized;
// 在护盾边界上生成(稍微向内一点避免立即再次碰撞)
float spawnDistance = Props.radius * 0.9f;
Vector3 spawnPosition = Wearer.DrawPos + directionFromCenter * spawnDistance;
Vector3 spawnPosition = GetHolderCenter() + directionFromCenter * spawnDistance;
return spawnPosition;
}
@@ -310,26 +495,26 @@ namespace WulaFallenEmpire
public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
{
absorbed = false;
if (!Active || Wearer == null) return;
if (!Active || Holder == null) return;
if (dinfo.Def.isRanged) return;
if (dinfo.Instigator != null)
{
float distance = Wearer.Position.DistanceTo(dinfo.Instigator.Position);
float distance = Holder.Position.DistanceTo(dinfo.Instigator.Position);
if (distance > Props.radius) return;
}
if (currentHitPoints <= 0) return;
Props.absorbEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.absorbEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
ApplyCosts();
absorbed = true;
}
private void Break()
{
Props.breakEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.breakEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
ticksToReset = Props.rechargeDelay;
currentHitPoints = 0;
AreaShieldManager.NotifyShieldStateChanged(this);
@@ -337,85 +522,50 @@ namespace WulaFallenEmpire
private void Reset()
{
if (Wearer != null && Wearer.Spawned)
if (Holder != null && Holder.Spawned)
{
Props.reactivateEffecter?.Spawn(Wearer.Position, Wearer.Map).Cleanup();
Props.reactivateEffecter?.Spawn(Holder.Position, Holder.Map).Cleanup();
}
currentHitPoints = HitPointsMax;
AreaShieldManager.NotifyShieldStateChanged(this);
}
// 护盾绘制方法 - 只有在立定时才绘制
public override void CompDrawWornExtras()
{
base.CompDrawWornExtras();
// 修改:移动时不绘制护盾
if (!Active || Wearer?.Map == null || !ShouldDisplay || IsWearerMoving)
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)
if (Holder == null || !Holder.Spawned || Holder.Destroyed || !Active)
return false;
if (Wearer.Drafted || Wearer.InAggroMentalState ||
(Wearer.Faction != null && Wearer.Faction.HostileTo(Faction.OfPlayer) && !Wearer.IsPrisoner))
// 对于装备:只在特定条件下显示
if (IsEquipment && Holder is Pawn pawn)
{
if (pawn.Dead || pawn.Downed)
return false;
// 装备护盾只在以下情况显示:
// 1. 穿戴者被选中
// 2. 穿戴者处于战斗状态(征召状态或敌对)
// 3. 穿戴者处于攻击性精神状态
if (Find.Selector.IsSelected(pawn))
return true;
if (pawn.Drafted || pawn.InAggroMentalState)
return true;
if (pawn.Faction != null && pawn.Faction.HostileTo(Faction.OfPlayer) && !pawn.IsPrisoner)
return true;
}
// 对于固定物品:始终显示(只要护盾激活)
else if (IsStandalone)
{
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;
@@ -433,7 +583,18 @@ namespace WulaFallenEmpire
{
EnsureInitialized();
if (Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
if (IsEquipment && Wearer != null && Find.Selector.SingleSelectedThing == Wearer)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
EnsureInitialized();
// 固定物品也显示护盾状态
if (IsStandalone && Find.Selector.SingleSelectedThing == parent)
{
yield return new Gizmo_AreaShieldStatus { shield = this };
}