11
This commit is contained in:
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
using RimWorld;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System.Reflection;
|
||||
@@ -21,12 +21,15 @@ namespace ArachnaeSwarm
|
||||
public float collisionRadius;
|
||||
public SoundDef impactSound;
|
||||
public bool damageHostileOnly;
|
||||
public int maxFlightTicks;
|
||||
public int maxFlightTicks = 1000; // 最大飞行时间,防止无限飞行
|
||||
|
||||
// --- Internal state ---
|
||||
private bool homing = true;
|
||||
private bool hasHitPrimaryTarget = false;
|
||||
private Vector3 exactPosition;
|
||||
private IntVec3? desiredLandingCell = null; // 新增:期望的降落位置
|
||||
private bool isLanding = false; // 新增:标记是否正在降落
|
||||
private bool positionAdjusted = false; // 新增:标记是否已调整位置
|
||||
|
||||
// --- Reflection Fields ---
|
||||
private static FieldInfo TicksFlyingInfo;
|
||||
@@ -81,48 +84,248 @@ namespace ArachnaeSwarm
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
// --- THE CORRECT APPROACH ---
|
||||
// Let the base class handle all flight mechanics (position, timing, etc.)
|
||||
// We only intervene to do two things:
|
||||
// 1. Continuously update the destination to "steer" the flyer.
|
||||
// 2. Perform our own collision checks (for primary target and AOE).
|
||||
// --- 安全防护:防止无限飞行 ---
|
||||
int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
|
||||
if (ticksFlying > maxFlightTicks)
|
||||
{
|
||||
ArachnaeLog.Debug("TrackingCharge reached max flight ticks, forcing landing.");
|
||||
ForceLanding();
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 飞行中逻辑 ---
|
||||
if (homing && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||
{
|
||||
// Steer the flyer by constantly updating its destination cell.
|
||||
// 更新目的地以引导飞行器
|
||||
DestCellInfo.SetValue(this, primaryTarget.Thing.Position);
|
||||
}
|
||||
|
||||
// --- Primary Target Collision Check ---
|
||||
// --- 主目标碰撞检测 ---
|
||||
if (!hasHitPrimaryTarget && primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||
{
|
||||
if ((this.DrawPos - primaryTarget.Thing.DrawPos).sqrMagnitude < this.collisionRadius * this.collisionRadius)
|
||||
float distanceToTargetSq = (this.DrawPos - primaryTarget.Thing.DrawPos).sqrMagnitude;
|
||||
float collisionRadiusSq = this.collisionRadius * this.collisionRadius;
|
||||
|
||||
if (distanceToTargetSq <= collisionRadiusSq)
|
||||
{
|
||||
// --- Impact! ---
|
||||
if (this.impactSound != null)
|
||||
{
|
||||
SoundStarter.PlayOneShot(this.impactSound, new TargetInfo(this.Position, this.Map));
|
||||
}
|
||||
|
||||
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||
float distance = (this.DrawPos - startPosition).magnitude;
|
||||
float calculatedDamage = this.initialDamage + (distance * this.damagePerTile);
|
||||
var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
|
||||
|
||||
primaryTarget.Thing.TakeDamage(dinfo);
|
||||
hasHitPrimaryTarget = true;
|
||||
|
||||
homing = false;
|
||||
|
||||
Vector3 direction = (this.DrawPos - startPosition).normalized;
|
||||
IntVec3 inertiaEndPos = (this.DrawPos + (direction * this.inertiaDistance)).ToIntVec3();
|
||||
DestCellInfo.SetValue(this, inertiaEndPos);
|
||||
// --- 碰撞发生! ---
|
||||
ImpactPrimaryTarget();
|
||||
}
|
||||
}
|
||||
|
||||
// --- AOE Damage Logic ---
|
||||
float distanceTravelled = ((Vector3)StartVecInfo.GetValue(this) - this.DrawPos).magnitude;
|
||||
// --- AOE伤害逻辑 ---
|
||||
ApplyAoeDamage();
|
||||
|
||||
// --- 基础Tick逻辑 ---
|
||||
base.Tick();
|
||||
|
||||
// --- 到达目的地后的处理 ---
|
||||
if (!homing && !isLanding)
|
||||
{
|
||||
HandlePostImpactMovement();
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:处理主目标碰撞
|
||||
private void ImpactPrimaryTarget()
|
||||
{
|
||||
// 播放音效
|
||||
if (this.impactSound != null)
|
||||
{
|
||||
SoundStarter.PlayOneShot(this.impactSound, new TargetInfo(this.Position, this.Map));
|
||||
}
|
||||
|
||||
// 计算伤害
|
||||
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||
float distance = (this.DrawPos - startPosition).magnitude;
|
||||
float calculatedDamage = this.initialDamage + (distance * this.damagePerTile);
|
||||
var dinfo = new DamageInfo(this.collisionDamageDef, calculatedDamage, 1f, -1, this.FlyingPawn);
|
||||
|
||||
primaryTarget.Thing.TakeDamage(dinfo);
|
||||
hasHitPrimaryTarget = true;
|
||||
|
||||
homing = false;
|
||||
|
||||
// 计算期望的降落位置(目标身后一格)
|
||||
CalculateDesiredLandingCell();
|
||||
}
|
||||
|
||||
// 新增:计算期望的降落位置
|
||||
private void CalculateDesiredLandingCell()
|
||||
{
|
||||
if (!primaryTarget.HasThing || !primaryTarget.Thing.Spawned)
|
||||
{
|
||||
// 如果没有主目标,就停在当前位置
|
||||
desiredLandingCell = this.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 startPos = (Vector3)StartVecInfo.GetValue(this);
|
||||
Vector3 targetPos = primaryTarget.Thing.DrawPos;
|
||||
|
||||
// 计算从起点到目标的方向
|
||||
Vector3 direction = (targetPos - startPos).normalized;
|
||||
|
||||
// 计算目标身后一格的位置(延长线方向)
|
||||
Vector3 behindTargetPos = targetPos + direction * 1.0f; // 1格距离
|
||||
|
||||
IntVec3 candidateCell = behindTargetPos.ToIntVec3();
|
||||
|
||||
// 验证这个位置是否可用
|
||||
if (IsValidLandingCell(candidateCell))
|
||||
{
|
||||
desiredLandingCell = candidateCell;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不可用,寻找最近的可用位置
|
||||
desiredLandingCell = FindNearbyLandableCell(candidateCell);
|
||||
}
|
||||
|
||||
if (desiredLandingCell.HasValue)
|
||||
{
|
||||
// 立即设置目的地
|
||||
DestCellInfo.SetValue(this, desiredLandingCell.Value);
|
||||
ArachnaeLog.Debug($"TrackingCharge will land at {desiredLandingCell.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:验证单元格是否适合降落
|
||||
private bool IsValidLandingCell(IntVec3 cell)
|
||||
{
|
||||
if (cell == this.Position)
|
||||
return true;
|
||||
|
||||
if (!cell.InBounds(this.Map))
|
||||
return false;
|
||||
|
||||
// 检查单元格是否可行走
|
||||
if (!cell.Walkable(this.Map))
|
||||
return false;
|
||||
|
||||
// 检查是否有障碍物
|
||||
if (cell.GetFirstThing<Building>(this.Map) != null)
|
||||
return false;
|
||||
|
||||
// 检查是否有其他生物
|
||||
Pawn pawnAtCell = cell.GetFirstPawn(this.Map);
|
||||
if (pawnAtCell != null && pawnAtCell != this.FlyingPawn)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 新增:寻找附近的可用降落单元格
|
||||
private IntVec3 FindNearbyLandableCell(IntVec3 centerCell)
|
||||
{
|
||||
// 优先检查当前位置
|
||||
if (IsValidLandingCell(this.Position))
|
||||
return this.Position;
|
||||
|
||||
// 从近到远搜索可用单元格
|
||||
for (int radius = 1; radius <= 5; radius++)
|
||||
{
|
||||
List<IntVec3> cellsInRadius = GenRadial.RadialCellsAround(centerCell, radius, true).ToList();
|
||||
|
||||
// 按与中心距离排序
|
||||
cellsInRadius.Sort((a, b) =>
|
||||
(a - centerCell).LengthHorizontalSquared.CompareTo((b - centerCell).LengthHorizontalSquared));
|
||||
|
||||
foreach (var cell in cellsInRadius)
|
||||
{
|
||||
if (IsValidLandingCell(cell))
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果都找不到,返回最近的可行走单元格
|
||||
CellFinder.TryFindRandomCellNear(centerCell, this.Map, 10,
|
||||
cell => cell.Walkable(this.Map), out IntVec3 fallbackCell);
|
||||
|
||||
return fallbackCell;
|
||||
}
|
||||
|
||||
// 新增:处理撞击后的移动
|
||||
private void HandlePostImpactMovement()
|
||||
{
|
||||
int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
|
||||
int ticksFlightTime = (int)TicksFlightTimeInfo.GetValue(this);
|
||||
|
||||
// 如果已经到达目的地或接近目的地,强制降落
|
||||
if (ticksFlying >= ticksFlightTime - 5) // 提前几tick准备降落
|
||||
{
|
||||
isLanding = true;
|
||||
|
||||
// 确保目的地是我们期望的位置
|
||||
if (desiredLandingCell.HasValue && this.Position != desiredLandingCell.Value && !positionAdjusted)
|
||||
{
|
||||
// 如果不在期望的位置,尝试移动到那里
|
||||
if (this.Position.DistanceTo(desiredLandingCell.Value) <= 2)
|
||||
{
|
||||
// 很近,直接设置位置
|
||||
this.Position = desiredLandingCell.Value;
|
||||
positionAdjusted = true;
|
||||
ArachnaeLog.Debug($"TrackingCharge adjusted position to {desiredLandingCell.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 额外的距离检查:如果已经超过惯性距离,强制结束飞行
|
||||
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||
float traveledDistance = (this.DrawPos - startPosition).magnitude;
|
||||
float maxAllowedDistance = (primaryTarget.HasThing && primaryTarget.Thing.Spawned)
|
||||
? (startPosition - primaryTarget.Thing.DrawPos).magnitude + inertiaDistance + 2.0f // 多加2格容差
|
||||
: inertiaDistance * 2;
|
||||
|
||||
if (traveledDistance > maxAllowedDistance)
|
||||
{
|
||||
ArachnaeLog.Debug($"TrackingCharge exceeded max allowed distance, forcing landing.");
|
||||
ForceLanding();
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:强制降落
|
||||
private void ForceLanding()
|
||||
{
|
||||
// 如果还没有计算降落位置,现在计算
|
||||
if (!desiredLandingCell.HasValue)
|
||||
{
|
||||
CalculateDesiredLandingCell();
|
||||
}
|
||||
|
||||
// 确保有降落位置
|
||||
if (!desiredLandingCell.HasValue)
|
||||
{
|
||||
desiredLandingCell = this.Position;
|
||||
}
|
||||
|
||||
// 立即设置目的地并强制完成飞行
|
||||
DestCellInfo.SetValue(this, desiredLandingCell.Value);
|
||||
int ticksFlightTime = (int)TicksFlightTimeInfo.GetValue(this);
|
||||
int currentTicksFlying = (int)TicksFlyingInfo.GetValue(this);
|
||||
|
||||
// 如果已经飞了很长时间,直接设置完成
|
||||
if (currentTicksFlying >= ticksFlightTime - 1)
|
||||
{
|
||||
TicksFlyingInfo.SetValue(this, ticksFlightTime);
|
||||
isLanding = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:应用AOE伤害
|
||||
private void ApplyAoeDamage()
|
||||
{
|
||||
if (!hasHitPrimaryTarget)
|
||||
return;
|
||||
|
||||
Vector3 startPosition = (Vector3)StartVecInfo.GetValue(this);
|
||||
float distanceTravelled = (this.DrawPos - startPosition).magnitude;
|
||||
float currentAOEDamage = this.initialDamage + (distanceTravelled * this.damagePerTile);
|
||||
|
||||
// 只应用一次AOE伤害,避免每帧都造成伤害
|
||||
int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
|
||||
if (ticksFlying % 5 != 0) // 每5帧检查一次
|
||||
return;
|
||||
|
||||
foreach (var thing in GenRadial.RadialDistinctThingsAround(this.Position, this.Map, this.collisionRadius, false))
|
||||
{
|
||||
@@ -143,15 +346,93 @@ namespace ArachnaeSwarm
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let the base class do its thing. This is crucial.
|
||||
base.Tick();
|
||||
}
|
||||
|
||||
// 新增:在飞行器销毁前调整位置
|
||||
protected override void TickInterval(int delta)
|
||||
{
|
||||
// 先让基类处理基础逻辑
|
||||
base.TickInterval(delta);
|
||||
|
||||
// 如果我们已经击中了目标,并且已经设置了期望的降落位置
|
||||
// 在飞行即将结束时,确保位置正确
|
||||
int ticksFlying = (int)TicksFlyingInfo.GetValue(this);
|
||||
int ticksFlightTime = (int)TicksFlightTimeInfo.GetValue(this);
|
||||
|
||||
if (hasHitPrimaryTarget && desiredLandingCell.HasValue && ticksFlying >= ticksFlightTime - 2 && !positionAdjusted)
|
||||
{
|
||||
// 在飞行结束前2tick,尝试调整到期望位置
|
||||
if (this.Position != desiredLandingCell.Value && IsValidLandingCell(desiredLandingCell.Value))
|
||||
{
|
||||
this.Position = desiredLandingCell.Value;
|
||||
positionAdjusted = true;
|
||||
ArachnaeLog.Debug($"TrackingCharge final position adjustment to {desiredLandingCell.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改:在降落时确保驾驶员在正确位置
|
||||
protected override void RespawnPawn()
|
||||
{
|
||||
// This is the correct place to call the base method.
|
||||
// 降落前最后的位置修正
|
||||
if (desiredLandingCell.HasValue && this.Position != desiredLandingCell.Value && !positionAdjusted)
|
||||
{
|
||||
// 检查期望位置是否可用
|
||||
if (IsValidLandingCell(desiredLandingCell.Value))
|
||||
{
|
||||
this.Position = desiredLandingCell.Value;
|
||||
positionAdjusted = true;
|
||||
}
|
||||
}
|
||||
|
||||
base.RespawnPawn();
|
||||
|
||||
// 确保驾驶员处于正确状态
|
||||
Pawn flyingPawn = this.FlyingPawn;
|
||||
if (flyingPawn != null && flyingPawn.Spawned)
|
||||
{
|
||||
// 如果驾驶员不在期望位置,尝试移动
|
||||
if (desiredLandingCell.HasValue && flyingPawn.Position != desiredLandingCell.Value && IsValidLandingCell(desiredLandingCell.Value))
|
||||
{
|
||||
flyingPawn.Position = desiredLandingCell.Value;
|
||||
ArachnaeLog.Debug($"TrackingCharge moved pawn to desired position {desiredLandingCell.Value}");
|
||||
}
|
||||
|
||||
// 停止所有工作,防止继续移动
|
||||
flyingPawn.pather?.StopDead();
|
||||
flyingPawn.jobs?.StopAll();
|
||||
|
||||
// 通知技能完成
|
||||
ArachnaeLog.Debug($"TrackingCharge completed at {flyingPawn.Position}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_Values.Look(ref homing, "homing", true);
|
||||
Scribe_Values.Look(ref hasHitPrimaryTarget, "hasHitPrimaryTarget", false);
|
||||
Scribe_Values.Look(ref isLanding, "isLanding", false);
|
||||
Scribe_Values.Look(ref positionAdjusted, "positionAdjusted", false);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.Saving)
|
||||
{
|
||||
if (desiredLandingCell.HasValue)
|
||||
{
|
||||
IntVec3 cell = desiredLandingCell.Value;
|
||||
Scribe_Values.Look(ref cell, "desiredLandingCell");
|
||||
}
|
||||
}
|
||||
else if (Scribe.mode == LoadSaveMode.LoadingVars)
|
||||
{
|
||||
IntVec3 cell = IntVec3.Invalid;
|
||||
Scribe_Values.Look(ref cell, "desiredLandingCell");
|
||||
if (cell.IsValid)
|
||||
{
|
||||
desiredLandingCell = cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user