232 lines
11 KiB
C#
232 lines
11 KiB
C#
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using RimWorld;
|
||
using UnityEngine;
|
||
using Verse;
|
||
using Verse.AI;
|
||
using Verse.Sound;
|
||
|
||
namespace WulaFallenEmpire
|
||
{
|
||
public class Projectile_TrackingBullet : Bullet
|
||
{
|
||
private TrackingBulletDef trackingDefInt;
|
||
|
||
protected Vector3 exactPositionInt;
|
||
public Vector3 curSpeed;
|
||
public bool homing = true;
|
||
private int destroyTicksAfterLosingTrack = -1; // 失去追踪后多少tick自毁,-1表示不自毁
|
||
private int Fleck_MakeFleckTick; // 拖尾特效的计时器
|
||
private Vector3 lastTickPosition; // 记录上一帧的位置,用于计算移动方向
|
||
|
||
private static class NonPublicFields
|
||
{
|
||
public static FieldInfo Projectile_AmbientSustainer = typeof(Projectile).GetField("ambientSustainer", BindingFlags.Instance | BindingFlags.NonPublic);
|
||
public static FieldInfo ThingWithComps_comps = typeof(ThingWithComps).GetField("comps", BindingFlags.Instance | BindingFlags.NonPublic);
|
||
public static MethodInfo ProjectileCheckForFreeInterceptBetween = typeof(Projectile).GetMethod("CheckForFreeInterceptBetween", BindingFlags.Instance | BindingFlags.NonPublic);
|
||
}
|
||
|
||
public TrackingBulletDef TrackingDef
|
||
{
|
||
get
|
||
{
|
||
if (trackingDefInt == null)
|
||
{
|
||
trackingDefInt = def.GetModExtension<TrackingBulletDef>();
|
||
if (trackingDefInt == null)
|
||
{
|
||
Log.ErrorOnce($"TrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||
this.trackingDefInt = new TrackingBulletDef();
|
||
}
|
||
}
|
||
return trackingDefInt;
|
||
}
|
||
}
|
||
|
||
public override Vector3 ExactPosition => exactPositionInt;
|
||
|
||
public override Quaternion ExactRotation => Quaternion.LookRotation(curSpeed);
|
||
|
||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||
{
|
||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||
exactPositionInt = origin.Yto0() + Vector3.up * def.Altitude;
|
||
|
||
// 初始化子弹速度,指向目标,并考虑初始旋转角度
|
||
Vector3 initialDirection = (destination - origin).Yto0().normalized;
|
||
float degrees = Rand.Range(0f - TrackingDef.initRotateAngle, TrackingDef.initRotateAngle);
|
||
Vector2 v = new Vector2(initialDirection.x, initialDirection.z);
|
||
v = v.RotatedBy(degrees);
|
||
Vector3 rotatedDirection = new Vector3(v.x, 0f, v.y);
|
||
curSpeed = rotatedDirection * def.projectile.SpeedTilesPerTick;
|
||
|
||
ReflectInit();
|
||
lastTickPosition = origin; // 初始化 lastTickPosition
|
||
}
|
||
|
||
protected void ReflectInit()
|
||
{
|
||
// 确保私有字段的访问
|
||
if (!def.projectile.soundAmbient.NullOrUndefined())
|
||
{
|
||
// This line might cause issues if ambientSustainer is not directly settable or if Projectile type changes.
|
||
// For simplicity, we might omit it for now or find a safer way.
|
||
// ambientSustainer = (Sustainer)NonPublicFields.Projectile_AmbientSustainer.GetValue(this);
|
||
}
|
||
// comps = (List<ThingComp>)NonPublicFields.ThingWithComps_comps.GetValue(this); // 如果需要CompTick,需要这个
|
||
}
|
||
|
||
public virtual void MovementTick()
|
||
{
|
||
Vector3 vect = ExactPosition + curSpeed;
|
||
ShootLine shootLine = new ShootLine(ExactPosition.ToIntVec3(), vect.ToIntVec3());
|
||
Vector3 vectorToTarget = (intendedTarget.Cell.ToVector3() - ExactPosition).Yto0();
|
||
|
||
if (homing)
|
||
{
|
||
// 首先检查目标是否是一个有效的 Thing
|
||
if (!intendedTarget.HasThing)
|
||
{
|
||
homing = false; // 如果目标是地面,则禁用追踪
|
||
}
|
||
// 如果目标消失或距离太远,停止追踪
|
||
else if (!intendedTarget.IsValid || !intendedTarget.Thing.Spawned || (intendedTarget.Cell.ToVector3() - ExactPosition).magnitude > def.projectile.speed * 2f) // 假设2倍速度为最大追踪距离
|
||
{
|
||
homing = false;
|
||
destroyTicksAfterLosingTrack = TrackingDef.destroyTicksAfterLosingTrack.RandomInRange; // 失去追踪后根据XML配置的范围自毁
|
||
}
|
||
else
|
||
{
|
||
// 计算需要转向的方向
|
||
Vector3 desiredDirection = vectorToTarget.normalized;
|
||
Vector3 currentDirection = curSpeed.normalized;
|
||
|
||
// 计算方向差异
|
||
Vector3 directionDifference = desiredDirection - currentDirection;
|
||
|
||
// 如果方向差异过大,可能失去追踪,或者直接转向
|
||
if (directionDifference.sqrMagnitude > 0.001f) // 避免浮点数精度问题
|
||
{
|
||
// 调整当前速度,使其更接近目标方向
|
||
curSpeed += directionDifference * TrackingDef.homingSpeed * curSpeed.magnitude;
|
||
curSpeed = curSpeed.normalized * def.projectile.SpeedTilesPerTick; // 保持速度恒定
|
||
}
|
||
}
|
||
}
|
||
|
||
exactPositionInt = ExactPosition + curSpeed; // 更新位置
|
||
}
|
||
|
||
public override void ExposeData()
|
||
{
|
||
base.ExposeData();
|
||
Scribe_Values.Look(ref exactPositionInt, "exactPosition");
|
||
Scribe_Values.Look(ref curSpeed, "curSpeed");
|
||
Scribe_Values.Look(ref homing, "homing", defaultValue: true);
|
||
Scribe_Values.Look(ref destroyTicksAfterLosingTrack, "destroyTicksAfterLosingTrack", -1);
|
||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||
{
|
||
ReflectInit();
|
||
if (this.trackingDefInt == null)
|
||
{
|
||
this.trackingDefInt = this.def.GetModExtension<TrackingBulletDef>();
|
||
if (this.trackingDefInt == null)
|
||
{
|
||
Log.ErrorOnce($"TrackingBulletDef is null for projectile {this.def.defName} after PostLoadInit. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||
this.trackingDefInt = new TrackingBulletDef();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void Tick()
|
||
{
|
||
base.Tick(); // 调用父类Bullet的Tick,处理 ticksToImpact 减少和最终命中
|
||
|
||
if (destroyTicksAfterLosingTrack > 0)
|
||
{
|
||
destroyTicksAfterLosingTrack--;
|
||
if (destroyTicksAfterLosingTrack <= 0)
|
||
{
|
||
Destroy(); // 如果自毁计时器归零,直接销毁
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 处理拖尾特效
|
||
if (TrackingDef != null && TrackingDef.tailFleckDef != null)
|
||
{
|
||
Fleck_MakeFleckTick++;
|
||
// 只有当达到延迟时间后才开始生成Fleck
|
||
if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks)
|
||
{
|
||
if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax))
|
||
{
|
||
Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks; // 重置计时器,从延迟时间开始循环
|
||
}
|
||
|
||
Map map = base.Map;
|
||
int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange;
|
||
Vector3 currentPosition = ExactPosition;
|
||
Vector3 previousPosition = lastTickPosition;
|
||
|
||
for (int i = 0; i < randomInRange; i++)
|
||
{
|
||
float num = (currentPosition - previousPosition).AngleFlat();
|
||
float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num;
|
||
float randomInRange2 = TrackingDef.fleckScale.RandomInRange;
|
||
float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange;
|
||
|
||
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2);
|
||
dataStatic.rotation = (currentPosition - previousPosition).AngleFlat();
|
||
dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange;
|
||
dataStatic.velocityAngle = velocityAngle;
|
||
dataStatic.velocitySpeed = randomInRange3;
|
||
map.flecks.CreateFleck(dataStatic);
|
||
}
|
||
}
|
||
}
|
||
lastTickPosition = ExactPosition; // 更新上一帧位置
|
||
|
||
// 保存移动前的精确位置
|
||
Vector3 exactPositionBeforeMove = exactPositionInt;
|
||
|
||
MovementTick(); // 调用追踪移动逻辑,更新 exactPositionInt (即新的 ExactPosition)
|
||
|
||
// 检查是否超出地图边界
|
||
if (!ExactPosition.InBounds(base.Map))
|
||
{
|
||
// 如果超出地图,直接销毁,不触发 ImpactSomething()
|
||
Destroy();
|
||
return;
|
||
}
|
||
|
||
// 在调用 ProjectileCheckForFreeInterceptBetween 之前,添加近距离命中检测
|
||
if (intendedTarget != null && intendedTarget.Thing != null && intendedTarget.Thing.Spawned)
|
||
{
|
||
float distanceToTarget = (ExactPosition - intendedTarget.Thing.DrawPos).magnitude;
|
||
if (distanceToTarget <= TrackingDef.impactThreshold)
|
||
{
|
||
Impact(intendedTarget.Thing); // 强制命中目标
|
||
return; // 命中后立即返回,不再执行后续逻辑
|
||
}
|
||
}
|
||
|
||
// 检查是否有东西在路径上拦截
|
||
// ProjectileCheckForFreeInterceptBetween 会在内部处理命中,并调用 ImpactSomething()
|
||
// 所以这里不需要额外的 ImpactSomething() 调用
|
||
object[] parameters = new object[2] { exactPositionBeforeMove, exactPositionInt }; // 传入移动前和移动后的位置
|
||
|
||
// 调用 ProjectileCheckForFreeInterceptBetween
|
||
// 如果它返回 true,说明有拦截,并且拦截逻辑已在内部处理。
|
||
// 如果返回 false,说明没有拦截,子弹继续飞行。
|
||
NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters);
|
||
}
|
||
|
||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||
{
|
||
// 默认Impact逻辑,可以根据需要扩展
|
||
base.Impact(hitThing, blockedByShield);
|
||
}
|
||
}
|
||
} |