This commit is contained in:
2026-03-03 17:30:02 +08:00
parent e1dea4059c
commit 14c3654846
10 changed files with 711 additions and 86 deletions

View File

@@ -0,0 +1,115 @@
using RimWorld;
using Verse;
using UnityEngine;
namespace WulaFallenEmpire
{
/// <summary>
/// 地形阻挡Hediff组件用于调整移动速度
/// </summary>
public class HediffComp_TerrainBlocked : HediffComp
{
public HediffCompProperties_TerrainBlocked Props => (HediffCompProperties_TerrainBlocked)props;
/// <summary>
/// 当前地形阻挡严重度0-1之间
/// </summary>
public float currentBlockSeverity = 0f;
/// <summary>
/// 上一次更新时间ticks
/// </summary>
private int lastUpdateTick = -1;
public override void CompPostTick(ref float severityAdjustment)
{
base.CompPostTick(ref severityAdjustment);
// 降低更新频率每Props.checkIntervalTicks检查一次
if (Find.TickManager.TicksGame % Props.checkIntervalTicks != 0)
return;
// 如果Pawn已死亡或无法移动清除效果
if (Pawn == null || Pawn.Dead || Pawn.Downed || !Pawn.Spawned)
{
currentBlockSeverity = 0f;
severityAdjustment = 0f;
return;
}
// 获取CompHighSpeedCollision组件
var collisionComp = Pawn.GetComp<CompHighSpeedCollision>();
if (collisionComp == null)
{
currentBlockSeverity = 0f;
severityAdjustment = 0f;
return;
}
// 获取当前阻挡严重度
float newSeverity = collisionComp.GetCurrentTerrainBlockSeverity();
// 立即设置阻挡严重度(移除平滑过渡)
currentBlockSeverity = newSeverity;
// 更新Hediff严重度立即变化
severityAdjustment = currentBlockSeverity - Pawn.health.hediffSet.GetFirstHediffOfDef(parent.def).Severity;
lastUpdateTick = Find.TickManager.TicksGame;
}
/// <summary>
/// 获取移动速度乘数
/// </summary>
public float GetMoveSpeedMultiplier()
{
if (currentBlockSeverity <= 0f)
return 1f;
// 应用严重度对应的速度惩罚(线性)
return 1f - currentBlockSeverity * Props.maxSpeedPenalty;
}
public override string CompTipStringExtra
{
get
{
if (currentBlockSeverity > 0.01f)
{
float speedMultiplier = GetMoveSpeedMultiplier();
float speedPenalty = (1f - speedMultiplier) * 100f;
return $"地形阻挡: {currentBlockSeverity:P0}\n移动速度: -{speedPenalty:F0}%";
}
return null;
}
}
public override void CompExposeData()
{
base.CompExposeData();
Scribe_Values.Look(ref currentBlockSeverity, "currentBlockSeverity", 0f);
Scribe_Values.Look(ref lastUpdateTick, "lastUpdateTick", -1);
}
}
/// <summary>
/// 地形阻挡Hediff组件属性
/// </summary>
public class HediffCompProperties_TerrainBlocked : HediffCompProperties
{
/// <summary>
/// 最大速度惩罚0-1之间
/// </summary>
public float maxSpeedPenalty = 0.5f;
/// <summary>
/// 检查间隔ticks - 降低判断频率
/// </summary>
public int checkIntervalTicks = 60;
public HediffCompProperties_TerrainBlocked()
{
compClass = typeof(HediffComp_TerrainBlocked);
}
}
}

View File

@@ -36,7 +36,11 @@ namespace WulaFallenEmpire
// === 缓存 ===
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)
@@ -53,29 +57,169 @@ namespace WulaFallenEmpire
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)
return;
// 检查是否死亡或不能移动
if (!CanMove(pawn))
{
ResetToStage0();
return;
}
// 每帧更新
ProcessFrame(pawn);
// 地形阻挡检查(降低频率)
if (Props.narrowTerrainBlocked &&
(Find.TickManager.TicksGame - lastTerrainCheckTick >= Props.terrainCheckInterval))
{
UpdateTerrainBlockStatus(pawn);
lastTerrainCheckTick = Find.TickManager.TicksGame;
}
}
/// <summary>
/// 更新地形阻挡状态
/// </summary>
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}");
}
}
/// <summary>
/// 检查单元格是否被不可通行建筑占据
/// </summary>
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;
}
/// <summary>
/// 计算阻挡严重度
/// </summary>
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;
}
/// <summary>
/// 平滑过渡
/// </summary>
private float SmoothTransition(float current, float target, float speed)
{
if (Mathf.Abs(current - target) < 0.01f)
return target;
return Mathf.Lerp(current, target, speed);
}
/// <summary>
/// 更新阻挡Hediff
/// </summary>
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;
}
/// <summary>
/// 获取当前地形阻挡严重度供HediffComp使用
/// </summary>
public float GetCurrentTerrainBlockSeverity()
{
return currentTerrainBlockSeverity;
}
/// <summary>
/// 处理每帧逻辑
/// </summary>
@@ -846,7 +990,28 @@ namespace WulaFallenEmpire
/// 绘制速度历史图
/// </summary>
public bool debugDrawSpeedHistory = false;
// === 狭窄地形阻挡配置 ===
/// <summary>
/// 是否启用狭窄地形阻挡
/// </summary>
public bool narrowTerrainBlocked = false;
/// <summary>
/// 最高阻挡减益值0-1之间
/// </summary>
public float maxBlockPenalty = 0.5f;
/// <summary>
/// 地形检查间隔ticks
/// </summary>
public int terrainCheckInterval = 60;
/// <summary>
/// 阻挡减益Hediff定义
/// </summary>
public HediffDef blockedHediff;
public CompProperties_HighSpeedCollision()
{
compClass = typeof(CompHighSpeedCollision);

View File

@@ -48,9 +48,9 @@ namespace WulaFallenEmpire
private int lastAttackTargetTick;
// 集中火力目标
private static LocalTargetInfo focusTarget = LocalTargetInfo.Invalid;
private static int lastFocusSetTick = 0;
private static Thing lastFocusPawn = null;
public static LocalTargetInfo focusTarget = LocalTargetInfo.Invalid;
public static int lastFocusSetTick = 0;
public static Thing lastFocusPawn = null;
// Gizmo 缓存
private Command_Toggle cachedGizmo;
@@ -278,8 +278,8 @@ namespace WulaFallenEmpire
}
}
}
private void TryAcquireTarget()
public void TryAcquireTarget()
{
// 1. 首先检查是否有集中火力目标且可以对其开火
if (focusTarget.IsValid && focusTarget.Thing != null && focusTarget.Thing.Spawned)
@@ -690,7 +690,7 @@ namespace WulaFallenEmpire
if (IsMasterTurret)
{
// 如果有集中火力目标,添加清除按钮
if (focusTarget.IsValid && lastFocusPawn == parent)
if (focusTarget.IsValid && focusTarget != null && lastFocusPawn == parent)
{
cachedFocusGizmo.defaultLabel = "Wula_ClearFocus".Translate();
cachedFocusGizmo.defaultDesc = "Wula_ClearFocusDesc".Translate();
@@ -713,7 +713,7 @@ namespace WulaFallenEmpire
private void ShowTargetSelectMenu()
{
// 如果已经有集中火力目标,清除它
if (focusTarget.IsValid && lastFocusPawn == parent)
if (focusTarget.IsValid && focusTarget != null && lastFocusPawn == parent)
{
focusTarget = LocalTargetInfo.Invalid;
return;

View File

@@ -0,0 +1,237 @@
// File: Verb_RangeChecker.cs
using RimWorld;
using System.Collections.Generic;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
/// <summary>
/// 用于距离判断的Verb不发射任何射弹不造成伤害仅用于距离计算和AI判断
/// 当发射成功时会设置Pawn身上所有Comp_MultiTurretGun的focusTarget为目标
/// </summary>
public class Verb_RangeChecker : Verb_LaunchProjectile
{
protected override bool TryCastShot()
{
if (currentTarget.HasThing && currentTarget.Thing.Map != caster.Map)
{
return false;
}
ThingDef projectile = Projectile;
if (projectile == null)
{
return false;
}
ShootLine resultingLine;
bool flag = TryFindShootLineFromTo(caster.Position, currentTarget, out resultingLine);
if (verbProps.stopBurstWithoutLos && !flag)
{
return false;
}
if (base.EquipmentSource != null)
{
base.EquipmentSource.GetComp<CompChangeableProjectile>()?.Notify_ProjectileLaunched();
base.EquipmentSource.GetComp<CompApparelVerbOwner_Charged>()?.UsedOnce();
}
lastShotTick = Find.TickManager.TicksGame;
Thing manningPawn = caster;
Thing equipmentSource = base.EquipmentSource;
CompMannable compMannable = caster.TryGetComp<CompMannable>();
if (compMannable?.ManningPawn != null)
{
manningPawn = compMannable.ManningPawn;
equipmentSource = caster;
}
Vector3 drawPos = caster.DrawPos;
Projectile projectile2 = (Projectile)GenSpawn.Spawn(projectile, resultingLine.Source, caster.Map);
if (equipmentSource.TryGetComp(out CompUniqueWeapon comp))
{
foreach (WeaponTraitDef item in comp.TraitsListForReading)
{
if (item.damageDefOverride != null)
{
projectile2.damageDefOverride = item.damageDefOverride;
}
if (!item.extraDamages.NullOrEmpty())
{
Projectile projectile3 = projectile2;
if (projectile3.extraDamages == null)
{
projectile3.extraDamages = new List<ExtraDamage>();
}
projectile2.extraDamages.AddRange(item.extraDamages);
}
}
}
if (verbProps.ForcedMissRadius > 0.5f)
{
float num = verbProps.ForcedMissRadius;
if (manningPawn is Pawn pawn)
{
num *= verbProps.GetForceMissFactorFor(equipmentSource, pawn);
}
float num2 = VerbUtility.CalculateAdjustedForcedMiss(num, currentTarget.Cell - caster.Position);
if (num2 > 0.5f)
{
IntVec3 forcedMissTarget = GetForcedMissTarget(num2);
if (forcedMissTarget != currentTarget.Cell)
{
ProjectileHitFlags projectileHitFlags = ProjectileHitFlags.NonTargetWorld;
if (Rand.Chance(0.5f))
{
projectileHitFlags = ProjectileHitFlags.All;
}
if (!canHitNonTargetPawnsNow)
{
projectileHitFlags &= ~ProjectileHitFlags.NonTargetPawns;
}
// 模拟发射成功,但不实际发射
bool shotResult = SimulateShotSuccess(drawPos, forcedMissTarget, currentTarget, projectileHitFlags, preventFriendlyFire, equipmentSource);
if (shotResult)
{
UpdateTurretFocusTargets();
}
return shotResult;
}
}
}
ShotReport shotReport = ShotReport.HitReportFor(caster, this, currentTarget);
Thing randomCoverToMissInto = shotReport.GetRandomCoverToMissInto();
ThingDef targetCoverDef = randomCoverToMissInto?.def;
if (verbProps.canGoWild && !Rand.Chance(shotReport.AimOnTargetChance_IgnoringPosture))
{
bool flyOverhead = projectile2?.def?.projectile != null && projectile2.def.projectile.flyOverhead;
resultingLine.ChangeDestToMissWild(shotReport.AimOnTargetChance_StandardTarget, flyOverhead, caster.Map);
ProjectileHitFlags projectileHitFlags2 = ProjectileHitFlags.NonTargetWorld;
if (Rand.Chance(0.5f) && canHitNonTargetPawnsNow)
{
projectileHitFlags2 |= ProjectileHitFlags.NonTargetPawns;
}
// 模拟发射成功,但不实际发射
bool shotResult = SimulateShotSuccess(drawPos, resultingLine.Dest, currentTarget, projectileHitFlags2, preventFriendlyFire, equipmentSource, targetCoverDef);
if (shotResult)
{
UpdateTurretFocusTargets();
}
return shotResult;
}
if (currentTarget.Thing != null && currentTarget.Thing.def.CanBenefitFromCover && !Rand.Chance(shotReport.PassCoverChance))
{
ProjectileHitFlags projectileHitFlags3 = ProjectileHitFlags.NonTargetWorld;
if (canHitNonTargetPawnsNow)
{
projectileHitFlags3 |= ProjectileHitFlags.NonTargetPawns;
}
// 模拟发射成功,但不实际发射
bool shotResult = SimulateShotSuccess(drawPos, randomCoverToMissInto, currentTarget, projectileHitFlags3, preventFriendlyFire, equipmentSource, targetCoverDef);
if (shotResult)
{
UpdateTurretFocusTargets();
}
return shotResult;
}
ProjectileHitFlags projectileHitFlags4 = ProjectileHitFlags.IntendedTarget;
if (canHitNonTargetPawnsNow)
{
projectileHitFlags4 |= ProjectileHitFlags.NonTargetPawns;
}
if (!currentTarget.HasThing || currentTarget.Thing.def.Fillage == FillCategory.Full)
{
projectileHitFlags4 |= ProjectileHitFlags.NonTargetWorld;
}
// 模拟发射成功,但不实际发射
bool finalShotResult = SimulateFinalShotSuccess(drawPos, resultingLine.Dest, currentTarget, projectileHitFlags4, preventFriendlyFire, equipmentSource, targetCoverDef);
if (finalShotResult)
{
UpdateTurretFocusTargets();
}
return finalShotResult;
}
/// <summary>
/// 模拟射击成功的情况
/// </summary>
private bool SimulateShotSuccess(Vector3 drawPos, IntVec3 targetCell, LocalTargetInfo target, ProjectileHitFlags hitFlags, bool preventFriendlyFire, Thing equipmentSource, ThingDef targetCoverDef = null)
{
// 这里不实际发射射弹,只返回成功
// 销毁之前创建的射弹对象,因为我们不需要它
return true;
}
/// <summary>
/// 模拟射击成功的情况(带目标)
/// </summary>
private bool SimulateShotSuccess(Vector3 drawPos, Thing target, LocalTargetInfo originalTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire, Thing equipmentSource, ThingDef targetCoverDef = null)
{
// 这里不实际发射射弹,只返回成功
// 销毁之前创建的射弹对象,因为我们不需要它
return true;
}
/// <summary>
/// 模拟最终射击成功的情况
/// </summary>
private bool SimulateFinalShotSuccess(Vector3 drawPos, IntVec3 targetCell, LocalTargetInfo target, ProjectileHitFlags hitFlags, bool preventFriendlyFire, Thing equipmentSource, ThingDef targetCoverDef = null)
{
// 这里不实际发射射弹,只返回成功
// 销毁之前创建的射弹对象,因为我们不需要它
return true;
}
/// <summary>
/// 更新Pawn身上所有Comp_MultiTurretGun的focusTarget
/// </summary>
private void UpdateTurretFocusTargets()
{
if (caster is Pawn pawn && pawn.Spawned)
{
// 获取Pawn身上所有的Comp_MultiTurretGun组件
var turretComps = pawn.GetComps<Comp_MultiTurretGun>();
foreach (var turretComp in turretComps)
{
// 设置集中火力目标
Comp_MultiTurretGun.focusTarget = currentTarget;
Comp_MultiTurretGun.lastFocusSetTick = Find.TickManager.TicksGame;
Comp_MultiTurretGun.lastFocusPawn = pawn;
// 强制炮塔立即重新索敌,以便它们能检测到新的集中火力目标
turretComp.TryAcquireTarget();
}
}
}
}
/// <summary>
/// 用于距离判断的Verb属性
/// </summary>
public class VerbProperties_RangeChecker : VerbProperties
{
public VerbProperties_RangeChecker()
{
verbClass = typeof(Verb_RangeChecker);
// 默认设置为不发射射弹
defaultProjectile = null;
}
}
}

View File

@@ -97,6 +97,7 @@
<Compile Include="HediffComp\WULA_RegenerateBackstory\HediffComp_RegenerateBackstory.cs" />
<Compile Include="HediffComp\WULA_SwitchableHediff\HediffCompProperties_SwitchableHediff.cs" />
<Compile Include="HediffComp\WULA_SyncedWithMech\HediffCompProperties_SyncedWithMech.cs" />
<Compile Include="HediffComp\WULA_TerrainBlocked\HediffComp_TerrainBlocked.cs" />
<Compile Include="HediffComp\WULA_TimedExplosion\HediffComp_TimedExplosion.cs" />
<Compile Include="ITab\ITab_MechSkills.cs" />
<Compile Include="MentalState\MentalState_MechNoPilot.cs" />
@@ -105,6 +106,7 @@
<Compile Include="Pawn_Comps\MechCrewHolder\CompProperties_MechCrewHolder.cs" />
<Compile Include="Pawn_Comps\PawnRenderExtra\Comp_PawnRenderExtra.cs" />
<Compile Include="Pawn_Comps\HighSpeedCollision\CompHighSpeedCollision.cs" />
<Compile Include="Verb\Verb_RangeChecker.cs" />
<Compile Include="Verb\Verb_TurretOffestShoot.cs" />
<Compile Include="Work\BoardMech\FloatMenuOptionProvider_BoardMech.cs" />
<Compile Include="Work\BoardMech\JobDriver_BoardMech.cs" />