using System; using System.Collections.Generic; using System.Linq; using RimWorld; using UnityEngine; using Verse; using Verse.AI; using Verse.Sound; namespace WulaFallenEmpire { /// /// 高速移动撞击组件 /// public class CompHighSpeedCollision : ThingComp { // === 运行时状态 === private enum SpeedStage { Stage0, // 0阶段:不移动 Stage1, // 1阶段:低速碰撞 Stage2 // 2阶段:高速击飞 } private SpeedStage currentStage = SpeedStage.Stage0; private int stageTransitionCooldown = 0; // 用于计算速度的帧历史 private Queue speedHistory = new Queue(); private IntVec3 lastPosition = IntVec3.Invalid; private int lastPositionTick = -1; // 已处理的敌人记录(避免同一帧重复处理) private HashSet processedPawnsThisTick = new HashSet(); // === 缓存 === private CellRect collisionAreaCache = default; private int lastAreaRecalculationTick = -1; // === 地形阻挡相关 === private float currentTerrainBlockSeverity = 0f; private int lastTerrainCheckTick = -1; public CompProperties_HighSpeedCollision Props => (CompProperties_HighSpeedCollision)props; public override void PostSpawnSetup(bool respawningAfterLoad) { base.PostSpawnSetup(respawningAfterLoad); // 初始化速度历史 speedHistory.Clear(); for (int i = 0; i < Props.speedHistoryFrameCount; i++) { speedHistory.Enqueue(0f); } lastPosition = parent.Position; lastPositionTick = Find.TickManager.TicksGame; } public override void CompTick() { base.CompTick(); if (!parent.Spawned || parent.Destroyed) return; Pawn pawn = parent as Pawn; if (pawn == null || pawn.Dead || pawn.Downed) { ResetToStage0(); return; } // 每帧更新 ProcessFrame(pawn); // 地形阻挡检查(降低频率) if (Props.narrowTerrainBlocked && (Find.TickManager.TicksGame - lastTerrainCheckTick >= Props.terrainCheckInterval)) { UpdateTerrainBlockStatus(pawn); lastTerrainCheckTick = Find.TickManager.TicksGame; } } /// /// 更新地形阻挡状态 /// private void UpdateTerrainBlockStatus(Pawn pawn) { if (!Props.narrowTerrainBlocked || Props.maxBlockPenalty <= 0f) { currentTerrainBlockSeverity = 0f; UpdateBlockedHediff(pawn, 0f); return; } // 获取碰撞区域 CellRect collisionArea = GetCollisionArea(pawn); // 统计空闲格子和被占据格子 int totalCells = 0; int occupiedCells = 0; foreach (IntVec3 cell in collisionArea) { if (!cell.InBounds(pawn.Map)) continue; totalCells++; // 检查是否被不可通行建筑占据 if (IsCellBlockedByImpassable(cell, pawn.Map)) { occupiedCells++; } } // 计算阻挡严重度 float blockSeverity = CalculateBlockSeverity(totalCells, occupiedCells); // 立即设置阻挡严重度(移除平滑过渡) currentTerrainBlockSeverity = blockSeverity; // 更新Hediff UpdateBlockedHediff(pawn, currentTerrainBlockSeverity); // 调试日志 if (Props.enableDebugLogging && currentTerrainBlockSeverity > 0.01f) { Log.Message($"[HighSpeedCollision] Terrain Block: {pawn.Label}, " + $"Occupied: {occupiedCells}/{totalCells}, " + $"Severity: {currentTerrainBlockSeverity:P0}"); } } /// /// 检查单元格是否被不可通行建筑占据 /// private bool IsCellBlockedByImpassable(IntVec3 cell, Map map) { // 获取单元格内的所有建筑 var things = cell.GetThingList(map); foreach (var thing in things) { if (thing is Building building) { // 检查是否可通行 if (building.def.passability == Traversability.Impassable) { return true; } } } return false; } /// /// 计算阻挡严重度 /// private float CalculateBlockSeverity(int totalCells, int occupiedCells) { if (totalCells == 0 || occupiedCells == 0) return 0f; int freeCells = totalCells - occupiedCells; float freeRatio = (float)freeCells / totalCells; // 使用公式:(1 - maxBlockPenalty) + maxBlockPenalty * (空闲格子/(空闲格子+被占据格子)) // 简化后:1 - maxBlockPenalty + maxBlockPenalty * (freeCells / totalCells) // 但实际上,我们应该计算减速比例,然后转换为严重度 float speedMultiplier = (1f - Props.maxBlockPenalty) + Props.maxBlockPenalty * (freeRatio); // 严重度 = 1 - 速度乘数 return 1f - speedMultiplier; } /// /// 平滑过渡 /// private float SmoothTransition(float current, float target, float speed) { if (Mathf.Abs(current - target) < 0.01f) return target; return Mathf.Lerp(current, target, speed); } /// /// 更新阻挡Hediff /// private void UpdateBlockedHediff(Pawn pawn, float severity) { if (Props.blockedHediff == null) return; // 获取或添加Hediff Hediff hediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.blockedHediff); if (severity <= 0.01f) { // 移除Hediff if (hediff != null) { pawn.health.RemoveHediff(hediff); } return; } // 添加或更新Hediff if (hediff == null) { hediff = HediffMaker.MakeHediff(Props.blockedHediff, pawn); pawn.health.AddHediff(hediff); } // 更新严重度 hediff.Severity = severity; } /// /// 获取当前地形阻挡严重度(供HediffComp使用) /// public float GetCurrentTerrainBlockSeverity() { return currentTerrainBlockSeverity; } /// /// 处理每帧逻辑 /// private void ProcessFrame(Pawn pawn) { int currentTick = Find.TickManager.TicksGame; // 1. 计算当前速度 float currentSpeed = CalculateCurrentSpeed(pawn, currentTick); // 2. 更新速度历史 UpdateSpeedHistory(currentSpeed); // 3. 计算平均速度 float averageSpeed = GetAverageSpeed(); // 4. 确定阶段 DetermineSpeedStage(averageSpeed); // 5. 根据阶段应用效果 ApplyStageEffects(pawn); // 6. 清理每帧记录 processedPawnsThisTick.Clear(); // 7. 更新冷却 if (stageTransitionCooldown > 0) stageTransitionCooldown--; // 8. 调试绘制 if (Props.enableDebugVisuals && DebugSettings.godMode) DrawDebugVisuals(pawn); } /// /// 计算当前速度(每秒格数) /// private float CalculateCurrentSpeed(Pawn pawn, int currentTick) { // 如果没有上次位置记录,无法计算速度 if (lastPositionTick < 0 || lastPosition == IntVec3.Invalid) { lastPosition = pawn.Position; lastPositionTick = currentTick; return 0f; } // 计算时间差(秒) float timeDelta = (currentTick - lastPositionTick) / 60f; if (timeDelta <= 0f) return 0f; // 计算距离(格数) float distance = pawn.Position.DistanceTo(lastPosition); // 计算速度(格/秒) float speed = distance / timeDelta; // 更新记录 lastPosition = pawn.Position; lastPositionTick = currentTick; return speed; } /// /// 更新速度历史 /// private void UpdateSpeedHistory(float currentSpeed) { speedHistory.Enqueue(currentSpeed); while (speedHistory.Count > Props.speedHistoryFrameCount) { speedHistory.Dequeue(); } } /// /// 获取平均速度 /// private float GetAverageSpeed() { if (speedHistory.Count == 0) return 0f; float sum = 0f; foreach (float speed in speedHistory) { sum += speed; } return sum / speedHistory.Count; } /// /// 确定速度阶段 /// private void DetermineSpeedStage(float averageSpeed) { // 如果有冷却,保持当前阶段 if (stageTransitionCooldown > 0) return; SpeedStage newStage; if (averageSpeed <= Props.minSpeedForStage1) { newStage = SpeedStage.Stage0; } else if (averageSpeed >= Props.minSpeedForStage2) { newStage = SpeedStage.Stage2; } else { newStage = SpeedStage.Stage1; } // 阶段变化时设置冷却 if (newStage != currentStage) { currentStage = newStage; stageTransitionCooldown = Props.stageTransitionCooldownTicks; if (Props.enableDebugLogging) { Log.Message($"[HighSpeedCollision] {parent.Label} transitioned to Stage {(int)currentStage} " + $"at speed {averageSpeed:F2} cells/sec"); } } } /// /// 应用阶段效果 /// private void ApplyStageEffects(Pawn pawn) { if (currentStage == SpeedStage.Stage0) return; // 获取碰撞区域内的所有敌人 List enemiesInArea = GetEnemiesInCollisionArea(pawn); foreach (Pawn enemy in enemiesInArea) { if (enemy == null || enemy.Destroyed || enemy.Dead || processedPawnsThisTick.Contains(enemy)) continue; switch (currentStage) { case SpeedStage.Stage1: ApplyStage1Effects(pawn, enemy); break; case SpeedStage.Stage2: ApplyStage2Effects(pawn, enemy); break; } processedPawnsThisTick.Add(enemy); } } /// /// 应用阶段1效果(伤害+hediff) /// private void ApplyStage1Effects(Pawn attacker, Pawn target) { // 检查目标是否已有hediff bool alreadyHasHediff = target.health.hediffSet.HasHediff(Props.stage1Hediff); // 如果已有hediff,不造成伤害 if (alreadyHasHediff && Props.stage1HediffPreventsDamage) return; // 造成伤害 if (Props.stage1DamageAmount > 0f) { ApplyDamage(attacker, target, Props.stage1DamageAmount, Props.stage1DamageDef); } // 应用hediff if (Props.stage1Hediff != null) { Hediff hediff = HediffMaker.MakeHediff(Props.stage1Hediff, target); if (Props.stage1HediffDurationTicks > 0) { hediff.Severity = 1f; hediff.TryGetComp()?.CompPostMake(); } target.health.AddHediff(hediff); } // 播放效果 PlayStage1Effects(attacker, target); if (Props.enableDebugLogging) { Log.Message($"[HighSpeedCollision] Stage1: {attacker.Label} -> {target.Label}, " + $"Damage: {Props.stage1DamageAmount}, Hediff: {Props.stage1Hediff?.defName}"); } } /// /// 应用阶段2效果(伤害+击飞) /// private void ApplyStage2Effects(Pawn attacker, Pawn target) { // 造成伤害 if (Props.stage2DamageAmount > 0f) { ApplyDamage(attacker, target, Props.stage2DamageAmount, Props.stage2DamageDef); } // 执行击飞 PerformKnockback(attacker, target); // 播放效果 PlayStage2Effects(attacker, target); if (Props.enableDebugLogging) { Log.Message($"[HighSpeedCollision] Stage2: {attacker.Label} -> {target.Label}, " + $"Damage: {Props.stage2DamageAmount}, Knockback"); } } /// /// 执行击飞(参考CompAbilityEffect_FanShapedStunKnockback) /// private void PerformKnockback(Pawn attacker, Pawn target) { if (target == null || target.Destroyed || target.Dead || attacker.Map == null) return; // 计算击飞方向(从攻击者指向目标) IntVec3 knockbackDirection = CalculateKnockbackDirection(attacker, target.Position); // 寻找击飞位置 IntVec3 knockbackDestination = FindKnockbackDestination(attacker, target, knockbackDirection); // 如果找到有效位置,执行击飞 if (knockbackDestination.IsValid && knockbackDestination != target.Position) { CreateKnockbackFlyer(attacker, target, knockbackDestination); } } /// /// 计算击飞方向 /// private IntVec3 CalculateKnockbackDirection(Pawn attacker, IntVec3 targetPosition) { IntVec3 direction = targetPosition - attacker.Position; // 标准化方向 if (direction.x != 0 || direction.z != 0) { if (Mathf.Abs(direction.x) > Mathf.Abs(direction.z)) { return new IntVec3(Mathf.Sign(direction.x) > 0 ? 1 : -1, 0, 0); } else { return new IntVec3(0, 0, Mathf.Sign(direction.z) > 0 ? 1 : -1); } } // 如果攻击者和目标在同一位置,使用随机方向 return new IntVec3(Rand.Value > 0.5f ? 1 : -1, 0, 0); } /// /// 寻找击飞位置 /// private IntVec3 FindKnockbackDestination(Pawn attacker, Pawn target, IntVec3 direction) { Map map = attacker.Map; IntVec3 currentPos = target.Position; // 从最大距离开始向回找 for (int distance = Props.stage2KnockbackDistance; distance >= 1; distance--) { IntVec3 testPos = currentPos + (direction * distance); if (!IsValidKnockbackDestination(testPos, map, target, attacker)) continue; return testPos; } return currentPos; } /// /// 检查击飞位置是否有效 /// private bool IsValidKnockbackDestination(IntVec3 destination, Map map, Pawn victim, Pawn attacker) { if (!destination.IsValid || !destination.InBounds(map)) return false; if (!destination.Standable(map)) return false; // 检查是否有其他pawn Pawn existingPawn = destination.GetFirstPawn(map); if (existingPawn != null && existingPawn != victim) return false; // 检查视线 if (Props.requireLineOfSightForKnockback && !GenSight.LineOfSight(victim.Position, destination, map)) return false; return true; } /// /// 创建击飞飞行器 /// private void CreateKnockbackFlyer(Pawn attacker, Pawn target, IntVec3 destination) { try { Map map = attacker.Map; // 使用自定义飞行器或默认飞行器 ThingDef flyerDef = Props.knockbackFlyerDef ?? ThingDefOf.PawnFlyer; // 创建飞行器 PawnFlyer flyer = PawnFlyer.MakeFlyer( flyerDef, target, destination, Props.flightEffecterDef, Props.landingSound, false, null, null, new LocalTargetInfo(destination) ); if (flyer != null) { GenSpawn.Spawn(flyer, destination, map); } } catch (Exception ex) { Log.Error($"[HighSpeedCollision] Exception creating PawnFlyer: {ex}"); } } /// /// 应用伤害 /// private void ApplyDamage(Pawn attacker, Pawn target, float amount, DamageDef damageDef) { if (amount <= 0f || damageDef == null) return; DamageInfo damageInfo = new DamageInfo( damageDef, amount, Props.armorPenetration, -1f, attacker, null ); target.TakeDamage(damageInfo); } /// /// 播放阶段1效果 /// private void PlayStage1Effects(Pawn attacker, Pawn target) { if (Props.stage1Effecter != null && attacker.Map != null) { Effecter effect = Props.stage1Effecter.Spawn(); effect.Trigger(new TargetInfo(attacker.Position, attacker.Map), new TargetInfo(target.Position, attacker.Map)); effect.Cleanup(); } if (Props.stage1Sound != null && attacker.Map != null) { Props.stage1Sound.PlayOneShot(new TargetInfo(target.Position, attacker.Map)); } } /// /// 播放阶段2效果 /// private void PlayStage2Effects(Pawn attacker, Pawn target) { if (Props.stage2Effecter != null && attacker.Map != null) { Effecter effect = Props.stage2Effecter.Spawn(); effect.Trigger(new TargetInfo(attacker.Position, attacker.Map), new TargetInfo(target.Position, attacker.Map)); effect.Cleanup(); } if (Props.stage2Sound != null && attacker.Map != null) { Props.stage2Sound.PlayOneShot(new TargetInfo(target.Position, attacker.Map)); } } /// /// 获取碰撞区域内的所有敌人 /// private List GetEnemiesInCollisionArea(Pawn pawn) { List enemies = new List(); // 获取碰撞区域 CellRect collisionArea = GetCollisionArea(pawn); // 检查区域内的每个单元格 foreach (IntVec3 cell in collisionArea) { if (!cell.InBounds(pawn.Map)) continue; // 获取单元格内的所有pawn List things = cell.GetThingList(pawn.Map); foreach (Thing thing in things) { if (thing is Pawn otherPawn && otherPawn != pawn) { // 检查是否为敌人 if (IsValidTarget(pawn, otherPawn)) { enemies.Add(otherPawn); } } } } return enemies; } /// /// 获取碰撞区域 /// private CellRect GetCollisionArea(Pawn pawn) { int currentTick = Find.TickManager.TicksGame; // 每10帧重新计算一次区域,或当位置变化时 if (currentTick - lastAreaRecalculationTick > 10 || pawn.Position != collisionAreaCache.CenterCell) { int radius = Props.collisionAreaRadius; IntVec3 center = pawn.Position; collisionAreaCache = new CellRect( center.x - radius, center.z - radius, radius * 2 + 1, radius * 2 + 1 ); collisionAreaCache.ClipInsideMap(pawn.Map); lastAreaRecalculationTick = currentTick; } return collisionAreaCache; } /// /// 检查是否是有效目标 /// private bool IsValidTarget(Pawn attacker, Pawn target) { if (target == null || target.Destroyed || target.Dead) return false; // 检查是否为敌人 if (Props.onlyAffectEnemies && !target.HostileTo(attacker)) return false; // 检查是否排除友方 if (Props.excludeAlliedPawns && target.Faction == attacker.Faction) return false; // 检查是否排除中立 if (Props.excludeNeutralPawns && !target.HostileTo(attacker)) return false; return true; } /// /// 检查Pawn是否可以移动 /// private bool CanMove(Pawn pawn) { if (pawn.Downed || pawn.Dead || pawn.InMentalState) return false; if (pawn.stances?.stunner?.Stunned ?? false) return false; return true; } /// /// 重置到阶段0 /// private void ResetToStage0() { currentStage = SpeedStage.Stage0; // 清空速度历史 speedHistory.Clear(); for (int i = 0; i < Props.speedHistoryFrameCount; i++) { speedHistory.Enqueue(0f); } } /// /// 绘制调试视觉效果 /// private void DrawDebugVisuals(Pawn pawn) { if (!pawn.Spawned) return; // 绘制碰撞区域 CellRect area = GetCollisionArea(pawn); GenDraw.DrawFieldEdges(area.Cells.ToList(), GetStageColor(currentStage)); // 绘制速度指示器 float averageSpeed = GetAverageSpeed(); string speedText = $"Speed: {averageSpeed:F1} cells/sec\nStage: {(int)currentStage}"; Vector3 drawPos = pawn.DrawPos + new Vector3(0, 0, 1f); GenMapUI.DrawText(drawPos, speedText, GetStageColor(currentStage)); } /// /// 获取阶段颜色 /// private Color GetStageColor(SpeedStage stage) { switch (stage) { case SpeedStage.Stage0: return Color.gray; case SpeedStage.Stage1: return Color.yellow; case SpeedStage.Stage2: return Color.red; default: return Color.white; } } /// /// 获取调试信息 /// public string GetDebugInfo() { float averageSpeed = GetAverageSpeed(); return $"HighSpeedCollision Debug Info:\n" + $"Current Stage: {(int)currentStage}\n" + $"Average Speed: {averageSpeed:F2} cells/sec\n" + $"Stage 1 Threshold: {Props.minSpeedForStage1:F2}\n" + $"Stage 2 Threshold: {Props.minSpeedForStage2:F2}\n" + $"Speed History: {speedHistory.Count} frames\n" + $"Stage Cooldown: {stageTransitionCooldown} ticks"; } public override void PostExposeData() { base.PostExposeData(); Scribe_Values.Look(ref currentStage, "currentStage", SpeedStage.Stage0); Scribe_Values.Look(ref stageTransitionCooldown, "stageTransitionCooldown", 0); Scribe_Values.Look(ref lastPosition, "lastPosition", IntVec3.Invalid); Scribe_Values.Look(ref lastPositionTick, "lastPositionTick", -1); // 保存速度历史 if (Scribe.mode == LoadSaveMode.Saving) { List speedList = speedHistory.ToList(); Scribe_Collections.Look(ref speedList, "speedHistory", LookMode.Value); } else if (Scribe.mode == LoadSaveMode.LoadingVars) { List speedList = null; Scribe_Collections.Look(ref speedList, "speedHistory", LookMode.Value); speedHistory.Clear(); if (speedList != null) { foreach (float speed in speedList) { speedHistory.Enqueue(speed); } } // 确保有足够的历史数据 while (speedHistory.Count < Props.speedHistoryFrameCount) { speedHistory.Enqueue(0f); } } } } /// /// 高速移动撞击组件属性 /// public class CompProperties_HighSpeedCollision : CompProperties { // === 速度阈值配置 === /// /// 进入阶段1所需的最小速度(格/秒) /// public float minSpeedForStage1 = 3f; /// /// 进入阶段2所需的最小速度(格/秒) /// public float minSpeedForStage2 = 6f; /// /// 速度历史帧数(用于计算平均速度) /// public int speedHistoryFrameCount = 10; /// /// 阶段转换冷却时间(tick) /// public int stageTransitionCooldownTicks = 5; // === 碰撞区域配置 === /// /// 碰撞区域半径(以pawn为中心的正方形) /// public int collisionAreaRadius = 1; // === 目标过滤 === /// /// 只影响敌人 /// public bool onlyAffectEnemies = true; /// /// 排除友方单位 /// public bool excludeAlliedPawns = true; /// /// 排除中立单位 /// public bool excludeNeutralPawns = false; // === 阶段1效果配置 === /// /// 阶段1伤害类型 /// public DamageDef stage1DamageDef = DamageDefOf.Blunt; /// /// 阶段1伤害量 /// public float stage1DamageAmount = 5f; /// /// 阶段1护甲穿透 /// public float armorPenetration = 0f; /// /// 阶段1应用的hediff /// public HediffDef stage1Hediff; /// /// 阶段1hediff持续时间(tick) /// public int stage1HediffDurationTicks = 60; /// /// 拥有hediff的目标是否免疫伤害 /// public bool stage1HediffPreventsDamage = true; /// /// 阶段1效果器 /// public EffecterDef stage1Effecter; /// /// 阶段1音效 /// public SoundDef stage1Sound; // === 阶段2效果配置 === /// /// 阶段2伤害类型 /// public DamageDef stage2DamageDef = DamageDefOf.Blunt; /// /// 阶段2伤害量 /// public float stage2DamageAmount = 10f; /// /// 阶段2击退距离 /// public int stage2KnockbackDistance = 3; /// /// 击退是否需要视线 /// public bool requireLineOfSightForKnockback = true; /// /// 阶段2效果器 /// public EffecterDef stage2Effecter; /// /// 阶段2音效 /// public SoundDef stage2Sound; // === 击飞配置 === /// /// 击退飞行器定义 /// public ThingDef knockbackFlyerDef; /// /// 飞行效果器 /// public EffecterDef flightEffecterDef; /// /// 落地音效 /// public SoundDef landingSound; // === 调试配置 === /// /// 启用调试日志 /// public bool enableDebugLogging = false; /// /// 启用调试视觉效果 /// public bool enableDebugVisuals = false; /// /// 绘制速度历史图 /// public bool debugDrawSpeedHistory = false; // === 狭窄地形阻挡配置 === /// /// 是否启用狭窄地形阻挡 /// public bool narrowTerrainBlocked = false; /// /// 最高阻挡减益值(0-1之间) /// public float maxBlockPenalty = 0.5f; /// /// 地形检查间隔(ticks) /// public int terrainCheckInterval = 60; /// /// 阻挡减益Hediff定义 /// public HediffDef blockedHediff; public CompProperties_HighSpeedCollision() { compClass = typeof(CompHighSpeedCollision); } } }