3707 lines
147 KiB
C#
3707 lines
147 KiB
C#
/*
|
|
* @Author: Codex
|
|
* @Description: Hakurei Empire Norway hero skills except Reimu
|
|
* @Date: 2026-06-15
|
|
* @Modify:
|
|
*/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using Logic.Action;
|
|
using Logic.CrashSight;
|
|
using MemoryPack;
|
|
using RuntimeData;
|
|
using TH1_Anim.Fragments;
|
|
using TH1_Core.Managers;
|
|
using TH1_Renderer;
|
|
using TH1_Logic.Core;
|
|
using TH1Renderer;
|
|
using UnityEngine;
|
|
|
|
namespace Logic.Skill
|
|
{
|
|
public static class HakureiNorwayHeroSkillUtil
|
|
{
|
|
private const int SumirekoOrbAuraRange = 1;
|
|
private const int SuikaFallingSplashRange = 3;
|
|
private const int AunnPortalRange = 1;
|
|
private const int AunnPairMaxDistance = 8;
|
|
private const int KasenBeastGuideRange = 4;
|
|
private const int KasenBeastGuideAuraRange = 1;
|
|
private const int KasenBeastGuideTurnHeal = 2;
|
|
private const int KasenBeastGuideRecallHeal = 5;
|
|
private static readonly List<GridData> SumirekoOrbAroundBuf = new();
|
|
|
|
public static bool IsSumireko(UnitData unit)
|
|
{
|
|
return unit != null && unit.UnitType == UnitType.Giant && unit.GiantType == GiantType.NorwaySumireko;
|
|
}
|
|
|
|
public static bool IsKasen(UnitData unit)
|
|
{
|
|
return unit != null && unit.UnitType == UnitType.Giant && unit.GiantType == GiantType.NorwayKasen;
|
|
}
|
|
|
|
public static bool IsAunn(UnitData unit)
|
|
{
|
|
return unit != null && unit.UnitType == UnitType.Giant && unit.GiantType == GiantType.NorwayAunn;
|
|
}
|
|
|
|
public static bool IsSuika(UnitData unit)
|
|
{
|
|
return unit != null && unit.UnitType == UnitType.Giant && unit.GiantType == GiantType.NorwaySuika;
|
|
}
|
|
|
|
public static int HeroLevel(UnitData unit)
|
|
{
|
|
return unit == null ? 1 : Mathf.Max(1, (int)unit.UnitLevel);
|
|
}
|
|
|
|
public static bool TryGetSumirekoOccultOrbReady(UnitData unit, out SumirekoOccultOrbOwnerSkill readySkill)
|
|
{
|
|
readySkill = null;
|
|
if (!IsSumireko(unit)) return false;
|
|
if (!unit.HasEffectiveSkill(SkillType.SumirekoOccultOrbOwner, out var skill)) return false;
|
|
readySkill = skill as SumirekoOccultOrbOwnerSkill;
|
|
return readySkill != null && readySkill.Level > 0;
|
|
}
|
|
|
|
public static bool TryGetKasenBeastGuideReady(UnitData unit, out KasenBeastGuideReserveSkill reserveSkill)
|
|
{
|
|
reserveSkill = null;
|
|
if (!IsKasen(unit)) return false;
|
|
if (!unit.HasEffectiveSkill(SkillType.KasenBeastGuideReserve, out var skill)) return false;
|
|
reserveSkill = skill as KasenBeastGuideReserveSkill;
|
|
return reserveSkill != null
|
|
&& reserveSkill.Level > 0
|
|
&& unit.HasEffectiveSkill(SkillType.KasenBeastGuideReady, out _)
|
|
&& unit.GetActionPoint(ActionPointType.Attack) > 0;
|
|
}
|
|
|
|
public static bool SameUnion(MapData map, UnitData a, UnitData b)
|
|
{
|
|
return map != null && a != null && b != null && map.IsLeagueUnitByUnit(a.Id, b.Id);
|
|
}
|
|
|
|
public static bool IsEnemy(MapData map, UnitData a, UnitData b)
|
|
{
|
|
return map != null && a != null && b != null && !map.IsLeagueUnitByUnit(a.Id, b.Id);
|
|
}
|
|
|
|
private static bool IsUnitLandingTerrain(MapData map, UnitData unit, GridData grid)
|
|
{
|
|
if (map == null || unit == null || grid == null) return false;
|
|
if (!map.GetPlayerDataByUnitId(unit.Id, out var player) || player == null) return false;
|
|
return Main.UnitLogic.CheckUnitAbleForGrid_OfflineStatus(map, player, unit, grid)
|
|
&& Main.UnitLogic.CheckUnitCanEnterGridByDiplomacyStatus(map, unit, grid);
|
|
}
|
|
|
|
private static bool CanUnitLandOnGrid(MapData map, UnitData unit, GridData grid)
|
|
{
|
|
if (map == null || unit == null || grid == null) return false;
|
|
if (grid.RealUnit(map, out var occupant))
|
|
return occupant.Id == unit.Id && IsUnitLandingTerrain(map, unit, grid);
|
|
return Main.UnitLogic.CheckUnitAbleForGrid_RealTimeStatus(map, unit, grid);
|
|
}
|
|
|
|
public static bool TryFindFirstEmptyAround(MapData map, GridData center, int minRange, int maxRange,
|
|
out GridData emptyGrid)
|
|
{
|
|
emptyGrid = null;
|
|
if (map == null || center == null) return false;
|
|
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(minRange, maxRange, center, buf);
|
|
foreach (var grid in buf)
|
|
{
|
|
if (grid == null || grid.RealUnit(map, out _)) continue;
|
|
emptyGrid = grid;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool TryFindFirstEmptyAround(UnitData unit, MapData map, out GridData emptyGrid)
|
|
{
|
|
emptyGrid = null;
|
|
if (unit == null || !unit.Grid(map, out var grid)) return false;
|
|
return TryFindFirstEmptyAround(map, grid, 1, 1, out emptyGrid);
|
|
}
|
|
|
|
private const float GroundAttackSpawnVisualDelay = 0.25f;
|
|
|
|
public static bool TrySpawnUnitNear(MapData map, UnitData owner, UnitFullType fullType, GridData preferred,
|
|
out UnitData newUnit, float waitTime = 0f)
|
|
{
|
|
newUnit = null;
|
|
if (map == null || owner == null || preferred == null) return false;
|
|
var city = owner.City(map);
|
|
if (city == null) return false;
|
|
|
|
if (!preferred.RealUnit(map, out _) &&
|
|
map.AddUnitData(preferred.Id, city.Id, fullType, out newUnit, waitTime))
|
|
{
|
|
newUnit.OriginUnitFullType = owner.UnitFullType;
|
|
return true;
|
|
}
|
|
|
|
if (!TryFindFirstEmptyAround(map, preferred, 1, 1, out var fallback)) return false;
|
|
if (!map.AddUnitData(fallback.Id, city.Id, fullType, out newUnit, waitTime)) return false;
|
|
newUnit.OriginUnitFullType = owner.UnitFullType;
|
|
return true;
|
|
}
|
|
|
|
public static float GetGroundAttackSpawnVisualDelay()
|
|
{
|
|
return GroundAttackSpawnVisualDelay;
|
|
}
|
|
|
|
public static bool TryResolveSumirekoOrbGrid(MapData map, UnitData sumireko, GridData targetGrid,
|
|
out UnitType orbType, out SkillType skillType)
|
|
{
|
|
orbType = UnitType.None;
|
|
skillType = SkillType.NONE;
|
|
if (map == null || !IsSumireko(sumireko) || targetGrid == null) return false;
|
|
if (!sumireko.Grid(map, out var sumirekoGrid)) return false;
|
|
if (map.GridMap.CalcDistance(sumirekoGrid, targetGrid) > 3) return false;
|
|
if (targetGrid.RealUnit(map, out _)) return false;
|
|
if (IsEnemyMilitaryOrCityCenter(map, sumireko, targetGrid)) return false;
|
|
return TryGetSumirekoOrbForGrid(HeroLevel(sumireko), targetGrid, out orbType, out skillType);
|
|
}
|
|
|
|
private static bool IsEnemyMilitaryOrCityCenter(MapData map, UnitData self, GridData grid)
|
|
{
|
|
if (map == null || self == null || grid == null) return false;
|
|
if (!TryGetOwnerPlayer(map, self, out var selfPlayer)) return false;
|
|
|
|
if (Main.CityLogic.IsMilitaryBuildingResource(grid.Resource))
|
|
{
|
|
return grid.Player(map, out var gridPlayer) && !map.SameUnion(gridPlayer.Id, selfPlayer.Id);
|
|
}
|
|
|
|
if (grid.Resource != ResourceType.CityCenter) return false;
|
|
return map.GetCityDataByGid(grid.Id, out var city)
|
|
&& city.Player(map, out var cityPlayer)
|
|
&& !map.SameUnion(cityPlayer.Id, selfPlayer.Id);
|
|
}
|
|
|
|
public static bool IsSumirekoOccultOrb(UnitData unit)
|
|
{
|
|
return unit != null && UnitData.IsHeroDerivativeType(unit.UnitType) && unit.UnitType is
|
|
UnitType.SumirekoNorwayOrb or
|
|
UnitType.SumirekoDenmarkOrb or
|
|
UnitType.SumirekoEnglandOrb;
|
|
}
|
|
|
|
public static bool CanUseOccultOrbAsMoveAnchor(MapData map, UnitData sumireko, UnitData orb)
|
|
{
|
|
if (map == null || !IsSumireko(sumireko)) return false;
|
|
if (!sumireko.HasEffectiveSkill(SkillType.SumirekoOccultOrbOwner, out _)) return false;
|
|
if (!IsSumirekoOccultOrb(orb)) return false;
|
|
if (!TryGetOwnerPlayer(map, sumireko, out var sumirekoPlayer)) return false;
|
|
if (!TryGetOwnerPlayer(map, orb, out var orbPlayer)) return false;
|
|
return sumirekoPlayer.Id == orbPlayer.Id;
|
|
}
|
|
|
|
public static bool IsAroundUsableSumirekoOccultOrbAnchor(MapData map, UnitData sumireko, GridData target)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsSumireko(sumireko) || target == null) return false;
|
|
foreach (var orb in map.UnitMap.UnitList)
|
|
{
|
|
if (orb == null || !orb.IsAlive()) continue;
|
|
if (!CanUseOccultOrbAsMoveAnchor(map, sumireko, orb)) continue;
|
|
if (!orb.Grid(map, out var orbGrid)) continue;
|
|
if (map.GridMap.CalcDistance(orbGrid, target) <= SumirekoOrbAuraRange)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsSumirekoOrbAuraSkill(SkillType skillType)
|
|
{
|
|
return skillType is SkillType.SumirekoNorwayOrbBuff
|
|
or SkillType.SumirekoDenmarkOrbBuff
|
|
or SkillType.SumirekoEnglandOrbBuff
|
|
or SkillType.SumirekoOrbSwapMaxValueBuff;
|
|
}
|
|
|
|
public static SkillType GetSumirekoOrbBuffSkillType(SkillType sourceSkillType)
|
|
{
|
|
return sourceSkillType switch
|
|
{
|
|
SkillType.SumirekoNorwayOrbSwapMoveAttack => SkillType.SumirekoNorwayOrbBuff,
|
|
SkillType.SumirekoDenmarkOrbSwapAttackDefense => SkillType.SumirekoDenmarkOrbBuff,
|
|
SkillType.SumirekoEnglandOrbDamageProxy => SkillType.SumirekoEnglandOrbBuff,
|
|
SkillType.SumirekoOrbSwapMaxValue => SkillType.SumirekoOrbSwapMaxValueBuff,
|
|
_ => sourceSkillType,
|
|
};
|
|
}
|
|
|
|
public static void RefreshAllSumirekoOccultOrbBuffs(MapData map)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null) return;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (unit == null || !unit.IsAlive()) continue;
|
|
RefreshSumirekoOccultOrbBuffsForUnit(map, unit);
|
|
}
|
|
}
|
|
|
|
public static void RefreshAllSumirekoOccultOrbBuffsForMoveEvent(MapData map, UnitData moveUnit)
|
|
{
|
|
if (IsSumirekoOccultOrb(moveUnit))
|
|
{
|
|
RefreshAllSumirekoOccultOrbBuffs(map);
|
|
return;
|
|
}
|
|
|
|
RefreshSumirekoOccultOrbBuffsForUnit(map, moveUnit);
|
|
}
|
|
|
|
public static void RefreshSumirekoOccultOrbBuffsAround(MapData map, UnitData orb)
|
|
{
|
|
if (map == null || orb == null || !orb.Grid(map, out var orbGrid)) return;
|
|
SumirekoOrbAroundBuf.Clear();
|
|
map.GridMap.GetAroundGridData(SumirekoOrbAuraRange, SumirekoOrbAuraRange, orbGrid, SumirekoOrbAroundBuf);
|
|
foreach (var grid in SumirekoOrbAroundBuf)
|
|
{
|
|
if (grid == null || !grid.RealUnit(map, out var unit)) continue;
|
|
RefreshSumirekoOccultOrbBuffsForUnit(map, unit);
|
|
}
|
|
}
|
|
|
|
public static void RefreshSumirekoOccultOrbBuffsForUnit(MapData map, UnitData unit)
|
|
{
|
|
if (map == null || unit == null || !unit.IsAlive()) return;
|
|
if (IsSumirekoOccultOrb(unit)) return;
|
|
|
|
RemoveLegacySumirekoOrbBuffs(map, unit);
|
|
|
|
var hasNorway = TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoNorwayOrb, out var norwayOrb);
|
|
SetSumirekoOrbBuff(map, unit, SkillType.SumirekoNorwayOrbBuff, norwayOrb, hasNorway);
|
|
|
|
var hasDenmark = TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoDenmarkOrb, out var denmarkOrb);
|
|
SetSumirekoOrbBuff(map, unit, SkillType.SumirekoDenmarkOrbBuff, denmarkOrb, hasDenmark);
|
|
|
|
var hasEngland = TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoEnglandOrb, out var englandOrb);
|
|
SetSumirekoOrbBuff(map, unit, SkillType.SumirekoEnglandOrbBuff, englandOrb, hasEngland);
|
|
|
|
var hasMaxValue = IsMaxValueOrb(norwayOrb) || IsMaxValueOrb(denmarkOrb);
|
|
SetSumirekoOrbBuff(map, unit, SkillType.SumirekoOrbSwapMaxValueBuff,
|
|
IsMaxValueOrb(norwayOrb) ? norwayOrb : denmarkOrb, hasMaxValue);
|
|
}
|
|
|
|
private static void RemoveLegacySumirekoOrbBuffs(MapData map, UnitData unit)
|
|
{
|
|
unit.RemoveSkill(SkillType.SumirekoNorwayOrbSwapMoveAttack, map);
|
|
unit.RemoveSkill(SkillType.SumirekoDenmarkOrbSwapAttackDefense, map);
|
|
unit.RemoveSkill(SkillType.SumirekoEnglandOrbDamageProxy, map);
|
|
unit.RemoveSkill(SkillType.SumirekoOrbSwapMaxValue, map);
|
|
}
|
|
|
|
public static bool TryFindBestSumirekoOccultOrb(MapData map, UnitData unit, UnitType orbType, out UnitData orb)
|
|
{
|
|
orb = null;
|
|
if (map?.UnitMap?.UnitList == null || unit == null || !unit.Grid(map, out var unitGrid)) return false;
|
|
|
|
var bestDistance = int.MaxValue;
|
|
foreach (var candidate in map.UnitMap.UnitList)
|
|
{
|
|
if (candidate == null || candidate.UnitType != orbType || !candidate.IsAlive()) continue;
|
|
if (candidate.Id == unit.Id) continue;
|
|
if (!candidate.Grid(map, out var orbGrid)) continue;
|
|
var distance = map.GridMap.CalcDistance(orbGrid, unitGrid);
|
|
if (distance > SumirekoOrbAuraRange) continue;
|
|
if (!SameUnion(map, candidate, unit)) continue;
|
|
if (!HasActiveOrbAuraSkill(candidate, orbType)) continue;
|
|
if (distance > bestDistance) continue;
|
|
if (distance == bestDistance && orb != null && candidate.Id >= orb.Id) continue;
|
|
orb = candidate;
|
|
bestDistance = distance;
|
|
}
|
|
|
|
return orb != null;
|
|
}
|
|
|
|
public static bool HasActiveOrbAuraSkill(UnitData orb, UnitType orbType)
|
|
{
|
|
if (orb == null) return false;
|
|
return orbType switch
|
|
{
|
|
UnitType.SumirekoNorwayOrb => orb.HasEffectiveSkill(SkillType.SumirekoNorwayOrbSwapMoveAttack, out _),
|
|
UnitType.SumirekoDenmarkOrb => orb.HasEffectiveSkill(SkillType.SumirekoDenmarkOrbSwapAttackDefense, out _),
|
|
UnitType.SumirekoEnglandOrb => orb.HasEffectiveSkill(SkillType.SumirekoEnglandOrbDamageProxy, out var skill)
|
|
&& skill is SumirekoEnglandOrbDamageProxySkill proxy
|
|
&& !proxy.IsFinished()
|
|
&& proxy.Level > 0,
|
|
_ => false,
|
|
};
|
|
}
|
|
|
|
public static bool IsMaxValueOrb(UnitData orb)
|
|
{
|
|
return orb != null && orb.HasEffectiveSkill(SkillType.SumirekoOrbSwapMaxValue, out _);
|
|
}
|
|
|
|
public static void SyncSumirekoOccultOrbsAfterHeroUpgrade(MapData map, UnitData sumireko)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsSumireko(sumireko) || HeroLevel(sumireko) < 4) return;
|
|
if (!TryGetOwnerPlayer(map, sumireko, out var sumirekoPlayer)) return;
|
|
|
|
foreach (var orb in map.UnitMap.UnitList)
|
|
{
|
|
if (orb == null || !orb.IsAlive() || !IsSumirekoOccultOrb(orb)) continue;
|
|
if (!TryGetOwnerPlayer(map, orb, out var orbPlayer) || orbPlayer.Id != sumirekoPlayer.Id) continue;
|
|
|
|
orb.AddOrOverrideSkill(SkillType.SumirekoOrbSwapMaxValue, map, sumireko.Id);
|
|
orb.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
RefreshAllSumirekoOccultOrbBuffs(map);
|
|
}
|
|
|
|
public static bool HasSumirekoOrbMaxValueBuff(MapData map, UnitData unit)
|
|
{
|
|
if (unit == null) return false;
|
|
return unit.HasEffectiveSkill(SkillType.SumirekoOrbSwapMaxValueBuff, out _)
|
|
&& (HasMaxValueAura(map, unit, UnitType.SumirekoNorwayOrb)
|
|
|| HasMaxValueAura(map, unit, UnitType.SumirekoDenmarkOrb));
|
|
}
|
|
|
|
public static bool HasMaxValueAura(MapData map, UnitData unit, UnitType orbType)
|
|
{
|
|
if (unit == null) return false;
|
|
return TryFindBestSumirekoOccultOrb(map, unit, orbType, out var orb) && IsMaxValueOrb(orb);
|
|
}
|
|
|
|
public static bool HasMoveAttackSwap(MapData map, UnitData unit, out bool maxValue)
|
|
{
|
|
maxValue = unit != null
|
|
&& unit.HasEffectiveSkill(SkillType.SumirekoOrbSwapMaxValueBuff, out _)
|
|
&& HasMaxValueAura(map, unit, UnitType.SumirekoNorwayOrb);
|
|
return unit != null
|
|
&& !IsSumirekoOccultOrb(unit)
|
|
&& unit.HasEffectiveSkill(SkillType.SumirekoNorwayOrbBuff, out _)
|
|
&& TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoNorwayOrb, out _);
|
|
}
|
|
|
|
public static bool HasAttackDefenseSwap(MapData map, UnitData unit, out bool maxValue)
|
|
{
|
|
maxValue = unit != null
|
|
&& unit.HasEffectiveSkill(SkillType.SumirekoOrbSwapMaxValueBuff, out _)
|
|
&& HasMaxValueAura(map, unit, UnitType.SumirekoDenmarkOrb);
|
|
return unit != null
|
|
&& !IsSumirekoOccultOrb(unit)
|
|
&& unit.HasEffectiveSkill(SkillType.SumirekoDenmarkOrbBuff, out _)
|
|
&& TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoDenmarkOrb, out _);
|
|
}
|
|
|
|
public static bool TryFindDamageProxyOrb(MapData map, UnitData unit, out UnitData orb,
|
|
out SumirekoEnglandOrbDamageProxySkill proxySkill)
|
|
{
|
|
orb = null;
|
|
proxySkill = null;
|
|
if (unit == null || !unit.HasEffectiveSkill(SkillType.SumirekoEnglandOrbBuff, out _)) return false;
|
|
if (!TryFindBestSumirekoOccultOrb(map, unit, UnitType.SumirekoEnglandOrb, out var foundOrb)) return false;
|
|
if (!foundOrb.HasEffectiveSkill(SkillType.SumirekoEnglandOrbDamageProxy, out var skill)) return false;
|
|
if (skill is not SumirekoEnglandOrbDamageProxySkill proxy || proxy.Level <= 0 || proxy.IsFinished())
|
|
return false;
|
|
orb = foundOrb;
|
|
proxySkill = proxy;
|
|
return true;
|
|
}
|
|
|
|
private static void SetSumirekoOrbBuff(MapData map, UnitData unit, SkillType skillType, UnitData orb,
|
|
bool shouldHave)
|
|
{
|
|
if (unit == null) return;
|
|
if (!shouldHave)
|
|
{
|
|
if (unit.GetSkill(skillType, out var existing) && existing is ISumirekoOrbAuraBuff)
|
|
unit.RemoveSkill(skillType, map);
|
|
return;
|
|
}
|
|
|
|
var sourceId = orb?.Id ?? 0;
|
|
if (unit.GetSkill(skillType, out var skill) && skill is ISumirekoOrbAuraBuff)
|
|
return;
|
|
|
|
unit.AddOrOverrideSkill(skillType, map, sourceId);
|
|
}
|
|
|
|
private static bool TryGetSumirekoOrbForGrid(int level, GridData grid, out UnitType orbType,
|
|
out SkillType skillType)
|
|
{
|
|
orbType = UnitType.None;
|
|
skillType = SkillType.NONE;
|
|
if (grid == null) return false;
|
|
if (grid.Terrain == TerrainType.DeepSea || grid.Feature == TerrainFeature.Mountain)
|
|
{
|
|
orbType = UnitType.SumirekoNorwayOrb;
|
|
skillType = SkillType.SumirekoNorwayOrbSwapMoveAttack;
|
|
return true;
|
|
}
|
|
if (level >= 2 && (grid.Terrain == TerrainType.ShallowSea || grid.Vegetation == Vegetation.Trees))
|
|
{
|
|
orbType = UnitType.SumirekoDenmarkOrb;
|
|
skillType = SkillType.SumirekoDenmarkOrbSwapAttackDefense;
|
|
return true;
|
|
}
|
|
if (level >= 3 && grid.Terrain == TerrainType.Land)
|
|
{
|
|
orbType = UnitType.SumirekoEnglandOrb;
|
|
skillType = SkillType.SumirekoEnglandOrbDamageProxy;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool TryMoveUnit(MapData map, UnitData unit, GridData target, MoveType moveType,
|
|
bool playFogVfx = false)
|
|
{
|
|
if (map == null || unit == null || target == null) return false;
|
|
if (target.RealUnit(map, out _)) return false;
|
|
var originGrid = unit.Grid(map);
|
|
var originCity = unit.City(map);
|
|
var targetCity = target.City(map, out var city) ? city : null;
|
|
var unitRenderer = unit.Renderer(map);
|
|
|
|
if (!Main.UnitLogic.MoveToLogic(map, unit, target, moveType)) return false;
|
|
RefreshSkillMovedUnitVisual(map, unit, originGrid, target, originCity, targetCity, unitRenderer,
|
|
playFogVfx);
|
|
return true;
|
|
}
|
|
|
|
private static void RefreshSkillMovedUnitVisual(MapData map, UnitData unit, GridData originGrid,
|
|
GridData targetGrid, CityData originCity, CityData targetCity, UnitRenderer unitRenderer,
|
|
bool playFogVfx = false)
|
|
{
|
|
if (map != Main.MapData || unit == null || targetGrid == null) return;
|
|
|
|
unitRenderer ??= unit.Renderer(map);
|
|
unitRenderer?.InstantUpdateUnitPos();
|
|
unitRenderer?.InstantUpdateUnit(true);
|
|
unitRenderer?.SyncStatusWithUnitSkills();
|
|
|
|
var originGridRenderer = originGrid?.Renderer(map);
|
|
var targetGridRenderer = targetGrid.Renderer(map);
|
|
if (playFogVfx)
|
|
{
|
|
originGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
targetGridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
}
|
|
|
|
originGridRenderer?.InstantUpdateGrid();
|
|
targetGridRenderer?.InstantUpdateGrid();
|
|
originCity?.SetCityRenderer(map);
|
|
if (targetCity != null && targetCity.Id != originCity?.Id)
|
|
targetCity.SetCityRenderer(map);
|
|
|
|
if (originGrid != null)
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, originGrid);
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, targetGrid);
|
|
}
|
|
|
|
private static bool TryRepositionUnitWithoutMoveSideEffects(MapData map, UnitData unit, GridData target)
|
|
{
|
|
if (map == null || unit == null || target == null) return false;
|
|
if (!CanUnitLandOnGrid(map, unit, target)) return false;
|
|
if (unit.GetLandType() == LandType.WaterAndAshore && target.Terrain == TerrainType.Land)
|
|
{
|
|
var carryType = unit.CarryUnitFullType;
|
|
if (carryType.UnitType == UnitType.None || !map.CheckLandTypeForGrid(carryType, target)) return false;
|
|
}
|
|
|
|
map.SetUnitIdToGridId(unit.Id, target.Id);
|
|
if (unit.GetLandType() == LandType.LandAndPort && target.Resource == ResourceType.Port)
|
|
Main.UnitLogic.LandToBoat(map, unit);
|
|
else if (unit.GetLandType() == LandType.WaterAndAshore && target.Terrain == TerrainType.Land)
|
|
Main.UnitLogic.BoatToLand(map, unit);
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void PlaySkillIcon(MapData map, UnitData unit, SkillType skillType)
|
|
{
|
|
if (unit == null) return;
|
|
unit.Grid(map)?.Renderer(map)?.PlayVFXInSight(
|
|
new GridVFXParams(GridVFXType.SkillIcon, skillType, unit.UnitFullType));
|
|
}
|
|
|
|
public static void RemoveUnitWithVfx(MapData map, UnitData unit)
|
|
{
|
|
if (map == null || unit == null) return;
|
|
var renderer = unit.Renderer(map);
|
|
var grid = unit.Grid(map);
|
|
Main.UnitLogic.UnitUnnaturalDie(map, unit);
|
|
if (map != Main.MapData) return;
|
|
renderer?.Die();
|
|
grid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
grid?.Renderer(map)?.InstantUpdateGrid();
|
|
}
|
|
|
|
private static void RemoveUnitWithVfxAfterCurrentAttack(MapData map, UnitData unit)
|
|
{
|
|
if (map == null || unit == null) return;
|
|
var renderer = unit.Renderer(map);
|
|
var grid = unit.Grid(map);
|
|
var city = unit.City(map);
|
|
Main.UnitLogic.UnitUnnaturalDie(map, unit);
|
|
if (map != Main.MapData) return;
|
|
|
|
void Refresh()
|
|
{
|
|
renderer?.Die();
|
|
grid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
grid?.Renderer(map)?.InstantUpdateGrid();
|
|
city?.SetCityRenderer(map);
|
|
if (grid != null)
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, grid);
|
|
}
|
|
|
|
if (PresentationManager.CurrentScope != null)
|
|
{
|
|
PresentationManager.CurrentScope.Add(new FragmentStep
|
|
{
|
|
Phase = AnimPhase.Settle,
|
|
Duration = 0f,
|
|
Execute = Refresh
|
|
});
|
|
return;
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
public static bool TryGetOwnerPlayer(MapData map, UnitData owner, out PlayerData player)
|
|
{
|
|
player = null;
|
|
return map != null && owner != null && map.GetPlayerDataByUnitId(owner.Id, out player) && player != null;
|
|
}
|
|
|
|
public static bool TryGetKasenBeastGuideReserve(UnitData kasen, out KasenBeastGuideReserveSkill reserve)
|
|
{
|
|
reserve = null;
|
|
if (!IsKasen(kasen)) return false;
|
|
if (!kasen.HasEffectiveSkill(SkillType.KasenBeastGuideReserve, out var skill)) return false;
|
|
reserve = skill as KasenBeastGuideReserveSkill;
|
|
return reserve != null;
|
|
}
|
|
|
|
public static bool TryGetKasenBeastGuideSkill(GridData grid, out KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
guideSkill = null;
|
|
if (grid == null || !grid.HasSpType(GridSpType.KasenBeastGuide)) return false;
|
|
if (!grid.GetSkill(SkillType.KasenBeastGuideGrid, out var skill)) return false;
|
|
guideSkill = skill as KasenBeastGuideGridSkill;
|
|
return guideSkill != null;
|
|
}
|
|
|
|
public static bool IsKasenBeastGuideGrid(GridData grid)
|
|
{
|
|
return TryGetKasenBeastGuideSkill(grid, out _);
|
|
}
|
|
|
|
public static GridData FindKasenBeastGuideGrid(MapData map, UnitData kasen)
|
|
{
|
|
if (map?.GridMap?.GridList == null || !IsKasen(kasen)) return null;
|
|
foreach (var grid in map.GridMap.GridList)
|
|
{
|
|
if (!TryGetKasenBeastGuideSkill(grid, out var guideSkill)) continue;
|
|
if (guideSkill.KasenOriginId == kasen.Id) return grid;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static bool CanPlaceKasenBeastGuide(MapData map, UnitData kasen, GridData target,
|
|
bool requireTarget = true, bool requireEmptyTarget = true)
|
|
{
|
|
if (map == null || !IsKasen(kasen)) return false;
|
|
if (!kasen.HasEffectiveSkill(SkillType.KasenBeastGuideReserve, out var reserveSkill) ||
|
|
reserveSkill is not KasenBeastGuideReserveSkill reserve)
|
|
return false;
|
|
if (!kasen.HasEffectiveSkill(SkillType.KasenBeastGuideReady, out _)) return false;
|
|
if (kasen.GetActionPoint(ActionPointType.Attack) <= 0) return false;
|
|
if (FindKasenBeastGuideGrid(map, kasen) != null) return false;
|
|
if (reserve.Level <= 0) return false;
|
|
if (!requireTarget) return true;
|
|
if (target == null || !kasen.Grid(map, out var kasenGrid)) return false;
|
|
if (map.GridMap.CalcDistance(kasenGrid, target) > KasenBeastGuideRange) return false;
|
|
if (target.Resource == ResourceType.CityCenter) return false;
|
|
if (target.Terrain != TerrainType.Land) return false;
|
|
if (target.HasSpType(GridSpType.KasenBeastGuide)) return false;
|
|
if (requireEmptyTarget && target.RealUnit(map, out _)) return false;
|
|
return true;
|
|
}
|
|
|
|
public static bool TryPlaceKasenBeastGuide(MapData map, UnitData kasen, GridData target)
|
|
{
|
|
if (!CanPlaceKasenBeastGuide(map, kasen, target, requireEmptyTarget: false)) return false;
|
|
if (!target.AddSpType(GridSpType.KasenBeastGuide, map, kasen)) return false;
|
|
target.AddOrOverrideSkill(SkillType.KasenBeastGuideGrid, map, kasen.Id);
|
|
if (target.GetSkill(SkillType.KasenBeastGuideGrid, out var skill) &&
|
|
skill is KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
guideSkill.SyncOwnerState(map);
|
|
}
|
|
|
|
SetKasenBeastGuideReserve(map, kasen, 0);
|
|
kasen.RemoveSkill(SkillType.KasenBeastGuideReady, map);
|
|
kasen.ClearActionPoint();
|
|
RefreshKasenBeastGuideStatBuffs(map);
|
|
|
|
if (map == Main.MapData)
|
|
{
|
|
target.Renderer(map)?.InstantUpdateGrid(true);
|
|
target.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.SkillIcon,
|
|
SkillType.KasenBeastGuideGrid, kasen.UnitFullType));
|
|
kasen.Renderer(map)?.InstantUpdateUnit(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static bool TryRecallKasenBeastGuide(MapData map, UnitData actor)
|
|
{
|
|
if (map == null || actor == null || !actor.Grid(map, out var grid)) return false;
|
|
if (!CanRecallKasenBeastGuide(map, actor, grid, out var guideSkill)) return false;
|
|
var ownerLevel = guideSkill.OwnerLevel;
|
|
RemoveKasenBeastGuide(map, grid, restoreOwnerReserve: true);
|
|
actor.AddActionPoint(ActionPointType.Move);
|
|
if (ownerLevel >= 2)
|
|
Main.UnitLogic.RecoverHealth_Legacy(map, actor, actor, KasenBeastGuideRecallHeal);
|
|
actor.Renderer(map)?.InstantUpdateUnit(false);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryClearKasenBeastGuide(MapData map, UnitData actor)
|
|
{
|
|
if (map == null || actor == null || !actor.Grid(map, out var grid)) return false;
|
|
if (!CanClearKasenBeastGuide(map, actor, grid, out _)) return false;
|
|
RemoveKasenBeastGuide(map, grid, restoreOwnerReserve: true);
|
|
actor.ReduceActionPoint(ActionPointType.Capture);
|
|
actor.Renderer(map)?.InstantUpdateUnit(false);
|
|
return true;
|
|
}
|
|
|
|
public static bool CanRecallKasenBeastGuide(MapData map, UnitData actor, GridData grid,
|
|
out KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
guideSkill = null;
|
|
if (map == null || actor == null || grid == null) return false;
|
|
if (!TryGetKasenBeastGuideSkill(grid, out guideSkill)) return false;
|
|
return IsFriendlyToKasenGuide(map, actor, guideSkill);
|
|
}
|
|
|
|
public static bool CanClearKasenBeastGuide(MapData map, UnitData actor, GridData grid,
|
|
out KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
guideSkill = null;
|
|
if (map == null || actor == null || grid == null) return false;
|
|
if (actor.GetActionPoint(ActionPointType.Capture) <= 0) return false;
|
|
if (!TryGetKasenBeastGuideSkill(grid, out guideSkill)) return false;
|
|
return !IsFriendlyToKasenGuide(map, actor, guideSkill);
|
|
}
|
|
|
|
public static bool IsFriendlyToKasenGuide(MapData map, UnitData unit, KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
if (map == null || unit == null || guideSkill == null) return false;
|
|
if (!unit.Player(map, out var unitPlayer)) return false;
|
|
var ownerPlayerId = guideSkill.OwnerPlayerId;
|
|
return ownerPlayerId != 0 && map.SameUnion(unitPlayer.Id, ownerPlayerId);
|
|
}
|
|
|
|
public static void RemoveKasenBeastGuide(MapData map, GridData grid, bool restoreOwnerReserve)
|
|
{
|
|
if (map == null || grid == null) return;
|
|
uint ownerId = 0;
|
|
if (TryGetKasenBeastGuideSkill(grid, out var guideSkill))
|
|
ownerId = guideSkill.KasenOriginId;
|
|
grid.RemoveSpType(GridSpType.KasenBeastGuide, map);
|
|
grid.RemoveSkill(SkillType.KasenBeastGuideGrid, map);
|
|
|
|
if (restoreOwnerReserve && ownerId != 0 &&
|
|
map.UnitMap.GetUnitDataByUnitId(ownerId, out var kasen) && IsKasen(kasen))
|
|
{
|
|
SetKasenBeastGuideReserve(map, kasen, 1);
|
|
}
|
|
|
|
RefreshKasenBeastGuideStatBuffs(map);
|
|
if (map == Main.MapData)
|
|
{
|
|
grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
grid.Renderer(map)?.InstantUpdateGrid(true);
|
|
}
|
|
}
|
|
|
|
public static void SetKasenBeastGuideReserve(MapData map, UnitData kasen, int level)
|
|
{
|
|
if (!TryGetKasenBeastGuideReserve(kasen, out var reserve)) return;
|
|
reserve.SetLevel(Mathf.Clamp(level, 0, 1));
|
|
kasen?.Renderer(map)?.InstantUpdateUnit(false);
|
|
}
|
|
|
|
public static void SyncKasenBeastGuideReserve(MapData map, UnitData kasen)
|
|
{
|
|
if (!TryGetKasenBeastGuideReserve(kasen, out var reserve)) return;
|
|
reserve.SetLevel(FindKasenBeastGuideGrid(map, kasen) == null ? 1 : 0);
|
|
}
|
|
|
|
public static void RefreshKasenBeastGuideStatBuffs(MapData map)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || map.GridMap?.GridList == null) return;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
unit.RemoveSkill(SkillType.KasenBeastGuideDefenseBuff, map);
|
|
unit.RemoveSkill(SkillType.KasenBeastGuideAttackBuff, map);
|
|
}
|
|
|
|
foreach (var grid in map.GridMap.GridList)
|
|
{
|
|
if (!TryGetKasenBeastGuideSkill(grid, out var guideSkill)) continue;
|
|
guideSkill.SyncOwnerState(map);
|
|
var ownerLevel = guideSkill.OwnerLevel;
|
|
if (ownerLevel < 2) continue;
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(KasenBeastGuideAuraRange, KasenBeastGuideAuraRange, grid, buf);
|
|
foreach (var around in buf)
|
|
{
|
|
if (!around.RealUnit(map, out var unit)) continue;
|
|
if (!IsFriendlyToKasenGuide(map, unit, guideSkill)) continue;
|
|
unit.AddOrOverrideSkill(SkillType.KasenBeastGuideDefenseBuff, map, guideSkill.KasenOriginId);
|
|
if (ownerLevel >= 3)
|
|
unit.AddOrOverrideSkill(SkillType.KasenBeastGuideAttackBuff, map, guideSkill.KasenOriginId);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void ApplyKasenBeastGuideTurnStartEffects(MapData map, GridData grid,
|
|
KasenBeastGuideGridSkill guideSkill)
|
|
{
|
|
if (map == null || grid == null || guideSkill == null) return;
|
|
guideSkill.SyncOwnerState(map);
|
|
var ownerLevel = guideSkill.OwnerLevel;
|
|
if (ownerLevel < 2) return;
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(KasenBeastGuideAuraRange, KasenBeastGuideAuraRange, grid, buf);
|
|
foreach (var around in buf)
|
|
{
|
|
if (!around.RealUnit(map, out var unit)) continue;
|
|
if (!IsFriendlyToKasenGuide(map, unit, guideSkill)) continue;
|
|
Main.UnitLogic.RecoverHealth_Legacy(map, null, unit, KasenBeastGuideTurnHeal);
|
|
if (ownerLevel < 3) continue;
|
|
unit.AddSkill_Legacy(SkillType.KasenBeastGuideBerserkBuff, map, false, 0, false, -1, false,
|
|
SpecialAddSkillType.Force, guideSkill.KasenOriginId);
|
|
PlaySkillIcon(map, unit, SkillType.KasenBeastGuideBerserkBuff);
|
|
}
|
|
}
|
|
|
|
public static bool CanUseKasenBeastGuideTeleport(MapData map, UnitData unit)
|
|
{
|
|
return map != null && unit != null && unit.TreatedAsHero(map, unit);
|
|
}
|
|
|
|
public static void CollectKasenBeastGuideTeleportTargets(MapData map, UnitData unit, PlayerData player,
|
|
GridData currentGrid, List<GridData> buffer)
|
|
{
|
|
if (!CanUseKasenBeastGuideTeleport(map, unit) || player == null || currentGrid == null || buffer == null)
|
|
return;
|
|
|
|
if (TryGetKasenBeastGuideSkill(currentGrid, out var standingGuide) &&
|
|
IsFriendlyToKasenGuide(map, unit, standingGuide) &&
|
|
map.UnitMap.GetUnitDataByUnitId(standingGuide.KasenOriginId, out var ownerKasen) &&
|
|
ownerKasen.Grid(map, out var ownerGrid))
|
|
{
|
|
map.GridMap.GetAroundGridDataSet_NOCENTER(1, 1, ownerGrid, buffer);
|
|
return;
|
|
}
|
|
|
|
foreach (var guideGrid in map.GridMap.GridList)
|
|
{
|
|
if (!TryGetKasenBeastGuideSkill(guideGrid, out var guideSkill)) continue;
|
|
if (!IsFriendlyToKasenGuide(map, unit, guideSkill)) continue;
|
|
if (!map.UnitMap.GetUnitDataByUnitId(guideSkill.KasenOriginId, out var guideOwnerKasen)) continue;
|
|
if (!guideOwnerKasen.Grid(map, out var guideOwnerGrid)) continue;
|
|
if (map.GridMap.CalcDistance(currentGrid, guideOwnerGrid) > 1) continue;
|
|
buffer.Add(guideGrid);
|
|
}
|
|
}
|
|
|
|
public static UnitData FindAunnTwin(MapData map, UnitData aunn)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsAunn(aunn)) return null;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (unit.Id == aunn.Id) continue;
|
|
if (!IsAunn(unit)) continue;
|
|
if (!SameUnion(map, aunn, unit)) continue;
|
|
if (!unit.GetSkill(SkillType.AunnTwinBody, out var skill) ||
|
|
skill is not AunnTwinBodySkill twinBody) continue;
|
|
if (twinBody.AunnOwnerId == aunn.Id) return unit;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static UnitData FindAunnOwnerByTwin(MapData map, UnitData twin)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsAunn(twin)) return null;
|
|
if (!twin.GetSkill(SkillType.AunnTwinBody, out var skill) ||
|
|
skill is not AunnTwinBodySkill twinBody ||
|
|
twinBody.AunnOwnerId == 0 ||
|
|
twinBody.AunnOwnerId == twin.Id)
|
|
return null;
|
|
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (!IsAunn(unit)) continue;
|
|
if (unit.Id == twinBody.AunnOwnerId && SameUnion(map, unit, twin)) return unit;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static bool IsAunnTwinBody(UnitData unit)
|
|
{
|
|
return IsAunn(unit)
|
|
&& unit.GetSkill(SkillType.AunnTwinBody, out var skill)
|
|
&& skill is AunnTwinBodySkill twinBody
|
|
&& twinBody.AunnOwnerId != 0
|
|
&& twinBody.AunnOwnerId != unit.Id;
|
|
}
|
|
|
|
public static bool IsAunnPrimaryBody(UnitData unit)
|
|
{
|
|
return IsAunn(unit)
|
|
&& unit.GetSkill(SkillType.AunnTwinBody, out var skill)
|
|
&& skill is AunnTwinBodySkill twinBody
|
|
&& twinBody.AunnOwnerId == unit.Id;
|
|
}
|
|
|
|
public static bool TrySpawnAunnTwin(MapData map, UnitData aunn, out UnitData twin)
|
|
{
|
|
twin = null;
|
|
if (!EnsureAunnPrimaryBody(map, aunn) || HeroLevel(aunn) < 2 || CountAunnBodiesForSamePlayer(map, aunn) != 1 ||
|
|
FindAunnTwin(map, aunn) != null) return false;
|
|
if (!TryFindAunnTwinSpawnGrid(map, aunn, out var spawnGrid)) return false;
|
|
if (!TrySpawnUnitNear(map, aunn, aunn.UnitFullType, spawnGrid,
|
|
out twin)) return false;
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnPetrification, map, aunn.Id);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnTwinBody, map, aunn.Id);
|
|
NormalizeAunnTwinBodyDisplay(aunn);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnSharedHealth, map, aunn.Id);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnPetrification, map, aunn.Id);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnTwinBody, map, aunn.Id);
|
|
NormalizeAunnTwinBodyDisplay(twin);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnSharedHealth, map, aunn.Id);
|
|
MarkAunnTwinSpawnAction(map, aunn);
|
|
twin.AddActionPoint(ActionPointType.Common);
|
|
SyncAunnTwinLevelRules(map, aunn, twin);
|
|
SyncAunnSharedHealth(map, aunn, twin);
|
|
SyncAunnPortalStateDisplays(map);
|
|
return true;
|
|
}
|
|
|
|
private static void MarkAunnTwinSpawnAction(MapData map, UnitData aunn)
|
|
{
|
|
if (aunn == null || !aunn.GetSkill(SkillType.AunnTwinBody, out var skill) ||
|
|
skill is not AunnTwinBodySkill twinBody) return;
|
|
twinBody.MarkTwinSpawnedForCurrentAction(map);
|
|
}
|
|
|
|
private static bool TryFindAunnTwinSpawnGrid(MapData map, UnitData aunn, out GridData spawnGrid)
|
|
{
|
|
spawnGrid = null;
|
|
if (map == null || aunn == null || !aunn.Grid(map, out var aunnGrid)) return false;
|
|
var maxRange = map.MapConfig == null ? 1 : Mathf.CeilToInt(Mathf.Max(map.MapConfig.Width, map.MapConfig.Height));
|
|
var candidates = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(maxRange, maxRange, aunnGrid, candidates);
|
|
foreach (var candidate in candidates)
|
|
{
|
|
if (candidate == null || candidate.Id == aunnGrid.Id) continue;
|
|
if (!CanUnitLandOnGrid(map, aunn, candidate)) continue;
|
|
spawnGrid = candidate;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool EnsureAunnPrimaryBody(MapData map, UnitData aunn)
|
|
{
|
|
if (map == null || !IsAunn(aunn)) return false;
|
|
if (IsAunnTwinBody(aunn)) return false;
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnPetrification, map, aunn.Id);
|
|
if (HeroLevel(aunn) < 2)
|
|
{
|
|
aunn.RemoveSkill(SkillType.AunnTwinBody, map);
|
|
aunn.RemoveSkill(SkillType.AunnSharedHealth, map);
|
|
aunn.RemoveSkill(SkillType.AunnTwinOperable, map);
|
|
SyncAunnPortalStateDisplays(map);
|
|
return true;
|
|
}
|
|
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnTwinBody, map, aunn.Id);
|
|
NormalizeAunnTwinBodyDisplay(aunn);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnSharedHealth, map, aunn.Id);
|
|
return IsAunnPrimaryBody(aunn);
|
|
}
|
|
|
|
private static void AddAunnSkillIfMissing(UnitData unit, SkillType skillType, MapData map, uint originId)
|
|
{
|
|
if (unit == null) return;
|
|
if (unit.GetSkill(skillType, out _))
|
|
{
|
|
if (skillType == SkillType.AunnTwinBody)
|
|
unit.AddOrOverrideSkill(skillType, map, originId);
|
|
return;
|
|
}
|
|
unit.AddOrOverrideSkill(skillType, map, originId);
|
|
}
|
|
|
|
private static void NormalizeAunnTwinBodyDisplay(UnitData unit)
|
|
{
|
|
if (unit == null || !unit.GetSkill(SkillType.AunnTwinBody, out var skill)) return;
|
|
if (skill is AunnTwinBodySkill twinBodySkill)
|
|
twinBodySkill.NormalizeDisplayState();
|
|
}
|
|
|
|
private static void SyncAunnHeroDamageBearerSourceSkill(MapData map, UnitData aunn, uint originId)
|
|
{
|
|
if (map == null || !IsAunnBody(aunn)) return;
|
|
if (GetAunnBodyLevel(map, aunn) >= 3)
|
|
{
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnHeroDamageBearer, map, originId);
|
|
return;
|
|
}
|
|
|
|
if (aunn.GetSkill(SkillType.AunnHeroDamageBearer, out _))
|
|
aunn.RemoveSkill(SkillType.AunnHeroDamageBearer, map);
|
|
}
|
|
|
|
public static void SyncAunnPrimaryBodyLevelState(MapData map, UnitData aunn)
|
|
{
|
|
EnsureAunnPrimaryBody(map, aunn);
|
|
}
|
|
|
|
private static int CountAunnBodiesForSamePlayer(MapData map, UnitData aunn)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsAunn(aunn) || !map.GetPlayerIdByUnitId(aunn.Id, out var playerId))
|
|
return 0;
|
|
var count = 0;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (!IsAunn(unit)) continue;
|
|
if (!map.GetPlayerIdByUnitId(unit.Id, out var unitPlayerId) || unitPlayerId != playerId) continue;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public static int GetAunnBodyLevel(MapData map, UnitData body)
|
|
{
|
|
if (!IsAunn(body)) return 1;
|
|
var owner = FindAunnOwnerByTwin(map, body);
|
|
return HeroLevel(owner ?? body);
|
|
}
|
|
|
|
public static void SyncAunnTwinLevelRules(MapData map, UnitData aunn, UnitData twin = null)
|
|
{
|
|
if (!IsAunnPrimaryBody(aunn)) return;
|
|
twin ??= FindAunnTwin(map, aunn);
|
|
SyncAunnHeroDamageBearerSourceSkill(map, aunn, aunn.Id);
|
|
if (twin != null)
|
|
SyncAunnHeroDamageBearerSourceSkill(map, twin, aunn.Id);
|
|
if (HeroLevel(aunn) < 4 || twin == null) return;
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnTwinOperable, map, aunn.Id);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnTwinOperable, map, aunn.Id);
|
|
aunn.Renderer(map)?.InstantUpdateUnit(false);
|
|
twin.Renderer(map)?.InstantUpdateUnit(false);
|
|
SyncAunnPortalStateDisplays(map);
|
|
}
|
|
|
|
public static void SyncAunnTwinAfterHeroUpgrade(MapData map, UnitData aunn)
|
|
{
|
|
if (!EnsureAunnPrimaryBody(map, aunn)) return;
|
|
if (HeroLevel(aunn) < 2) return;
|
|
var twin = FindAunnTwin(map, aunn);
|
|
if (twin == null)
|
|
{
|
|
if (!TrySpawnAunnTwin(map, aunn, out twin)) return;
|
|
}
|
|
|
|
if (twin.UnitLevel != aunn.UnitLevel)
|
|
Main.UnitLogic.UnitTypeTransform(map, twin, aunn.UnitFullType);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnPetrification, map, aunn.Id);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnTwinBody, map, aunn.Id);
|
|
NormalizeAunnTwinBodyDisplay(aunn);
|
|
AddAunnSkillIfMissing(aunn, SkillType.AunnSharedHealth, map, aunn.Id);
|
|
SyncAunnHeroDamageBearerSourceSkill(map, aunn, aunn.Id);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnPetrification, map, aunn.Id);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnTwinBody, map, aunn.Id);
|
|
NormalizeAunnTwinBodyDisplay(twin);
|
|
AddAunnSkillIfMissing(twin, SkillType.AunnSharedHealth, map, aunn.Id);
|
|
SyncAunnHeroDamageBearerSourceSkill(map, twin, aunn.Id);
|
|
SyncAunnTwinLevelRules(map, aunn, twin);
|
|
SyncAunnSharedHealth(map, aunn, twin);
|
|
SyncAunnPortalStateDisplays(map);
|
|
}
|
|
|
|
public static void SyncAunnSharedHealth(MapData map, UnitData a, UnitData b)
|
|
{
|
|
if (a == null || b == null || !IsAunnBody(a) || !IsAunnBody(b)) return;
|
|
if (!a.IsAlive() || !b.IsAlive()) return;
|
|
var sharedHealth = Mathf.Min(a.Health, b.Health);
|
|
SetAunnSharedHealthValue(map, a, sharedHealth, DamageType.True);
|
|
SetAunnSharedHealthValue(map, b, sharedHealth, DamageType.True);
|
|
}
|
|
|
|
public static bool SyncAunnSharedHealthAfterDamage(MapData map, UnitData damagedUnit, DamageType damageType)
|
|
{
|
|
if (!TryFindAunnSharedHealthPair(map, damagedUnit, out var pair)) return false;
|
|
var sharedHealth = Mathf.Min(damagedUnit.Health, pair.Health);
|
|
SetAunnSharedHealthValue(map, damagedUnit, sharedHealth, damageType);
|
|
SetAunnSharedHealthValue(map, pair, sharedHealth, damageType);
|
|
RemoveAunnSharedHealthSyncedDeath(map, pair);
|
|
return true;
|
|
}
|
|
|
|
public static bool SyncAunnSharedHealthAfterHeal(MapData map, UnitData healedUnit)
|
|
{
|
|
if (!TryFindAunnSharedHealthPair(map, healedUnit, out var pair)) return false;
|
|
var sharedHealth = Mathf.Min(healedUnit.Health, pair.GetMaxHealth());
|
|
SetAunnSharedHealthValue(map, healedUnit, sharedHealth, DamageType.True);
|
|
SetAunnSharedHealthValue(map, pair, sharedHealth, DamageType.True);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryFindAunnSharedHealthPair(MapData map, UnitData unit, out UnitData pair)
|
|
{
|
|
pair = null;
|
|
if (map?.UnitMap?.UnitList == null || !IsAunnBody(unit)) return false;
|
|
if (!unit.GetSkill(SkillType.AunnSharedHealth, out _)) return false;
|
|
|
|
if (!unit.GetSkill(SkillType.AunnTwinBody, out var skill) ||
|
|
skill is not AunnTwinBodySkill twinBody)
|
|
return false;
|
|
|
|
if (twinBody.AunnOwnerId != 0 && twinBody.AunnOwnerId != unit.Id)
|
|
{
|
|
map.UnitMap.GetUnitDataByUnitId(twinBody.AunnOwnerId, out pair);
|
|
}
|
|
else
|
|
{
|
|
foreach (var candidate in map.UnitMap.UnitList)
|
|
{
|
|
if (!IsAunn(candidate) || candidate.Id == unit.Id) continue;
|
|
if (!candidate.GetSkill(SkillType.AunnTwinBody, out var candidateSkill) ||
|
|
candidateSkill is not AunnTwinBodySkill candidateTwinBody) continue;
|
|
if (candidateTwinBody.AunnOwnerId != unit.Id) continue;
|
|
pair = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pair != null
|
|
&& pair.IsAlive()
|
|
&& pair.GetSkill(SkillType.AunnSharedHealth, out _)
|
|
&& pair.Id != unit.Id;
|
|
}
|
|
|
|
private static void SetAunnSharedHealthValue(MapData map, UnitData unit, int health, DamageType damageType)
|
|
{
|
|
if (unit == null) return;
|
|
var oldHealth = unit.Health;
|
|
unit.Health = Mathf.Min(Mathf.Max(health, 0), unit.GetMaxHealth());
|
|
if (unit.Health < oldHealth)
|
|
RefreshUnitAfterDamage(map, unit, damageType);
|
|
else if (unit.Health > oldHealth)
|
|
RefreshUnitAfterHeal(map, unit);
|
|
}
|
|
|
|
private static void RemoveAunnSharedHealthSyncedDeath(MapData map, UnitData unit)
|
|
{
|
|
if (map == null || unit == null || !unit.IsZombie()) return;
|
|
var grid = unit.Grid(map);
|
|
var city = unit.City(map);
|
|
var renderer = unit.Renderer(map);
|
|
unit.BeforeDisappear(map);
|
|
map.OnAnyUnitDie(map, unit);
|
|
map.SetUnitDataDie(unit);
|
|
if (map != Main.MapData) return;
|
|
grid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
grid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
|
|
renderer?.Die();
|
|
city?.SetCityRenderer(map);
|
|
if (grid != null)
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, grid);
|
|
}
|
|
|
|
public static bool TryFindPairedAunn(MapData map, UnitData unit, out UnitData pair)
|
|
{
|
|
pair = null;
|
|
if (!IsAunn(unit)) return false;
|
|
pair = IsAunnTwinBody(unit) ? FindAunnOwnerByTwin(map, unit) : FindAunnTwin(map, unit);
|
|
return pair != null && pair.IsAlive() && SameUnion(map, unit, pair);
|
|
}
|
|
|
|
public static bool IsAunnPairWithinDistanceLimit(MapData map, UnitData unit, GridData unitGrid,
|
|
int maxDistance = AunnPairMaxDistance)
|
|
{
|
|
if (!TryFindPairedAunn(map, unit, out var pair)) return true;
|
|
if (unitGrid == null || !pair.Grid(map, out var pairGrid)) return true;
|
|
return map.GridMap.CalcDistance(unitGrid, pairGrid) <= maxDistance;
|
|
}
|
|
|
|
public static bool HasAunnPortalState(MapData map, UnitData unit)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || unit == null || !unit.IsAlive()) return false;
|
|
if (!unit.Grid(map, out var unitGrid)) return false;
|
|
foreach (var aunn in map.UnitMap.UnitList)
|
|
{
|
|
if (!CanAunnProvidePortal(map, aunn, unit, unitGrid, out _)) continue;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsAunnPortalSource(MapData map, UnitData unit)
|
|
{
|
|
return IsAunnBody(unit) && GetAunnBodyLevel(map, unit) >= 2;
|
|
}
|
|
|
|
public static bool HasAunnPetrifiedDefenseAuraState(MapData map, UnitData unit)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || unit == null || !unit.IsAlive()) return false;
|
|
if (!unit.Grid(map, out var unitGrid)) return false;
|
|
foreach (var aunn in map.UnitMap.UnitList)
|
|
{
|
|
if (CanAunnProvidePetrifiedDefenseAura(map, aunn, unit, unitGrid)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool HasAunnHeroDamageBearerBuffState(MapData map, UnitData unit)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || unit == null || !unit.IsAlive()) return false;
|
|
if (!unit.Grid(map, out var unitGrid)) return false;
|
|
foreach (var aunn in map.UnitMap.UnitList)
|
|
{
|
|
if (CanAunnProvideHeroDamageBearer(map, aunn, unit, unitGrid)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool CanAunnProvidePetrifiedDefenseAura(MapData map, UnitData aunn, UnitData unit,
|
|
GridData unitGrid)
|
|
{
|
|
if (map == null || !IsAunnPetrified(aunn) || unit == null || unitGrid == null) return false;
|
|
if (!aunn.IsAlive() || !unit.IsAlive()) return false;
|
|
if (aunn.Id == unit.Id) return false;
|
|
if (!SameUnion(map, aunn, unit)) return false;
|
|
if (!aunn.Grid(map, out var aunnGrid)) return false;
|
|
return map.GridMap.CalcDistance(aunnGrid, unitGrid) <= 1;
|
|
}
|
|
|
|
public static bool CanAunnProvideHeroDamageBearer(MapData map, UnitData aunn, UnitData unit,
|
|
GridData unitGrid)
|
|
{
|
|
if (!CanAunnProvidePetrifiedDefenseAura(map, aunn, unit, unitGrid)) return false;
|
|
if (IsAunnBody(unit)) return false;
|
|
if (GetAunnBodyLevel(map, aunn) < 3) return false;
|
|
return true;
|
|
}
|
|
|
|
public static bool CanAunnPortalMoveToGrid(MapData map, UnitData unit, GridData targetGrid)
|
|
{
|
|
if (targetGrid == null) return false;
|
|
return TryFindAunnPortalSourceForTarget(map, unit, targetGrid, out _, out _, out _);
|
|
}
|
|
|
|
public static bool TryFindAunnPortalSourceForTarget(MapData map, UnitData unit, GridData targetGrid,
|
|
out UnitData sourceAunn, out UnitData pairAunn, out GridData sourceGrid)
|
|
{
|
|
sourceAunn = null;
|
|
pairAunn = null;
|
|
sourceGrid = null;
|
|
if (map?.UnitMap?.UnitList == null || unit == null || targetGrid == null || !unit.IsAlive()) return false;
|
|
if (!unit.Player(map, out var player)) return false;
|
|
if (!unit.Grid(map, out var unitGrid)) return false;
|
|
if (!IsValidAunnPortalTarget(map, unit, player, targetGrid)) return false;
|
|
|
|
foreach (var aunn in map.UnitMap.UnitList)
|
|
{
|
|
if (!CanAunnProvidePortal(map, aunn, unit, unitGrid, out var pair)) continue;
|
|
if (!aunn.Grid(map, out var aunnGrid)) continue;
|
|
if (IsAunnPortalTargetNearAnchor(map, aunn, targetGrid)
|
|
|| IsAunnPortalTargetNearAnchor(map, pair, targetGrid))
|
|
{
|
|
sourceAunn = aunn;
|
|
pairAunn = pair;
|
|
sourceGrid = aunnGrid;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool IsEligibleAunnPortalAnchor(MapData map, UnitData aunn, UnitData unit)
|
|
{
|
|
if (map == null || !IsAunnBody(aunn) || unit == null) return false;
|
|
if (!aunn.IsAlive() || !unit.IsAlive()) return false;
|
|
if (GetAunnBodyLevel(map, aunn) < 2) return false;
|
|
if (!SameUnion(map, aunn, unit)) return false;
|
|
return TryFindPairedAunn(map, aunn, out var pair)
|
|
&& aunn.Grid(map, out var aunnGrid)
|
|
&& pair.Grid(map, out var pairGrid)
|
|
&& map.GridMap.CalcDistance(aunnGrid, pairGrid) <= AunnPairMaxDistance;
|
|
}
|
|
|
|
private static bool IsAunnPortalTargetNearAnchor(MapData map, UnitData aunn, GridData targetGrid)
|
|
{
|
|
return IsAunnBody(aunn)
|
|
&& aunn.IsAlive()
|
|
&& aunn.Grid(map, out var aunnGrid)
|
|
&& map.GridMap.CalcDistance(aunnGrid, targetGrid) <= AunnPortalRange;
|
|
}
|
|
|
|
public static bool CanAunnProvidePortal(MapData map, UnitData aunn, UnitData unit, GridData unitGrid,
|
|
out UnitData pair)
|
|
{
|
|
pair = null;
|
|
if (map == null || !IsAunnBody(aunn) || unit == null || unitGrid == null) return false;
|
|
if (!aunn.IsAlive() || !unit.IsAlive()) return false;
|
|
if (GetAunnBodyLevel(map, aunn) < 2) return false;
|
|
if (!SameUnion(map, aunn, unit)) return false;
|
|
if (!aunn.Grid(map, out var aunnGrid)) return false;
|
|
if (map.GridMap.CalcDistance(aunnGrid, unitGrid) > AunnPortalRange) return false;
|
|
if (!TryFindPairedAunn(map, aunn, out pair)) return false;
|
|
if (!pair.Grid(map, out var pairGrid)) return false;
|
|
if (map.GridMap.CalcDistance(aunnGrid, pairGrid) > AunnPairMaxDistance) return false;
|
|
return true;
|
|
}
|
|
|
|
public static bool IsValidAunnPortalTarget(MapData map, UnitData unit, PlayerData player, GridData targetGrid)
|
|
{
|
|
if (map == null || unit == null || player == null || targetGrid == null) return false;
|
|
if (unit.Grid(map, out var unitGrid) && unitGrid.Id == targetGrid.Id) return false;
|
|
if (!Main.UnitLogic.CheckUnitAbleForGrid_RealTimeStatus(map, unit, targetGrid)) return false;
|
|
if (targetGrid.VisibleUnit(map, player, out _)) return false;
|
|
if (unit.IsLimitMoveToSelfTerrain(map) && targetGrid.Player(map) != unit.Player(map)) return false;
|
|
return true;
|
|
}
|
|
|
|
public static bool IsAunnBody(UnitData unit)
|
|
{
|
|
return IsAunn(unit);
|
|
}
|
|
|
|
public static bool IsAunnPetrified(UnitData unit)
|
|
{
|
|
return unit != null &&
|
|
unit.GetSkill(SkillType.AunnPetrifiedState, out var skill) &&
|
|
skill is AunnPetrifiedStateSkill petrifiedState &&
|
|
petrifiedState.IsPetrified();
|
|
}
|
|
|
|
public static bool HasAnyActionPoint(UnitData unit)
|
|
{
|
|
if (unit == null || unit.IsLimitActionPoint()) return false;
|
|
return unit.ActionPoint.GetValueOrDefault(ActionPointType.Attack, 0) > 0 ||
|
|
unit.ActionPoint.GetValueOrDefault(ActionPointType.Move, 0) > 0 ||
|
|
unit.ActionPoint.GetValueOrDefault(ActionPointType.Capture, 0) > 0 ||
|
|
unit.ActionPoint.GetValueOrDefault(ActionPointType.Common, 0) > 0;
|
|
}
|
|
|
|
public static ActionPointType SelectAnyActionPoint(UnitData unit)
|
|
{
|
|
if (unit == null) return ActionPointType.Common;
|
|
if (unit.ActionPoint.GetValueOrDefault(ActionPointType.Attack, 0) > 0) return ActionPointType.Attack;
|
|
if (unit.ActionPoint.GetValueOrDefault(ActionPointType.Move, 0) > 0) return ActionPointType.Move;
|
|
if (unit.ActionPoint.GetValueOrDefault(ActionPointType.Capture, 0) > 0) return ActionPointType.Capture;
|
|
return ActionPointType.Common;
|
|
}
|
|
|
|
public static void SetAunnPetrified(MapData map, UnitData body, bool petrified)
|
|
{
|
|
if (map == null || !IsAunnBody(body)) return;
|
|
body.AddOrOverrideSkill(SkillType.AunnPetrifiedState, map, body.Id);
|
|
if (body.GetSkill(SkillType.AunnPetrifiedState, out var skill) &&
|
|
skill is AunnPetrifiedStateSkill petrifiedState)
|
|
petrifiedState.SetPetrified(petrified);
|
|
body.Renderer(map)?.InstantUpdateUnit(false);
|
|
body.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
SyncAunnPetrifiedDefenseAuras(map);
|
|
}
|
|
|
|
public static void SetAunnPetrifiedAndClearActionPoint(MapData map, UnitData body)
|
|
{
|
|
if (map == null || !IsAunnBody(body)) return;
|
|
if (IsAunnPetrified(body) && !HasAnyActionPoint(body)) return;
|
|
UnitActionRecoverHelper.ExecuteRecover(map, body);
|
|
SetAunnPetrified(map, body, true);
|
|
body.ClearActionPoint();
|
|
body.Renderer(map)?.InstantUpdateUnit(false);
|
|
}
|
|
|
|
public static void SyncAunnPetrifiedDefenseAuras(MapData map)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null) return;
|
|
var units = map.UnitMap.UnitList;
|
|
var desiredTargets = new HashSet<uint>();
|
|
|
|
foreach (var aunn in units)
|
|
{
|
|
if (!IsAunnPetrified(aunn) || !aunn.Grid(map, out var aunnGrid)) continue;
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, aunnGrid, buf);
|
|
foreach (var grid in buf)
|
|
{
|
|
if (!grid.RealUnit(map, out var target)) continue;
|
|
if (!CanAunnProvidePetrifiedDefenseAura(map, aunn, target, grid)) continue;
|
|
desiredTargets.Add(target.Id);
|
|
if (!target.GetSkill(SkillType.AunnPetrifiedDefenseAura, out _))
|
|
target.AddOrOverrideSkill(SkillType.AunnPetrifiedDefenseAura, map, aunn.Id);
|
|
}
|
|
}
|
|
|
|
foreach (var unit in units)
|
|
{
|
|
if (unit == null || !unit.GetSkill(SkillType.AunnPetrifiedDefenseAura, out _)) continue;
|
|
if (desiredTargets.Contains(unit.Id)) continue;
|
|
unit.RemoveSkill(SkillType.AunnPetrifiedDefenseAura, map);
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
foreach (var unitId in desiredTargets)
|
|
{
|
|
if (!map.UnitMap.GetUnitDataByUnitId(unitId, out var unit)) continue;
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
SyncAunnHeroDamageBearerBuffs(map);
|
|
SyncAunnPortalStateDisplays(map);
|
|
}
|
|
|
|
private static void SyncAunnHeroDamageBearerBuffs(MapData map)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null) return;
|
|
var units = map.UnitMap.UnitList;
|
|
var desiredTargets = new HashSet<uint>();
|
|
|
|
foreach (var aunn in units)
|
|
{
|
|
if (!IsAunnPetrified(aunn) || GetAunnBodyLevel(map, aunn) < 3 || !aunn.Grid(map, out var aunnGrid))
|
|
continue;
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, aunnGrid, buf);
|
|
foreach (var grid in buf)
|
|
{
|
|
if (!grid.RealUnit(map, out var target)) continue;
|
|
if (!CanAunnProvideHeroDamageBearer(map, aunn, target, grid)) continue;
|
|
desiredTargets.Add(target.Id);
|
|
if (!target.GetSkill(SkillType.AunnHeroDamageBearerBuff, out _))
|
|
target.AddOrOverrideSkill(SkillType.AunnHeroDamageBearerBuff, map, aunn.Id);
|
|
}
|
|
}
|
|
|
|
foreach (var unit in units)
|
|
{
|
|
if (unit == null || !unit.GetSkill(SkillType.AunnHeroDamageBearerBuff, out _)) continue;
|
|
if (desiredTargets.Contains(unit.Id)) continue;
|
|
unit.RemoveSkill(SkillType.AunnHeroDamageBearerBuff, map);
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
foreach (var unitId in desiredTargets)
|
|
{
|
|
if (!map.UnitMap.GetUnitDataByUnitId(unitId, out var unit)) continue;
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
}
|
|
|
|
public static void SyncAunnPortalStateDisplays(MapData map)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null) return;
|
|
var desiredTargets = new HashSet<uint>();
|
|
var units = map.UnitMap.UnitList;
|
|
|
|
foreach (var unit in units)
|
|
{
|
|
if (unit == null || !unit.IsAlive() || !unit.Grid(map, out var unitGrid)) continue;
|
|
foreach (var aunn in units)
|
|
{
|
|
if (!CanAunnProvidePortal(map, aunn, unit, unitGrid, out _)) continue;
|
|
desiredTargets.Add(unit.Id);
|
|
if (!unit.GetSkill(SkillType.AunnPortalState, out _))
|
|
unit.AddOrOverrideSkill(SkillType.AunnPortalState, map, aunn.Id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var unit in units)
|
|
{
|
|
if (unit == null || !unit.GetSkill(SkillType.AunnPortalState, out _)) continue;
|
|
if (desiredTargets.Contains(unit.Id)) continue;
|
|
if (IsAunnPortalSource(map, unit)) continue;
|
|
unit.RemoveSkill(SkillType.AunnPortalState, map);
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
foreach (var unitId in desiredTargets)
|
|
{
|
|
if (!map.UnitMap.GetUnitDataByUnitId(unitId, out var unit)) continue;
|
|
unit?.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
}
|
|
|
|
public static int ExecuteAunnAutoPetrifyBeforeTurnEnd(MapData map, PlayerData player)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || player == null) return 0;
|
|
var actionId = new CommonActionId
|
|
{
|
|
ActionType = CommonActionType.UnitAction,
|
|
UnitActionType = UnitActionType.AunnPetrify
|
|
};
|
|
var action = ActionLogicFactory.GetActionLogic(actionId);
|
|
if (action == null) return 0;
|
|
|
|
var units = new List<UnitData>(map.UnitMap.UnitList);
|
|
var count = 0;
|
|
foreach (var unit in units)
|
|
{
|
|
if (!IsAunnBody(unit)) continue;
|
|
if (!map.GetPlayerIdByUnitId(unit.Id, out var unitPlayerId) || unitPlayerId != player.Id) continue;
|
|
var param = new CommonActionParams(map, playerData: player, unitData: unit);
|
|
param.RefreshParams();
|
|
if (!action.CheckCan(param)) continue;
|
|
if (action.CompleteExecute(param)) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public static bool TryAddMiniSuikaStack(MapData map, UnitData suika, int amount)
|
|
{
|
|
if (!IsSuika(suika) || amount <= 0) return false;
|
|
var limit = HeroLevel(suika);
|
|
suika.AddSkill_Legacy(SkillType.SuikaMiniStack, map, true, 0, true, amount, true,
|
|
SpecialAddSkillType.AddLevel, suika.Id);
|
|
if (suika.GetSkill(SkillType.SuikaMiniStack, out var stack) && stack.Level > limit)
|
|
stack.SetLevel(limit);
|
|
RefreshSuikaForm(map, suika);
|
|
PlaySkillIcon(map, suika, SkillType.SuikaMiniStack);
|
|
return true;
|
|
}
|
|
|
|
public static int GetMiniSuikaStack(UnitData suika)
|
|
{
|
|
return suika != null && suika.GetSkill(SkillType.SuikaMiniStack, out var stack)
|
|
? Mathf.Max(0, stack.Level)
|
|
: 0;
|
|
}
|
|
|
|
public static void RefreshSuikaForm(MapData map, UnitData suika)
|
|
{
|
|
if (!IsSuika(suika)) return;
|
|
var level = GetMiniSuikaStack(suika);
|
|
if (level >= 3)
|
|
suika.AddOrOverrideSkill(SkillType.SuikaThrowUnitBuff, map, suika.Id);
|
|
else
|
|
suika.RemoveSkill(SkillType.SuikaThrowUnitBuff, map);
|
|
|
|
if (level >= 4)
|
|
suika.AddOrOverrideSkill(SkillType.SuikaFallingSplash, map, suika.Id);
|
|
else
|
|
suika.RemoveSkill(SkillType.SuikaFallingSplash, map);
|
|
|
|
if (level < 3)
|
|
ClearSuikaThrowState(map, suika);
|
|
|
|
suika.Renderer(map)?.InstantUpdateUnit(false);
|
|
suika.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
|
|
public static bool TrySpawnMiniSuika(MapData map, UnitData suika, GridData preferred, out UnitData mini)
|
|
{
|
|
mini = null;
|
|
if (!IsSuika(suika) || preferred == null) return false;
|
|
if (!TrySpawnUnitNear(map, suika, new UnitFullType(UnitType.SuikaMini, GiantType.None, 0), preferred,
|
|
out mini)) return false;
|
|
mini.AddOrOverrideSkill(SkillType.SuikaMiniUnitMarker, map, suika.Id);
|
|
if (mini.GetSkill(SkillType.SuikaMiniUnitMarker, out var marker))
|
|
marker.SetLevel(HeroLevel(suika));
|
|
mini.SetFullActionPoint();
|
|
if (mini.Player(map) is { } player && mini.Grid(map, out var miniGrid))
|
|
{
|
|
var exploredCount = Main.PlayerLogic.UpdateSight_LogicView(map, player,
|
|
map.GridMap.GetAroundGridIdList(mini.GetSightRange(map), miniGrid));
|
|
mini.HeroTask(map)?.OnExploredGrids(map, mini, exploredCount);
|
|
foreach (var kv in player.PlayerHeroData.HeroTaskDict)
|
|
kv.Value.OnAnyExploredGrids(map, mini, exploredCount);
|
|
}
|
|
if (map == Main.MapData)
|
|
{
|
|
RefreshCityFireForMiniSuika(map, mini);
|
|
mini.Renderer(map)?.RenderUpdateUnitGlow();
|
|
if (mini.Grid(map, out var highlightGrid))
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, highlightGrid);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static void SyncMiniSuikaAfterHeroUpgrade(MapData map, UnitData suika)
|
|
{
|
|
if (map?.UnitMap?.UnitList == null || !IsSuika(suika)) return;
|
|
var level = HeroLevel(suika);
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (unit?.UnitType != UnitType.SuikaMini) continue;
|
|
if (!unit.GetSkill(SkillType.SuikaMiniUnitMarker, out var marker)) continue;
|
|
if (marker is not SuikaMiniUnitMarkerSkill miniMarker || miniMarker.SuikaOriginId != suika.Id) continue;
|
|
if (marker.Level >= level) continue;
|
|
marker.SetLevel(level);
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
}
|
|
|
|
private static void RefreshCityFireForMiniSuika(MapData map, UnitData mini)
|
|
{
|
|
if (map != Main.MapData || mini?.UnitType != UnitType.SuikaMini) return;
|
|
if (!mini.Grid(map, out var grid)) return;
|
|
if (!grid.CityOnGrid(map, out _)) return;
|
|
grid.Renderer(map)?.InstantUpdateGrid(true);
|
|
}
|
|
|
|
public static bool TryFindSuikaForMini(MapData map, UnitData mini, out UnitData suika)
|
|
{
|
|
suika = null;
|
|
if (map == null || mini == null || !mini.Grid(map, out var miniGrid)) return false;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (!IsSuika(unit) || !SameUnion(map, mini, unit)) continue;
|
|
if (!unit.Grid(map, out var suikaGrid)) continue;
|
|
if (map.GridMap.CalcDistance(miniGrid, suikaGrid) > 1) continue;
|
|
suika = unit;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool CanMiniSuikaAttach(MapData map, UnitData mini, UnitData suika)
|
|
{
|
|
if (map == null || mini?.UnitType != UnitType.SuikaMini || !IsSuika(suika)) return false;
|
|
if (!mini.GetSkill(SkillType.SuikaMiniUnitMarker, out var marker)) return false;
|
|
if (Mathf.Max(marker.Level, HeroLevel(suika)) < 2) return false;
|
|
if (!SameUnion(map, mini, suika)) return false;
|
|
if (!mini.Grid(map, out var miniGrid) || !suika.Grid(map, out var suikaGrid)) return false;
|
|
return map.GridMap.CalcDistance(miniGrid, suikaGrid) <= 1;
|
|
}
|
|
|
|
public static bool IsSuikaThrowForm(UnitData suika)
|
|
{
|
|
return IsSuika(suika) && GetMiniSuikaStack(suika) >= 3;
|
|
}
|
|
|
|
public static bool IsSuikaGiantThrowForm(UnitData suika)
|
|
{
|
|
return IsSuika(suika) && GetMiniSuikaStack(suika) >= 4;
|
|
}
|
|
|
|
public static bool CanSuikaPrepareThrow(MapData map, UnitData suika, UnitData target)
|
|
{
|
|
if (map == null || suika == null || target == null) return false;
|
|
if (!IsSuikaThrowForm(suika)) return false;
|
|
if (suika.GetSkill(SkillType.SuikaThrowReady, out _)) return false;
|
|
if (TryFindSuikaThrownUnit(map, suika, out _)) return false;
|
|
if (!SameUnion(map, suika, target)) return false;
|
|
if (suika.Id == target.Id) return false;
|
|
if (!suika.IsAlive() || !target.IsAlive()) return false;
|
|
if (suika.GetActionPoint(ActionPointType.Attack) <= 0) return false;
|
|
if (!suika.Grid(map, out var suikaGrid) || !target.Grid(map, out var targetGrid)) return false;
|
|
return map.GridMap.CalcDistance(suikaGrid, targetGrid) <= 1;
|
|
}
|
|
|
|
public static bool ExecuteSuikaPrepareThrow(MapData map, UnitData suika, UnitData target)
|
|
{
|
|
if (!CanSuikaPrepareThrow(map, suika, target)) return false;
|
|
ClearSuikaThrowState(map, suika);
|
|
suika.AddOrOverrideSkill(SkillType.SuikaThrowReady, map, suika.Id);
|
|
target.AddOrOverrideSkill(SkillType.SuikaThrownUnit, map, suika.Id);
|
|
suika.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
target.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
return true;
|
|
}
|
|
|
|
public static bool TryFindSuikaThrownUnit(MapData map, UnitData suika, out UnitData thrownUnit)
|
|
{
|
|
thrownUnit = null;
|
|
if (map == null || suika == null) return false;
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (unit == null || !unit.IsAlive()) continue;
|
|
if (!unit.GetSkill(SkillType.SuikaThrownUnit, out var thrownSkill)) continue;
|
|
if (thrownSkill is not SuikaThrownUnitSkill suikaThrownSkill) continue;
|
|
if (suikaThrownSkill.SuikaOriginId != suika.Id) continue;
|
|
thrownUnit = unit;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool TryFindSuikaByThrownUnit(MapData map, UnitData thrownUnit, out UnitData suika)
|
|
{
|
|
suika = null;
|
|
if (map == null || thrownUnit == null) return false;
|
|
if (!thrownUnit.GetSkill(SkillType.SuikaThrownUnit, out var thrownSkill)) return false;
|
|
if (thrownSkill is not SuikaThrownUnitSkill suikaThrownSkill) return false;
|
|
if (!map.UnitMap.GetUnitDataByUnitId(suikaThrownSkill.SuikaOriginId, out suika)) return false;
|
|
return IsSuika(suika);
|
|
}
|
|
|
|
public static void ClearSuikaThrowState(MapData map, UnitData suika, UnitData thrownUnit = null)
|
|
{
|
|
if (map == null || suika == null) return;
|
|
suika.RemoveSkill(SkillType.SuikaThrowReady, map);
|
|
suika.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
if (thrownUnit != null)
|
|
{
|
|
thrownUnit.RemoveSkill(SkillType.SuikaThrownUnit, map);
|
|
thrownUnit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
return;
|
|
}
|
|
|
|
foreach (var unit in map.UnitMap.UnitList)
|
|
{
|
|
if (unit == null) continue;
|
|
if (!unit.GetSkill(SkillType.SuikaThrownUnit, out var thrownSkill)) continue;
|
|
if (thrownSkill is SuikaThrownUnitSkill suikaThrownSkill && suikaThrownSkill.SuikaOriginId == suika.Id)
|
|
{
|
|
unit.RemoveSkill(SkillType.SuikaThrownUnit, map);
|
|
unit.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static bool CanSuikaThrowToGround(MapData map, UnitData suika, GridData target)
|
|
{
|
|
if (map == null || suika == null || target == null) return false;
|
|
if (!IsSuikaThrowForm(suika)) return false;
|
|
if (!suika.GetSkill(SkillType.SuikaThrowReady, out _)) return false;
|
|
if (!TryFindSuikaThrownUnit(map, suika, out var thrownUnit)) return false;
|
|
if (!thrownUnit.IsAlive() || !SameUnion(map, suika, thrownUnit)) return false;
|
|
if (!CanUnitLandOnGrid(map, thrownUnit, target)) return false;
|
|
if (target.RealUnit(map, out _)) return false;
|
|
if (!suika.Grid(map, out var suikaGrid)) return false;
|
|
if (map.GridMap.CalcDistance(suikaGrid, target) != 3) return false;
|
|
var player = suika.Player(map);
|
|
return player?.Sight != null && player.Sight.CheckIsInSight(target.Id);
|
|
}
|
|
|
|
public static bool CanSuikaFlyToGrid(MapData map, UnitData suika, GridData target)
|
|
{
|
|
if (map == null || suika == null || target == null) return false;
|
|
if (!IsSuikaGiantThrowForm(suika)) return false;
|
|
if (suika.GetSkill(SkillType.SuikaThrowReady, out _)) return false;
|
|
if (suika.GetActionPoint(ActionPointType.Attack) <= 0) return false;
|
|
if (!CanUnitLandOnGrid(map, suika, target)) return false;
|
|
if (target.RealUnit(map, out _)) return false;
|
|
if (!suika.Grid(map, out var suikaGrid)) return false;
|
|
if (map.GridMap.CalcDistance(suikaGrid, target) != SuikaFallingSplashRange) return false;
|
|
var player = suika.Player(map);
|
|
return player?.Sight != null && player.Sight.CheckIsInSight(target.Id);
|
|
}
|
|
|
|
public static bool CanSuikaFlyAttackTarget(MapData map, UnitData suika, UnitData target)
|
|
{
|
|
if (map == null || suika == null || target == null) return false;
|
|
if (!IsSuikaGiantThrowForm(suika)) return false;
|
|
if (suika.GetSkill(SkillType.SuikaThrowReady, out _)) return false;
|
|
if (suika.GetActionPoint(ActionPointType.Attack) <= 0) return false;
|
|
if (!IsEnemy(map, suika, target)) return false;
|
|
if (!suika.Grid(map, out var suikaGrid) || !target.Grid(map, out var targetGrid)) return false;
|
|
if (map.GridMap.CalcDistance(suikaGrid, targetGrid) != SuikaFallingSplashRange) return false;
|
|
var player = suika.Player(map);
|
|
return player?.Sight != null && player.Sight.CheckIsInSight(targetGrid.Id);
|
|
}
|
|
|
|
public static bool TryExecuteSuikaFlyToGround(MapData map, UnitData suika, GridData target,
|
|
out SkillType animSkillType)
|
|
{
|
|
animSkillType = SkillType.SuikaFallingSplash;
|
|
if (!CanSuikaFlyToGrid(map, suika, target)) return false;
|
|
if (!TryRepositionUnitWithoutMoveSideEffects(map, suika, target)) return false;
|
|
suika.ClearActionPoint();
|
|
var player = suika.Player(map);
|
|
player?.Sight.UpdateSightByPath(suika, player, target.Pos.V2(), map);
|
|
return true;
|
|
}
|
|
|
|
public static bool TryExecuteSuikaFlyAfterAttack(MapData map, AttackInfo attackInfo)
|
|
{
|
|
var suika = attackInfo?.DamageOrigin;
|
|
var target = attackInfo?.DamageTarget;
|
|
var targetGrid = attackInfo?.DamageTargetGrid;
|
|
if (map == null || suika == null || target == null || targetGrid == null) return false;
|
|
if (!IsSuikaGiantThrowForm(suika)) return false;
|
|
if (suika.GetSkill(SkillType.SuikaThrowReady, out _)) return false;
|
|
if (suika.GetActionPoint(ActionPointType.Attack) > 0) return false;
|
|
if (attackInfo.OriginPlayer == null || attackInfo.TargetPlayer == null) return false;
|
|
if (map.SameUnion(attackInfo.OriginPlayer.Id, attackInfo.TargetPlayer.Id)) return false;
|
|
if (attackInfo.DamageOriginGrid == null) return false;
|
|
if (map.GridMap.CalcDistance(attackInfo.DamageOriginGrid, targetGrid) != SuikaFallingSplashRange)
|
|
return false;
|
|
if (attackInfo.OriginPlayer.Sight == null || !attackInfo.OriginPlayer.Sight.CheckIsInSight(targetGrid.Id))
|
|
return false;
|
|
|
|
ExecuteSuikaFallingSplashDamage(map, suika, targetGrid, AnimPhase.AttackImpact + 10);
|
|
|
|
if (!suika.IsValidOnMap(map) || !suika.IsAlive()) return true;
|
|
|
|
GridData landingGrid = targetGrid;
|
|
if (!CanUnitLandOnGrid(map, suika, landingGrid))
|
|
{
|
|
if (!TryFindRandomEmptyAround(map, suika, targetGrid, out landingGrid))
|
|
{
|
|
LogSuikaLandingFailure(map, attackInfo, suika, targetGrid);
|
|
RemoveUnitWithVfxAfterCurrentAttack(map, suika);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var originGrid = suika.Grid(map);
|
|
var originCity = suika.City(map);
|
|
var landingCity = landingGrid.City(map, out var city) ? city : null;
|
|
var sightRefreshGrids = CollectSuikaFallingSplashNewSightGrids(map, suika, attackInfo.OriginPlayer,
|
|
landingGrid);
|
|
|
|
if (!TryRepositionUnitWithoutMoveSideEffects(map, suika, landingGrid)) return false;
|
|
attackInfo.IsSuikaFallingSplash = true;
|
|
attackInfo.SuikaFallingSplashFinalGrid = landingGrid;
|
|
attackInfo.SuikaFallingSplashFinalCity = landingCity;
|
|
attackInfo.SuikaFallingSplashOriginCity = originCity;
|
|
attackInfo.SuikaFallingSplashSightRefreshGrids = sightRefreshGrids;
|
|
attackInfo.OriginPlayer?.Sight.UpdateSightByPath(suika, attackInfo.OriginPlayer, landingGrid.Pos.V2(), map);
|
|
return true;
|
|
}
|
|
|
|
public static List<GridData> CollectSuikaFallingSplashNewSightGrids(MapData map, UnitData suika,
|
|
PlayerData player, GridData landingGrid)
|
|
{
|
|
if (map == null || suika == null || player?.Sight == null || landingGrid == null) return null;
|
|
var range = suika.GetSightRange(map, landingGrid);
|
|
var gridIds = map.GridMap.GetAroundGridIdList(range, landingGrid);
|
|
var result = new List<GridData>();
|
|
foreach (var gid in gridIds)
|
|
{
|
|
if (player.Sight.CheckIsInSight(gid)) continue;
|
|
if (!map.GridMap.GetGridDataByGid(gid, out var grid)) continue;
|
|
result.Add(grid);
|
|
}
|
|
|
|
return result.Count > 0 ? result : null;
|
|
}
|
|
|
|
private static void ExecuteSuikaFallingSplashDamage(MapData map, UnitData suika, GridData center,
|
|
int visualPhase = AnimPhase.Settle)
|
|
{
|
|
if (map == null || suika == null || center == null) return;
|
|
if (!map.GetPlayerDataByUnitId(suika.Id, out _)) return;
|
|
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, center, buf);
|
|
foreach (var grid in buf)
|
|
{
|
|
if (grid == null) continue;
|
|
if (!grid.RealUnit(map, out var hitUnit)) continue;
|
|
if (hitUnit.Id == suika.Id) continue;
|
|
if (map.IsLeagueOrJustBreakByUnit(hitUnit.Id, suika.Id)) continue;
|
|
|
|
var damage = Table.Instance.CalcDamage(map, suika, hitUnit, damagePara: 0.5f);
|
|
var visualCollector = ActionVisualEventCollector.Current;
|
|
if (visualCollector != null && map == Main.MapData)
|
|
{
|
|
visualCollector.SettleDamageWithVisual(
|
|
suika,
|
|
hitUnit,
|
|
damage,
|
|
DamageType.Splash,
|
|
visualPhase);
|
|
continue;
|
|
}
|
|
|
|
var hitGrid = hitUnit.Grid(map);
|
|
var hitCity = hitUnit.City(map);
|
|
var hitRenderer = hitUnit.Renderer(map);
|
|
var canBeKilled = hitUnit.CanBeKilled(map);
|
|
var settlement = Main.UnitLogic.DamageSettlement(map, suika, hitUnit, damage, DamageType.Splash);
|
|
PlayDamageVisual(map, hitUnit, hitGrid, hitCity, hitRenderer, canBeKilled, settlement, damage);
|
|
}
|
|
}
|
|
|
|
private static bool TryFindRandomEmptyAround(MapData map, UnitData suika, GridData center,
|
|
out GridData emptyGrid)
|
|
{
|
|
emptyGrid = null;
|
|
if (map == null || suika == null || center == null) return false;
|
|
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, center, buf);
|
|
var candidates = new List<GridData>();
|
|
foreach (var grid in buf)
|
|
{
|
|
if (grid == null || grid == center) continue;
|
|
if (!CanUnitLandOnGrid(map, suika, grid)) continue;
|
|
candidates.Add(grid);
|
|
}
|
|
|
|
if (candidates.Count <= 0) return false;
|
|
var index = map.Net.GetRandom(map).Next(0, candidates.Count);
|
|
emptyGrid = candidates[index];
|
|
return true;
|
|
}
|
|
|
|
private static void LogSuikaLandingFailure(MapData map, AttackInfo attackInfo, UnitData suika, GridData center)
|
|
{
|
|
if (map == null || suika == null || center == null) return;
|
|
|
|
var player = attackInfo?.OriginPlayer ?? suika.Player(map);
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, center, buf);
|
|
var sb = new StringBuilder(512);
|
|
sb.Append("Suika falling splash has no landing grid. ")
|
|
.Append("suika=").Append(suika.Id)
|
|
.Append(", targetGrid=").Append(center.Id)
|
|
.Append(", targetPos=(").Append(center.Pos.X).Append(',').Append(center.Pos.Y).Append(')')
|
|
.Append(", originGrid=").Append(attackInfo?.DamageOriginGrid?.Id ?? 0)
|
|
.Append(", checked=");
|
|
|
|
foreach (var grid in buf)
|
|
{
|
|
if (grid == null) continue;
|
|
if (grid == center)
|
|
{
|
|
sb.Append("[center ").Append(grid.Id).Append("];");
|
|
continue;
|
|
}
|
|
|
|
var real = grid.RealUnit(map, out var realUnit);
|
|
UnitData visibleUnit = null;
|
|
var visible = player != null && grid.VisibleUnit(map, player, out visibleUnit);
|
|
var canLand = CanUnitLandOnGrid(map, suika, grid);
|
|
sb.Append("[grid=").Append(grid.Id)
|
|
.Append(",pos=(").Append(grid.Pos.X).Append(',').Append(grid.Pos.Y).Append(')')
|
|
.Append(",terrain=").Append(grid.Terrain)
|
|
.Append(",feature=").Append(grid.Feature)
|
|
.Append(",resource=").Append(grid.Resource)
|
|
.Append(",realUnit=").Append(real ? realUnit.Id : 0)
|
|
.Append(",visibleUnit=").Append(visible ? visibleUnit.Id : 0)
|
|
.Append(",canLand=").Append(canLand)
|
|
.Append("];");
|
|
}
|
|
|
|
LogSystem.LogWarning(sb.ToString());
|
|
}
|
|
|
|
public static bool TryExecuteSuikaThrowToGround(MapData map, UnitData suika, GridData target,
|
|
out SkillType animSkillType)
|
|
{
|
|
animSkillType = SkillType.SuikaFallingSplash;
|
|
if (!TryFindSuikaThrownUnit(map, suika, out var thrownUnit))
|
|
{
|
|
ClearSuikaThrowState(map, suika);
|
|
return false;
|
|
}
|
|
if (!CanSuikaThrowToGround(map, suika, target)) return false;
|
|
if (!TryMoveUnit(map, thrownUnit, target, MoveType.PushMove, playFogVfx: true)) return false;
|
|
RefreshSightForThrownUnit(map, thrownUnit);
|
|
|
|
var damage = Mathf.Max(1, Mathf.RoundToInt(suika.GetAllAttackValue(map)));
|
|
var impactDelay = map == Main.MapData
|
|
? GetProjectileAnimTime(ProjectileType.Bomb, 0.5f)
|
|
: 0f;
|
|
var buf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(1, 1, target, buf);
|
|
foreach (var grid in buf)
|
|
{
|
|
if (!grid.RealUnit(map, out var hitUnit)) continue;
|
|
if (!IsEnemy(map, suika, hitUnit)) continue;
|
|
|
|
var hitGrid = hitUnit.Grid(map);
|
|
var hitCity = hitUnit.City(map);
|
|
var hitRenderer = hitUnit.Renderer(map);
|
|
var canBeKilled = hitUnit.CanBeKilled(map);
|
|
var settlement = Main.UnitLogic.DamageSettlement(map, suika, hitUnit, damage, DamageType.Splash);
|
|
PlayDamageVisual(map, hitUnit, hitGrid, hitCity, hitRenderer, canBeKilled, settlement, damage,
|
|
delay: impactDelay);
|
|
}
|
|
|
|
ClearSuikaThrowState(map, suika, thrownUnit);
|
|
suika.ClearActionPoint();
|
|
return true;
|
|
}
|
|
|
|
private static void RefreshSightForThrownUnit(MapData map, UnitData unit)
|
|
{
|
|
if (map == null || unit == null) return;
|
|
var player = unit.Player(map);
|
|
if (player == null || !unit.Grid(map, out var grid)) return;
|
|
var exploredCount = Main.PlayerLogic.UpdateSight_LogicView(map, player,
|
|
map.GridMap.GetAroundGridIdList(unit.GetSightRange(map), grid));
|
|
unit.HeroTask(map)?.OnExploredGrids(map, unit, exploredCount);
|
|
foreach (var kv in player.PlayerHeroData.HeroTaskDict)
|
|
kv.Value.OnAnyExploredGrids(map, unit, exploredCount);
|
|
}
|
|
|
|
public static void PlayDamageVisual(MapData map, UnitData target, GridData targetGrid, CityData targetCity,
|
|
UnitRenderer targetRenderer, bool targetCanBeKilled, SettlementInfo settlement, int damage,
|
|
bool showoff = false, float delay = 0f)
|
|
{
|
|
if (map != Main.MapData || targetGrid == null || settlement == null) return;
|
|
|
|
void RunVisual()
|
|
{
|
|
if (map != Main.MapData || targetGrid == null) return;
|
|
|
|
var gridRenderer = targetGrid.Renderer(map);
|
|
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Hurt));
|
|
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Damage, damage));
|
|
|
|
var killed = settlement.IsKill || target == null || !target.IsAlive();
|
|
if (killed)
|
|
{
|
|
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Fog));
|
|
if (targetCanBeKilled)
|
|
gridRenderer?.PlayVFXInSight(new GridVFXParams(GridVFXType.Die));
|
|
targetRenderer?.Die();
|
|
targetCity?.SetCityRenderer(map);
|
|
MapRenderer.Instance?.UpdateAroundHighlight(map, targetGrid);
|
|
}
|
|
else
|
|
{
|
|
targetRenderer?.InstantUpdateUnit(showoff);
|
|
}
|
|
|
|
gridRenderer?.InstantUpdateGrid();
|
|
}
|
|
|
|
if (delay > 0f && Timer.Instance != null)
|
|
Timer.Instance.TimerRegister(MapRenderer.Current ?? (object)targetGrid, RunVisual, delay,
|
|
"HakureiNorwayDamageVisual");
|
|
else
|
|
RunVisual();
|
|
}
|
|
|
|
public static float GetProjectileAnimTime(ProjectileType projectileType, float fallback)
|
|
{
|
|
return Table.Instance?.ProjectileTypeDataAssets != null &&
|
|
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(projectileType, out var info)
|
|
? info.AnimTime
|
|
: fallback;
|
|
}
|
|
|
|
public static void RefreshUnitAfterDamage(MapData map, UnitData unit, DamageType damageType)
|
|
{
|
|
if (map != Main.MapData || unit == null || !unit.IsAlive()) return;
|
|
if (MapRenderer.Instance == null ||
|
|
!MapRenderer.Instance.ROUnitMap.TryGetValue(unit.Id, out var renderer)) return;
|
|
|
|
if (damageType is DamageType.ActiveAttack or DamageType.CounterAttack &&
|
|
PresentationManager.CurrentScope != null)
|
|
{
|
|
int phase = damageType == DamageType.ActiveAttack
|
|
? AnimPhase.AttackImpact + 50
|
|
: AnimPhase.CounterImpact + 50;
|
|
PresentationManager.CurrentScope.Add(new FragmentStep
|
|
{
|
|
Phase = phase,
|
|
Duration = 0f,
|
|
Execute = () => renderer.InstantUpdateUnit(false)
|
|
});
|
|
return;
|
|
}
|
|
|
|
renderer.InstantUpdateUnit(false);
|
|
}
|
|
|
|
public static void RefreshUnitAfterHeal(MapData mapData, UnitData unit)
|
|
{
|
|
if (mapData != Main.MapData || unit == null || !unit.IsAlive()) return;
|
|
unit.Grid(mapData)?.Renderer(mapData)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Heal));
|
|
unit.Renderer(mapData)?.InstantUpdateUnit(true);
|
|
}
|
|
}
|
|
|
|
public interface ISumirekoOrbAuraBuff
|
|
{
|
|
}
|
|
|
|
public partial class SumirekoOccultOrbOwnerSkill : SkillBase
|
|
{
|
|
public SumirekoOccultOrbOwnerSkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_levelLimit = 1;
|
|
_autoDisappear = false;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoOccultOrbOwner;
|
|
}
|
|
|
|
public override bool IsFinished()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
SetLevel(0);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
OriginId = originId;
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType,
|
|
List<Vector2Int> path = null)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsSumireko(self)) return;
|
|
if (!IsOccultOrbChargeMove(mapData, self, grid, moveType)) return;
|
|
if (Turns != 0) return;
|
|
SetOccultOrbLevel(mapData, self, 1);
|
|
self.AddActionPoint(ActionPointType.Attack);
|
|
Turns = 1;
|
|
HakureiNorwayHeroSkillUtil.PlaySkillIcon(mapData, self, SkillType.SumirekoOccultOrbOwner);
|
|
self.Renderer(mapData)?.InstantUpdateUnit(false);
|
|
}
|
|
|
|
private static bool IsOccultOrbChargeMove(MapData map, UnitData self, GridData target, MoveType moveType)
|
|
{
|
|
if (moveType == MoveType.ActiveMove) return true;
|
|
return moveType == MoveType.SkillMove
|
|
&& HakureiNorwayHeroSkillUtil.IsAroundUsableSumirekoOccultOrbAnchor(map, self, target);
|
|
}
|
|
|
|
public override bool IsCanAttackTargetGrid(MapData map, UnitData self, GridData target)
|
|
{
|
|
return CanShootOccultOrb(map, self, target);
|
|
}
|
|
|
|
public override bool AttackGroundExecute(MapData map, UnitData self, GridData target, out SkillType animSkillType)
|
|
{
|
|
animSkillType = SkillType.SumirekoOccultOrbOwner;
|
|
if (!CanShootOccultOrb(map, self, target)) return false;
|
|
if (!HakureiNorwayHeroSkillUtil.TryResolveSumirekoOrbGrid(map, self, target, out var orbType,
|
|
out var skillType)) return false;
|
|
animSkillType = skillType;
|
|
if (!HakureiNorwayHeroSkillUtil.TrySpawnUnitNear(map, self,
|
|
new UnitFullType(orbType, GiantType.None, 0), target, out var orb,
|
|
map == Main.MapData ? HakureiNorwayHeroSkillUtil.GetGroundAttackSpawnVisualDelay() : 0f)) return false;
|
|
|
|
if (skillType == SkillType.SumirekoEnglandOrbDamageProxy)
|
|
orb.AddSkill_Legacy(skillType, map, true, 0, true, 3, true, SpecialAddSkillType.Force, self.Id);
|
|
else
|
|
orb.AddOrOverrideSkill(skillType, map, self.Id);
|
|
if (HakureiNorwayHeroSkillUtil.HeroLevel(self) >= 4)
|
|
orb.AddOrOverrideSkill(SkillType.SumirekoOrbSwapMaxValue, map, self.Id);
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, orb);
|
|
self.HeroTask(map)?.OnSetSumirekoOccultOrb(map, self, orb);
|
|
|
|
ConsumeOccultOrbReady(map, self);
|
|
self.ClearActionPoint();
|
|
self.Renderer(map)?.InstantUpdateUnit(false);
|
|
return true;
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
Turns = 0;
|
|
SetOccultOrbLevel(mapData, self as UnitData, 0);
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
SetOccultOrbLevel(mapData, self as UnitData, 0);
|
|
}
|
|
|
|
public bool CanShootOccultOrb(MapData map, UnitData self, GridData target)
|
|
{
|
|
return self != null
|
|
&& self.GetActionPoint(ActionPointType.Attack) > 0
|
|
&& Level > 0
|
|
&& HakureiNorwayHeroSkillUtil.TryResolveSumirekoOrbGrid(map, self, target, out _, out _);
|
|
}
|
|
|
|
public bool ConsumeOccultOrbReady(MapData map, UnitData self)
|
|
{
|
|
if (Level <= 0) return false;
|
|
SetOccultOrbLevel(map, self, 0);
|
|
return true;
|
|
}
|
|
|
|
private void SetOccultOrbLevel(MapData map, UnitData self, int level)
|
|
{
|
|
SetLevel(level);
|
|
if (map == Main.MapData)
|
|
self?.Renderer(map)?.SyncStatusWithUnitSkills();
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoNorwayOrbSwapMoveAttackSkill : SkillBase
|
|
{
|
|
public SumirekoNorwayOrbSwapMoveAttackSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoNorwayOrbSwapMoveAttack;
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoNorwayOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitMove(MapData map, IdentifierBase identifier, UnitData moveUnit, GridData target,
|
|
MoveType moveType)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoNorwayOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffsForMoveEvent(map, moveUnit);
|
|
}
|
|
|
|
public override void OnAnyUnitCreate(MapData map, IdentifierBase identifier, UnitData newUnit)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoNorwayOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsForUnit(map, newUnit);
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitDie(MapData map, UnitData self, UnitData dieUnit)
|
|
{
|
|
if (self?.UnitType != UnitType.SumirekoNorwayOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(map);
|
|
}
|
|
|
|
public override void BeforeDisappear(MapData mapData, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoNorwayOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(mapData);
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoDenmarkOrbSwapAttackDefenseSkill : SkillBase
|
|
{
|
|
public SumirekoDenmarkOrbSwapAttackDefenseSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoDenmarkOrbSwapAttackDefense;
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoDenmarkOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitMove(MapData map, IdentifierBase identifier, UnitData moveUnit, GridData target,
|
|
MoveType moveType)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoDenmarkOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffsForMoveEvent(map, moveUnit);
|
|
}
|
|
|
|
public override void OnAnyUnitCreate(MapData map, IdentifierBase identifier, UnitData newUnit)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoDenmarkOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsForUnit(map, newUnit);
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitDie(MapData map, UnitData self, UnitData dieUnit)
|
|
{
|
|
if (self?.UnitType != UnitType.SumirekoDenmarkOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(map);
|
|
}
|
|
|
|
public override void BeforeDisappear(MapData mapData, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoDenmarkOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(mapData);
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoEnglandOrbDamageProxySkill : SkillBase
|
|
{
|
|
public SumirekoEnglandOrbDamageProxySkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_levelLimit = 3;
|
|
_autoDisappear = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoEnglandOrbDamageProxy;
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoEnglandOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitMove(MapData map, IdentifierBase identifier, UnitData moveUnit, GridData target,
|
|
MoveType moveType)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoEnglandOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffsForMoveEvent(map, moveUnit);
|
|
}
|
|
|
|
public override void OnAnyUnitCreate(MapData map, IdentifierBase identifier, UnitData newUnit)
|
|
{
|
|
if (identifier is not UnitData self || self.UnitType != UnitType.SumirekoEnglandOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsForUnit(map, newUnit);
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(map, self);
|
|
}
|
|
|
|
public override void OnAnyUnitDie(MapData map, UnitData self, UnitData dieUnit)
|
|
{
|
|
if (self?.UnitType != UnitType.SumirekoEnglandOrb) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(map);
|
|
}
|
|
|
|
public override void BeforeDisappear(MapData mapData, UnitData self)
|
|
{
|
|
if (self?.UnitType == UnitType.SumirekoEnglandOrb)
|
|
HakureiNorwayHeroSkillUtil.RefreshAllSumirekoOccultOrbBuffs(mapData);
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoNorwayOrbBuffSkill : SkillBase, ISumirekoOrbAuraBuff
|
|
{
|
|
public SumirekoNorwayOrbBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoNorwayOrbBuff;
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoDenmarkOrbBuffSkill : SkillBase, ISumirekoOrbAuraBuff
|
|
{
|
|
public SumirekoDenmarkOrbBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoDenmarkOrbBuff;
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoEnglandOrbBuffSkill : SkillBase, ISumirekoOrbAuraBuff
|
|
{
|
|
public SumirekoEnglandOrbBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoEnglandOrbBuff;
|
|
}
|
|
|
|
public override void BeforeDamagedSupportStage(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageTarget == null || info.DamageValue <= 0 || info.DamageBearer != null) return;
|
|
if (info.DamageType == DamageType.KillSelf) return;
|
|
if (!HakureiNorwayHeroSkillUtil.TryFindDamageProxyOrb(mapData, info.DamageTarget, out var orb,
|
|
out var proxySkill)) return;
|
|
|
|
info.DamageBearer = orb;
|
|
proxySkill.ReduceLevel(mapData, orb, 1);
|
|
if (proxySkill.Level <= 0)
|
|
{
|
|
orb.RemoveSkill(SkillType.SumirekoEnglandOrbDamageProxy, mapData);
|
|
HakureiNorwayHeroSkillUtil.RefreshSumirekoOccultOrbBuffsAround(mapData, orb);
|
|
}
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoOrbSwapMaxValueBuffSkill : SkillBase, ISumirekoOrbAuraBuff
|
|
{
|
|
public SumirekoOrbSwapMaxValueBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoOrbSwapMaxValueBuff;
|
|
}
|
|
}
|
|
|
|
public partial class SumirekoOrbSwapMaxValueSkill : SkillBase
|
|
{
|
|
public SumirekoOrbSwapMaxValueSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SumirekoOrbSwapMaxValue;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideOwnerSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideOwnerSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 0;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideOwner;
|
|
}
|
|
|
|
public override bool IsFinished()
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideAuraSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideAuraSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideAura;
|
|
}
|
|
}
|
|
|
|
public partial class KasenOniFormSkill : SkillBase
|
|
{
|
|
public KasenOniFormSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenOniForm;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideReserveSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideReserveSkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_levelLimit = 1;
|
|
_autoDisappear = false;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideReserve;
|
|
}
|
|
|
|
public override bool IsFinished()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(originId, out var kasen))
|
|
HakureiNorwayHeroSkillUtil.SyncKasenBeastGuideReserve(mapData, kasen);
|
|
else
|
|
SetLevel(1);
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsKasen(self)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncKasenBeastGuideReserve(map, self);
|
|
}
|
|
|
|
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType,
|
|
List<Vector2Int> path = null)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsKasen(self)) return;
|
|
if (moveType != MoveType.ActiveMove) return;
|
|
HakureiNorwayHeroSkillUtil.SyncKasenBeastGuideReserve(mapData, self);
|
|
if (Level <= 0) return;
|
|
if (HakureiNorwayHeroSkillUtil.FindKasenBeastGuideGrid(mapData, self) != null) return;
|
|
self.AddOrOverrideSkill(SkillType.KasenBeastGuideReady, mapData, self.Id);
|
|
HakureiNorwayHeroSkillUtil.PlaySkillIcon(mapData, self, SkillType.KasenBeastGuideReady);
|
|
self.Renderer(mapData)?.InstantUpdateUnit(false);
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is not UnitData kasen) return;
|
|
kasen.RemoveSkill(SkillType.KasenBeastGuideReady, mapData);
|
|
HakureiNorwayHeroSkillUtil.SyncKasenBeastGuideReserve(mapData, kasen);
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is UnitData kasen)
|
|
kasen.RemoveSkill(SkillType.KasenBeastGuideReady, mapData);
|
|
}
|
|
|
|
public override void OnActionExecuted(ActionLogicBase logic, CommonActionParams param, UnitData self)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsKasen(self)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncKasenBeastGuideReserve(param?.MapData, self);
|
|
HakureiNorwayHeroSkillUtil.RefreshKasenBeastGuideStatBuffs(param?.MapData);
|
|
}
|
|
|
|
public override bool IsCanAttackTargetGrid(MapData map, UnitData self, GridData target)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.CanPlaceKasenBeastGuide(map, self, target);
|
|
}
|
|
|
|
public override bool AttackGroundExecute(MapData map, UnitData self, GridData target, out SkillType animSkillType)
|
|
{
|
|
animSkillType = SkillType.KasenBeastGuideGrid;
|
|
return HakureiNorwayHeroSkillUtil.TryPlaceKasenBeastGuide(map, self, target);
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return 4;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return AttackAllyEnable(map, self, target);
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
if (mapData == null || self == null || target == null) return false;
|
|
if (!mapData.IsLeagueUnitByUnit(self.Id, target.Id)) return false;
|
|
if (!target.Grid(mapData, out var targetGrid)) return false;
|
|
return HakureiNorwayHeroSkillUtil.CanPlaceKasenBeastGuide(mapData, self, targetGrid,
|
|
requireEmptyTarget: false);
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return AttackAllyEnable(mapData, self, target);
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
if (target == null || !target.Grid(mapData, out var targetGrid)) return false;
|
|
return HakureiNorwayHeroSkillUtil.TryPlaceKasenBeastGuide(mapData, self, targetGrid);
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideReadySkill : SkillBase
|
|
{
|
|
public KasenBeastGuideReadySkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideReady;
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is UnitData unit)
|
|
unit.RemoveSkill(SkillType.KasenBeastGuideReady, mapData);
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideGridSkill : SkillBase
|
|
{
|
|
[MemoryPackInclude]
|
|
private uint _ownerPlayerId;
|
|
|
|
[MemoryPackInclude]
|
|
private int _ownerLevel;
|
|
|
|
public uint KasenOriginId => OriginId;
|
|
public uint OwnerPlayerId => _ownerPlayerId;
|
|
public int OwnerLevel => Mathf.Max(1, _ownerLevel);
|
|
|
|
public KasenBeastGuideGridSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
_ownerLevel = 1;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideGrid;
|
|
}
|
|
|
|
public void SyncOwnerState(MapData map)
|
|
{
|
|
if (map == null || OriginId == 0) return;
|
|
if (!map.UnitMap.GetUnitDataByUnitId(OriginId, out var kasen) ||
|
|
!HakureiNorwayHeroSkillUtil.IsKasen(kasen)) return;
|
|
_ownerLevel = HakureiNorwayHeroSkillUtil.HeroLevel(kasen);
|
|
if (map.GetPlayerDataByUnitId(kasen.Id, out var player))
|
|
_ownerPlayerId = player.Id;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
SyncOwnerState(mapData);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
OriginId = originId;
|
|
SyncOwnerState(mapData);
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is not GridData grid) return;
|
|
HakureiNorwayHeroSkillUtil.RefreshKasenBeastGuideStatBuffs(mapData);
|
|
HakureiNorwayHeroSkillUtil.ApplyKasenBeastGuideTurnStartEffects(mapData, grid, this);
|
|
}
|
|
|
|
public override void GetSkillCopy(SkillBase skill)
|
|
{
|
|
base.GetSkillCopy(skill);
|
|
if (skill is not KasenBeastGuideGridSkill copy) return;
|
|
copy._ownerPlayerId = _ownerPlayerId;
|
|
copy._ownerLevel = _ownerLevel;
|
|
}
|
|
|
|
public override void DeepCopy(SkillBase copySkill)
|
|
{
|
|
base.DeepCopy(copySkill);
|
|
if (copySkill is not KasenBeastGuideGridSkill copy) return;
|
|
_ownerPlayerId = copy._ownerPlayerId;
|
|
_ownerLevel = copy._ownerLevel;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideDefenseBuffSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideDefenseBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideDefenseBuff;
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override float GetDefenseAdditionParam(MapData mapData, UnitData self, UnitData target = null)
|
|
{
|
|
return 1f;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideAttackBuffSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideAttackBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideAttackBuff;
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override float GetAttackAdditionParam(MapData mapData, UnitData self, UnitData target = null)
|
|
{
|
|
return 1f;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideLv2DisplaySkill : SkillBase
|
|
{
|
|
public KasenBeastGuideLv2DisplaySkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideLv2Display;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideLv3DisplaySkill : SkillBase
|
|
{
|
|
public KasenBeastGuideLv3DisplaySkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideLv3Display;
|
|
}
|
|
}
|
|
|
|
public partial class KasenBeastGuideBerserkBuffSkill : SkillBase
|
|
{
|
|
public KasenBeastGuideBerserkBuffSkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenBeastGuideBerserkBuff;
|
|
}
|
|
}
|
|
|
|
public partial class KasenPermanentBerserkSkill : SkillBase
|
|
{
|
|
public KasenPermanentBerserkSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.KasenPermanentBerserk;
|
|
}
|
|
}
|
|
|
|
public partial class AunnPetrificationSkill : SkillBase
|
|
{
|
|
public AunnPetrificationSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnPetrification;
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(self)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPrimaryBodyLevelState(map, self);
|
|
}
|
|
|
|
public override void OnAfterTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is not UnitData aunn || !HakureiNorwayHeroSkillUtil.IsAunnBody(aunn)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPrimaryBodyLevelState(mapData, aunn);
|
|
}
|
|
|
|
public override void AfterActiveAttackOther(MapData mapData, AttackInfo attackInfo)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(attackInfo?.DamageOrigin)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPrimaryBodyLevelState(mapData, attackInfo.DamageOrigin);
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrified(mapData, attackInfo.DamageOrigin, false);
|
|
}
|
|
|
|
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType,
|
|
List<Vector2Int> path = null)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(self)) return;
|
|
if (moveType != MoveType.ActiveMove) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPrimaryBodyLevelState(mapData, self);
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrified(mapData, self, false);
|
|
}
|
|
|
|
public override void OnActionExecuted(ActionLogicBase logic, CommonActionParams param, UnitData self)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(self)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPrimaryBodyLevelState(param?.MapData, self);
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPetrifiedDefenseAuras(param?.MapData);
|
|
}
|
|
|
|
public override void OnAnyUnitMove(MapData map, IdentifierBase identifier, UnitData moveUnit, GridData target,
|
|
MoveType moveType)
|
|
{
|
|
if (identifier is not UnitData aunn || !HakureiNorwayHeroSkillUtil.IsAunnBody(aunn)) return;
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnPetrified(aunn)) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnPetrifiedDefenseAuras(map);
|
|
}
|
|
|
|
}
|
|
|
|
public partial class AunnPetrifiedStateSkill : SkillBase
|
|
{
|
|
public AunnPetrifiedStateSkill()
|
|
{
|
|
IsPermanent = true;
|
|
_levelLimit = 1;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override bool HasLevel => false;
|
|
public override bool ShowSkillLevel => false;
|
|
public override bool ShowSkill => IsPetrified();
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnPetrifiedState;
|
|
}
|
|
|
|
public void NormalizeDisplayState()
|
|
{
|
|
IsLevelSkill = false;
|
|
_level = Mathf.Clamp(_level, 0, 1);
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
NormalizeDisplayState();
|
|
SetLevel(0);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
OriginId = originId;
|
|
NormalizeDisplayState();
|
|
}
|
|
|
|
public override void GetSkillCopy(SkillBase skill)
|
|
{
|
|
base.GetSkillCopy(skill);
|
|
if (skill is AunnPetrifiedStateSkill copy)
|
|
copy.NormalizeDisplayState();
|
|
}
|
|
|
|
public override void DeepCopy(SkillBase copySkill)
|
|
{
|
|
base.DeepCopy(copySkill);
|
|
NormalizeDisplayState();
|
|
}
|
|
|
|
[MemoryPackOnDeserialized]
|
|
public void OnAfterMemoryPackDeserialize()
|
|
{
|
|
NormalizeDisplayState();
|
|
}
|
|
|
|
public override float GetDefenseAdditionParam(MapData mapData, UnitData self, UnitData target = null)
|
|
{
|
|
return IsPetrified()
|
|
? 2f
|
|
: 0f;
|
|
}
|
|
|
|
public override bool IsLimitSelfMove(UnitData self, MapData mapData)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsLimitSelfAttack(UnitData self, MapData mapData)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsLimitSelfCounterAttack(UnitData self, MapData mapData)
|
|
{
|
|
return IsPetrified();
|
|
}
|
|
|
|
public override void BeforeUnitDamaged(UnitData self, MapData mapData, SettlementInfo info)
|
|
{
|
|
if (!IsPetrified()) return;
|
|
if (self == null || info?.DamageTarget == null) return;
|
|
if (info.DamageBearer == null &&
|
|
HakureiNorwayHeroSkillUtil.CanAunnProvideHeroDamageBearer(
|
|
mapData,
|
|
self,
|
|
info.DamageTarget,
|
|
info.DamageTargetGrid))
|
|
{
|
|
info.DamageBearer = self;
|
|
}
|
|
|
|
if (info.DamageTarget.Id == self.Id || info.DamageBearer?.Id == self.Id)
|
|
{
|
|
var reduce = HakureiNorwayHeroSkillUtil.GetAunnBodyLevel(mapData, self);
|
|
info.DamageValue = Mathf.Max(0, info.DamageValue - reduce);
|
|
}
|
|
}
|
|
|
|
public bool IsPetrified()
|
|
{
|
|
return Level > 0;
|
|
}
|
|
|
|
public void SetPetrified(bool petrified)
|
|
{
|
|
NormalizeDisplayState();
|
|
SetLevel(petrified ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
public partial class AunnPetrifiedDefenseAuraSkill : SkillBase
|
|
{
|
|
public AunnPetrifiedDefenseAuraSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnPetrifiedDefenseAura;
|
|
}
|
|
|
|
public override float GetDefenseAdditionParam(MapData mapData, UnitData self, UnitData target = null)
|
|
{
|
|
if (HakureiNorwayHeroSkillUtil.IsAunnBody(self)) return 0f;
|
|
return HakureiNorwayHeroSkillUtil.HasAunnPetrifiedDefenseAuraState(mapData, self) ? 1f : 0f;
|
|
}
|
|
}
|
|
|
|
public partial class AunnHeroDamageBearerSkill : SkillBase
|
|
{
|
|
public AunnHeroDamageBearerSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnHeroDamageBearer;
|
|
}
|
|
}
|
|
|
|
public partial class AunnHeroDamageBearerBuffSkill : SkillBase
|
|
{
|
|
public AunnHeroDamageBearerBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnHeroDamageBearerBuff;
|
|
}
|
|
|
|
public override void BeforeDamagedSupportStage(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (mapData == null || info?.DamageTarget == null || info.DamageBearer != null) return;
|
|
if (OriginId == 0 || !mapData.UnitMap.GetUnitDataByUnitId(OriginId, out var aunn)) return;
|
|
if (!info.DamageTarget.Grid(mapData, out var targetGrid)) return;
|
|
if (!HakureiNorwayHeroSkillUtil.CanAunnProvideHeroDamageBearer(
|
|
mapData,
|
|
aunn,
|
|
info.DamageTarget,
|
|
targetGrid))
|
|
return;
|
|
|
|
info.DamageBearer = aunn;
|
|
}
|
|
}
|
|
|
|
public partial class AunnPortalStateSkill : SkillBase
|
|
{
|
|
public AunnPortalStateSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnPortalState;
|
|
}
|
|
}
|
|
|
|
public partial class AunnTwinBodySkill : SkillBase
|
|
{
|
|
public AunnTwinBodySkill()
|
|
{
|
|
IsPermanent = true;
|
|
_levelLimit = 1;
|
|
_autoDisappear = false;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnTwinBody;
|
|
}
|
|
|
|
public uint AunnOwnerId => OriginId;
|
|
|
|
public void NormalizeDisplayState()
|
|
{
|
|
IsLevelSkill = false;
|
|
}
|
|
|
|
public override bool IsFinished()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
NormalizeDisplayState();
|
|
SetLevel(0);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
NormalizeDisplayState();
|
|
OriginId = originId;
|
|
}
|
|
|
|
public override void OnSelfCreated(MapData map, UnitData self)
|
|
{
|
|
NormalizeDisplayState();
|
|
if (self is UnitData aunn)
|
|
HakureiNorwayHeroSkillUtil.SyncAunnTwinLevelRules(map, aunn);
|
|
}
|
|
|
|
public override void OnAfterTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
NormalizeDisplayState();
|
|
if (self is UnitData aunn)
|
|
{
|
|
HakureiNorwayHeroSkillUtil.SyncAunnTwinLevelRules(mapData, aunn);
|
|
}
|
|
}
|
|
|
|
public override void AfterActiveAttackOther(MapData mapData, AttackInfo attackInfo)
|
|
{
|
|
}
|
|
|
|
public override void OnMove(UnitData unitData, GridData grid, MapData mapData, MoveType moveType,
|
|
List<Vector2Int> path = null)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(unitData)) return;
|
|
if (moveType != MoveType.ActiveMove && moveType != MoveType.SkillMove) return;
|
|
var spawnedTwin = false;
|
|
if (HakureiNorwayHeroSkillUtil.IsAunnPrimaryBody(unitData))
|
|
spawnedTwin = HakureiNorwayHeroSkillUtil.TrySpawnAunnTwin(mapData, unitData, out _);
|
|
if (spawnedTwin) return;
|
|
if (moveType == MoveType.SkillMove)
|
|
ApplyAunnSkillMoveTwinActionResult(mapData, unitData);
|
|
}
|
|
|
|
public override void OnActionExecuted(ActionLogicBase logic, CommonActionParams param, UnitData self)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnPrimaryBody(self)) return;
|
|
var skipSpawnAction = ShouldSkipTwinActionResultForSpawnAction(logic, param?.MapData);
|
|
if (param?.UnitData == null) return;
|
|
if (!HakureiNorwayHeroSkillUtil.IsAunnBody(param.UnitData)) return;
|
|
if (self.HasEffectiveSkill(SkillType.AunnTwinOperable, out _)) return;
|
|
var twin = HakureiNorwayHeroSkillUtil.FindAunnTwin(param.MapData, self);
|
|
if (twin == null) return;
|
|
if (skipSpawnAction) return;
|
|
if (!TryGetAunnTwinActionResult(logic, param.UnitData, self, twin, out var actor, out var waitingBody))
|
|
return;
|
|
|
|
if (!IsManualAunnPetrifyAction(logic))
|
|
SetAunnPetrified(param.MapData, actor, false);
|
|
SetAunnPetrifiedAndClearActionPoint(param.MapData, waitingBody);
|
|
}
|
|
|
|
[MemoryPackIgnore]
|
|
private int _skipTwinActionResultActionIndex = -1;
|
|
|
|
public void MarkTwinSpawnedForCurrentAction(MapData mapData)
|
|
{
|
|
var currentActionIndex = GetCurrentActionIndex(mapData);
|
|
if (currentActionIndex <= 0) return;
|
|
_skipTwinActionResultActionIndex = currentActionIndex;
|
|
}
|
|
|
|
private bool ShouldSkipTwinActionResultForSpawnAction(ActionLogicBase logic, MapData mapData)
|
|
{
|
|
if (_skipTwinActionResultActionIndex < 0) return false;
|
|
var currentActionIndex = GetCurrentActionIndex(mapData);
|
|
var skip = currentActionIndex == _skipTwinActionResultActionIndex &&
|
|
IsAunnTwinSpawnInitializationAction(logic);
|
|
_skipTwinActionResultActionIndex = -1;
|
|
return skip;
|
|
}
|
|
|
|
private static int GetCurrentActionIndex(MapData mapData)
|
|
{
|
|
return mapData?.Net?.Actions?.Count ?? -1;
|
|
}
|
|
|
|
private static bool IsAunnTwinSpawnInitializationAction(ActionLogicBase logic)
|
|
{
|
|
var actionId = logic?.ActionId;
|
|
if (actionId == null) return false;
|
|
if (actionId.ActionType == CommonActionType.TrainUnit ||
|
|
actionId.ActionType == CommonActionType.UnitMove)
|
|
return true;
|
|
return actionId.ActionType == CommonActionType.UnitAction &&
|
|
actionId.UnitActionType == UnitActionType.HeroUpgrade;
|
|
}
|
|
|
|
private static bool TryGetAunnTwinActionResult(ActionLogicBase logic, UnitData actionUnit, UnitData primary,
|
|
UnitData twin, out UnitData actor, out UnitData waitingBody)
|
|
{
|
|
actor = null;
|
|
waitingBody = null;
|
|
if (logic?.ActionId?.ActionType == CommonActionType.AIParamControl) return false;
|
|
if (actionUnit.Id == primary.Id)
|
|
{
|
|
actor = primary;
|
|
waitingBody = twin;
|
|
return true;
|
|
}
|
|
|
|
if (actionUnit.Id == twin.Id)
|
|
{
|
|
actor = twin;
|
|
waitingBody = primary;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool IsManualAunnPetrifyAction(ActionLogicBase logic)
|
|
{
|
|
return logic?.ActionId?.ActionType == CommonActionType.UnitAction
|
|
&& logic.ActionId.UnitActionType == UnitActionType.AunnPetrify;
|
|
}
|
|
|
|
private static void ApplyAunnSkillMoveTwinActionResult(MapData mapData, UnitData actor)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.TryFindPairedAunn(mapData, actor, out var waitingBody)) return;
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrified(mapData, actor, false);
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrifiedAndClearActionPoint(mapData, waitingBody);
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return base.GetAttackAllyRange(mapData, self);
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
}
|
|
|
|
private void SetAunnPetrified(MapData mapData, UnitData body, bool petrified)
|
|
{
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrified(mapData, body, petrified);
|
|
}
|
|
|
|
private void SetAunnPetrifiedAndClearActionPoint(MapData mapData, UnitData body)
|
|
{
|
|
HakureiNorwayHeroSkillUtil.SetAunnPetrifiedAndClearActionPoint(mapData, body);
|
|
}
|
|
}
|
|
|
|
public partial class AunnSharedHealthSkill : SkillBase
|
|
{
|
|
public AunnSharedHealthSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnSharedHealth;
|
|
}
|
|
|
|
public override void OnDamaged(MapData mapData, SettlementInfo info)
|
|
{
|
|
var damagedUnit = GetActualDamagedUnit(info);
|
|
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterDamage(mapData, damagedUnit, info?.DamageType ?? DamageType.True);
|
|
}
|
|
|
|
public override void OnUnitDamaged(UnitData self, MapData mapData, SettlementInfo info)
|
|
{
|
|
var damagedUnit = GetActualDamagedUnit(info);
|
|
if (self == null || damagedUnit == null || self.Id != damagedUnit.Id) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterDamage(mapData, damagedUnit, info.DamageType);
|
|
}
|
|
|
|
public override void OnHealedSelf(MapData mapData, UnitData self, UnitData origin, HealType healType,
|
|
int realHealed)
|
|
{
|
|
if (realHealed <= 0) return;
|
|
HakureiNorwayHeroSkillUtil.SyncAunnSharedHealthAfterHeal(mapData, self);
|
|
}
|
|
|
|
private static UnitData GetActualDamagedUnit(SettlementInfo info)
|
|
{
|
|
return info?.DamageBearer ?? info?.DamageTarget;
|
|
}
|
|
}
|
|
|
|
public partial class AunnTwinOperableSkill : SkillBase
|
|
{
|
|
public AunnTwinOperableSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.AunnTwinOperable;
|
|
}
|
|
}
|
|
|
|
public partial class SuikaMiniSpawnAfterMoveSkill : SkillBase
|
|
{
|
|
public SuikaMiniSpawnAfterMoveSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaMiniSpawnAfterMove;
|
|
}
|
|
|
|
public override void OnMove(UnitData self, GridData grid, MapData mapData, MoveType moveType,
|
|
List<Vector2Int> path = null)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsSuika(self) || moveType != MoveType.ActiveMove) return;
|
|
var originGrid = grid;
|
|
if (path is { Count: > 0 } && mapData.GridMap.GetGridDataByV2(path[0], out var pathOriginGrid))
|
|
originGrid = pathOriginGrid;
|
|
if (HakureiNorwayHeroSkillUtil.TryFindFirstEmptyAround(mapData, originGrid, 1, 1, out var spawnGrid))
|
|
HakureiNorwayHeroSkillUtil.TrySpawnMiniSuika(mapData, self, spawnGrid, out _);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaMiniStackSkill : SkillBase
|
|
{
|
|
public SuikaMiniStackSkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_levelLimit = 4;
|
|
_autoDisappear = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaMiniStack;
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
base.OnSkillOverride(mapData, originId, selfId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(selfId, out var suika))
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyConsumeActionPointBeforeExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public partial class SuikaDamageHalveDropMiniSkill : SkillBase
|
|
{
|
|
public SuikaDamageHalveDropMiniSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaDamageHalveDropMini;
|
|
}
|
|
|
|
public override void BeforeDamagedSupportStage(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (!HakureiNorwayHeroSkillUtil.IsSuika(info?.DamageTarget)) return;
|
|
var suika = info.DamageTarget;
|
|
if (!suika.GetSkill(SkillType.SuikaMiniStack, out var stack) || stack.Level <= 0) return;
|
|
if (!suika.Grid(mapData, out var grid)) return;
|
|
info.DamageValue = Mathf.CeilToInt(info.DamageValue * 0.5f);
|
|
stack.ReduceLevel(mapData, suika, 1);
|
|
if (HakureiNorwayHeroSkillUtil.TryFindFirstEmptyAround(mapData, grid, 1, 1, out var spawnGrid))
|
|
HakureiNorwayHeroSkillUtil.TrySpawnMiniSuika(mapData, suika, spawnGrid, out _);
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaBigFormSkill : SkillBase
|
|
{
|
|
public SuikaBigFormSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaBigForm;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(originId, out var suika))
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
base.OnSkillOverride(mapData, originId, selfId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(selfId, out var suika))
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaGiantFormSkill : SkillBase
|
|
{
|
|
public SuikaGiantFormSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaGiantForm;
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(originId, out var suika))
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
base.OnSkillOverride(mapData, originId, selfId);
|
|
if (mapData?.UnitMap != null && mapData.UnitMap.GetUnitDataByUnitId(selfId, out var suika))
|
|
HakureiNorwayHeroSkillUtil.RefreshSuikaForm(mapData, suika);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaThrowUnitBuffSkill : SkillBase
|
|
{
|
|
public SuikaThrowUnitBuffSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaThrowUnitBuff;
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.CanSuikaPrepareThrow(map, self, target);
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.CanSuikaPrepareThrow(mapData, self, target);
|
|
}
|
|
|
|
public override bool AttackAllyConsumeActionPointBeforeExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return AttackAllyEnable(mapData, self, target);
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.ExecuteSuikaPrepareThrow(mapData, self, target);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaFallingSplashSkill : SkillBase
|
|
{
|
|
public SuikaFallingSplashSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaFallingSplash;
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyConsumeActionPointBeforeExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetGrid(MapData map, UnitData self, GridData target)
|
|
{
|
|
if (self != null && self.GetSkill(SkillType.SuikaThrowReady, out _))
|
|
return HakureiNorwayHeroSkillUtil.CanSuikaThrowToGround(map, self, target);
|
|
return HakureiNorwayHeroSkillUtil.CanSuikaFlyToGrid(map, self, target);
|
|
}
|
|
|
|
public override bool AttackGroundExecute(MapData map, UnitData self, GridData target, out SkillType animSkillType)
|
|
{
|
|
if (self != null && self.GetSkill(SkillType.SuikaThrowReady, out _))
|
|
return HakureiNorwayHeroSkillUtil.TryExecuteSuikaThrowToGround(map, self, target, out animSkillType);
|
|
return HakureiNorwayHeroSkillUtil.TryExecuteSuikaFlyToGround(map, self, target, out animSkillType);
|
|
}
|
|
|
|
public override void AfterActiveAttackOther(MapData mapData, AttackInfo attackInfo)
|
|
{
|
|
HakureiNorwayHeroSkillUtil.TryExecuteSuikaFlyAfterAttack(mapData, attackInfo);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaThrowReadySkill : SkillBase
|
|
{
|
|
public SuikaThrowReadySkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaThrowReady;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetGrid(MapData map, UnitData self, GridData target)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.CanSuikaThrowToGround(map, self, target);
|
|
}
|
|
|
|
public override bool AttackGroundExecute(MapData map, UnitData self, GridData target, out SkillType animSkillType)
|
|
{
|
|
return HakureiNorwayHeroSkillUtil.TryExecuteSuikaThrowToGround(map, self, target, out animSkillType);
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is UnitData suika)
|
|
HakureiNorwayHeroSkillUtil.ClearSuikaThrowState(mapData, suika);
|
|
}
|
|
|
|
public override void BeforeDisappear(MapData mapData, UnitData self)
|
|
{
|
|
if (self is UnitData suika)
|
|
HakureiNorwayHeroSkillUtil.ClearSuikaThrowState(mapData, suika);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaThrownUnitSkill : SkillBase
|
|
{
|
|
public SuikaThrownUnitSkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public uint SuikaOriginId => OriginId;
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaThrownUnit;
|
|
}
|
|
|
|
public override void OnTurnStart(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is not UnitData thrownUnit) return;
|
|
if (!mapData.UnitMap.GetUnitDataByUnitId(OriginId, out var suika) ||
|
|
!HakureiNorwayHeroSkillUtil.IsSuika(suika))
|
|
{
|
|
thrownUnit.RemoveSkill(SkillType.SuikaThrownUnit, mapData);
|
|
return;
|
|
}
|
|
|
|
HakureiNorwayHeroSkillUtil.ClearSuikaThrowState(mapData, suika, thrownUnit);
|
|
}
|
|
|
|
public override void BeforeDisappear(MapData mapData, UnitData self)
|
|
{
|
|
if (self is not UnitData thrownUnit) return;
|
|
if (HakureiNorwayHeroSkillUtil.TryFindSuikaByThrownUnit(mapData, thrownUnit, out var suika))
|
|
HakureiNorwayHeroSkillUtil.ClearSuikaThrowState(mapData, suika, thrownUnit);
|
|
}
|
|
}
|
|
|
|
public partial class SuikaMiniUnitMarkerSkill : SkillBase
|
|
{
|
|
public uint SuikaOriginId => OriginId;
|
|
|
|
public SuikaMiniUnitMarkerSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.SuikaMiniUnitMarker;
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return self != null
|
|
&& self.GetActionPoint(ActionPointType.Attack) > 0
|
|
&& HakureiNorwayHeroSkillUtil.CanMiniSuikaAttach(map, self, target);
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
public override bool AttackAllyEnable(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return IsCanAttackTargetAlly(mapData, self, target);
|
|
}
|
|
|
|
public override bool AttackAllyExecute(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
if (!IsCanAttackTargetAlly(mapData, self, target)) return false;
|
|
if (!HakureiNorwayHeroSkillUtil.TryAddMiniSuikaStack(mapData, target, 1)) return false;
|
|
Main.UnitLogic.RecoverHealth(mapData, self, target, 1, HealType.AttackAllyHeal);
|
|
HakureiNorwayHeroSkillUtil.RemoveUnitWithVfx(mapData, self);
|
|
return true;
|
|
}
|
|
|
|
public override bool AttackAllyIsNotProjectile(MapData mapData, UnitData self, UnitData target)
|
|
{
|
|
return AttackAllyEnable(mapData, self, target);
|
|
}
|
|
}
|
|
}
|