532 lines
19 KiB
C#
532 lines
19 KiB
C#
using RimWorld;
|
||
using Verse;
|
||
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
|
||
namespace WulaFallenEmpire
|
||
{
|
||
public class CompAbilityEffect_CircularBombardment : CompAbilityEffect
|
||
{
|
||
public new CompProperties_AbilityCircularBombardment Props => (CompProperties_AbilityCircularBombardment)props;
|
||
|
||
// 轰炸状态
|
||
private CircularBombardmentState currentState = CircularBombardmentState.Idle;
|
||
private List<IntVec3> targetCells = new List<IntVec3>();
|
||
private List<IntVec3> remainingTargets = new List<IntVec3>();
|
||
private IntVec3 bombardmentCenter;
|
||
private int warmupTicksRemaining = 0;
|
||
private int nextLaunchTick = 0;
|
||
private int launchesCompleted = 0;
|
||
|
||
// 组内间隔状态
|
||
private List<IntVec3> currentGroupTargets = new List<IntVec3>();
|
||
private int currentGroupIndex = 0;
|
||
private int nextInnerLaunchTick = 0;
|
||
private bool isInGroupLaunch = false;
|
||
|
||
// 预览状态
|
||
private List<IntVec3> currentPreviewCells = new List<IntVec3>();
|
||
private List<IntVec3> impactPreviewCells = new List<IntVec3>();
|
||
|
||
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
|
||
{
|
||
base.Apply(target, dest);
|
||
|
||
if (parent.pawn == null || parent.pawn.Map == null)
|
||
return;
|
||
|
||
try
|
||
{
|
||
Log.Message($"[CircularBombardment] Starting circular bombardment at {target.Cell}");
|
||
|
||
// 设置轰炸中心
|
||
bombardmentCenter = target.Cell;
|
||
|
||
// 选择目标格子
|
||
SelectTargetCells();
|
||
|
||
// 初始化剩余目标列表
|
||
remainingTargets = new List<IntVec3>(targetCells);
|
||
|
||
// 开始前摇
|
||
StartWarmup();
|
||
|
||
Log.Message($"[CircularBombardment] Bombardment initialized: {targetCells.Count} targets, " +
|
||
$"{Props.simultaneousLaunches} simultaneous launches, " +
|
||
$"independent intervals: {Props.useIndependentIntervals}");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Log.Error($"[CircularBombardment] Error starting bombardment: {ex}");
|
||
}
|
||
}
|
||
|
||
// 绘制预览效果
|
||
public override void DrawEffectPreview(LocalTargetInfo target)
|
||
{
|
||
base.DrawEffectPreview(target);
|
||
|
||
if (!Props.showBombardmentArea || parent.pawn == null || parent.pawn.Map == null)
|
||
return;
|
||
|
||
try
|
||
{
|
||
// 计算预览区域
|
||
CalculatePreviewArea(target.Cell);
|
||
|
||
// 绘制轰炸区域预览
|
||
DrawCircularAreaPreview(target.Cell);
|
||
|
||
// 绘制预计落点预览
|
||
if (Props.showImpactPreview)
|
||
{
|
||
DrawImpactPreview(target.Cell);
|
||
}
|
||
}
|
||
catch (System.Exception)
|
||
{
|
||
// 忽略预览绘制错误
|
||
}
|
||
}
|
||
|
||
// 计算预览区域
|
||
private void CalculatePreviewArea(IntVec3 center)
|
||
{
|
||
Map map = parent.pawn.Map;
|
||
currentPreviewCells.Clear();
|
||
impactPreviewCells.Clear();
|
||
|
||
// 计算圆形区域内的所有单元格
|
||
currentPreviewCells = GenRadial.RadialCellsAround(center, Props.radius, true).ToList();
|
||
|
||
// 随机选择一些单元格作为预计落点预览
|
||
var potentialTargets = currentPreviewCells
|
||
.Where(cell => cell.InBounds(map))
|
||
.Where(cell => IsValidTargetCell(cell, map))
|
||
.ToList();
|
||
|
||
// 随机选择预览目标
|
||
int previewCount = Mathf.Min(Props.maxTargets, potentialTargets.Count);
|
||
impactPreviewCells = potentialTargets
|
||
.InRandomOrder()
|
||
.Take(previewCount)
|
||
.ToList();
|
||
}
|
||
|
||
// 绘制圆形区域预览
|
||
private void DrawCircularAreaPreview(IntVec3 center)
|
||
{
|
||
Map map = parent.pawn.Map;
|
||
|
||
// 绘制圆形区域边界
|
||
foreach (var cell in currentPreviewCells)
|
||
{
|
||
if (cell.InBounds(map))
|
||
{
|
||
GenDraw.DrawFieldEdges(new List<IntVec3> { cell }, Props.areaPreviewColor, 0.2f);
|
||
}
|
||
}
|
||
|
||
// 绘制圆形边界线
|
||
DrawCircularBoundary(center);
|
||
}
|
||
|
||
// 绘制圆形边界
|
||
private void DrawCircularBoundary(IntVec3 center)
|
||
{
|
||
Map map = parent.pawn.Map;
|
||
|
||
// 绘制圆形边界(使用多个线段近似)
|
||
int segments = 36; // 36段,每段10度
|
||
float angleStep = 360f / segments;
|
||
|
||
Vector3 centerPos = center.ToVector3Shifted();
|
||
|
||
for (int i = 0; i < segments; i++)
|
||
{
|
||
float angle1 = i * angleStep * Mathf.Deg2Rad;
|
||
float angle2 = (i + 1) * angleStep * Mathf.Deg2Rad;
|
||
|
||
Vector3 point1 = centerPos + new Vector3(
|
||
Mathf.Cos(angle1) * Props.radius,
|
||
0,
|
||
Mathf.Sin(angle1) * Props.radius
|
||
);
|
||
|
||
Vector3 point2 = centerPos + new Vector3(
|
||
Mathf.Cos(angle2) * Props.radius,
|
||
0,
|
||
Mathf.Sin(angle2) * Props.radius
|
||
);
|
||
|
||
GenDraw.DrawLineBetween(point1, point2, SimpleColor.Orange, 0.2f);
|
||
}
|
||
}
|
||
|
||
// 绘制预计落点预览
|
||
private void DrawImpactPreview(IntVec3 center)
|
||
{
|
||
Map map = parent.pawn.Map;
|
||
|
||
foreach (var cell in impactPreviewCells)
|
||
{
|
||
if (cell.InBounds(map))
|
||
{
|
||
// 绘制落点标记
|
||
GenDraw.DrawTargetHighlight(cell);
|
||
|
||
// 绘制落点范围指示
|
||
GenDraw.DrawRadiusRing(cell, 1f, Color.red, (c) => true);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 选择目标格子
|
||
private void SelectTargetCells()
|
||
{
|
||
Map map = parent.pawn.Map;
|
||
|
||
// 获取圆形区域内的所有单元格
|
||
var areaCells = GenRadial.RadialCellsAround(bombardmentCenter, Props.radius, true)
|
||
.Where(cell => cell.InBounds(map))
|
||
.Where(cell => IsValidTargetCell(cell, map))
|
||
.ToList();
|
||
|
||
var selectedCells = new List<IntVec3>();
|
||
var missedCells = new List<IntVec3>();
|
||
|
||
// 根据概率选择目标格子
|
||
foreach (var cell in areaCells)
|
||
{
|
||
if (Rand.Value <= Props.targetSelectionChance)
|
||
{
|
||
selectedCells.Add(cell);
|
||
}
|
||
else
|
||
{
|
||
missedCells.Add(cell);
|
||
}
|
||
}
|
||
|
||
// 应用最小/最大限制
|
||
if (selectedCells.Count < Props.minTargets)
|
||
{
|
||
// 补充不足的格子
|
||
int needed = Props.minTargets - selectedCells.Count;
|
||
if (missedCells.Count > 0)
|
||
{
|
||
selectedCells.AddRange(missedCells.InRandomOrder().Take(Mathf.Min(needed, missedCells.Count)));
|
||
}
|
||
}
|
||
else if (selectedCells.Count > Props.maxTargets)
|
||
{
|
||
// 随机移除多余的格子
|
||
selectedCells = selectedCells.InRandomOrder().Take(Props.maxTargets).ToList();
|
||
}
|
||
|
||
targetCells = selectedCells;
|
||
Log.Message($"[CircularBombardment] Selected {targetCells.Count} target cells from {areaCells.Count} area cells");
|
||
}
|
||
|
||
// 检查单元格是否有效
|
||
private bool IsValidTargetCell(IntVec3 cell, Map map)
|
||
{
|
||
// 检查最小距离
|
||
if (Props.minDistanceFromCenter > 0)
|
||
{
|
||
float distance = Vector3.Distance(cell.ToVector3(), bombardmentCenter.ToVector3());
|
||
if (distance < Props.minDistanceFromCenter)
|
||
return false;
|
||
}
|
||
|
||
// 检查友军误伤
|
||
if (Props.avoidFriendlyFire)
|
||
{
|
||
var pawnsInCell = map.thingGrid.ThingsListAt(cell).OfType<Pawn>();
|
||
foreach (var pawn in pawnsInCell)
|
||
{
|
||
if (pawn.Faction != null && pawn.Faction == parent.pawn.Faction)
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 检查建筑物
|
||
if (Props.avoidBuildings)
|
||
{
|
||
var buildingsInCell = map.thingGrid.ThingsListAt(cell).OfType<Building>();
|
||
if (buildingsInCell.Any())
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private void StartWarmup()
|
||
{
|
||
currentState = CircularBombardmentState.Warmup;
|
||
warmupTicksRemaining = Props.warmupTicks;
|
||
launchesCompleted = 0;
|
||
currentGroupIndex = 0;
|
||
|
||
Log.Message($"[CircularBombardment] Warmup started: {warmupTicksRemaining} ticks remaining");
|
||
}
|
||
|
||
private void UpdateWarmup()
|
||
{
|
||
warmupTicksRemaining--;
|
||
|
||
if (warmupTicksRemaining <= 0)
|
||
{
|
||
// 前摇结束,开始发射
|
||
currentState = CircularBombardmentState.Launching;
|
||
nextLaunchTick = Find.TickManager.TicksGame;
|
||
Log.Message($"[CircularBombardment] Warmup completed, starting launches");
|
||
}
|
||
}
|
||
|
||
// 开始新的一组发射
|
||
private void StartNewGroup()
|
||
{
|
||
if (remainingTargets.Count == 0 || launchesCompleted >= Props.maxLaunches)
|
||
{
|
||
currentState = CircularBombardmentState.Completed;
|
||
Log.Message($"[CircularBombardment] All launches completed: {launchesCompleted}/{Props.maxLaunches}");
|
||
return;
|
||
}
|
||
|
||
// 选择本组的目标
|
||
int groupSize = Mathf.Min(Props.simultaneousLaunches, remainingTargets.Count);
|
||
currentGroupTargets = remainingTargets.Take(groupSize).ToList();
|
||
remainingTargets.RemoveRange(0, groupSize);
|
||
|
||
if (Props.useIndependentIntervals)
|
||
{
|
||
// 启用组内独立间隔
|
||
isInGroupLaunch = true;
|
||
nextInnerLaunchTick = Find.TickManager.TicksGame;
|
||
currentGroupIndex++;
|
||
|
||
Log.Message($"[CircularBombardment] Starting group {currentGroupIndex} with {currentGroupTargets.Count} targets, using independent intervals");
|
||
}
|
||
else
|
||
{
|
||
// 传统模式:同时发射所有目标
|
||
foreach (var target in currentGroupTargets)
|
||
{
|
||
if (launchesCompleted < Props.maxLaunches)
|
||
{
|
||
LaunchSkyfaller(target);
|
||
launchesCompleted++;
|
||
}
|
||
}
|
||
|
||
// 设置下一组发射时间
|
||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||
Log.Message($"[CircularBombardment] Launched group {currentGroupIndex + 1} simultaneously: {currentGroupTargets.Count} targets");
|
||
}
|
||
}
|
||
|
||
// 更新组内独立发射
|
||
private void UpdateIndependentGroupLaunch()
|
||
{
|
||
if (Find.TickManager.TicksGame < nextInnerLaunchTick)
|
||
return;
|
||
|
||
if (currentGroupTargets.Count == 0)
|
||
{
|
||
// 当前组发射完毕
|
||
isInGroupLaunch = false;
|
||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||
Log.Message($"[CircularBombardment] Group {currentGroupIndex} completed");
|
||
return;
|
||
}
|
||
|
||
// 发射当前目标
|
||
var target = currentGroupTargets[0];
|
||
currentGroupTargets.RemoveAt(0);
|
||
|
||
LaunchSkyfaller(target);
|
||
launchesCompleted++;
|
||
|
||
// 设置下一个组内发射时间
|
||
if (currentGroupTargets.Count > 0 && launchesCompleted < Props.maxLaunches)
|
||
{
|
||
nextInnerLaunchTick = Find.TickManager.TicksGame + Props.innerLaunchIntervalTicks;
|
||
}
|
||
else
|
||
{
|
||
// 当前组发射完毕或达到最大发射数量
|
||
isInGroupLaunch = false;
|
||
nextLaunchTick = Find.TickManager.TicksGame + Props.launchIntervalTicks;
|
||
}
|
||
|
||
Log.Message($"[CircularBombardment] Launched target in group {currentGroupIndex} ({launchesCompleted}/{Props.maxLaunches})");
|
||
}
|
||
|
||
// 更新发射逻辑
|
||
private void UpdateLaunching()
|
||
{
|
||
if (Props.useIndependentIntervals && isInGroupLaunch)
|
||
{
|
||
UpdateIndependentGroupLaunch();
|
||
}
|
||
else
|
||
{
|
||
if (Find.TickManager.TicksGame >= nextLaunchTick)
|
||
{
|
||
StartNewGroup();
|
||
}
|
||
}
|
||
|
||
// 检查是否完成
|
||
if (launchesCompleted >= Props.maxLaunches ||
|
||
(remainingTargets.Count == 0 && !isInGroupLaunch))
|
||
{
|
||
currentState = CircularBombardmentState.Completed;
|
||
Log.Message($"[CircularBombardment] Bombardment completed: {launchesCompleted} launches");
|
||
}
|
||
}
|
||
|
||
private void LaunchSkyfaller(IntVec3 targetCell)
|
||
{
|
||
try
|
||
{
|
||
if (Props.skyfallerDef != null)
|
||
{
|
||
// 使用 Skyfaller
|
||
Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(Props.skyfallerDef);
|
||
GenSpawn.Spawn(skyfaller, targetCell, parent.pawn.Map);
|
||
}
|
||
else if (Props.projectileDef != null)
|
||
{
|
||
// 使用抛射体作为备用
|
||
LaunchProjectileAt(targetCell);
|
||
}
|
||
else
|
||
{
|
||
Log.Error($"[CircularBombardment] No skyfaller or projectile defined");
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Log.Error($"[CircularBombardment] Error launching at {targetCell}: {ex}");
|
||
}
|
||
}
|
||
|
||
private void LaunchProjectileAt(IntVec3 targetCell)
|
||
{
|
||
// 从上方发射抛射体
|
||
IntVec3 spawnCell = new IntVec3(targetCell.x, 0, targetCell.z);
|
||
Vector3 spawnPos = spawnCell.ToVector3() + new Vector3(0, 20f, 0);
|
||
|
||
Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, spawnCell, parent.pawn.Map);
|
||
if (projectile != null)
|
||
{
|
||
projectile.Launch(
|
||
parent.pawn,
|
||
spawnPos,
|
||
new LocalTargetInfo(targetCell),
|
||
new LocalTargetInfo(targetCell),
|
||
ProjectileHitFlags.All,
|
||
false
|
||
);
|
||
}
|
||
}
|
||
|
||
private void Cleanup()
|
||
{
|
||
// 重置状态
|
||
currentState = CircularBombardmentState.Idle;
|
||
targetCells.Clear();
|
||
remainingTargets.Clear();
|
||
currentGroupTargets.Clear();
|
||
currentPreviewCells.Clear();
|
||
impactPreviewCells.Clear();
|
||
currentGroupIndex = 0;
|
||
isInGroupLaunch = false;
|
||
launchesCompleted = 0;
|
||
|
||
Log.Message($"[CircularBombardment] Cleanup completed");
|
||
}
|
||
|
||
public override void CompTick()
|
||
{
|
||
base.CompTick();
|
||
|
||
if (currentState == CircularBombardmentState.Idle)
|
||
return;
|
||
|
||
switch (currentState)
|
||
{
|
||
case CircularBombardmentState.Warmup:
|
||
UpdateWarmup();
|
||
break;
|
||
|
||
case CircularBombardmentState.Launching:
|
||
UpdateLaunching();
|
||
break;
|
||
|
||
case CircularBombardmentState.Completed:
|
||
Cleanup();
|
||
break;
|
||
}
|
||
}
|
||
|
||
public override void PostExposeData()
|
||
{
|
||
base.PostExposeData();
|
||
|
||
Scribe_Values.Look(ref currentState, "currentState", CircularBombardmentState.Idle);
|
||
Scribe_Collections.Look(ref targetCells, "targetCells", LookMode.Value);
|
||
Scribe_Collections.Look(ref remainingTargets, "remainingTargets", LookMode.Value);
|
||
Scribe_Collections.Look(ref currentGroupTargets, "currentGroupTargets", LookMode.Value);
|
||
Scribe_Values.Look(ref warmupTicksRemaining, "warmupTicksRemaining", 0);
|
||
Scribe_Values.Look(ref nextLaunchTick, "nextLaunchTick", 0);
|
||
Scribe_Values.Look(ref nextInnerLaunchTick, "nextInnerLaunchTick", 0);
|
||
Scribe_Values.Look(ref launchesCompleted, "launchesCompleted", 0);
|
||
Scribe_Values.Look(ref currentGroupIndex, "currentGroupIndex", 0);
|
||
Scribe_Values.Look(ref isInGroupLaunch, "isInGroupLaunch", false);
|
||
}
|
||
|
||
// 技能提示信息
|
||
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
|
||
{
|
||
string baseInfo = $"圆形轰炸: 半径{Props.radius}格";
|
||
|
||
if (Props.useIndependentIntervals)
|
||
{
|
||
baseInfo += $"\n组内间隔: {Props.innerLaunchIntervalTicks}刻";
|
||
baseInfo += $"\n每组数量: {Props.simultaneousLaunches}个";
|
||
}
|
||
else
|
||
{
|
||
baseInfo += $"\n同时发射: {Props.simultaneousLaunches}个";
|
||
}
|
||
|
||
baseInfo += $"\n最大数量: {Props.maxLaunches}个";
|
||
baseInfo += $"\n组间间隔: {Props.launchIntervalTicks}刻";
|
||
|
||
return baseInfo;
|
||
}
|
||
|
||
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
|
||
{
|
||
return base.Valid(target, throwMessages) &&
|
||
parent.pawn != null &&
|
||
parent.pawn.Map != null &&
|
||
target.Cell.IsValid &&
|
||
target.Cell.InBounds(parent.pawn.Map);
|
||
}
|
||
}
|
||
|
||
// 圆形轰炸状态枚举
|
||
public enum CircularBombardmentState
|
||
{
|
||
Idle,
|
||
Warmup,
|
||
Launching,
|
||
Completed
|
||
}
|
||
}
|