光束炮

This commit is contained in:
2025-11-10 17:24:05 +08:00
parent 85d64e6dfa
commit 03be24e5c0
14 changed files with 433 additions and 912 deletions

View File

@@ -0,0 +1,10 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class AbilityWeaponDefExtension : DefModExtension
{
public ThingDef weaponDef; // 与此Ability相关的武器定义
}
}

View File

@@ -6,124 +6,117 @@ namespace WulaFallenEmpire
{
public class CompAbilityEffect_EnergyLance : CompAbilityEffect_WithDest
{
public new CompProperties_EnergyLance Props => (CompProperties_EnergyLance)props;
public new CompProperties_AbilityEnergyLance Props => (CompProperties_AbilityEnergyLance)props;
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
// 计算光束的起点和方向
IntVec3 startPos = target.Cell;
IntVec3 endPos = dest.Cell;
// 如果使用固定距离,则从起点向终点方向移动固定距离
if (Props.useFixedDistance)
{
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
Vector3 offset = direction * Props.moveDistance;
endPos = startPos + new IntVec3(Mathf.RoundToInt(offset.x), 0, Mathf.RoundToInt(offset.z));
}
// 创建移动的能量光束
EnergyLance obj = (EnergyLance)GenSpawn.Spawn(ThingDef.Named("EnergyLance"), startPos, parent.pawn.Map);
obj.duration = Props.durationTicks;
obj.instigator = parent.pawn;
obj.startPos = startPos;
obj.endPos = endPos;
obj.moveDistance = Props.moveDistance;
obj.useFixedDistance = Props.useFixedDistance;
obj.firesPerTick = Props.firesPerTick;
// 不再需要传递伤害范围因为现在从ModExtension读取
obj.StartStrike();
Log.Message($"[EnergyLance] Created energy lance from {startPos} to {endPos}, distance: {Props.moveDistance}");
}
// 绘制预览效果
public override void DrawEffectPreview(LocalTargetInfo target)
{
base.DrawEffectPreview(target);
if (parent.pawn == null || parent.pawn.Map == null || !target.IsValid)
if (parent.pawn == null || parent.pawn.Map == null)
return;
try
{
// 绘制起点预览
GenDraw.DrawTargetHighlight(target.Cell);
// 使用配置的光束类型
ThingDef lanceDef = Props.energyLanceDef ?? ThingDef.Named("EnergyLance");
// 如果选择了终点,绘制移动路径预览
if (selectedTarget.IsValid)
{
DrawMovePathPreview(target.Cell, selectedTarget.Cell);
}
// 创建EnergyLance
EnergyLance.MakeEnergyLance(
lanceDef,
target.Cell,
dest.Cell,
parent.pawn.Map,
Props.moveDistance,
Props.useFixedDistance,
Props.durationTicks,
parent.pawn
);
Log.Message($"[EnergyLance] Started {lanceDef.defName} from {target.Cell} to {dest.Cell}");
}
catch (System.Exception)
catch (System.Exception ex)
{
// 忽略预览绘制错误
Log.Error($"[EnergyLance] Error starting EnergyLance: {ex}");
}
}
private void DrawMovePathPreview(IntVec3 startPos, IntVec3 endPos)
// 绘制预览保持不变
public new void DrawHighlight(LocalTargetInfo target)
{
if (selectedTarget.IsValid)
{
DrawBeamPathPreview(selectedTarget.Cell, target.Cell);
}
else
{
GenDraw.DrawTargetHighlight(target);
}
}
private void DrawBeamPathPreview(IntVec3 startCell, IntVec3 endCell)
{
Map map = parent.pawn.Map;
// 计算实际终点
IntVec3 actualEndPos = endPos;
Vector3 startPos = startCell.ToVector3();
Vector3 direction = (endCell.ToVector3() - startPos).normalized;
Vector3 actualEndPos;
if (Props.useFixedDistance)
{
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
Vector3 offset = direction * Props.moveDistance;
actualEndPos = startPos + new IntVec3(Mathf.RoundToInt(offset.x), 0, Mathf.RoundToInt(offset.z));
actualEndPos = startPos + direction * Props.moveDistance;
}
else
{
actualEndPos = endCell.ToVector3();
}
// 绘制移动路径
Vector3 startVec = startPos.ToVector3Shifted();
Vector3 endVec = actualEndPos.ToVector3Shifted();
IntVec3 actualEndCell = new IntVec3(
Mathf.RoundToInt(actualEndPos.x),
Mathf.RoundToInt(actualEndPos.y),
Mathf.RoundToInt(actualEndPos.z)
);
GenDraw.DrawLineBetween(startVec, endVec, SimpleColor.Yellow, 0.2f);
// 绘制终点预览
GenDraw.DrawTargetHighlight(actualEndPos);
// 绘制作用范围预览(在移动路径上)
DrawEffectRangePreview(startPos, actualEndPos);
DrawBeamLine(startCell, actualEndCell);
GenDraw.DrawTargetHighlight(startCell);
GenDraw.DrawTargetHighlight(actualEndCell);
DrawEffectRadiusPreview(startCell);
DrawEffectRadiusPreview(actualEndCell);
}
private void DrawEffectRangePreview(IntVec3 startPos, IntVec3 endPos)
private void DrawBeamLine(IntVec3 startCell, IntVec3 endCell)
{
Vector3 startPos = startCell.ToVector3Shifted();
Vector3 endPos = endCell.ToVector3Shifted();
GenDraw.DrawLineBetween(startPos, endPos, SimpleColor.Yellow, 0.3f);
}
private void DrawEffectRadiusPreview(IntVec3 center)
{
Map map = parent.pawn.Map;
// 沿着移动路径绘制作用范围
Vector3 currentPos = startPos.ToVector3();
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
float totalDistance = Vector3.Distance(startPos.ToVector3(), endPos.ToVector3());
float step = 1f; // 每格绘制
for (float distance = 0; distance <= totalDistance; distance += step)
{
Vector3 checkPos = startPos.ToVector3() + direction * distance;
IntVec3 checkCell = new IntVec3(Mathf.RoundToInt(checkPos.x), 0, Mathf.RoundToInt(checkPos.z));
if (checkCell.InBounds(map))
{
// 绘制作用范围指示
GenDraw.DrawRadiusRing(checkCell, 1.5f, Color.red, (c) => true);
}
}
GenDraw.DrawRadiusRing(center, 15f, Color.yellow);
}
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
{
string baseInfo = $"能量长矛: 持续{Props.durationTicks}刻";
if (Props.useFixedDistance)
if (selectedTarget.IsValid)
{
baseInfo += $"\n移动距离: {Props.moveDistance}格";
string beamType = Props.energyLanceDef?.label ?? "EnergyLance";
return $"选择{beamType}方向\n移动距离: {Props.moveDistance}格\n模式: {(Props.useFixedDistance ? "" : "")}";
}
else
{
return "选择光束起点";
}
baseInfo += $"\n选择起点后再选择移动方向";
return baseInfo;
}
public override TargetingParameters targetParams => new TargetingParameters
{
canTargetLocations = true,
canTargetPawns = false,
canTargetBuildings = false,
canTargetItems = false,
mapObjectTargetsMustBeAutoAttackable = false
};
}
}

View File

@@ -0,0 +1,25 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_AbilityEnergyLance : CompProperties_EffectWithDest
{
// 光束持续时间
public int durationTicks = 600;
// 移动配置
public float moveDistance = 15f;
public bool useFixedDistance = true;
// 光束类型配置 - 新增:暴露光束类型
public ThingDef energyLanceDef; // 使用的EnergyLance ThingDef
public int firesPerTick = 4; // 每刻造成的火灾数量
public CompProperties_AbilityEnergyLance()
{
this.compClass = typeof(CompAbilityEffect_EnergyLance);
this.destination = AbilityEffectDestination.Selected;
}
}
}

View File

@@ -1,22 +0,0 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_EnergyLance : CompProperties_EffectWithDest
{
public int durationTicks = 600; // 光束持续时间
public float moveDistance = 15f; // 光束移动距离
public bool useFixedDistance = true; // 是否使用固定距离
// 伤害配置
public int firesPerTick = 4; // 每刻产生的火焰数量
public IntRange flameDamageRange = new IntRange(65, 100); // 火焰伤害范围
public IntRange corpseFlameDamageRange = new IntRange(5, 10); // 尸体火焰伤害范围
public CompProperties_EnergyLance()
{
this.compClass = typeof(CompAbilityEffect_EnergyLance);
}
}
}

View File

@@ -3,82 +3,141 @@ using Verse;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class EnergyLance : OrbitalStrike
[StaticConstructorOnStartup]
public class EnergyLance : ThingWithComps
{
// 移动相关属性
public IntVec3 startPos;
public IntVec3 endPos;
public IntVec3 startPosition;
public IntVec3 endPosition;
public float moveDistance;
public bool useFixedDistance;
public float flightSpeed = 1f;
public float currentProgress = 0f;
public float altitude = 20f;
// 伤害配置
public int firesPerTick = 4;
// ModExtension引用
private EnergyLanceExtension extension;
public float effectRadius = 15f;
public int durationTicks = 600;
private int ticksPassed = 0;
// 移动状态
private Vector3 currentPos;
private Vector3 exactPosition;
private Vector3 moveDirection;
private float moveSpeed;
private float traveledDistance;
private const float effectRadius = 3f; // 作用半径
private bool hasStarted = false;
private bool hasCompleted = false;
private static List<Thing> tmpThings = new List<Thing>();
// 视觉效果
private CompOrbitalBeam orbitalBeamComp;
private Sustainer sustainer;
public override void StartStrike()
// 伤害相关
private static List<Thing> tmpThings = new List<Thing>();
private static readonly IntRange FlameDamageAmountRange = new IntRange(65, 100);
private static readonly IntRange CorpseFlameDamageAmountRange = new IntRange(5, 10);
public Thing instigator;
public ThingDef weaponDef;
// 精确位置计算基于FlyOver的逻辑
public override Vector3 DrawPos
{
base.StartStrike();
// 获取ModExtension
extension = def.GetModExtension<EnergyLanceExtension>();
if (extension == null)
get
{
Log.Error($"[EnergyLance] No EnergyLanceExtension found on {def.defName}");
return;
Vector3 start = startPosition.ToVector3();
Vector3 end = CalculateEndPosition();
Vector3 basePos = Vector3.Lerp(start, end, currentProgress);
basePos.y = altitude;
return basePos;
}
// 初始化移动参数
currentPos = startPos.ToVector3();
if (useFixedDistance)
{
// 从起点向终点方向移动固定距离
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3()).normalized;
moveDirection = direction;
moveSpeed = moveDistance / duration; // 根据持续时间计算移动速度
}
else
{
// 直接从起点移动到终点
Vector3 direction = (endPos.ToVector3() - startPos.ToVector3());
moveDirection = direction.normalized;
moveSpeed = direction.magnitude / duration;
}
traveledDistance = 0f;
// 创建视觉效果
CreateVisualEffect();
Log.Message($"[EnergyLance] Strike started from {startPos} to {endPos}, " +
$"damage: {extension.damageDef.defName}, speed: {moveSpeed}");
}
private void CreateVisualEffect()
// 计算实际终点位置
private Vector3 CalculateEndPosition()
{
// 使用ModExtension中定义的Mote如果没有则使用默认的PowerBeam
if (extension.moteDef != null)
if (useFixedDistance)
{
Mote mote = MoteMaker.MakeStaticMote(base.Position, base.Map, extension.moteDef);
Vector3 direction = (endPosition.ToVector3() - startPosition.ToVector3()).normalized;
return startPosition.ToVector3() + direction * moveDistance;
}
else
{
// 使用原版PowerBeam的视觉效果
MoteMaker.MakePowerBeamMote(base.Position, base.Map);
return endPosition.ToVector3();
}
}
// 精确旋转
public virtual Quaternion ExactRotation
{
get
{
Vector3 direction = (CalculateEndPosition() - startPosition.ToVector3()).normalized;
return Quaternion.LookRotation(direction.Yto0());
}
}
public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
orbitalBeamComp = GetComp<CompOrbitalBeam>();
if (!respawningAfterLoad)
{
base.Position = startPosition;
hasStarted = true;
// 计算移动方向
Vector3 endPos = CalculateEndPosition();
moveDirection = (endPos - startPosition.ToVector3()).normalized;
// 初始化光束组件
if (orbitalBeamComp != null)
{
// 使用反射调用StartAnimation方法
StartOrbitalBeamAnimation();
}
// 开始音效
StartSound();
Log.Message($"[EnergyLance] Spawned at {startPosition}, moving to {endPosition}, " +
$"distance: {moveDistance}, fixed: {useFixedDistance}");
}
}
// 使用反射调用StartAnimation方法
private void StartOrbitalBeamAnimation()
{
var startAnimationMethod = orbitalBeamComp.GetType().GetMethod("StartAnimation");
if (startAnimationMethod != null)
{
startAnimationMethod.Invoke(orbitalBeamComp, new object[] { durationTicks, 10, 0f });
Log.Message("[EnergyLance] Orbital beam animation started");
}
else
{
Log.Warning("[EnergyLance] Could not find StartAnimation method on CompOrbitalBeam");
}
}
private void StartSound()
{
var soundProp = orbitalBeamComp?.GetType().GetProperty("Props")?.GetValue(orbitalBeamComp);
if (soundProp != null)
{
var soundField = soundProp.GetType().GetField("sound");
if (soundField != null)
{
SoundDef soundDef = soundField.GetValue(soundProp) as SoundDef;
if (soundDef != null)
{
sustainer = soundDef.TrySpawnSustainer(SoundInfo.InMap(this, MaintenanceType.PerTick));
}
}
}
}
@@ -86,110 +145,142 @@ namespace WulaFallenEmpire
{
base.Tick();
if (!base.Destroyed && extension != null)
if (!hasStarted || hasCompleted)
return;
ticksPassed++;
// 更新移动进度
UpdateMovement();
// 造成伤害
for (int i = 0; i < firesPerTick; i++)
{
// 移动光束
MoveBeam();
// 造成伤害
for (int i = 0; i < firesPerTick; i++)
{
DoBeamDamage();
}
StartRandomFireAndDoFlameDamage();
}
// 更新音效
sustainer?.Maintain();
// 检查是否完成
if (ticksPassed >= durationTicks || currentProgress >= 1f)
{
CompleteEnergyLance();
}
}
private void MoveBeam()
private void UpdateMovement()
{
// 计算移动距离
float moveThisTick = moveSpeed;
// 计算距离
float totalDistance = useFixedDistance ? moveDistance : Vector3.Distance(startPosition.ToVector3(), endPosition.ToVector3());
// 更新位置
currentPos += moveDirection * moveThisTick;
traveledDistance += moveThisTick;
// 计算移动速度(基于持续时间和总距离)
float progressPerTick = 1f / durationTicks;
currentProgress += progressPerTick;
currentProgress = Mathf.Clamp01(currentProgress);
// 更新精确位置
exactPosition = Vector3.Lerp(startPosition.ToVector3(), CalculateEndPosition(), currentProgress);
// 更新格子位置
IntVec3 newCell = new IntVec3(
Mathf.RoundToInt(exactPosition.x),
Mathf.RoundToInt(exactPosition.y),
Mathf.RoundToInt(exactPosition.z)
);
// 更新光束的实际位置
IntVec3 newCell = new IntVec3(Mathf.RoundToInt(currentPos.x), 0, Mathf.RoundToInt(currentPos.z));
if (newCell != base.Position && newCell.InBounds(base.Map))
{
base.Position = newCell;
}
// 检查是否到达终点
if (useFixedDistance && traveledDistance >= moveDistance)
{
// 固定距离模式:移动指定距离后结束
Destroy();
Log.Message($"[EnergyLance] Reached fixed distance, destroying");
}
else if (!useFixedDistance && traveledDistance >= Vector3.Distance(startPos.ToVector3(), endPos.ToVector3()))
{
// 终点模式:到达终点后结束
Destroy();
Log.Message($"[EnergyLance] Reached end position, destroying");
}
}
private void DoBeamDamage()
private void StartRandomFireAndDoFlameDamage()
{
if (extension == null) return;
// 在当前光束位置周围随机选择一个单元格
IntVec3 targetCell = (from x in GenRadial.RadialCellsAround(base.Position, effectRadius, useCenter: true)
where x.InBounds(base.Map)
select x).RandomElementByWeight((IntVec3 x) => 1f - Mathf.Min(x.DistanceTo(base.Position) / effectRadius, 1f) + 0.05f);
// 尝试在该单元格点火(如果配置了点火)
if (extension.igniteFires)
{
FireUtility.TryStartFireIn(targetCell, base.Map, Rand.Range(0.1f, extension.fireIgniteChance), instigator);
}
FireUtility.TryStartFireIn(targetCell, base.Map, Rand.Range(0.1f, 0.925f), instigator);
// 对该单元格内的物体造成伤害
tmpThings.Clear();
tmpThings.AddRange(targetCell.GetThingList(base.Map));
for (int i = 0; i < tmpThings.Count; i++)
{
Thing thing = tmpThings[i];
// 检查是否对尸体造成伤害
if (!extension.applyDamageToCorpses && thing is Corpse)
continue;
// 计算伤害量
int damageAmount = (thing is Corpse) ?
extension.corpseDamageAmountRange.RandomInRange :
extension.damageAmountRange.RandomInRange;
Pawn pawn = thing as Pawn;
BattleLogEntry_DamageTaken battleLogEntry = null;
int num = ((tmpThings[i] is Corpse) ? CorpseFlameDamageAmountRange.RandomInRange : FlameDamageAmountRange.RandomInRange);
Pawn pawn = tmpThings[i] as Pawn;
BattleLogEntry_DamageTaken battleLogEntry_DamageTaken = null;
if (pawn != null)
{
battleLogEntry = new BattleLogEntry_DamageTaken(pawn, RulePackDefOf.DamageEvent_PowerBeam, instigator as Pawn);
Find.BattleLog.Add(battleLogEntry);
battleLogEntry_DamageTaken = new BattleLogEntry_DamageTaken(pawn, RulePackDefOf.DamageEvent_PowerBeam, instigator as Pawn);
Find.BattleLog.Add(battleLogEntry_DamageTaken);
}
// 使用ModExtension中定义的伤害类型
DamageInfo damageInfo = new DamageInfo(extension.damageDef, damageAmount, 0f, -1f, instigator, null, weaponDef);
thing.TakeDamage(damageInfo).AssociateWithLog(battleLogEntry);
Log.Message($"[EnergyLance] Applied {extension.damageDef.defName} damage {damageAmount} to {thing.Label}");
DamageInfo damageInfo = new DamageInfo(DamageDefOf.Flame, num, 0f, -1f, instigator, null, weaponDef);
tmpThings[i].TakeDamage(damageInfo).AssociateWithLog(battleLogEntry_DamageTaken);
}
tmpThings.Clear();
}
private void CompleteEnergyLance()
{
hasCompleted = true;
// 停止音效
sustainer?.End();
sustainer = null;
Log.Message($"[EnergyLance] Completed at position {base.Position}");
// 销毁自身
Destroy();
}
// 重写绘制方法,确保光束正确显示
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
{
// 让CompOrbitalBeam处理绘制
Comps_PostDraw();
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref startPos, "startPos");
Scribe_Values.Look(ref endPos, "endPos");
Scribe_Values.Look(ref startPosition, "startPosition");
Scribe_Values.Look(ref endPosition, "endPosition");
Scribe_Values.Look(ref moveDistance, "moveDistance");
Scribe_Values.Look(ref useFixedDistance, "useFixedDistance");
Scribe_Values.Look(ref flightSpeed, "flightSpeed", 1f);
Scribe_Values.Look(ref currentProgress, "currentProgress", 0f);
Scribe_Values.Look(ref altitude, "altitude", 20f);
Scribe_Values.Look(ref firesPerTick, "firesPerTick", 4);
Scribe_Values.Look(ref effectRadius, "effectRadius", 15f);
Scribe_Values.Look(ref durationTicks, "durationTicks", 600);
Scribe_Values.Look(ref ticksPassed, "ticksPassed", 0);
Scribe_Values.Look(ref hasStarted, "hasStarted", false);
Scribe_Values.Look(ref hasCompleted, "hasCompleted", false);
}
// 创建EnergyLance的静态方法
public static EnergyLance MakeEnergyLance(ThingDef energyLanceDef, IntVec3 start, IntVec3 end, Map map,
float distance = 15f, bool fixedDistance = true, int duration = 600, Pawn instigatorPawn = null)
{
EnergyLance energyLance = (EnergyLance)ThingMaker.MakeThing(energyLanceDef);
energyLance.startPosition = start;
energyLance.endPosition = end;
energyLance.moveDistance = distance;
energyLance.useFixedDistance = fixedDistance;
energyLance.durationTicks = duration;
energyLance.instigator = instigatorPawn;
GenSpawn.Spawn(energyLance, start, map);
Log.Message($"[EnergyLance] Created {energyLanceDef.defName} from {start} to {end}");
return energyLance;
}
}
}

View File

@@ -5,18 +5,13 @@ namespace WulaFallenEmpire
{
public class EnergyLanceExtension : DefModExtension
{
// 伤害类型配置
public DamageDef damageDef = DamageDefOf.Flame; // 伤害类型,默认为火焰伤害
public bool applyDamageToCorpses = true; // 是否对尸体造成伤害
public bool igniteFires = true; // 是否点燃火焰
public float fireIgniteChance = 0.8f; // 点燃火焰的概率
// 移动平滑配置
public float moveSmoothing = 0.1f; // 移动平滑度 (0-1),值越小越平滑
public int moteSpawnInterval = 3; // Mote生成间隔值越大密度越低
public float moteScale = 0.8f; // Mote缩放比例
// 伤害配置
public IntRange damageAmountRange = new IntRange(65, 100); // 对生物的伤害范围
public IntRange corpseDamageAmountRange = new IntRange(5, 10); // 对尸体的伤害范围
// 视觉效果配置
public ThingDef moteDef; // 使用的Mote类型
public SoundDef impactSound; // 撞击音效
// 伤害配置
public int firesPerTick = 4;
public float effectRadius = 15f;
}
}