404 lines
16 KiB
C#
404 lines
16 KiB
C#
using ThreatSource.Utils;
|
||
using ThreatSource.Jammer;
|
||
using ThreatSource.Simulation;
|
||
using ThreatSource.Jammable;
|
||
using ThreatSource.Equipment;
|
||
using System.Diagnostics;
|
||
|
||
namespace ThreatSource.Indicator
|
||
{
|
||
/// <summary>
|
||
/// 指示器抽象基类,实现IIndicator接口的基本功能
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该类提供了指示器的通用实现:
|
||
/// - 位置和姿态管理
|
||
/// - 激活状态控制
|
||
/// - 干扰处理机制
|
||
/// 是具体指示器类的实现基础
|
||
/// </remarks>
|
||
public abstract class BaseIndicator : SimulationElement, IIndicator, IJammable
|
||
{
|
||
/// <summary>
|
||
/// 视觉遮挡阈值 (0.0 - 1.0)
|
||
/// </summary>
|
||
protected const double TargetObscurationThreshold = 0.8;
|
||
|
||
/// <summary>
|
||
/// 干扰处理组件
|
||
/// </summary>
|
||
protected readonly JammableComponent _jammingComponent;
|
||
|
||
/// <summary>
|
||
/// 获取目标是否被烟幕遮挡 (由任何烟幕引起)
|
||
/// </summary>
|
||
protected bool IsTargetObscured { get; private set; } = false;
|
||
|
||
/// <summary>
|
||
/// 最后一次成功获取的目标位置
|
||
/// </summary>
|
||
protected Vector3D? _lastKnownTargetPosition = null;
|
||
|
||
/// <summary>
|
||
/// 最后一次成功获取的目标朝向
|
||
/// </summary>
|
||
protected Orientation? _lastKnownTargetOrientation = null;
|
||
|
||
/// <summary>
|
||
/// 获取设备支持的干扰类型
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 定义指示器可以被哪些类型的干扰影响
|
||
/// 子类需要重写此属性以指定支持的干扰类型
|
||
/// </remarks>
|
||
public abstract IEnumerable<JammingType> SupportedJammingTypes { get; }
|
||
|
||
/// <summary>
|
||
/// 获取设备当前是否处于被干扰状态
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// true表示指示器当前受到干扰影响
|
||
/// false表示指示器正常工作
|
||
/// </remarks>
|
||
public bool IsJammed => _jammingComponent.IsJammed;
|
||
|
||
/// <summary>
|
||
/// 获取或设置目标ID
|
||
/// </summary>
|
||
public string? TargetId { get; protected set; }
|
||
|
||
/// <summary>
|
||
/// 获取或设置导弹ID
|
||
/// </summary>
|
||
public string? MissileId { get; protected set; }
|
||
|
||
/// <summary>
|
||
/// 初始化指示器基类的新实例
|
||
/// </summary>
|
||
/// <param name="id">指示器ID</param>
|
||
/// <param name="motionParameters">初始运动参数</param>
|
||
/// <param name="manager">仿真管理器实例</param>
|
||
/// <remarks>
|
||
/// 构造过程:
|
||
/// - 调用基类构造器
|
||
/// - 初始化基本属性
|
||
/// - 初始化干扰处理组件
|
||
/// </remarks>
|
||
protected BaseIndicator(string id, MotionParameters motionParameters, ISimulationManager manager)
|
||
: base(id, motionParameters, manager)
|
||
{
|
||
_jammingComponent = new JammableComponent(
|
||
positionProvider: () => base.Position
|
||
);
|
||
|
||
_jammingComponent.JammingApplied += HandleJammingApplied;
|
||
_jammingComponent.JammingCleared += HandleJammingCleared;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化干扰阈值和支持的干扰类型
|
||
/// </summary>
|
||
/// <param name="jammingResistanceThreshold">干扰抗性阈值,单位:瓦特</param>
|
||
/// <param name="supportedTypes">支持的干扰类型</param>
|
||
/// <remarks>
|
||
/// 使用此方法初始化指示器的干扰处理参数:
|
||
/// - 设置干扰抗性阈值
|
||
/// - 配置支持的干扰类型
|
||
/// 子类应在构造函数中调用此方法
|
||
/// </remarks>
|
||
protected void InitializeJamming(double jammingResistanceThreshold, IEnumerable<JammingType> supportedTypes)
|
||
{
|
||
_jammingComponent.LoadJammingConfigFromThreshold(jammingResistanceThreshold, supportedTypes);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用干扰
|
||
/// </summary>
|
||
/// <param name="parameters">干扰参数</param>
|
||
/// <remarks>
|
||
/// 处理对指示器的干扰影响
|
||
/// 只有支持的干扰类型才会被处理
|
||
/// </remarks>
|
||
public virtual void ApplyJamming(JammingParameters parameters)
|
||
{
|
||
_jammingComponent.ApplyJamming(parameters);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除干扰
|
||
/// </summary>
|
||
/// <param name="type">要清除的干扰类型</param>
|
||
/// <remarks>
|
||
/// 移除特定类型的干扰影响
|
||
/// 恢复指示器的正常工作状态
|
||
/// </remarks>
|
||
public virtual void ClearJamming(JammingType type)
|
||
{
|
||
_jammingComponent.ClearJamming(type);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理指示器被干扰的事件
|
||
/// </summary>
|
||
/// <param name="parameters">干扰参数</param>
|
||
protected virtual void HandleJammingApplied(JammingParameters parameters)
|
||
{
|
||
// 如果是烟幕干扰,重新计算遮挡状态
|
||
if (parameters.Type == JammingType.SmokeScreen)
|
||
{
|
||
RecalculateObscurationStatus();
|
||
Debug.WriteLine($"[BaseIndicator] 烟幕状态更新,IsTargetObscured: {IsTargetObscured}", "Jamming");
|
||
}
|
||
else // 其他干扰类型打印通用消息
|
||
{
|
||
// 子类可以重写此方法以实现特定的干扰响应
|
||
Debug.WriteLine($"[BaseIndicator] {this.GetType().Name} {Id} 受到 {parameters.Type} 类型干扰,功率:{parameters.Power}W", "Jamming");
|
||
}
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理指示器干扰被清除的事件
|
||
/// </summary>
|
||
/// <param name="type">被清除的干扰类型</param>
|
||
protected virtual void HandleJammingCleared(JammingType type)
|
||
{
|
||
// 如果是烟幕干扰,重新计算遮挡状态
|
||
if (type == JammingType.SmokeScreen)
|
||
{
|
||
RecalculateObscurationStatus();
|
||
Debug.WriteLine($"[BaseIndicator] 烟幕状态更新,IsTargetObscured: {IsTargetObscured}", "Jamming");
|
||
}
|
||
else // 其他干扰类型打印通用消息
|
||
{
|
||
// 子类可以重写此方法以实现干扰清除后的特定行为
|
||
Debug.WriteLine($"[BaseIndicator] {this.GetType().Name} {Id} {type} 类型干扰已清除", "Jamming");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 激活指示器 (基类统一处理干扰事件订阅)
|
||
/// </summary>
|
||
public override void Activate()
|
||
{
|
||
if (!IsActive)
|
||
{
|
||
IsActive = true;
|
||
// 统一订阅 JammingEvent
|
||
SimulationManager.SubscribeToEvent<JammingEvent>(HandleJammingEvent);
|
||
// 子类仍需在此处订阅其他特定事件(如导弹热源事件)
|
||
}
|
||
base.Activate(); // 调用 SimulationElement 的基类 Activate
|
||
// 子类需要在此之后调用 base.Activate() 并订阅烟幕事件 <--- 旧注释,移除或更新
|
||
// 现在基类处理通用 JammingEvent,子类只需要处理非干扰事件
|
||
RecalculateObscurationStatus(); // 激活时检查一次初始遮挡状态
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停用指示器 (基类统一处理干扰事件取消订阅)
|
||
/// </summary>
|
||
public override void Deactivate()
|
||
{
|
||
if (IsActive)
|
||
{
|
||
IsActive = false;
|
||
// 统一取消订阅 JammingEvent
|
||
SimulationManager.UnsubscribeFromEvent<JammingEvent>(HandleJammingEvent);
|
||
// 子类仍需在此处取消订阅其他特定事件
|
||
}
|
||
// 子类需要在此之前取消订阅烟幕事件 <--- 旧注释,移除或更新
|
||
IsTargetObscured = false;
|
||
// 重置最后目标状态
|
||
_lastKnownTargetPosition = null;
|
||
_lastKnownTargetOrientation = null;
|
||
base.Deactivate(); // 调用 SimulationElement 的基类 Deactivate
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新指示器状态
|
||
/// </summary>
|
||
public override void Update(double deltaTime)
|
||
{
|
||
_jammingComponent.UpdateJammingStatus(deltaTime); // 更新干扰状态(包括持续时间等)
|
||
|
||
if (IsActive)
|
||
{
|
||
// 只有未被电子干扰时才更新指示器特定功能
|
||
// 烟幕遮挡 (IsTargetObscured) 在 UpdateIndicator 内部处理
|
||
if (!IsJammed)
|
||
{
|
||
UpdateIndicator(deltaTime);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新指示器特定功能
|
||
/// </summary>
|
||
/// <param name="deltaTime">时间步长,单位:秒</param>
|
||
/// <remarks>
|
||
/// 子类需要重写此方法以实现特定的更新逻辑
|
||
/// 只有在未被干扰时才应执行正常的功能逻辑
|
||
/// </remarks>
|
||
protected virtual void UpdateIndicator(double deltaTime)
|
||
{
|
||
// 子类中实现具体的指示器更新逻辑
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指示器运行状态
|
||
/// </summary>
|
||
/// <returns>包含指示器完整状态信息的结构体</returns>
|
||
/// <remarks>
|
||
/// 子类需要重写此方法以提供完整的状态信息
|
||
/// 包括位置、姿态、目标关联和干扰状态等
|
||
/// </remarks>
|
||
public abstract IndicatorRunningState GetRunningState();
|
||
|
||
/// <summary>
|
||
/// 获取指示器状态信息
|
||
/// </summary>
|
||
/// <returns>包含指示器状态信息的字符串</returns>
|
||
public override string GetStatus()
|
||
{
|
||
return $"指示器 {Id} 当前状态: 上次目标位置: {_lastKnownTargetPosition}, 上次目标朝向: {_lastKnownTargetOrientation}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重新计算目标是否被任何活动的烟幕遮挡,并更新 IsTargetObscured 状态。
|
||
/// 子类应在处理烟幕相关事件时调用此方法。
|
||
/// </summary>
|
||
protected void RecalculateObscurationStatus()
|
||
{
|
||
bool currentlyObscured = CheckIfTargetIsObscured(out BaseEquipment? currentTarget);
|
||
|
||
if (IsTargetObscured != currentlyObscured)
|
||
{
|
||
IsTargetObscured = currentlyObscured;
|
||
}
|
||
|
||
// 仅当目标被看到 (未遮挡) 且目标有效时,才更新最后已知状态
|
||
if (!currentlyObscured && currentTarget != null)
|
||
{
|
||
_lastKnownTargetPosition = currentTarget.Position;
|
||
_lastKnownTargetOrientation = currentTarget.Orientation;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查当前目标是否被活动烟幕遮挡。
|
||
/// </summary>
|
||
/// <param name="targetFound">如果成功找到目标实体,则输出该实体。</param>
|
||
/// <returns>如果目标被遮挡,则返回 true;否则返回 false。</returns>
|
||
private bool CheckIfTargetIsObscured(out BaseEquipment? targetFound)
|
||
{
|
||
targetFound = null;
|
||
|
||
// 1. 检查指示器状态和目标ID
|
||
if (!IsActive || string.IsNullOrEmpty(TargetId))
|
||
{
|
||
return false; // 未激活或无目标,视为未遮挡
|
||
}
|
||
|
||
// 2. 尝试获取目标实体
|
||
targetFound = SimulationManager.GetEntityById(TargetId) as BaseEquipment;
|
||
if (targetFound == null)
|
||
{
|
||
return false; // 找不到目标实体,视为未遮挡
|
||
}
|
||
|
||
// 3. 获取活动的烟幕
|
||
var activeSmokeGrenades = SimulationManager.GetEntitiesByType<SmokeGrenade>()
|
||
.Where(sg => sg != null && sg.IsActive)
|
||
.ToList(); // 获取列表以便检查是否为空
|
||
|
||
if (!activeSmokeGrenades.Any())
|
||
{
|
||
return false; // 没有活动的烟幕,视为未遮挡
|
||
}
|
||
|
||
// 4. 准备计算所需数据
|
||
Vector3D observerPos = Position;
|
||
Vector3D targetCenter = targetFound.Position;
|
||
Orientation targetOrient = targetFound.Orientation;
|
||
Vector3D targetDims = new(targetFound.Properties.Width, targetFound.Properties.Height, targetFound.Properties.Length);
|
||
|
||
// 5. 遍历烟幕检查遮挡
|
||
foreach (var smokeGrenade in activeSmokeGrenades)
|
||
{
|
||
try
|
||
{
|
||
Vector3D smokeCenter = smokeGrenade.Position;
|
||
Orientation smokeOrient = smokeGrenade.Orientation;
|
||
Vector3D smokeDims;
|
||
if (smokeGrenade.config.SmokeType == SmokeScreenType.Cloud)
|
||
{
|
||
smokeDims = new(smokeGrenade.config.Thickness, smokeGrenade.config.CloudDiameter, smokeGrenade.config.CloudDiameter);
|
||
}
|
||
else // Wall
|
||
{
|
||
smokeDims = new(smokeGrenade.config.Thickness, smokeGrenade.config.WallHeight, smokeGrenade.config.WallWidth);
|
||
}
|
||
|
||
double ratio = ObscurationUtils.CalculateProjectedOverlapRatio(
|
||
observerPos,
|
||
smokeCenter, smokeDims, smokeOrient,
|
||
targetCenter, targetDims, targetOrient
|
||
);
|
||
|
||
// 只要有一个烟幕满足遮挡条件,即可判定为遮挡
|
||
if (ratio >= TargetObscurationThreshold)
|
||
{
|
||
return true; // 被遮挡
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"检查烟幕遮挡状态时发生错误: {ex.Message}");
|
||
// 在发生错误时,可以选择继续检查其他烟幕,或者直接返回一个默认值(例如 false)
|
||
}
|
||
}
|
||
|
||
// 6. 如果遍历完所有烟幕都没有达到遮挡阈值
|
||
return false; // 未被遮挡
|
||
}
|
||
|
||
// --- 新增干扰事件处理逻辑 ---
|
||
|
||
/// <summary>
|
||
/// 统一处理干扰事件
|
||
/// </summary>
|
||
/// <param name="evt">干扰事件</param>
|
||
protected virtual void HandleJammingEvent(JammingEvent evt)
|
||
{
|
||
if (evt == null) return;
|
||
|
||
// 在应用干扰前检查是否应该处理此干扰
|
||
if (!ShouldHandleJamming(evt.Parameters))
|
||
{
|
||
// Debug.WriteLine($"[BaseIndicator] {Id} 忽略干扰事件,类型: {evt.Parameters.Type}");
|
||
return;
|
||
}
|
||
|
||
// 如果应该处理,则应用干扰
|
||
ApplyJamming(evt.Parameters);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断是否应该处理传入的干扰参数(可被子类重写以添加特定检查)
|
||
/// </summary>
|
||
/// <param name="parameters">干扰参数</param>
|
||
/// <returns>如果应该处理则返回 true,否则返回 false</returns>
|
||
protected virtual bool ShouldHandleJamming(JammingParameters parameters)
|
||
{
|
||
// 基类默认处理所有受支持类型的干扰 (包括烟幕)
|
||
// 子类可以重写此方法添加额外检查,如波段匹配
|
||
// 注意:烟幕干扰虽然通过这里,但主要效果通过 IsTargetObscured 和 RecalculateObscurationStatus 体现
|
||
bool supported = SupportedJammingTypes.Contains(parameters.Type);
|
||
// if(!supported) Debug.WriteLine($"[BaseIndicator] {Id} 不支持干扰类型: {parameters.Type}");
|
||
return supported;
|
||
}
|
||
|
||
// --- 结束新增干扰事件处理逻辑 ---
|
||
}
|
||
} |