202 lines
9.4 KiB
C#
202 lines
9.4 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description: 空准备移动技能
|
||
* @Date: 2026年03月09日
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System.Collections.Generic;
|
||
using Logic.Pool;
|
||
using RuntimeData;
|
||
using TH1_Anim;
|
||
using TH1_Anim.Fragments;
|
||
using TH1_Core.Managers;
|
||
using TH1_Logic.Core;
|
||
using TH1_Presentation.Sequencer.Task;
|
||
using TH1Renderer;
|
||
using UnityEngine;
|
||
|
||
|
||
namespace Logic.Skill
|
||
{
|
||
public partial class UtsuhoReadyMoveSkill : SkillBase
|
||
{
|
||
public UtsuhoReadyMoveSkill()
|
||
{
|
||
IsPermanent = true;
|
||
TurnsLimit = 0;
|
||
Score = 4;
|
||
}
|
||
|
||
public override SkillType GetSkillType()
|
||
{
|
||
return SkillType.UtsuhoReadyMove;
|
||
}
|
||
|
||
// ========== ReadyMove 冲锋完整逻辑 ==========
|
||
//
|
||
// Step 1 - 确定终点:
|
||
// 沿路径从path[1]开始扫描,找到第一个挡路目标:
|
||
// - 盟友/背盟单位 或 盟友/背盟城市中心格 → 终点 = 该格前一格 (按盟友挡路处理)
|
||
// - 敌方单位 → 终点 = 该敌方所在格 (path[i]),标记 hasEnemyAtEnd
|
||
// - 没有遇到任何挡路目标 → 终点 = 路径末尾
|
||
// 截断路径为 path[0..endIndex]
|
||
//
|
||
// Step 2 - 溅射伤害:
|
||
// 从path[1]到终点,对每个路径格子周围1格范围内的敌方单位造成一次溅射伤害
|
||
// 每个敌方单位最多被溅射一次 (HashSet去重)
|
||
//
|
||
// Step 3 - DelayAttack:
|
||
// 如果终点有活着的敌方单位(可能已被溅射杀死),对其造成一次常规DelayAttack伤害
|
||
// - 击杀 → 终点不变,留在目标格子
|
||
// - 未击杀 → 终点回退到前一格 (path[^2]),在路径末尾追加该格
|
||
//
|
||
// Step 4 - 处理终点占位:
|
||
// 如果最终移动终点上有任何单位(骨堆等),将其PassiveMoveAway
|
||
//
|
||
// Step 5 - 执行移动:
|
||
// 播放移动动画 → MoveToLogic → 更新视野 → 刷新迷雾
|
||
//
|
||
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
||
{
|
||
var selfUnit = self as UnitData;
|
||
if (selfUnit == null) return;
|
||
var playerData = selfUnit.Player(mapData);
|
||
if (playerData == null) return;
|
||
if (!selfUnit.GetSkill(SkillType.UtsuhoDelayAct, out var skill)) return;
|
||
var delayActSkill = skill as UtsuhoDelayActSkill;
|
||
if (delayActSkill == null) return;
|
||
// 拷贝path,避免修改原始序列化数据导致主客机不同步
|
||
var path = new List<Vector2Int>(delayActSkill.Path);
|
||
if (path == null || path.Count == 0) return;
|
||
|
||
// 如果单位当前位置和路径起点不一致(例如被PassiveMove推走过),取消本次冲锋
|
||
var currentGrid = selfUnit.Grid(mapData);
|
||
if (currentGrid == null || currentGrid.Pos.V2() != path[0])
|
||
{
|
||
return;
|
||
}
|
||
|
||
// === Step 1: 确定终点 ===
|
||
int endIndex = path.Count - 1;
|
||
bool hasEnemyAtEnd = false;
|
||
bool hasAllyAtEnd = false;
|
||
for (int i = 1; i < path.Count; i++)
|
||
{
|
||
if (!mapData.GridMap.GetGridDataByV2(path[i], out var grid)) continue;
|
||
|
||
// 盟友/刚背盟的城心即使无驻军也算挡路,否则终点落在城心会触发自动宣战
|
||
bool isLeagueCity = mapData.IsLeagueOrJustBreakCityCenter(selfUnit.Id, grid);
|
||
bool hasOtherUnit = grid.RealUnit(mapData, out var unit) && unit.Id != selfUnit.Id;
|
||
if (!hasOtherUnit && !isLeagueCity) continue;
|
||
|
||
bool isAlly = isLeagueCity || (hasOtherUnit && mapData.IsLeagueOrJustBreakByUnit(selfUnit.Id, unit.Id));
|
||
endIndex = i;
|
||
if (isAlly) hasAllyAtEnd = true;
|
||
else hasEnemyAtEnd = true;
|
||
break;
|
||
}
|
||
// 截断路径到 [0..endIndex]
|
||
if (endIndex + 1 < path.Count)
|
||
path.RemoveRange(endIndex + 1, path.Count - (endIndex + 1));
|
||
|
||
//如果友方在终点
|
||
if (hasAllyAtEnd)
|
||
{
|
||
path.Add(path[endIndex - 1]);
|
||
endIndex--;
|
||
}
|
||
|
||
|
||
// === Step 2: 溅射伤害 ===
|
||
bool hasRadiation = selfUnit.GetSkill(SkillType.UtsuhoRadiation, out _);
|
||
if (hasRadiation)
|
||
{
|
||
using var pooledSplashedUnits = THCollectionPool.GetHashSetHandle<uint>(out var splashedUnits);
|
||
for (int i = 1; i < path.Count; i++)
|
||
{
|
||
if (!mapData.GridMap.GetGridDataByV2(path[i], out var pathGrid)) continue;
|
||
var aroundBuf = RentAroundBuf();
|
||
mapData.GridMap.GetAroundGridData(1, 1, pathGrid, aroundBuf);
|
||
foreach (var aroundGrid in aroundBuf)
|
||
{
|
||
if (!aroundGrid.RealUnit(mapData, out var splashTarget)) continue;
|
||
if (splashTarget.Id == selfUnit.Id) continue;
|
||
if (mapData.IsLeagueOrJustBreakByUnit(selfUnit.Id, splashTarget.Id)) continue;
|
||
if (!splashedUnits.Add(splashTarget.Id)) continue;
|
||
|
||
var splashDmg = Table.Instance.CalcDamage(mapData, selfUnit, splashTarget, damagePara: 0.5f);
|
||
var splashGrid = splashTarget.Grid(mapData);
|
||
Main.UnitLogic.DamageSettlement(mapData, selfUnit, splashTarget, splashDmg, DamageType.Splash);
|
||
if (splashGrid != null && splashGrid.InMainSight())
|
||
{
|
||
splashGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage, splashDmg));
|
||
splashGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
|
||
splashTarget.Renderer(mapData)?.InstantUpdateUnit(false);
|
||
if (!splashTarget.IsAlive())
|
||
splashGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
|
||
splashTarget.Renderer(mapData)?.InstantUpdateTryDie();
|
||
splashGrid.Renderer(mapData)?.InstantUpdateGrid();
|
||
}
|
||
}
|
||
ReturnAroundBuf();
|
||
}
|
||
}
|
||
|
||
// === Step 3: DelayAttack ===
|
||
// 终点敌人可能已被溅射杀死,需要再次检查是否仍然活着
|
||
if (hasEnemyAtEnd &&
|
||
mapData.GridMap.GetGridDataByV2(path[^1], out var endGrid) &&
|
||
endGrid.RealUnit(mapData, out var endEnemy) &&
|
||
!mapData.IsLeagueOrJustBreakByUnit(selfUnit.Id, endEnemy.Id) &&
|
||
endEnemy.IsAlive())
|
||
{
|
||
var dmg = Table.Instance.CalcDamage(mapData, selfUnit, endEnemy);
|
||
var targetGrid = endEnemy.Grid(mapData);
|
||
Main.UnitLogic.DamageSettlement(mapData, selfUnit, endEnemy, dmg, DamageType.DelayAttack);
|
||
if (targetGrid != null && targetGrid.InMainSight())
|
||
{
|
||
targetGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage, dmg));
|
||
targetGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
|
||
endEnemy.Renderer(mapData)?.InstantUpdateUnit(false);
|
||
if (!endEnemy.IsAlive())
|
||
targetGrid.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
|
||
endEnemy.Renderer(mapData)?.InstantUpdateTryDie();
|
||
targetGrid.Renderer(mapData)?.InstantUpdateGrid();
|
||
}
|
||
|
||
// 未击杀:终点回退到前一格
|
||
if (endEnemy.IsAlive())
|
||
path.Add(path[^2]);
|
||
}
|
||
|
||
// === Step 4: 处理终点占位 ===
|
||
if (!mapData.GridMap.GetGridDataByV2(path[^1], out var finalGrid)) return;
|
||
if (finalGrid.RealUnit(mapData, out var finalOccupant) && finalOccupant.Id != selfUnit.Id)
|
||
Main.UnitLogic.PassiveMoveAway(mapData, finalOccupant);
|
||
|
||
// === Step 5: 执行移动 ===
|
||
var originGrid = selfUnit.Grid(mapData);
|
||
if (mapData == Main.MapData && originGrid != null)
|
||
{
|
||
bool inSight = Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id)
|
||
|| Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(finalGrid.Id);
|
||
if (inSight && MapRenderer.Instance.ROUnitMap.TryGetValue(selfUnit.Id, out var unitRenderer))
|
||
{
|
||
var data = FragmentDataFactory.Create(FragmentType.Move, unitRenderer, originGrid, finalGrid, path);
|
||
var fragment = FragmentFactory.Create(FragmentType.Move, data);
|
||
PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment));
|
||
}
|
||
}
|
||
|
||
Main.UnitLogic.MoveToLogic(mapData, selfUnit, finalGrid, MoveType.ActiveMove, path);
|
||
|
||
// 沿路径更新视野(FogDisappear fragments会排在Move动画之后自动处理)
|
||
foreach (var pathNode in path)
|
||
playerData.Sight.UpdateSightByPath(selfUnit, playerData, pathNode, mapData);
|
||
}
|
||
}
|
||
}
|
||
|