Files
WulaFallenEmpireRW/Source/WulaFallenEmpire/Projectiles/Projectile_TrackingBullet.cs

232 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}