using RimWorld; using Verse; using UnityEngine; using System.Collections.Generic; using System.Linq; namespace WulaFallenEmpire { public class CompAbilityEffect_Bombardment : CompAbilityEffect_WithDest { public new CompProperties_AbilityBombardment Props => (CompProperties_AbilityBombardment)props; // 轰炸状态 private BombardmentState currentState = BombardmentState.Idle; private List targetCells = new List(); private List bombardmentRows = new List(); private IntVec3 bombardmentCenter; private Vector3 bombardmentDirection; // 轰炸前进方向 private int currentRowIndex = 0; private int currentCellIndex = 0; private int warmupTicksRemaining = 0; private int nextBombardmentTick = 0; // 视觉效果 private Effecter areaEffecter; // 预览状态 private List currentPreviewCells = new List(); public override void Apply(LocalTargetInfo target, LocalTargetInfo dest) { base.Apply(target, dest); if (parent.pawn == null || parent.pawn.Map == null) return; try { WulaLog.Debug($"[Bombardment] Starting bombardment at {target.Cell} with direction to {dest.Cell}"); // 计算轰炸区域和方向(基于两个目标点) CalculateBombardmentArea(target.Cell, dest.Cell); // 选择目标格子 SelectTargetCells(); // 组织成排 - 修复:确保正确的排序 OrganizeTargetCellsIntoRows(); // 开始前摇 StartWarmup(); WulaLog.Debug($"[Bombardment] Bombardment initialized: {targetCells.Count} targets in {bombardmentRows.Count} rows"); } catch (System.Exception ex) { WulaLog.Debug($"[Bombardment] 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 { // 如果正在选择第二个目标(方向点),则显示轰炸区域预览 if (selectedTarget.IsValid) { // 动态计算轰炸区域(基于第一个目标和当前鼠标位置) CalculateDynamicBombardmentArea(selectedTarget.Cell, target.Cell); // 绘制轰炸区域预览 DrawBombardmentAreaPreview(selectedTarget.Cell, target.Cell); } } catch (System.Exception) { // 忽略预览绘制错误 } } // 新增:基于两个目标点动态计算轰炸区域 private void CalculateDynamicBombardmentArea(IntVec3 startCell, IntVec3 directionCell) { Map map = parent.pawn.Map; // 计算轰炸方向(从起点指向方向点) Vector3 direction = (directionCell.ToVector3() - startCell.ToVector3()).normalized; // 如果方向为零向量,使用默认方向 if (direction == Vector3.zero) { direction = Vector3.forward; } // 计算轰炸区域的所有单元格 currentPreviewCells = CalculateBombardmentAreaCells(startCell, direction); } // 绘制轰炸区域预览(基于两个目标点) private void DrawBombardmentAreaPreview(IntVec3 startCell, IntVec3 directionCell) { Map map = parent.pawn.Map; // 绘制轰炸区域内部的单元格 foreach (var cell in currentPreviewCells) { if (cell.InBounds(map)) { GenDraw.DrawFieldEdges(new List { cell }, Props.areaPreviewColor, 0.4f); } } // 绘制轰炸区域边界 DrawBombardmentBoundaries(startCell, directionCell); } // 绘制轰炸区域边界(基于两个目标点) private void DrawBombardmentBoundaries(IntVec3 startCell, IntVec3 directionCell) { Map map = parent.pawn.Map; // 计算轰炸方向 Vector3 direction = (directionCell.ToVector3() - startCell.ToVector3()).normalized; // 如果方向为零向量,使用默认方向 if (direction == Vector3.zero) { direction = Vector3.forward; } // 计算垂直于轰炸方向的方向(作为轰炸区域的宽度方向) Vector3 perpendicularDirection = new Vector3(-direction.z, 0, direction.x).normalized; // 计算轰炸区域的四个角 Vector3 startCenter = startCell.ToVector3(); float halfWidth = Props.bombardmentWidth * 0.5f; float totalLength = Props.bombardmentLength; // 轰炸起点 Vector3 startLeft = startCenter + perpendicularDirection * halfWidth; Vector3 startRight = startCenter - perpendicularDirection * halfWidth; // 轰炸终点(沿方向前进轰炸长度) Vector3 endCenter = startCenter + direction * totalLength; Vector3 endLeft = endCenter + perpendicularDirection * halfWidth; Vector3 endRight = endCenter - perpendicularDirection * halfWidth; // 转换为 IntVec3 并确保在地图范围内 IntVec3 startLeftCell = GetSafeMapPosition(new IntVec3((int)startLeft.x, (int)startLeft.y, (int)startLeft.z), map); IntVec3 startRightCell = GetSafeMapPosition(new IntVec3((int)startRight.x, (int)startRight.y, (int)startRight.z), map); IntVec3 endLeftCell = GetSafeMapPosition(new IntVec3((int)endLeft.x, (int)endLeft.y, (int)endLeft.z), map); IntVec3 endRightCell = GetSafeMapPosition(new IntVec3((int)endRight.x, (int)endRight.y, (int)endRight.z), map); // 绘制边界线 if (startLeftCell.InBounds(map) && endLeftCell.InBounds(map)) GenDraw.DrawLineBetween(startLeftCell.ToVector3Shifted(), endLeftCell.ToVector3Shifted(), SimpleColor.Red, 0.2f); if (startRightCell.InBounds(map) && endRightCell.InBounds(map)) GenDraw.DrawLineBetween(startRightCell.ToVector3Shifted(), endRightCell.ToVector3Shifted(), SimpleColor.Red, 0.2f); if (startLeftCell.InBounds(map) && startRightCell.InBounds(map)) GenDraw.DrawLineBetween(startLeftCell.ToVector3Shifted(), startRightCell.ToVector3Shifted(), SimpleColor.Red, 0.2f); if (endLeftCell.InBounds(map) && endRightCell.InBounds(map)) GenDraw.DrawLineBetween(endLeftCell.ToVector3Shifted(), endRightCell.ToVector3Shifted(), SimpleColor.Red, 0.2f); // 绘制方向箭头 DrawDirectionArrow(startCell, directionCell); } // 绘制方向箭头 private void DrawDirectionArrow(IntVec3 startCell, IntVec3 directionCell) { Map map = parent.pawn.Map; Vector3 startPos = startCell.ToVector3Shifted(); Vector3 directionPos = directionCell.ToVector3Shifted(); // 绘制从起点到方向点的连线 GenDraw.DrawLineBetween(startPos, directionPos, SimpleColor.Yellow, 0.1f); // 在方向点绘制箭头标记 GenDraw.DrawTargetHighlight(directionCell); } // 计算轰炸区域的所有单元格(基于起点和方向) private List CalculateBombardmentAreaCells(IntVec3 startCell, Vector3 direction) { var areaCells = new List(); Map map = parent.pawn.Map; Vector3 start = startCell.ToVector3(); // 计算垂直于轰炸方向的方向(宽度方向) Vector3 perpendicularDirection = new Vector3(-direction.z, 0, direction.x).normalized; float halfWidth = Props.bombardmentWidth * 0.5f; float totalLength = Props.bombardmentLength; // 使用浮点步进计算所有单元格 int widthSteps = Mathf.Max(1, Props.bombardmentWidth); int lengthSteps = Mathf.Max(1, Props.bombardmentLength); for (int l = 0; l <= lengthSteps; l++) { float lengthProgress = (float)l / lengthSteps; float lengthOffset = Mathf.Lerp(0, totalLength, lengthProgress); for (int w = 0; w <= widthSteps; w++) { float widthProgress = (float)w / widthSteps; float widthOffset = Mathf.Lerp(-halfWidth, halfWidth, widthProgress); // 计算单元格位置 Vector3 cellPos = start + direction * lengthOffset + perpendicularDirection * widthOffset; IntVec3 cell = new IntVec3( Mathf.RoundToInt(cellPos.x), Mathf.RoundToInt(cellPos.y), Mathf.RoundToInt(cellPos.z) ); if (cell.InBounds(map) && !areaCells.Contains(cell)) { areaCells.Add(cell); } } } return areaCells; } // 计算轰炸区域和方向(基于两个目标点) private void CalculateBombardmentArea(IntVec3 startCell, IntVec3 directionCell) { bombardmentCenter = startCell; // 计算轰炸方向(从起点指向方向点) Vector3 direction = (directionCell.ToVector3() - startCell.ToVector3()).normalized; // 如果方向为零向量,使用默认方向 if (direction == Vector3.zero) { direction = Vector3.forward; } bombardmentDirection = direction; WulaLog.Debug($"[Bombardment] Bombardment direction: {bombardmentDirection} (from {startCell} to {directionCell})"); } private void SelectTargetCells() { // 计算轰炸区域的所有单元格 var areaCells = CalculateBombardmentAreaCells(bombardmentCenter, bombardmentDirection); var selectedCells = new List(); var missedCells = new List(); // 根据概率选择目标格子 foreach (var cell in areaCells) { if (Rand.Value <= Props.targetSelectionChance) { selectedCells.Add(cell); } else { missedCells.Add(cell); } } // 应用最小/最大限制 if (selectedCells.Count < Props.minTargetCells) { // 补充不足的格子 int needed = Props.minTargetCells - selectedCells.Count; if (missedCells.Count > 0) { selectedCells.AddRange(missedCells.InRandomOrder().Take(Mathf.Min(needed, missedCells.Count))); } } else if (selectedCells.Count > Props.maxTargetCells) { // 随机移除多余的格子 selectedCells = selectedCells.InRandomOrder().Take(Props.maxTargetCells).ToList(); } targetCells = selectedCells; WulaLog.Debug($"[Bombardment] Selected {targetCells.Count} target cells from {areaCells.Count} area cells"); } // 修复:重新组织目标格子成排,确保正确的渐进顺序 private void OrganizeTargetCellsIntoRows() { bombardmentRows.Clear(); // 计算垂直于轰炸方向的方向(宽度方向) Vector3 perpendicularDirection = new Vector3(-bombardmentDirection.z, 0, bombardmentDirection.x).normalized; // 根据轰炸前进方向将格子分组到不同的排 var rows = new Dictionary>(); foreach (var cell in targetCells) { // 计算格子相对于轰炸起点的"行索引"(在轰炸前进方向上的投影) Vector3 cellVector = cell.ToVector3() - bombardmentCenter.ToVector3(); float dotProduct = Vector3.Dot(cellVector, bombardmentDirection); int rowIndex = Mathf.RoundToInt(dotProduct); if (!rows.ContainsKey(rowIndex)) { rows[rowIndex] = new List(); } rows[rowIndex].Add(cell); } // 修复:按照轰炸方向正确排序行索引 // 从起点(行索引=0)开始,向轰炸方向前进 var sortedRowIndices = rows.Keys.OrderBy(x => x).ToList(); foreach (var rowIndex in sortedRowIndices) { // 修复:在每排内按照宽度方向正确排序 // 从轰炸区域的一侧到另一侧 var sortedCells = rows[rowIndex].OrderBy(cell => { Vector3 cellVector = cell.ToVector3() - bombardmentCenter.ToVector3(); return Vector3.Dot(cellVector, perpendicularDirection); }).ToList(); bombardmentRows.Add(new BombardmentRow { rowIndex = rowIndex, cells = sortedCells }); } WulaLog.Debug($"[Bombardment] Organized into {bombardmentRows.Count} rows in progressive order"); } private void StartWarmup() { currentState = BombardmentState.Warmup; warmupTicksRemaining = Props.warmupTicks; currentRowIndex = 0; currentCellIndex = 0; // 创建区域效果器 if (Props.showBombardmentArea) { CreateAreaEffecter(); } WulaLog.Debug($"[Bombardment] Warmup started: {warmupTicksRemaining} ticks remaining"); } private void UpdateWarmup() { warmupTicksRemaining--; if (warmupTicksRemaining <= 0) { // 前摇结束,开始轰炸 currentState = BombardmentState.Bombarding; nextBombardmentTick = Find.TickManager.TicksGame; WulaLog.Debug($"[Bombardment] Warmup completed, starting progressive bombardment"); } } private void UpdateBombardment() { if (Find.TickManager.TicksGame < nextBombardmentTick) return; if (currentRowIndex >= bombardmentRows.Count) { // 所有排都轰炸完毕 currentState = BombardmentState.Completed; WulaLog.Debug($"[Bombardment] Progressive bombardment completed"); return; } var currentRow = bombardmentRows[currentRowIndex]; if (currentCellIndex >= currentRow.cells.Count) { // 当前排轰炸完毕,移动到下一排 currentRowIndex++; currentCellIndex = 0; nextBombardmentTick = Find.TickManager.TicksGame + Props.rowDelayTicks; WulaLog.Debug($"[Bombardment] Moving to next row {currentRowIndex + 1}/{bombardmentRows.Count}"); return; } // 轰炸当前格子 var targetCell = currentRow.cells[currentCellIndex]; LaunchBombardment(targetCell); currentCellIndex++; nextBombardmentTick = Find.TickManager.TicksGame + Props.impactDelayTicks; // 记录轰炸进度 WulaLog.Debug($"[Bombardment] Bombarding cell {currentCellIndex}/{currentRow.cells.Count} in row {currentRowIndex + 1}/{bombardmentRows.Count}"); } private void LaunchBombardment(IntVec3 targetCell) { try { if (Props.skyfallerDef != null) { // 使用 Skyfaller Skyfaller skyfaller = SkyfallerMaker.MakeSkyfaller(Props.skyfallerDef); GenSpawn.Spawn(skyfaller, targetCell, parent.pawn.Map); WulaLog.Debug($"[Bombardment] Launched skyfaller at {targetCell}"); } else if (Props.projectileDef != null) { // 使用抛射体作为备用 LaunchProjectileAt(targetCell); } else { WulaLog.Debug($"[Bombardment] No skyfaller or projectile defined for bombardment"); } } catch (System.Exception ex) { WulaLog.Debug($"[Bombardment] Error launching bombardment 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 ); WulaLog.Debug($"[Bombardment] Launched projectile at {targetCell}"); } } private void CreateAreaEffecter() { // 创建轰炸区域视觉效果 if (DefDatabase.GetNamedSilentFail("BombardmentArea") != null) { areaEffecter = DefDatabase.GetNamed("BombardmentArea").Spawn(); areaEffecter.offset = bombardmentCenter.ToVector3Shifted(); areaEffecter.scale = Props.effecterScale; } } private void Cleanup() { // 清理效果器 areaEffecter?.Cleanup(); areaEffecter = null; // 重置状态 currentState = BombardmentState.Idle; targetCells.Clear(); bombardmentRows.Clear(); currentPreviewCells.Clear(); WulaLog.Debug($"[Bombardment] Cleanup completed"); } private IntVec3 GetSafeMapPosition(IntVec3 pos, Map map) { if (map == null) return pos; pos.x = Mathf.Clamp(pos.x, 0, map.Size.x - 1); pos.z = Mathf.Clamp(pos.z, 0, map.Size.z - 1); return pos; } public override void CompTick() { base.CompTick(); if (currentState == BombardmentState.Idle) return; switch (currentState) { case BombardmentState.Warmup: UpdateWarmup(); break; case BombardmentState.Bombarding: UpdateBombardment(); break; case BombardmentState.Completed: Cleanup(); break; } } public override void PostExposeData() { base.PostExposeData(); Scribe_Values.Look(ref currentState, "currentState", BombardmentState.Idle); Scribe_Collections.Look(ref targetCells, "targetCells", LookMode.Value); Scribe_Values.Look(ref currentRowIndex, "currentRowIndex", 0); Scribe_Values.Look(ref currentCellIndex, "currentCellIndex", 0); Scribe_Values.Look(ref warmupTicksRemaining, "warmupTicksRemaining", 0); Scribe_Values.Look(ref nextBombardmentTick, "nextBombardmentTick", 0); } // 重写:获取目标参数 public override TargetingParameters targetParams => new TargetingParameters { canTargetLocations = true, canTargetPawns = false, canTargetBuildings = false, canTargetItems = false, mapObjectTargetsMustBeAutoAttackable = false }; // 重写:验证目标 public override bool ValidateTarget(LocalTargetInfo target, bool showMessages = true) { if (!target.IsValid) { return false; } // 检查是否可以放置目标 if (!CanPlaceSelectedTargetAt(target)) { if (showMessages) { // 修复:使用 LookTargets 而不是 LocalTargetInfo Messages.Message("CannotBombardInvalidLocation".Translate(), new LookTargets(target.Cell, parent.pawn.Map), MessageTypeDefOf.RejectInput); } return false; } return true; } // 修复:使用 new 而不是 override,因为基类方法不是 virtual public new void DrawHighlight(LocalTargetInfo target) { if (selectedTarget.IsValid) { // 当选择第二个目标时,显示轰炸区域 CalculateDynamicBombardmentArea(selectedTarget.Cell, target.Cell); DrawBombardmentAreaPreview(selectedTarget.Cell, target.Cell); } else { // 当选择第一个目标时,显示普通高亮 GenDraw.DrawTargetHighlight(target); } } // 修复:使用 new 而不是 override,因为基类方法不是 virtual public new void OnGUI(LocalTargetInfo target) { Texture2D icon = ((!target.IsValid) ? TexCommand.CannotShoot : parent.def.uiIcon); GenUI.DrawMouseAttachment(icon); string text = ExtraLabelMouseAttachment(target); if (!text.NullOrEmpty()) { Widgets.MouseAttachedLabel(text); } } // 重写:额外标签 public override string ExtraLabelMouseAttachment(LocalTargetInfo target) { if (selectedTarget.IsValid) { return "SelectBombardmentDirection".Translate(); } else { return "SelectBombardmentStart".Translate(); } } } // 轰炸状态枚举 public enum BombardmentState { Idle, Warmup, Bombarding, Completed } // 轰炸排数据结构 public struct BombardmentRow { public int rowIndex; public List cells; } }