TH1/Unity/Assets/Scripts/TH1_Logic/Skill/AllSkill/HakureiNorwayHeroSkill.cs
2026-06-26 16:36:01 +08:00

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);
}
}
}