1
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_TrapLauncher : CompProperties
|
||||
{
|
||||
public float detectionRadius = 10f; // 检测半径
|
||||
public int scanIntervalTicks = 60; // 扫描间隔(ticks)
|
||||
public ThingDef projectileDef; // 抛射体定义
|
||||
public bool ignoreNonHostilePawns = true; // 是否忽略非敌对Pawn
|
||||
public bool requireLineOfSight = true; // 是否需要视线
|
||||
public int maxTargets = 1; // 最大目标数量
|
||||
public int warmupTicks = 30; // 发射前预热ticks
|
||||
public bool showDetectionRadius = true; // 是否显示检测半径
|
||||
|
||||
// 触发时的音效
|
||||
public SoundDef triggerSound;
|
||||
public SoundDef launchSound;
|
||||
public SoundDef selfDestructSound;
|
||||
|
||||
// 视觉效果
|
||||
public EffecterDef triggerEffect;
|
||||
public EffecterDef launchEffect;
|
||||
public EffecterDef selfDestructEffect;
|
||||
|
||||
// 扩展选项
|
||||
public bool canRetarget = false; // 发射后是否可以重新锁定新目标
|
||||
public int burstCount = 1; // 连发数量
|
||||
public float burstDelay = 0.1f; // 连发延迟
|
||||
public bool targetBuildings = false; // 是否瞄准建筑
|
||||
|
||||
public CompProperties_TrapLauncher()
|
||||
{
|
||||
this.compClass = typeof(CompTrapLauncher);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> ConfigErrors(ThingDef parentDef)
|
||||
{
|
||||
foreach (var error in base.ConfigErrors(parentDef))
|
||||
{
|
||||
yield return error;
|
||||
}
|
||||
|
||||
if (projectileDef == null)
|
||||
{
|
||||
yield return $"CompProperties_TrapLauncher: projectileDef must be set for {parentDef.defName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
using static UnityEngine.GraphicsBuffer;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompTrapLauncher : ThingComp
|
||||
{
|
||||
public CompProperties_TrapLauncher Props => (CompProperties_TrapLauncher)props;
|
||||
|
||||
private int scanTickCounter = 0;
|
||||
private bool hasTriggered = false;
|
||||
private Pawn currentTarget = null;
|
||||
private int warmupCounter = 0;
|
||||
private bool isWarmingUp = false;
|
||||
private int burstCounter = 0;
|
||||
|
||||
// 已检测过的目标(避免重复攻击)
|
||||
private HashSet<Pawn> detectedTargets = new HashSet<Pawn>();
|
||||
|
||||
// 用于绘制检测范围
|
||||
private Material cachedRadiusMat;
|
||||
private Material RadiusMat
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cachedRadiusMat == null)
|
||||
{
|
||||
cachedRadiusMat = SolidColorMaterials.SimpleSolidColorMaterial(
|
||||
new Color(1f, 0.2f, 0.2f, 0.1f));
|
||||
}
|
||||
return cachedRadiusMat;
|
||||
}
|
||||
}
|
||||
|
||||
public override void CompTick()
|
||||
{
|
||||
base.CompTick();
|
||||
|
||||
if (!parent.Spawned || hasTriggered)
|
||||
return;
|
||||
|
||||
// 预热计数
|
||||
if (isWarmingUp)
|
||||
{
|
||||
warmupCounter++;
|
||||
if (warmupCounter >= Props.warmupTicks)
|
||||
{
|
||||
LaunchProjectile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 扫描计数
|
||||
scanTickCounter++;
|
||||
if (scanTickCounter >= Props.scanIntervalTicks)
|
||||
{
|
||||
scanTickCounter = 0;
|
||||
ScanForTargets();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描范围内的敌对目标
|
||||
/// </summary>
|
||||
private void ScanForTargets()
|
||||
{
|
||||
if (!parent.Spawned)
|
||||
return;
|
||||
|
||||
Map map = parent.Map;
|
||||
IntVec3 center = parent.Position;
|
||||
|
||||
// 获取范围内的所有Pawn
|
||||
List<Pawn> pawnsInRange = new List<Pawn>();
|
||||
|
||||
foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, Props.detectionRadius, true))
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查视线(如果需要)
|
||||
if (Props.requireLineOfSight)
|
||||
{
|
||||
if (!GenSight.LineOfSight(center, cell, map, skipFirstCell: true))
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取格子上的所有Pawn
|
||||
List<Thing> things = map.thingGrid.ThingsListAtFast(cell);
|
||||
foreach (Thing thing in things)
|
||||
{
|
||||
if (thing is Pawn pawn && !detectedTargets.Contains(pawn))
|
||||
{
|
||||
// 检查是否敌对
|
||||
if (IsValidTarget(pawn))
|
||||
{
|
||||
pawnsInRange.Add(pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有目标,直接返回
|
||||
if (pawnsInRange.Count == 0)
|
||||
return;
|
||||
|
||||
// 选择第一个目标
|
||||
currentTarget = pawnsInRange[0];
|
||||
detectedTargets.Add(currentTarget);
|
||||
|
||||
// 触发陷阱
|
||||
TriggerTrap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为有效目标
|
||||
/// </summary>
|
||||
private bool IsValidTarget(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || pawn.Dead || pawn.Downed)
|
||||
return false;
|
||||
|
||||
if (Props.ignoreNonHostilePawns)
|
||||
{
|
||||
// 检查是否为敌对派系
|
||||
if (pawn.Faction == null || !pawn.Faction.HostileTo(Faction.OfPlayer))
|
||||
return false;
|
||||
|
||||
// 检查是否为自然敌对生物
|
||||
if (!pawn.Faction.IsPlayer && !pawn.Faction.HostileTo(Faction.OfPlayer))
|
||||
{
|
||||
// 检查是否为敌对性生物(如人形生物巢穴的敌人)
|
||||
if (!pawn.RaceProps.Humanlike && !pawn.def.race.Animal)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否为机械体(如果设定)
|
||||
// 这里可以根据需要添加更多过滤条件
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发陷阱
|
||||
/// </summary>
|
||||
private void TriggerTrap()
|
||||
{
|
||||
if (hasTriggered)
|
||||
return;
|
||||
|
||||
// 播放触发音效
|
||||
if (Props.triggerSound != null)
|
||||
{
|
||||
Props.triggerSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
|
||||
// 播放触发特效
|
||||
if (Props.triggerEffect != null)
|
||||
{
|
||||
Effecter effecter = Props.triggerEffect.Spawn();
|
||||
effecter.Trigger(parent, parent);
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
if (currentTarget != null)
|
||||
{
|
||||
Messages.Message("WULA_TrapLauncherTriggered".Translate(
|
||||
parent.Label,
|
||||
currentTarget.LabelShort
|
||||
), parent, MessageTypeDefOf.ThreatBig);
|
||||
}
|
||||
|
||||
// 开始预热
|
||||
isWarmingUp = true;
|
||||
warmupCounter = 0;
|
||||
burstCounter = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发射抛射体
|
||||
/// </summary>
|
||||
private void LaunchProjectile()
|
||||
{
|
||||
if (currentTarget == null || !currentTarget.Spawned)
|
||||
{
|
||||
// 如果目标无效,尝试寻找新目标
|
||||
if (Props.canRetarget)
|
||||
{
|
||||
currentTarget = FindNewTarget();
|
||||
if (currentTarget == null)
|
||||
{
|
||||
// 没有新目标,取消发射
|
||||
isWarmingUp = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接自毁
|
||||
SelfDestruct();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 发射抛射体
|
||||
for (int i = 0; i < Props.burstCount; i++)
|
||||
{
|
||||
if (burstCounter >= Props.maxTargets)
|
||||
break;
|
||||
|
||||
// 创建抛射体
|
||||
Projectile projectile = (Projectile)GenSpawn.Spawn(
|
||||
Props.projectileDef,
|
||||
parent.Position,
|
||||
parent.Map
|
||||
);
|
||||
|
||||
// 发射
|
||||
projectile.Launch(parent, parent.DrawPos, currentTarget, currentTarget, ProjectileHitFlags.IntendedTarget, false);
|
||||
|
||||
// 连发延迟
|
||||
if (i < Props.burstCount - 1 && Props.burstDelay > 0)
|
||||
{
|
||||
// 使用简单的延迟实现
|
||||
// 在实际游戏中,可能需要更复杂的实现
|
||||
// 这里我们简化处理
|
||||
}
|
||||
}
|
||||
|
||||
// 播放发射音效
|
||||
if (Props.launchSound != null)
|
||||
{
|
||||
Props.launchSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
|
||||
// 播放发射特效
|
||||
if (Props.launchEffect != null)
|
||||
{
|
||||
Effecter effecter = Props.launchEffect.Spawn();
|
||||
effecter.Trigger(parent, parent);
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
// 检查是否需要继续攻击
|
||||
if (burstCounter >= Props.maxTargets || !Props.canRetarget)
|
||||
{
|
||||
// 自毁
|
||||
SelfDestruct();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 寻找新目标
|
||||
currentTarget = FindNewTarget();
|
||||
if (currentTarget == null)
|
||||
{
|
||||
SelfDestruct();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 重置预热,准备下一次发射
|
||||
warmupCounter = 0;
|
||||
burstCounter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 寻找新目标
|
||||
/// </summary>
|
||||
private Pawn FindNewTarget()
|
||||
{
|
||||
Map map = parent.Map;
|
||||
IntVec3 center = parent.Position;
|
||||
|
||||
foreach (IntVec3 cell in GenRadial.RadialCellsAround(center, Props.detectionRadius, true))
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
if (Props.requireLineOfSight && !GenSight.LineOfSight(center, cell, map, skipFirstCell: true))
|
||||
continue;
|
||||
|
||||
List<Thing> things = map.thingGrid.ThingsListAtFast(cell);
|
||||
foreach (Thing thing in things)
|
||||
{
|
||||
if (thing is Pawn pawn && !detectedTargets.Contains(pawn) && IsValidTarget(pawn))
|
||||
{
|
||||
detectedTargets.Add(pawn);
|
||||
return pawn;
|
||||
}
|
||||
|
||||
// 如果目标建筑
|
||||
if (Props.targetBuildings && thing is Building building &&
|
||||
building.Faction != null && building.Faction.HostileTo(Faction.OfPlayer))
|
||||
{
|
||||
// 注意:这里需要处理建筑目标,但抛射体可能需要调整
|
||||
// 为了简化,这里只处理Pawn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自毁
|
||||
/// </summary>
|
||||
private void SelfDestruct()
|
||||
{
|
||||
if (hasTriggered)
|
||||
return;
|
||||
|
||||
hasTriggered = true;
|
||||
|
||||
// 播放自毁音效
|
||||
if (Props.selfDestructSound != null)
|
||||
{
|
||||
Props.selfDestructSound.PlayOneShot(new TargetInfo(parent.Position, parent.Map));
|
||||
}
|
||||
|
||||
// 播放自毁特效
|
||||
if (Props.selfDestructEffect != null)
|
||||
{
|
||||
Effecter effecter = Props.selfDestructEffect.Spawn();
|
||||
effecter.Trigger(parent, parent);
|
||||
effecter.Cleanup();
|
||||
}
|
||||
|
||||
// 销毁建筑
|
||||
parent.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制检测范围(仅在选定和调试模式下)
|
||||
/// </summary>
|
||||
public override void PostDrawExtraSelectionOverlays()
|
||||
{
|
||||
base.PostDrawExtraSelectionOverlays();
|
||||
|
||||
if (Props.showDetectionRadius && Props.detectionRadius > 0)
|
||||
{
|
||||
GenDraw.DrawRadiusRing(parent.Position, Props.detectionRadius, Color.red);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Gizmo按钮
|
||||
/// </summary>
|
||||
public override IEnumerable<Gizmo> CompGetGizmosExtra()
|
||||
{
|
||||
foreach (Gizmo gizmo in base.CompGetGizmosExtra())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
if (!hasTriggered && DebugSettings.ShowDevGizmos)
|
||||
{
|
||||
// 调试:手动触发
|
||||
Command_Action debugTrigger = new Command_Action();
|
||||
debugTrigger.defaultLabel = "DEV: Trigger Trap";
|
||||
debugTrigger.action = delegate
|
||||
{
|
||||
currentTarget = FindClosestHostilePawn();
|
||||
if (currentTarget != null)
|
||||
{
|
||||
TriggerTrap();
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Message("WULA_TrapLauncherNoTargetFound".Translate(),
|
||||
parent, MessageTypeDefOf.RejectInput);
|
||||
}
|
||||
};
|
||||
yield return debugTrigger;
|
||||
|
||||
// 调试:立即自毁
|
||||
Command_Action debugDestruct = new Command_Action();
|
||||
debugDestruct.defaultLabel = "DEV: Self-Destruct";
|
||||
debugDestruct.action = delegate
|
||||
{
|
||||
SelfDestruct();
|
||||
};
|
||||
yield return debugDestruct;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找最近的敌对Pawn(调试用)
|
||||
/// </summary>
|
||||
private Pawn FindClosestHostilePawn()
|
||||
{
|
||||
if (!parent.Spawned)
|
||||
return null;
|
||||
|
||||
Pawn closestPawn = null;
|
||||
float closestDist = float.MaxValue;
|
||||
|
||||
foreach (Pawn pawn in parent.Map.mapPawns.AllPawnsSpawned)
|
||||
{
|
||||
if (IsValidTarget(pawn))
|
||||
{
|
||||
float dist = pawn.Position.DistanceTo(parent.Position);
|
||||
if (dist <= Props.detectionRadius && dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestPawn = pawn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestPawn;
|
||||
}
|
||||
|
||||
public override void PostExposeData()
|
||||
{
|
||||
base.PostExposeData();
|
||||
|
||||
Scribe_Values.Look(ref scanTickCounter, "scanTickCounter", 0);
|
||||
Scribe_Values.Look(ref hasTriggered, "hasTriggered", false);
|
||||
Scribe_Values.Look(ref isWarmingUp, "isWarmingUp", false);
|
||||
Scribe_Values.Look(ref warmupCounter, "warmupCounter", 0);
|
||||
Scribe_Values.Look(ref burstCounter, "burstCounter", 0);
|
||||
Scribe_References.Look(ref currentTarget, "currentTarget");
|
||||
Scribe_Collections.Look(ref detectedTargets, "detectedTargets", LookMode.Reference);
|
||||
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
if (detectedTargets == null)
|
||||
{
|
||||
detectedTargets = new HashSet<Pawn>();
|
||||
}
|
||||
|
||||
// 移除无效的目标引用
|
||||
detectedTargets.RemoveWhere(pawn => pawn == null || pawn.Destroyed);
|
||||
|
||||
// 如果已经在预热但目标无效,尝试恢复或自毁
|
||||
if (isWarmingUp && (currentTarget == null || !currentTarget.Spawned))
|
||||
{
|
||||
if (Props.canRetarget)
|
||||
{
|
||||
currentTarget = FindNewTarget();
|
||||
if (currentTarget == null)
|
||||
{
|
||||
SelfDestruct();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SelfDestruct();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user