406 lines
19 KiB
C#
406 lines
19 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description: 攻击友军行为逻辑类
|
||
* @Date: 2025年04月10日 星期四 11:04:44
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System;
|
||
using Logic.AI;
|
||
using Logic.Audio;
|
||
using Logic.CrashSight;
|
||
using Logic.Skill;
|
||
using RuntimeData;
|
||
using TH1_Anim;
|
||
using TH1_Anim.Fragments;
|
||
using TH1_Core.Events;
|
||
using TH1_Core.Managers;
|
||
using TH1_Logic.Core;
|
||
using TH1_Presentation.Sequencer.Task;
|
||
using TH1_Renderer;
|
||
using TH1Renderer;
|
||
using UnityEngine;
|
||
|
||
|
||
//这里是所有BuildAction派生子类的实现模块
|
||
namespace Logic.Action
|
||
{
|
||
public class UnitAttackAllyAction : ActionLogicBase
|
||
{
|
||
public UnitAttackAllyAction(CommonActionId id) : base(id)
|
||
{
|
||
}
|
||
|
||
protected override bool Execute(CommonActionParams actionParams)
|
||
{
|
||
//Step #0 为anim做数据准备
|
||
GridData originGrid = null;
|
||
GridData targetGrid = null;
|
||
UnitRenderer originUnitRenderer = null;
|
||
UnitRenderer targetUnitRenderer = null;
|
||
|
||
//如果不是AI预测相关的行为
|
||
if (actionParams.MapData == Main.MapData)
|
||
{
|
||
originGrid = actionParams.UnitData.Grid(Main.MapData);
|
||
targetGrid = actionParams.TargetUnitData.Grid(Main.MapData);
|
||
MapRenderer.Instance.ROUnitMap.TryGetValue(actionParams.UnitData.Id, out originUnitRenderer);
|
||
MapRenderer.Instance.ROUnitMap.TryGetValue(actionParams.TargetUnitData.Id, out targetUnitRenderer);
|
||
}
|
||
|
||
|
||
//Step #1 处理所有攻击友军的逻辑
|
||
UnitData unit1 = actionParams.UnitData;
|
||
UnitData unit2 = actionParams.TargetUnitData;
|
||
CityData city1 = unit1.City(actionParams.MapData);
|
||
MapData mapData = actionParams.MapData;
|
||
var originAliveBeforeAttackAlly = unit1.IsAlive();
|
||
var targetAliveBeforeAttackAlly = unit2.IsAlive();
|
||
|
||
SkillType animSkillData = SkillType.NONE;
|
||
|
||
bool NotProjectile = false;
|
||
bool unit1Die = false;
|
||
|
||
//========== 【新增】AttackAlly 生命周期版本 ==========
|
||
bool handledByLifecycle = false;
|
||
|
||
// 检查是否有技能支持 AttackAlly(通过新生命周期)
|
||
if (unit1.AttackAllyEnable(mapData, unit2))
|
||
{
|
||
// 计算治疗量
|
||
int baseHeal = unit1.AttackAllyBaseHeal(mapData, unit2);
|
||
int healAddition = unit1.AttackAllyHealAddition(mapData, unit2);
|
||
int finalHeal = baseHeal + healAddition;
|
||
|
||
if (finalHeal > 0)
|
||
{
|
||
// 获取动画技能类型(第一个支持AttackAlly的技能)
|
||
var isFrozen = unit1.IsFrozen();
|
||
foreach (var skill in unit1.Skills)
|
||
{
|
||
if (isFrozen && unit1.IsSkillFrozenFilter(skill)) continue;
|
||
if (skill.AttackAllyEnable(mapData, unit1, unit2))
|
||
{
|
||
animSkillData = skill.GetSkillType();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 新流程同样需要消耗所有行动点
|
||
unit1.ClearActionPoint();
|
||
|
||
// 执行治疗(走新版RecoverHealth,内部会触发生命周期:OnAttackAllyJustBeforeHeal -> AddHealth -> OnAttackAllyAfterHeal)
|
||
Main.UnitLogic.RecoverHealth(mapData, unit1, unit2, finalHeal, HealType.AttackAllyHeal);
|
||
|
||
handledByLifecycle = true;
|
||
}
|
||
else
|
||
{
|
||
var lifecycleNotProjectile = unit1.AttackAllyIsNotProjectile(mapData, unit2);
|
||
if (unit1.AttackAllyExecute(mapData, unit2, out var executeSkillData))
|
||
{
|
||
animSkillData = executeSkillData;
|
||
NotProjectile = lifecycleNotProjectile;
|
||
if (unit1.GetActionPoint(ActionPointType.Attack) > 0)
|
||
unit1.ClearActionPoint();
|
||
handledByLifecycle = true;
|
||
}
|
||
}
|
||
|
||
if (handledByLifecycle)
|
||
{
|
||
// 处理自身伤害(如Koakuma的治疗代价)
|
||
int selfDamage = unit1.AttackAllySelfDamage(mapData, unit2);
|
||
if (selfDamage > 0)
|
||
{
|
||
var damageInfo = Main.UnitLogic.DamageSettlement(mapData, unit1, unit1, selfDamage, DamageType.KillSelf);
|
||
if (mapData == Main.MapData)
|
||
{
|
||
unit1.Grid(mapData)?.Renderer(mapData)?.InstantUpdateGrid();
|
||
if (damageInfo.IsKill)
|
||
unit1.Renderer(mapData)?.Die();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果生命周期未处理,走原有的特判逻辑(向后兼容)
|
||
if (!handledByLifecycle)
|
||
{
|
||
//======================================================
|
||
|
||
//处理Eirin的友军攻击
|
||
/*if (unit1.GetSkill(SkillType.EIRINFRENCHATTACK, out var _))
|
||
{
|
||
animSkillData = SkillType.EIRINFRENCHATTACK;
|
||
unit1.ClearActionPoint();
|
||
int recover = 5;
|
||
if (unit1.GetSkill(SkillType.EIRINFRENCHBUFF, out var _) && unit2.TreatedAsHero(actionParams.MapData,unit1))
|
||
recover = 15;
|
||
|
||
var before = unit2.Health;
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, recover);
|
||
unit2.AddSkill_Legacy(SkillType.KAGUYAFRENCHSYNERGY, mapData,false,1,false,-1,false, SpecialAddSkillType.AddTurnLimit,0);
|
||
//如果溢出
|
||
if (unit2.Health - before < recover)
|
||
{
|
||
unit2.AddSkill_Legacy(SkillType.MOVERANGEUP,mapData,false,1,false,-1,false, SpecialAddSkillType.AddTurnLimit,0);
|
||
}
|
||
|
||
//Step #2 处理所有skill的 OnHealOther生命周期
|
||
unit1.OnHealOther(actionParams.MapData,unit2,HealType.AttackAllyHeal);
|
||
}
|
||
else*/
|
||
//处理Kaguya的友军攻击
|
||
if (unit1.GetSkill(SkillType.KAGUYAFRENCHATTACK, out var _) || unit1.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _) )
|
||
{
|
||
animSkillData = SkillType.KAGUYAFRENCHATTACK;
|
||
var lv = unit1.GetSkill(SkillType.KAGUYAFRENCHATTACKPRO, out var _) ? 9 : 1;
|
||
unit1.ClearActionPoint();
|
||
unit2.AddSkill_Legacy(SkillType.KAGUYAFRENCHFOREVERBUFF, mapData,false,1,true,lv,false, SpecialAddSkillType.AddLevel,unit1.Id);
|
||
//if (unit2.GetSkill(SkillType.KAGUYAFRENCHFOREVERBUFF, out var skill))
|
||
//skill.AddLevel(mapData, unit1, unit2, lv);
|
||
|
||
//Step #2 处理所有skill的 OnHealOther生命周期
|
||
unit1.OnHealOther(actionParams.MapData,unit2,HealType.AttackAllyHeal);
|
||
}
|
||
else
|
||
//处理Patchouli的友军施法
|
||
if (unit1.GetSkill(SkillType.PATCHOULIEARTH, out var tskill))
|
||
{
|
||
animSkillData = SkillType.PATCHOULIEARTH;
|
||
var lv = tskill.Level;
|
||
int recover = lv * 5;
|
||
unit1.ClearActionPoint();
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, recover);
|
||
tskill.ReduceLevel(mapData, unit1, lv);
|
||
unit1.HeroTask(mapData)?.OnReduceSkillLevels(mapData,SkillType.PATCHOULIEARTH,(uint)lv);
|
||
//如果同事存在金石,也要消耗掉金石头
|
||
if (unit1.GetSkill(SkillType.PATCHOULIMETAL, out var tskill2))
|
||
{
|
||
var lv2 = tskill2.Level;
|
||
tskill2.ReduceLevel(mapData, unit1, lv2);
|
||
unit1.HeroTask(mapData)?.OnReduceSkillLevels(mapData,SkillType.PATCHOULIMETAL,(uint)lv2);
|
||
}
|
||
|
||
//Step #2 处理所有skill的 OnHealOther生命周期
|
||
unit1.OnHealOther(actionParams.MapData,unit2,HealType.AttackAllyHeal);
|
||
}
|
||
// 【Koakuma友军施法 - 已迁移到新生命周期,见上方 handledByLifecycle 分支】
|
||
//else
|
||
//if (unit1.GetSkill(SkillType.KOAKUMADEVOTION, out var skill))
|
||
//{
|
||
// var t = skill as KoakumaDevotionSkill;
|
||
// animSkillData = SkillType.KOAKUMADEVOTION;
|
||
// int recover = 5;
|
||
// unit1.ClearActionPoint();
|
||
// Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, recover);
|
||
// if (t?.FirstDevotion ?? false)
|
||
// {
|
||
// t.FirstDevotion = false;
|
||
// }
|
||
// unit1.OnHealOther(actionParams.MapData,unit2,HealType.AttackAllyHeal);
|
||
//
|
||
// var grid = unit1.Grid(mapData);
|
||
// var info = Main.UnitLogic.DamageSettlement(mapData,unit1,unit1,5,DamageType.KillSelf);
|
||
// grid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
// if (info.IsKill)
|
||
// unit1.Renderer(mapData)?.Die();
|
||
//}
|
||
else
|
||
//处理SuwakoHebi的合并
|
||
if (unit1.GetSkill(SkillType.SUWAKOHEBI, out var _))
|
||
{
|
||
if (!SuwakoHebiSkill.CanCombineToNextLevel(unit2)) return false;
|
||
NotProjectile = true;
|
||
//unit2.UnitFullType.UnitLevel++;
|
||
var unitFullType = unit2.UnitFullType;
|
||
var health = unit2.Health + unit1.Health;
|
||
unitFullType.UnitLevel++;
|
||
Main.UnitLogic.UnitTypeTransform(mapData,unit2,unitFullType);
|
||
unit2.Health = Mathf.Min(unit2.GetMaxHealth(),health);
|
||
unit1.Grid(actionParams.MapData, out var grid);
|
||
Main.UnitLogic.UnitDie(actionParams.MapData,unit1,0);
|
||
if(grid?.InMainSight()??false)
|
||
grid.Renderer(actionParams.MapData)?.InstantUpdateGrid();
|
||
}
|
||
else
|
||
//处理KomeijiRider献祭给KomeijiKnight:原单位消失,目标恢复5HP,恢复1攻击行动点
|
||
if (unit1.GetSkill(SkillType.KomeijiRiderAdd, out _) && unit2.GetSkill(SkillType.KomeijiKnightAdd, out _))
|
||
{
|
||
NotProjectile = true;
|
||
unit1.Grid(mapData, out var grid);
|
||
Main.UnitLogic.DamageSettlement(mapData, unit1, unit1, unit1.Health, DamageType.KillSelf);
|
||
unit1.Renderer(mapData)?.Die();
|
||
Main.UnitLogic.UnitUnnaturalDie(mapData, unit1);
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, 5);
|
||
unit2.AddActionPoint(ActionPointType.Attack);
|
||
unit2.Renderer(mapData)?.InstantUpdateUnit(false);
|
||
grid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
unit1Die = true;
|
||
}
|
||
else
|
||
//处理拥有CorpseBuff的单位向BonePile喂食(消耗层数,每层回复5点)
|
||
if (unit1.GetSkill(SkillType.CorpseBuff, out var corpseBuffSkill) && unit2.UnitType == UnitType.BonePile)
|
||
{
|
||
NotProjectile = true;
|
||
int layers = corpseBuffSkill.Level;
|
||
int feedHealth = layers * 5;
|
||
corpseBuffSkill.ReduceLevel(mapData, unit1, layers);
|
||
unit1.ClearActionPoint();
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, feedHealth);
|
||
unit1.Grid(mapData, out var grid);
|
||
grid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
}
|
||
else
|
||
//处理古明地非Giant单位向BonePile献祭
|
||
if (unit1.CanFeedBonePile(mapData) && unit2.UnitType == UnitType.BonePile)
|
||
{
|
||
NotProjectile = true;
|
||
int feedHealth = unit1.Health;
|
||
unit1.Grid(mapData, out var grid);
|
||
Main.UnitLogic.DamageSettlement(mapData, unit1, unit1, unit1.Health, DamageType.KillSelf);
|
||
unit1.Renderer(mapData)?.Die();
|
||
Main.UnitLogic.UnitUnnaturalDie(mapData, unit1);
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, feedHealth);
|
||
grid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
unit1Die = true;
|
||
}
|
||
//处理sanae的友军攻击
|
||
if (unit1.GetSkill(SkillType.SANAEWIND, out var _))
|
||
{
|
||
animSkillData = SkillType.SANAEWIND;
|
||
int recover = 4;
|
||
unit1.ClearActionPoint();
|
||
Main.UnitLogic.RecoverHealth_Legacy(mapData, unit1, unit2, recover);
|
||
unit1.OnHealOther(actionParams.MapData,unit2,HealType.AttackAllyHeal);
|
||
|
||
}
|
||
|
||
//========== 【新增】向后兼容分支闭合 ==========
|
||
}
|
||
//================================================
|
||
|
||
//Step #3 处理动画
|
||
var originKilledByAttackAlly = originAliveBeforeAttackAlly && (!unit1.IsAlive() || !unit1.IsValidOnMap(mapData));
|
||
var targetKilledByAttackAlly = targetAliveBeforeAttackAlly && (!unit2.IsAlive() || !unit2.IsValidOnMap(mapData));
|
||
//如果是AI预测行为,return
|
||
if (actionParams.MapData != Main.MapData) return true;
|
||
//如果数据出错,return
|
||
if (originGrid == null || targetGrid == null || originUnitRenderer == null || targetUnitRenderer == null)
|
||
{
|
||
if (NotProjectile)
|
||
{
|
||
originGrid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
targetGrid?.Renderer(mapData)?.InstantUpdateGrid();
|
||
return true;
|
||
}
|
||
|
||
if (originKilledByAttackAlly || targetKilledByAttackAlly)
|
||
{
|
||
ReportUnitAttackAllyRendererMissingAfterKill(
|
||
actionParams,
|
||
_actionId,
|
||
originAliveBeforeAttackAlly,
|
||
targetAliveBeforeAttackAlly,
|
||
originKilledByAttackAlly,
|
||
targetKilledByAttackAlly,
|
||
handledByLifecycle,
|
||
NotProjectile,
|
||
unit1Die,
|
||
originGrid,
|
||
targetGrid,
|
||
originUnitRenderer,
|
||
targetUnitRenderer);
|
||
|
||
try
|
||
{
|
||
if (targetKilledByAttackAlly && targetUnitRenderer != null)
|
||
targetUnitRenderer.Die();
|
||
if (originKilledByAttackAlly && originUnitRenderer != null)
|
||
originUnitRenderer.Die();
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
LogSystem.LogWarning($"UnitAttackAllyRendererMissingAfterKill cleanup failed: {e}");
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
//如果不在视野,return
|
||
if (!Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(originGrid.Id) &&
|
||
!Main.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(targetGrid.Id)) return true;
|
||
|
||
|
||
//投射物类的动画
|
||
if (!NotProjectile)
|
||
{
|
||
var data = FragmentDataFactory.CreateAttackAllyData(FragmentType.AttackAlly, originUnitRenderer,
|
||
targetUnitRenderer, originGrid, targetGrid, animSkillData,unit1Die,city1);
|
||
|
||
var fragment = FragmentFactory.Create(FragmentType.AttackAlly,data);
|
||
PresentationManager.EnqueueTask(new FragmentSequencerTask(fragment));
|
||
_duration = fragment.Duration;
|
||
}
|
||
//非投射物动画(目前仅suwako hebi 的合并
|
||
else
|
||
{
|
||
if (!targetUnitRenderer.InstantUpdateTryDie())
|
||
targetUnitRenderer.InstantUpdateUnit(showoff: true);
|
||
if (!originUnitRenderer.InstantUpdateTryDie())
|
||
originUnitRenderer.InstantUpdateUnit(showoff: true);
|
||
targetGrid.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||
originGrid.Renderer(Main.MapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
||
|
||
//因为没有走包装好的fragmenData 所以由手动重置周围单位的高亮状态
|
||
MapRenderer.Instance.UpdateAroundHighlight(Main.MapData,originGrid);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public override bool CheckCan(CommonActionParams actionParams)
|
||
{
|
||
if (actionParams.UnitData.Id == actionParams.TargetUnitData.Id) return false;
|
||
if (!actionParams.UnitData.IsAlive() || !actionParams.TargetUnitData.IsAlive()) return false;
|
||
if (!actionParams.MapData.IsLeagueUnitByUnit(actionParams.UnitData.Id, actionParams.TargetUnitData.Id)) return false;
|
||
if (!actionParams.MapData.GetPlayerDataByUnitId(actionParams.UnitData.Id, out _)) return false;
|
||
if (!actionParams.MapData.GetPlayerDataByUnitId(actionParams.TargetUnitData.Id, out _)) return false;
|
||
if (!actionParams.MapData.GetCityDataByUnitId(actionParams.UnitData.Id, out _)) return false;
|
||
if (!actionParams.MapData.GetCityDataByUnitId(actionParams.TargetUnitData.Id, out _)) return false;
|
||
|
||
if (!actionParams.MapData.GetGridDataByUnitId(actionParams.UnitData.Id, out var unitGrid)) return false;
|
||
if (!actionParams.MapData.GetGridDataByUnitId(actionParams.TargetUnitData.Id, out var targetUnitGrid)) return false;
|
||
if (actionParams.MapData.GridMap.CalcDistance(unitGrid, targetUnitGrid) >
|
||
actionParams.UnitData.GetAttackAllyRange(actionParams.MapData)) return false;
|
||
|
||
// 古明地非Giant单位可以向BonePile献祭
|
||
if (actionParams.UnitData.CanFeedBonePile(actionParams.MapData)
|
||
&& actionParams.TargetUnitData.UnitType == UnitType.BonePile)
|
||
return true;
|
||
|
||
if (!actionParams.UnitData.IsCanAttackAlly()) return false;
|
||
if (!actionParams.UnitData.IsCanAttackTargetAlly(actionParams.MapData, actionParams.TargetUnitData))
|
||
return false;
|
||
return true;
|
||
}
|
||
|
||
public override bool CheckShow(CommonActionParams actionParams,out ShowType showType)
|
||
{
|
||
showType = ShowType.None;
|
||
return false;
|
||
}
|
||
public override ActionShowState CheckShowState(CommonActionParams actionParams) { return ActionShowState.None; }
|
||
public override bool CameraControl(CommonActionParams actionParams)
|
||
{
|
||
var main = GameObject.Find("Main").GetComponent<Main>();
|
||
if(actionParams.GridData != null && main != null && MapRenderer.Instance.CameraController != null
|
||
&& actionParams.MapData.PlayerMap.SelfPlayerData.Sight.CheckIsInSight(actionParams.GridData.Id))
|
||
MapRenderer.Instance.CameraController?.CameraFocusOnGrid(actionParams.GridData);
|
||
return true;
|
||
}
|
||
}
|
||
}
|