This commit is contained in:
2025-08-20 15:48:55 +08:00
parent b43ad04985
commit dec619663d
9 changed files with 251 additions and 60 deletions

View File

@@ -69,7 +69,7 @@
</graphicData>
<thingClass>WulaFallenEmpire.Projectile_TrackingBullet</thingClass>
<projectile>
<speed>30</speed>
<speed>60</speed>
<damageDef>Bullet</damageDef>
<damageAmountBase>15</damageAmountBase>
<armorPenetrationBase>0.5</armorPenetrationBase>
@@ -78,7 +78,12 @@
<modExtensions>
<li Class="WulaFallenEmpire.TrackingBulletDef">
<homingSpeed>0.5</homingSpeed>
<initRotateAngle>10</initRotateAngle>
<initRotateAngle>30</initRotateAngle>
<destroyTicksAfterLosingTrack>
<min>60</min>
<max>120</max>
</destroyTicksAfterLosingTrack>
<tailFleckDef>WULA_GunTail_Blue</tailFleckDef>
</li>
</modExtensions>
</ThingDef>

View File

@@ -89,7 +89,17 @@
<li Class="WulaFallenEmpire.TrackingBulletDef">
<homingSpeed>0.5</homingSpeed>
<initRotateAngle>5</initRotateAngle>
<!-- 爆炸相关配置可以直接在projectile节点下定义Projectile_TrackingBullet 不直接处理爆炸逻辑 -->
<destroyTicksAfterLosingTrack>
<min>60</min>
<max>120</max>
</destroyTicksAfterLosingTrack>
<tailFleckDef>Mote_BlastFlame</tailFleckDef>
<fleckMakeFleckTickMax>3</fleckMakeFleckTickMax>
<fleckMakeFleckNum>1</fleckMakeFleckNum>
<fleckAngle>30</fleckAngle>
<fleckScale>0.8~1.2</fleckScale>
<fleckSpeed>0.2~0.5</fleckSpeed>
<fleckRotation>0~360</fleckRotation>
</li>
</modExtensions>
</ThingDef>

View File

@@ -0,0 +1,24 @@
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
public class ExplosiveTrackingBulletDef : DefModExtension
{
public float explosionRadius = 1.9f;
public DamageDef damageDef;
public int explosionDelay = 0;
public SoundDef soundExplode;
public FleckDef preExplosionFlash;
public ThingDef postExplosionSpawnThingDef;
public float postExplosionSpawnChance = 0f;
public int postExplosionSpawnThingCount = 1;
public GasType? gasType; // 修改为可空类型
public bool applyDamageToExplosionCellsNeighbors = false;
public bool doExplosionDamageAfterThingDestroyed = false;
public float preExplosionSpawnMinMeleeThreat = -1f;
public float explosionChanceToStartFire = 0f; // 从bool改为float并设置默认值
public bool explosionDamageFalloff = false;
public bool doExplosionVFX = true;
}
}

View File

@@ -0,0 +1,77 @@
using RimWorld;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class Projectile_ExplosiveTrackingBullet : Projectile_TrackingBullet
{
private ExplosiveTrackingBulletDef explosiveDefInt;
public ExplosiveTrackingBulletDef ExplosiveDef
{
get
{
if (explosiveDefInt == null)
{
explosiveDefInt = def.GetModExtension<ExplosiveTrackingBulletDef>();
if (explosiveDefInt == null)
{
Log.ErrorOnce($"ExplosiveTrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345679);
this.explosiveDefInt = new ExplosiveTrackingBulletDef();
}
}
return explosiveDefInt;
}
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
base.Impact(hitThing, blockedByShield); // 调用基类的Impact逻辑
if (ExplosiveDef.explosionRadius > 0f)
{
// 爆炸逻辑
GenExplosion.DoExplosion(
center: Position, // 爆炸中心
map: Map, // 地图
radius: ExplosiveDef.explosionRadius, // 爆炸半径
damType: ExplosiveDef.damageDef ?? DamageDefOf.Bomb, // 伤害类型如果未配置则默认为Bomb
instigator: launcher, // 制造者
damAmount: this.DamageAmount, // 伤害量,使用子弹当前的伤害量
armorPenetration: this.ArmorPenetration, // 护甲穿透,使用子弹当前的护甲穿透
explosionSound: ExplosiveDef.soundExplode, // 爆炸音效
weapon: equipmentDef, // 武器
projectile: def, // 弹药定义
intendedTarget: intendedTarget.Thing, // 预期目标
postExplosionSpawnThingDef: ExplosiveDef.postExplosionSpawnThingDef, // 爆炸后生成物
postExplosionSpawnChance: ExplosiveDef.postExplosionSpawnChance, // 爆炸后生成几率
postExplosionSpawnThingCount: ExplosiveDef.postExplosionSpawnThingCount, // 爆炸后生成数量
postExplosionGasType: ExplosiveDef.gasType, // 气体类型 (注意参数名已修正)
postExplosionGasRadiusOverride: null, // 爆炸气体半径覆盖 (我没有定义这个参数)
postExplosionGasAmount: 255, // 爆炸气体数量 (默认值)
applyDamageToExplosionCellsNeighbors: ExplosiveDef.applyDamageToExplosionCellsNeighbors, // 是否对爆炸单元格邻居造成伤害
preExplosionSpawnThingDef: null, // 爆炸前生成物 (我没有定义这个参数)
preExplosionSpawnChance: 0f, // 爆炸前生成几率 (默认值)
preExplosionSpawnThingCount: 0, // 爆炸前生成数量 (默认值)
chanceToStartFire: ExplosiveDef.explosionChanceToStartFire, // 是否有几率点燃 (注意参数名已修正)
damageFalloff: ExplosiveDef.explosionDamageFalloff, // 爆炸伤害衰减
direction: null, // 方向 (我没有定义这个参数)
ignoredThings: null, // 忽略的物体 (我没有定义这个参数)
affectedAngle: null, // 受影响角度 (我没有定义这个参数)
doVisualEffects: ExplosiveDef.doExplosionVFX, // 是否显示视觉效果
propagationSpeed: 1f, // 传播速度 (默认值)
excludeRadius: 0f, // 排除半径 (默认值)
doSoundEffects: true, // 是否播放音效 (默认值)
postExplosionSpawnThingDefWater: null, // 爆炸后在水中生成物 (我没有定义这个参数)
screenShakeFactor: 1f, // 屏幕震动因子 (默认值)
flammabilityChanceCurve: null, // 易燃性几率曲线 (我没有定义这个参数)
overrideCells: null, // 覆盖单元格 (我没有定义这个参数)
postExplosionSpawnSingleThingDef: null, // 爆炸后生成单个物体 (我没有定义这个参数)
preExplosionSpawnSingleThingDef: null // 爆炸前生成单个物体 (我没有定义这个参数)
);
}
}
}
}

View File

@@ -15,6 +15,9 @@ namespace WulaFallenEmpire
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
{
@@ -58,6 +61,7 @@ namespace WulaFallenEmpire
curSpeed = rotatedDirection * def.projectile.SpeedTilesPerTick;
ReflectInit();
lastTickPosition = origin; // 初始化 lastTickPosition
}
protected void ReflectInit()
@@ -80,67 +84,41 @@ namespace WulaFallenEmpire
if (homing)
{
// 计算需要转向的方向
Vector3 desiredDirection = vectorToTarget.normalized;
Vector3 currentDirection = curSpeed.normalized;
// 计算方向差异
Vector3 directionDifference = desiredDirection - currentDirection;
// 如果方向差异过大,可能失去追踪,或者直接转向
if (directionDifference.sqrMagnitude > 0.001f) // 避免浮点数精度问题
// 如果目标消失或距离太远,停止追踪
if (!intendedTarget.IsValid || !intendedTarget.Thing.Spawned || (intendedTarget.Cell.ToVector3() - ExactPosition).magnitude > def.projectile.speed * 2f) // 假设2倍速度为最大追踪距离
{
// 调整当前速度,使其更接近目标方向
curSpeed += directionDifference * TrackingDef.homingSpeed * curSpeed.magnitude;
curSpeed = curSpeed.normalized * def.projectile.SpeedTilesPerTick; // 保持速度恒定
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; // 更新位置
}
protected override void Tick()
{
base.Tick(); // 调用父类Bullet的Tick处理一些基本逻辑如lifetime, ticksToImpact
MovementTick(); // 调用追踪移动逻辑
// 检查是否撞到东西或超出地图
Vector3 exactPosition = ExactPosition; // 之前的ExactPosition
ticksToImpact--; // 减少impact计时器
if (!ExactPosition.InBounds(base.Map)) // 超出地图边界
{
base.Position = exactPosition.ToIntVec3(); // 设回旧位置,然后销毁
Destroy();
return;
}
// 检查是否有东西在路径上拦截
Vector3 exactPositionAfterMove = ExactPosition; // 移动后的ExactPosition
object[] parameters = new object[2] { exactPosition, exactPositionAfterMove };
if (!(bool)NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters))
{
base.Position = ExactPosition.ToIntVec3(); // 更新位置到当前精确位置
if (ticksToImpact <= 0) // 达到impact时间
{
ImpactSomething(); // 触发Impact
}
}
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
// 默认Impact逻辑可以根据需要扩展
base.Impact(hitThing, blockedByShield);
}
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();
@@ -155,5 +133,84 @@ namespace WulaFallenEmpire
}
}
}
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 会在内部处理命中,并调用 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);
}
}
}

View File

@@ -15,6 +15,7 @@ namespace WulaFallenEmpire
// If true, this projectile will never cause friendly fire, regardless of game settings.
public bool preventFriendlyFire = false;
public FleckDef tailFleckDef; // 用于配置拖尾特效的 FleckDef
public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间tick
}
public class Projectile_WulaLineAttack : Bullet
@@ -62,26 +63,31 @@ namespace WulaFallenEmpire
if (this.Destroyed) return;
this.Fleck_MakeFleckTick++;
bool flag = this.Fleck_MakeFleckTick >= this.Fleck_MakeFleckTickMax;
if (flag)
// 只有当达到延迟时间后才开始生成Fleck
if (this.Fleck_MakeFleckTick >= Props.fleckDelayTicks)
{
this.Fleck_MakeFleckTick = 0;
if (this.Fleck_MakeFleckTick >= (Props.fleckDelayTicks + this.Fleck_MakeFleckTickMax))
{
this.Fleck_MakeFleckTick = Props.fleckDelayTicks; // 重置计时器,从延迟时间开始循环
}
Map map = base.Map;
int randomInRange = this.Fleck_MakeFleckNum.RandomInRange;
Vector3 vector = this.ExactPosition; // Current position of the bullet
Vector3 vector2 = this.lastTickPosition; // Previous position of the bullet
Vector3 currentPosition = this.ExactPosition; // Current position of the bullet
Vector3 previousPosition = this.lastTickPosition; // Previous position of the bullet
for (int i = 0; i < randomInRange; i++)
{
float num = (vector - vector2).AngleFlat(); // Angle based on movement direction
float velocityAngle = this.Fleck_Angle.RandomInRange + num;
float currentBulletAngle = ExactRotation.eulerAngles.y; // 使用子弹当前的水平旋转角度
float fleckRotationAngle = currentBulletAngle; // Fleck 的旋转角度与子弹方向一致
float velocityAngle = this.Fleck_Angle.RandomInRange + currentBulletAngle; // Fleck 的速度角度基于子弹方向加上随机偏移
float randomInRange2 = this.Fleck_Scale.RandomInRange;
float randomInRange3 = this.Fleck_Speed.RandomInRange;
if (Props?.tailFleckDef != null)
{
FleckCreationData dataStatic = FleckMaker.GetDataStatic(vector, map, Props.tailFleckDef, randomInRange2);
dataStatic.rotation = (vector - vector2).AngleFlat();
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, Props.tailFleckDef, randomInRange2);
dataStatic.rotation = fleckRotationAngle;
dataStatic.rotationRate = this.Fleck_Rotation.RandomInRange;
dataStatic.velocityAngle = velocityAngle;
dataStatic.velocitySpeed = randomInRange3;

View File

@@ -6,5 +6,15 @@ namespace WulaFallenEmpire
{
public float homingSpeed = 0.1f; // 追踪速度,值越大追踪越灵敏
public float initRotateAngle = 0f; // 初始旋转角度
public FleckDef tailFleckDef; // 拖尾特效的FleckDef
public int fleckMakeFleckTickMax = 1; // 拖尾特效的生成间隔tick
public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间tick
public IntRange fleckMakeFleckNum = new IntRange(1, 1); // 每次生成拖尾特效的数量
public FloatRange fleckAngle = new FloatRange(-180f, 180f); // 拖尾特效的初始角度范围
public FloatRange fleckScale = new FloatRange(1f, 1f); // 拖尾特效的缩放范围
public FloatRange fleckSpeed = new FloatRange(0f, 0f); // 拖尾特效的初始速度范围
public FloatRange fleckRotation = new FloatRange(-180f, 180f); // 拖尾特效的旋转速度范围
public IntRange destroyTicksAfterLosingTrack = new IntRange(60, 120); // 失去追踪后多少tick自毁
}
}

View File

@@ -128,6 +128,8 @@
<Compile Include="Projectile_Homing_Explosive.cs" />
<Compile Include="Projectile_TrackingBullet.cs" />
<Compile Include="TrackingBulletDef.cs" />
<Compile Include="ExplosiveTrackingBulletDef.cs" />
<Compile Include="Projectile_ExplosiveTrackingBullet.cs" />
<Compile Include="ModExtension_Cone.cs" />
<Compile Include="HomingProjectileDef.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />