1014 lines
38 KiB
C#
1014 lines
38 KiB
C#
/*
|
|
* @Author: Codex
|
|
* @Description: Norway Reimu hero skills and ofuda markers
|
|
* @Date: 2026年06月15日 星期一
|
|
* @Modify:
|
|
*/
|
|
|
|
using MemoryPack;
|
|
using System.Collections.Generic;
|
|
using RuntimeData;
|
|
using TH1_Anim.Fragments;
|
|
using TH1_Core.Events;
|
|
using TH1_Core.Managers;
|
|
using TH1_Logic.Core;
|
|
using TH1_Renderer;
|
|
using TH1Renderer;
|
|
using UnityEngine;
|
|
|
|
namespace Logic.Skill
|
|
{
|
|
public static class ReimuNorwaySkillUtil
|
|
{
|
|
private const int ProtectionCastRange = 2;
|
|
private const float ExterminationOrbInterval = 0.06f;
|
|
private const float DefaultExterminationOrbProjectileTime = 0.24f;
|
|
private static readonly ProjectileType[] DreamOrbProjectiles =
|
|
{
|
|
ProjectileType.ReimuDreamOrb1Red,
|
|
ProjectileType.ReimuDreamOrb2Blue,
|
|
ProjectileType.ReimuDreamOrb3Green,
|
|
ProjectileType.ReimuDreamOrb4Purple,
|
|
ProjectileType.ReimuDreamOrb5Gold
|
|
};
|
|
|
|
public static bool IsReimu(UnitData unit)
|
|
{
|
|
return unit != null && unit.UnitType == UnitType.Giant && unit.GiantType == GiantType.NorwayReimu;
|
|
}
|
|
|
|
public static int GetReimuLevel(UnitData reimu)
|
|
{
|
|
if (reimu == null) return 1;
|
|
return Mathf.Max(1, (int)reimu.UnitLevel);
|
|
}
|
|
|
|
public static int GetProtectionCastRange(UnitData reimu)
|
|
{
|
|
return ProtectionCastRange;
|
|
}
|
|
|
|
public static int GetExterminationDamagePerLayer(UnitData reimu)
|
|
{
|
|
return reimu != null && reimu.HasEffectiveSkill(SkillType.ReimuFantasyNature, out _) ? 5 : 2;
|
|
}
|
|
|
|
public static void EnqueueExterminationOrbProjectiles(MapData map, SettlementInfo info, int layers)
|
|
{
|
|
if (layers <= 0 || map != Main.MapData) return;
|
|
if (info?.DamageOriginGrid == null || info.DamageTargetGrid == null) return;
|
|
var scope = PresentationManager.CurrentScope;
|
|
if (scope == null) return;
|
|
if (MapRenderer.Current?.ProjectileManager == null) return;
|
|
|
|
float projectileTime = GetExterminationOrbProjectileTime();
|
|
for (int i = 0; i < layers; i++)
|
|
{
|
|
float delay = i < layers - 1 ? ExterminationOrbInterval : projectileTime;
|
|
scope.Add(new FragmentStep
|
|
{
|
|
Phase = AnimPhase.AttackImpact - 1,
|
|
Duration = delay,
|
|
Execute = () => CreateExterminationOrbProjectile(info.DamageOriginGrid, info.DamageTargetGrid)
|
|
});
|
|
}
|
|
}
|
|
|
|
private static float GetExterminationOrbProjectileTime()
|
|
{
|
|
foreach (var projectileType in DreamOrbProjectiles)
|
|
{
|
|
if (Table.Instance?.ProjectileTypeDataAssets != null &&
|
|
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(projectileType, out var info))
|
|
return info.AnimTime;
|
|
}
|
|
|
|
if (Table.Instance?.ProjectileTypeDataAssets != null &&
|
|
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(
|
|
ProjectileType.ReimuExterminationOrb, out var projectileInfo))
|
|
return projectileInfo.AnimTime;
|
|
|
|
return DefaultExterminationOrbProjectileTime;
|
|
}
|
|
|
|
private static ProjectileType GetExterminationOrbProjectile(GridData originGrid, GridData targetGrid)
|
|
{
|
|
var hash = 17;
|
|
hash = hash * 31 + (int)(originGrid?.Id ?? 0);
|
|
hash = hash * 31 + (int)(targetGrid?.Id ?? 0);
|
|
hash = hash * 31 + (int)Time.frameCount;
|
|
var projectileType = DreamOrbProjectiles[unchecked((int)((uint)hash % DreamOrbProjectiles.Length))];
|
|
if (Table.Instance?.ProjectileTypeDataAssets != null &&
|
|
Table.Instance.ProjectileTypeDataAssets.GetProjectileTypeInfo(projectileType, out _))
|
|
return projectileType;
|
|
|
|
return ProjectileType.ReimuExterminationOrb;
|
|
}
|
|
|
|
private static void CreateExterminationOrbProjectile(GridData originGrid, GridData targetGrid)
|
|
{
|
|
var projectileManager = MapRenderer.Current?.ProjectileManager;
|
|
if (originGrid == null || targetGrid == null || projectileManager == null || Table.Instance == null)
|
|
return;
|
|
|
|
Vector3 startPos = Table.Instance.GridToWorld(originGrid, "isUnit");
|
|
Vector3 endPos = Table.Instance.GridToWorld(targetGrid, "isUnit");
|
|
projectileManager.CreateProjectile(startPos, endPos, GetExterminationOrbProjectile(originGrid, targetGrid));
|
|
}
|
|
|
|
private static float PlayExterminationOrbProjectilesImmediate(
|
|
MapData map,
|
|
GridData originGrid,
|
|
GridData targetGrid,
|
|
int layers)
|
|
{
|
|
if (layers <= 0 || map != Main.MapData) return 0f;
|
|
if (originGrid == null || targetGrid == null) return 0f;
|
|
var mapRenderer = MapRenderer.Current;
|
|
if (mapRenderer?.ProjectileManager == null) return 0f;
|
|
|
|
float projectileTime = GetExterminationOrbProjectileTime();
|
|
for (int i = 0; i < layers; i++)
|
|
{
|
|
float delay = ExterminationOrbInterval * i;
|
|
if (delay > 0f && Timer.Instance != null)
|
|
{
|
|
Timer.Instance.TimerRegister(
|
|
mapRenderer,
|
|
() => CreateExterminationOrbProjectile(originGrid, targetGrid),
|
|
delay,
|
|
"ReimuExterminationPulseProjectile");
|
|
}
|
|
else
|
|
{
|
|
CreateExterminationOrbProjectile(originGrid, targetGrid);
|
|
}
|
|
}
|
|
|
|
return projectileTime + ExterminationOrbInterval * Mathf.Max(0, layers - 1);
|
|
}
|
|
|
|
public static bool SameUnionByOrigin(MapData map, uint originId, UnitData target)
|
|
{
|
|
if (map == null || target == null) return false;
|
|
if (!map.GetPlayerDataByUnitId(originId, out var originPlayer)) return false;
|
|
if (!map.GetPlayerDataByUnitId(target.Id, out var targetPlayer)) return false;
|
|
return map.SameUnion(originPlayer.Id, targetPlayer.Id);
|
|
}
|
|
|
|
public static bool IsEnemyToOrigin(MapData map, uint originId, UnitData target)
|
|
{
|
|
if (map == null || target == null) return false;
|
|
if (!map.GetPlayerDataByUnitId(originId, out var originPlayer)) return false;
|
|
if (!map.GetPlayerDataByUnitId(target.Id, out var targetPlayer)) return false;
|
|
return !map.SameUnion(originPlayer.Id, targetPlayer.Id);
|
|
}
|
|
|
|
public static bool TryGetOriginPlayer(MapData map, uint originId, out PlayerData player)
|
|
{
|
|
player = null;
|
|
return map != null && map.GetPlayerDataByUnitId(originId, out player) && player != null;
|
|
}
|
|
|
|
public static bool TryGetOriginPlayer(MapData map, uint originPlayerId, uint originId, out PlayerData player)
|
|
{
|
|
player = null;
|
|
if (map?.PlayerMap != null && originPlayerId != 0 &&
|
|
map.PlayerMap.GetPlayerDataByPlayerID(originPlayerId, out player) && player != null)
|
|
return true;
|
|
|
|
return TryGetOriginPlayer(map, originId, out player);
|
|
}
|
|
|
|
public static bool TryAwardOriginOwner(MapData map, uint originId, int amount, GridData sourceGrid)
|
|
{
|
|
if (amount <= 0) return false;
|
|
if (!TryGetOriginPlayer(map, originId, out var player)) return false;
|
|
player.AddCoin(amount, sourceGrid);
|
|
sourceGrid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Coin));
|
|
return true;
|
|
}
|
|
|
|
public static bool TryAwardOriginOwner(MapData map, uint originPlayerId, uint originId, int amount,
|
|
GridData sourceGrid)
|
|
{
|
|
if (amount <= 0) return false;
|
|
if (!TryGetOriginPlayer(map, originPlayerId, originId, out var player)) return false;
|
|
player.AddCoin(amount, sourceGrid);
|
|
sourceGrid?.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.Coin));
|
|
return true;
|
|
}
|
|
|
|
public static void EnqueueClearExterminationPaymentNotice(
|
|
MapData map,
|
|
PlayerData payer,
|
|
uint receiverPlayerId,
|
|
uint receiverOriginId,
|
|
UnitData target,
|
|
int coinAmount,
|
|
int layerCount)
|
|
{
|
|
EnqueueHakureiPaymentNotice(
|
|
map,
|
|
HakureiPaymentNoticeType.ClearExtermination,
|
|
payer,
|
|
receiverPlayerId,
|
|
receiverOriginId,
|
|
coinAmount,
|
|
target?.Id ?? 0,
|
|
layerCount,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
public static void EnqueueDanegeldPaymentNotice(
|
|
MapData map,
|
|
PlayerData payer,
|
|
uint receiverPlayerId,
|
|
int coinAmount,
|
|
uint targetPlayerId = 0,
|
|
uint targetCityId = 0)
|
|
{
|
|
EnqueueHakureiPaymentNotice(
|
|
map,
|
|
HakureiPaymentNoticeType.Danegeld,
|
|
payer,
|
|
receiverPlayerId,
|
|
0,
|
|
coinAmount,
|
|
0,
|
|
0,
|
|
targetPlayerId,
|
|
targetCityId);
|
|
}
|
|
|
|
public static void EnqueueDanegeldRejectedNotice(
|
|
MapData map,
|
|
PlayerData refusingPlayer,
|
|
uint receiverPlayerId,
|
|
int demandedCoinAmount,
|
|
uint targetPlayerId = 0,
|
|
uint targetCityId = 0)
|
|
{
|
|
EnqueueHakureiPaymentNotice(
|
|
map,
|
|
HakureiPaymentNoticeType.DanegeldRejected,
|
|
refusingPlayer,
|
|
receiverPlayerId,
|
|
0,
|
|
demandedCoinAmount,
|
|
0,
|
|
0,
|
|
targetPlayerId,
|
|
targetCityId);
|
|
}
|
|
|
|
private static void EnqueueHakureiPaymentNotice(
|
|
MapData map,
|
|
HakureiPaymentNoticeType noticeType,
|
|
PlayerData payer,
|
|
uint receiverPlayerId,
|
|
uint receiverOriginId,
|
|
int coinAmount,
|
|
uint targetUnitId,
|
|
int layerCount,
|
|
uint targetPlayerId,
|
|
uint targetCityId)
|
|
{
|
|
if (map == null || payer == null || coinAmount < 0) return;
|
|
if (map != Main.MapData) return;
|
|
if (!TryGetOriginPlayer(map, receiverPlayerId, receiverOriginId, out var receiver)) return;
|
|
if (!map.CanLocalControlPlayer(receiver.Id)) return;
|
|
|
|
PresentationManager.AddPendingHakureiPaymentNotice(map, new ShowUIAnnounceHakureiPayment
|
|
{
|
|
NoticeType = noticeType,
|
|
PayerPlayerId = payer.Id,
|
|
ReceiverPlayerId = receiver.Id,
|
|
CoinAmount = coinAmount,
|
|
TargetUnitId = targetUnitId,
|
|
LayerCount = layerCount,
|
|
TargetPlayerId = targetPlayerId,
|
|
TargetCityId = targetCityId
|
|
});
|
|
}
|
|
|
|
private static int GetCurrentOriginReimuLevel(MapData map, ReimuExterminationSkill extermination)
|
|
{
|
|
var reimuLevel = 0;
|
|
if (TryGetOriginPlayer(map, extermination.OriginPlayerId, extermination.ReimuOriginId,
|
|
out var originPlayer))
|
|
{
|
|
reimuLevel = (int)(originPlayer.PlayerHeroData?.GetHeroLevel(GiantType.NorwayReimu) ?? 0);
|
|
}
|
|
|
|
if (reimuLevel <= 0 && map != null &&
|
|
map.UnitMap.GetUnitDataByUnitId(extermination.ReimuOriginId, out var reimu))
|
|
reimuLevel = GetReimuLevel(reimu);
|
|
|
|
if (reimuLevel <= 0)
|
|
reimuLevel = extermination.OriginReimuLevel;
|
|
|
|
return Mathf.Max(1, reimuLevel);
|
|
}
|
|
|
|
public static bool IsValidProtectionTarget(MapData map, UnitData reimu, UnitData target)
|
|
{
|
|
if (map == null || reimu == null || target == null) return false;
|
|
if (!reimu.IsValidOnMap(map) || !target.IsValidOnMap(map)) return false;
|
|
if (!map.GetPlayerDataByUnitId(reimu.Id, out var reimuPlayer)) return false;
|
|
if (!map.GetPlayerDataByUnitId(target.Id, out var targetPlayer)) return false;
|
|
if (!map.SameUnion(reimuPlayer.Id, targetPlayer.Id)) return false;
|
|
var reimuGrid = reimu.Grid(map);
|
|
var targetGrid = target.Grid(map);
|
|
if (reimuGrid == null || targetGrid == null) return false;
|
|
return map.GridMap.CalcDistance(reimuGrid, targetGrid) <= GetProtectionCastRange(reimu);
|
|
}
|
|
|
|
public static bool ApplyHakureiProtection(MapData map, UnitData reimu, UnitData target)
|
|
{
|
|
if (!IsValidProtectionTarget(map, reimu, target)) return false;
|
|
target.AddSkill_Legacy(SkillType.HakureiProtection, map, true, 0, true, 4, true,
|
|
SpecialAddSkillType.AddLevel, reimu.Id);
|
|
PlaySkillIconScopeAware(map, target, SkillType.HakureiProtection);
|
|
return true;
|
|
}
|
|
|
|
public static bool ApplyExtermination(MapData map, UnitData reimu, UnitData target)
|
|
{
|
|
if (map == null || !IsReimu(reimu) || target == null) return false;
|
|
if (!reimu.IsValidOnMap(map) || !target.IsValidOnMap(map)) return false;
|
|
if (map.IsLeagueUnitByUnit(reimu.Id, target.Id)) return false;
|
|
|
|
var cap = GetReimuLevel(reimu);
|
|
target.AddSkill_Legacy(SkillType.ReimuExtermination, map, true, 0, true, 1, true,
|
|
SpecialAddSkillType.AddLevel, reimu.Id);
|
|
if (target.GetSkill(SkillType.ReimuExtermination, out var skill) &&
|
|
skill is ReimuExterminationSkill extermination)
|
|
{
|
|
extermination.SetOriginReimuLevel(cap);
|
|
if (map.GetPlayerDataByUnitId(reimu.Id, out var reimuPlayer))
|
|
extermination.SetOriginPlayerId(reimuPlayer.Id);
|
|
if (extermination.Level > cap) extermination.SetLevel(cap);
|
|
}
|
|
PlaySkillIconScopeAware(map, target, SkillType.ReimuExtermination);
|
|
return true;
|
|
}
|
|
|
|
public static int GetExterminationClearCost(MapData map, UnitData target)
|
|
{
|
|
if (target == null || !target.GetSkill(SkillType.ReimuExtermination, out var skill)) return 0;
|
|
if (skill is not ReimuExterminationSkill extermination || extermination.Level <= 0) return 0;
|
|
var reimuLevel = GetCurrentOriginReimuLevel(map, extermination);
|
|
return 2 * Mathf.Max(1, reimuLevel) * extermination.Level;
|
|
}
|
|
|
|
public static bool TryPayClearExtermination(MapData map, PlayerData payer, UnitData target)
|
|
{
|
|
if (map == null || payer == null || target == null) return false;
|
|
if (!target.GetSkill(SkillType.ReimuExtermination, out var skill)) return false;
|
|
if (skill is not ReimuExterminationSkill extermination) return false;
|
|
|
|
var cost = GetExterminationClearCost(map, target);
|
|
if (cost <= 0 || payer.PlayerCoin < cost) return false;
|
|
var layerCount = extermination.Level;
|
|
var targetGrid = target.Grid(map);
|
|
payer.SpendCoin(cost, targetGrid);
|
|
TryAwardOriginOwner(map, extermination.OriginPlayerId, extermination.ReimuOriginId, cost, targetGrid);
|
|
target.RemoveSkill(SkillType.ReimuExtermination, map);
|
|
target.Renderer(map)?.InstantUpdateUnit(false);
|
|
EnqueueClearExterminationPaymentNotice(
|
|
map,
|
|
payer,
|
|
extermination.OriginPlayerId,
|
|
extermination.ReimuOriginId,
|
|
target,
|
|
cost,
|
|
layerCount);
|
|
return true;
|
|
}
|
|
|
|
public static void PulseExterminationDamage(MapData map, UnitData reimu)
|
|
{
|
|
if (map == null || !IsReimu(reimu) || !reimu.IsValidOnMap(map)) return;
|
|
var reimuGrid = reimu.Grid(map);
|
|
if (reimuGrid == null) return;
|
|
|
|
var range = Mathf.Max(1, reimu.GetAttackRange(map));
|
|
var aroundBuf = new List<GridData>();
|
|
map.GridMap.GetAroundGridData(range, range, reimuGrid, aroundBuf);
|
|
foreach (var grid in aroundBuf)
|
|
{
|
|
if (!grid.RealUnit(map, out var target)) continue;
|
|
if (!target.IsHeroOrDerivative(true)) continue;
|
|
if (map.IsLeagueUnitByUnit(reimu.Id, target.Id)) continue;
|
|
if (!target.GetSkill(SkillType.ReimuExtermination, out var skill) || skill.Level <= 0) continue;
|
|
|
|
var damage = skill.Level * GetExterminationDamagePerLayer(reimu);
|
|
var targetGrid = target.Grid(map);
|
|
var targetCity = target.City(map);
|
|
var targetRenderer = target.Renderer(map);
|
|
var targetCanBeKilled = target.CanBeKilled(map);
|
|
var settlement = Main.UnitLogic.DamageSettlement(map, reimu, target, damage, DamageType.Splash);
|
|
if (settlement != null)
|
|
{
|
|
float impactDelay = PlayExterminationOrbProjectilesImmediate(
|
|
map, reimuGrid, targetGrid, skill.Level);
|
|
PlayExterminationPulseVisual(map, target, targetGrid, targetCity, targetRenderer,
|
|
targetCanBeKilled, settlement, damage, impactDelay);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void PlayExterminationPulseVisual(
|
|
MapData map,
|
|
UnitData target,
|
|
GridData targetGrid,
|
|
CityData targetCity,
|
|
UnitRenderer targetRenderer,
|
|
bool targetCanBeKilled,
|
|
SettlementInfo settlement,
|
|
int damage,
|
|
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.Current?.UpdateAroundHighlight(map, targetGrid);
|
|
}
|
|
else
|
|
{
|
|
targetRenderer?.InstantUpdateUnit(false);
|
|
}
|
|
|
|
gridRenderer?.InstantUpdateGrid();
|
|
}
|
|
|
|
if (delay > 0f && Timer.Instance != null)
|
|
Timer.Instance.TimerRegister(MapRenderer.Current ?? (object)targetGrid, RunVisual, delay,
|
|
"ReimuExterminationPulseImpact");
|
|
else
|
|
RunVisual();
|
|
}
|
|
|
|
public static void RollFriendlyOfuda(MapData map, UnitData reimu, UnitData target)
|
|
{
|
|
if (map == null || !IsReimu(reimu) || target == null || !target.IsValidOnMap(map)) return;
|
|
var roll = map.Net.GetRandom(map).Next(1, 101);
|
|
SkillType skillType;
|
|
var isPermanent = true;
|
|
var turnLimit = 0;
|
|
if (roll <= 30) skillType = SkillType.ReimuOfudaAlmsOnDamaged;
|
|
else if (roll <= 60) skillType = SkillType.ReimuOfudaAlmsOnAttack;
|
|
else if (roll <= 90) skillType = SkillType.ReimuOfudaRaidDouble;
|
|
else
|
|
{
|
|
skillType = SkillType.ReimuOfudaProtectionOffering;
|
|
isPermanent = false;
|
|
turnLimit = 1;
|
|
}
|
|
|
|
AddMarkerSkill(map, reimu, target, skillType, isPermanent, turnLimit);
|
|
}
|
|
|
|
public static void RollEnemyOfuda(MapData map, UnitData reimu, UnitData target)
|
|
{
|
|
if (map == null || !IsReimu(reimu) || target == null || !target.IsValidOnMap(map)) return;
|
|
if (map.IsLeagueUnitByUnit(reimu.Id, target.Id)) return;
|
|
|
|
var roll = map.Net.GetRandom(map).Next(1, 101);
|
|
SkillType skillType;
|
|
var isPermanent = true;
|
|
var turnLimit = 0;
|
|
if (roll <= 30) skillType = SkillType.ReimuOfudaEnemyAttackTax;
|
|
else if (roll <= 60) skillType = SkillType.ReimuOfudaEnemyDamagedTax;
|
|
else if (roll <= 90) skillType = SkillType.ReimuOfudaKillBounty;
|
|
else
|
|
{
|
|
skillType = SkillType.ReimuOfudaExterminationOffering;
|
|
isPermanent = false;
|
|
turnLimit = 1;
|
|
}
|
|
|
|
AddMarkerSkill(map, reimu, target, skillType, isPermanent, turnLimit);
|
|
}
|
|
|
|
private static void AddMarkerSkill(MapData map, UnitData reimu, UnitData target, SkillType skillType,
|
|
bool isPermanent, int turnLimit)
|
|
{
|
|
if (skillType == SkillType.ReimuOfudaRaidDouble)
|
|
{
|
|
if (target.GetSkill(skillType, out var existingSkill) && !existingSkill.HasLevel)
|
|
target.RemoveSkill(skillType, map);
|
|
target.AddSkill_Legacy(skillType, map, true, 0, true, 1, true,
|
|
SpecialAddSkillType.AddLevel, reimu.Id);
|
|
PlaySkillIconScopeAware(map, target, skillType);
|
|
return;
|
|
}
|
|
|
|
target.AddSkill_Legacy(skillType, map, isPermanent, turnLimit, false, -1, false,
|
|
SpecialAddSkillType.Force, reimu.Id);
|
|
PlaySkillIconScopeAware(map, target, skillType);
|
|
}
|
|
|
|
private static void PlaySkillIconScopeAware(MapData map, UnitData unit, SkillType skillType)
|
|
{
|
|
if (map != Main.MapData || unit == null) return;
|
|
var grid = unit.Grid(map);
|
|
if (grid == null) return;
|
|
var unitFullType = unit.UnitFullType;
|
|
var scope = PresentationManager.CurrentScope;
|
|
if (scope != null)
|
|
{
|
|
scope.Add(new FragmentStep
|
|
{
|
|
Phase = AnimPhase.AttackImpact + 50,
|
|
Duration = 0f,
|
|
Execute = () => grid.Renderer(map)?.PlayVFXInSight(
|
|
new GridVFXParams(GridVFXType.SkillIcon, skillType, unitFullType))
|
|
});
|
|
return;
|
|
}
|
|
|
|
grid.Renderer(map)?.PlayVFXInSight(new GridVFXParams(GridVFXType.SkillIcon, skillType, unitFullType));
|
|
}
|
|
}
|
|
|
|
public partial class ReimuHakureiProtectionCasterSkill : SkillBase
|
|
{
|
|
public ReimuHakureiProtectionCasterSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuHakureiProtectionCaster;
|
|
}
|
|
|
|
public override bool IsCanAttackAlly()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override bool IsCanAttackTargetAlly(MapData map, UnitData self, UnitData target)
|
|
{
|
|
return ReimuNorwaySkillUtil.IsValidProtectionTarget(map, self, target);
|
|
}
|
|
|
|
public override int GetAttackAllyRange(MapData mapData, UnitData self)
|
|
{
|
|
return ReimuNorwaySkillUtil.GetProtectionCastRange(self);
|
|
}
|
|
|
|
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 (!ReimuNorwaySkillUtil.ApplyHakureiProtection(mapData, self, target)) return false;
|
|
if (self.HasEffectiveSkill(SkillType.ReimuRandomOfuda, out _))
|
|
ReimuNorwaySkillUtil.RollFriendlyOfuda(mapData, self, target);
|
|
if (self.HasEffectiveSkill(SkillType.ReimuExterminationPulse, out _))
|
|
ReimuNorwaySkillUtil.PulseExterminationDamage(mapData, self);
|
|
self.Renderer(mapData)?.InstantUpdateUnit(false);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public partial class HakureiProtectionSkill : SkillBase
|
|
{
|
|
public HakureiProtectionSkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_autoDisappear = true;
|
|
_levelLimit = int.MaxValue;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.HakureiProtection;
|
|
}
|
|
|
|
public override void BeforeDamagedSupportStage(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageTarget == null || info.DamageValue <= 0 || Level <= 0) return;
|
|
|
|
var absorb = Mathf.Min(Level, info.DamageValue);
|
|
info.DamageValue -= absorb;
|
|
ReduceLevel(mapData, info.DamageTarget, absorb);
|
|
|
|
if (info.DamageTarget.GetSkill(SkillType.ReimuOfudaProtectionOffering, out var offeringSkill) &&
|
|
offeringSkill is ReimuOfudaProtectionOfferingSkill offering)
|
|
{
|
|
offering.Award(mapData, info.DamageTarget, absorb);
|
|
}
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public partial class ReimuExterminationAttackSkill : SkillBase
|
|
{
|
|
public ReimuExterminationAttackSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuExterminationAttack;
|
|
}
|
|
|
|
public override void BeforeDamageOther(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageOrigin == null || info.DamageTarget == null) return;
|
|
if (info.DamageType != DamageType.ActiveAttack) return;
|
|
if (!ReimuNorwaySkillUtil.IsReimu(info.DamageOrigin)) return;
|
|
if (!info.DamageTarget.GetSkill(SkillType.ReimuExtermination, out var extermination)) return;
|
|
ReimuNorwaySkillUtil.EnqueueExterminationOrbProjectiles(mapData, info, extermination.Level);
|
|
var perLayer = ReimuNorwaySkillUtil.GetExterminationDamagePerLayer(info.DamageOrigin);
|
|
info.DamageValue += extermination.Level * perLayer;
|
|
}
|
|
|
|
public override void AfterDamageOther(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageOrigin == null || info.DamageTarget == null) return;
|
|
if (info.DamageType is not (DamageType.ActiveAttack or DamageType.CounterAttack)) return;
|
|
if (!ReimuNorwaySkillUtil.IsReimu(info.DamageOrigin)) return;
|
|
if (!info.DamageTarget.IsValidOnMap(mapData)) return;
|
|
ReimuNorwaySkillUtil.ApplyExtermination(mapData, info.DamageOrigin, info.DamageTarget);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuExterminationSkill : SkillBase
|
|
{
|
|
[MemoryPackInclude]
|
|
private int _originReimuLevel;
|
|
[MemoryPackInclude]
|
|
private uint _originPlayerId;
|
|
|
|
public uint ReimuOriginId => OriginId;
|
|
public uint OriginPlayerId => _originPlayerId;
|
|
public int OriginReimuLevel => Mathf.Max(1, _originReimuLevel);
|
|
|
|
public ReimuExterminationSkill()
|
|
{
|
|
IsPermanent = true;
|
|
IsLevelSkill = true;
|
|
_autoDisappear = true;
|
|
_levelLimit = int.MaxValue;
|
|
TurnsLimit = 0;
|
|
Score = 2;
|
|
_originReimuLevel = 1;
|
|
_originPlayerId = 0;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuExtermination;
|
|
}
|
|
|
|
public void SetOriginReimuLevel(int level)
|
|
{
|
|
_originReimuLevel = Mathf.Max(1, level);
|
|
}
|
|
|
|
public void SetOriginPlayerId(uint playerId)
|
|
{
|
|
_originPlayerId = playerId;
|
|
}
|
|
|
|
private void RefreshOriginContext(MapData mapData, uint originId)
|
|
{
|
|
if (mapData == null || !mapData.UnitMap.GetUnitDataByUnitId(originId, out var origin)) return;
|
|
SetOriginReimuLevel(ReimuNorwaySkillUtil.GetReimuLevel(origin));
|
|
if (mapData.GetPlayerDataByUnitId(originId, out var originPlayer))
|
|
SetOriginPlayerId(originPlayer.Id);
|
|
}
|
|
|
|
public override void OnSkillAdd(MapData mapData, uint originId)
|
|
{
|
|
base.OnSkillAdd(mapData, originId);
|
|
RefreshOriginContext(mapData, originId);
|
|
}
|
|
|
|
public override void OnSkillOverride(MapData mapData, uint originId, uint selfId)
|
|
{
|
|
base.OnSkillOverride(mapData, originId, selfId);
|
|
RefreshOriginContext(mapData, originId);
|
|
}
|
|
|
|
public override void GetSkillCopy(SkillBase skill)
|
|
{
|
|
base.GetSkillCopy(skill);
|
|
if (skill is ReimuExterminationSkill extermination)
|
|
{
|
|
extermination._originReimuLevel = _originReimuLevel;
|
|
extermination._originPlayerId = _originPlayerId;
|
|
}
|
|
}
|
|
|
|
public override void DeepCopy(SkillBase copySkill)
|
|
{
|
|
base.DeepCopy(copySkill);
|
|
if (copySkill is ReimuExterminationSkill extermination)
|
|
{
|
|
_originReimuLevel = extermination._originReimuLevel;
|
|
_originPlayerId = extermination._originPlayerId;
|
|
}
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public partial class ReimuExterminationPulseSkill : SkillBase
|
|
{
|
|
public ReimuExterminationPulseSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuExterminationPulse;
|
|
}
|
|
|
|
public override void OnAnyUnitMove(MapData map, IdentifierBase identifier, UnitData moveUnit, GridData target,
|
|
MoveType moveType)
|
|
{
|
|
if (identifier is not UnitData reimu || moveUnit == null) return;
|
|
if (moveUnit.Id != reimu.Id || !ReimuNorwaySkillUtil.IsReimu(reimu)) return;
|
|
if (moveType != MoveType.ActiveMove && moveType != MoveType.SkillMove) return;
|
|
ReimuNorwaySkillUtil.PulseExterminationDamage(map, reimu);
|
|
}
|
|
|
|
public override void OnUnitDamaged(UnitData self, MapData mapData, SettlementInfo info)
|
|
{
|
|
if (self == null || !ReimuNorwaySkillUtil.IsReimu(self) || info?.DamageOrigin == null ||
|
|
info.DamageTarget == null) return;
|
|
if (info.DamageType != DamageType.ActiveAttack) return;
|
|
if (!info.DamageTarget.IsHeroOrDerivative(true)) return;
|
|
if (!mapData.IsLeagueUnitByUnit(self.Id, info.DamageTarget.Id)) return;
|
|
if (mapData.IsLeagueUnitByUnit(self.Id, info.DamageOrigin.Id)) return;
|
|
var selfGrid = self.Grid(mapData);
|
|
var damagedGrid = info.DamageTargetGrid;
|
|
if (selfGrid == null || damagedGrid == null || mapData.GridMap.CalcDistance(selfGrid, damagedGrid) > 2)
|
|
return;
|
|
|
|
ReimuNorwaySkillUtil.ApplyExtermination(mapData, self, info.DamageOrigin);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuFantasyNatureSkill : SkillBase
|
|
{
|
|
public ReimuFantasyNatureSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuFantasyNature;
|
|
}
|
|
}
|
|
|
|
public partial class ReimuRandomOfudaSkill : SkillBase
|
|
{
|
|
public ReimuRandomOfudaSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 3;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuRandomOfuda;
|
|
}
|
|
|
|
public override void AfterDamageOther(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageOrigin == null || info.DamageTarget == null) return;
|
|
if (info.DamageType != DamageType.ActiveAttack) return;
|
|
if (!ReimuNorwaySkillUtil.IsReimu(info.DamageOrigin)) return;
|
|
if (!info.DamageTarget.IsValidOnMap(mapData)) return;
|
|
ReimuNorwaySkillUtil.RollEnemyOfuda(mapData, info.DamageOrigin, info.DamageTarget);
|
|
}
|
|
}
|
|
|
|
public abstract partial class ReimuOfudaMarkerSkill : SkillBase
|
|
{
|
|
protected ReimuOfudaMarkerSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
Score = 1;
|
|
}
|
|
|
|
protected bool AwardOriginOwner(MapData mapData, UnitData self, int amount)
|
|
{
|
|
return ReimuNorwaySkillUtil.TryAwardOriginOwner(mapData, OriginId, amount, self?.Grid(mapData));
|
|
}
|
|
|
|
protected bool IsOriginOwnerUnion(MapData mapData, UnitData target)
|
|
{
|
|
return ReimuNorwaySkillUtil.SameUnionByOrigin(mapData, OriginId, target);
|
|
}
|
|
|
|
protected bool IsEnemyToOrigin(MapData mapData, UnitData target)
|
|
{
|
|
return ReimuNorwaySkillUtil.IsEnemyToOrigin(mapData, OriginId, target);
|
|
}
|
|
|
|
protected void Consume(MapData mapData, UnitData self)
|
|
{
|
|
self?.RemoveSkill(GetSkillType(), mapData);
|
|
}
|
|
|
|
public override bool ReservedOnTransform(UnitData self, UnitFullType fullType)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaAlmsOnDamagedSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaAlmsOnDamaged;
|
|
}
|
|
|
|
public override void OnDamaged(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageTarget == null || info.DamageOrigin == null) return;
|
|
if (!IsEnemyToOrigin(mapData, info.DamageOrigin)) return;
|
|
AwardOriginOwner(mapData, info.DamageTarget, 2);
|
|
Consume(mapData, info.DamageTarget);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaAlmsOnAttackSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaAlmsOnAttack;
|
|
}
|
|
|
|
public override void OnDamageOther(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageOrigin == null || info.DamageType != DamageType.ActiveAttack) return;
|
|
AwardOriginOwner(mapData, info.DamageOrigin, 2);
|
|
Consume(mapData, info.DamageOrigin);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaRaidDoubleSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public ReimuOfudaRaidDoubleSkill()
|
|
{
|
|
IsPermanent = true;
|
|
TurnsLimit = 0;
|
|
IsLevelSkill = true;
|
|
_autoDisappear = true;
|
|
_levelLimit = int.MaxValue;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaRaidDouble;
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaProtectionOfferingSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public ReimuOfudaProtectionOfferingSkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaProtectionOffering;
|
|
}
|
|
|
|
public void Award(MapData mapData, UnitData self, int layers)
|
|
{
|
|
AwardOriginOwner(mapData, self, layers);
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is UnitData unit)
|
|
Consume(mapData, unit);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaEnemyAttackTaxSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaEnemyAttackTax;
|
|
}
|
|
|
|
public override void OnDamageOther(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageOrigin == null || info.DamageTarget == null) return;
|
|
if (info.DamageType != DamageType.ActiveAttack) return;
|
|
if (!IsOriginOwnerUnion(mapData, info.DamageTarget)) return;
|
|
AwardOriginOwner(mapData, info.DamageOrigin, 2);
|
|
Consume(mapData, info.DamageOrigin);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaEnemyDamagedTaxSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaEnemyDamagedTax;
|
|
}
|
|
|
|
public override void OnDamaged(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageTarget == null || info.DamageOrigin == null) return;
|
|
if (!IsOriginOwnerUnion(mapData, info.DamageOrigin)) return;
|
|
AwardOriginOwner(mapData, info.DamageTarget, 2);
|
|
Consume(mapData, info.DamageTarget);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaKillBountySkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaKillBounty;
|
|
}
|
|
|
|
public override void OnDamaged(MapData mapData, SettlementInfo info)
|
|
{
|
|
if (info?.DamageTarget == null || info.DamageOrigin == null || !info.IsKill) return;
|
|
if (!IsOriginOwnerUnion(mapData, info.DamageOrigin)) return;
|
|
var cost = (int)(info.DamageTarget.GetTotalCostWithCarry() * 0.5f);
|
|
AwardOriginOwner(mapData, info.DamageTarget, cost);
|
|
Consume(mapData, info.DamageTarget);
|
|
}
|
|
}
|
|
|
|
public partial class ReimuOfudaExterminationOfferingSkill : ReimuOfudaMarkerSkill
|
|
{
|
|
public ReimuOfudaExterminationOfferingSkill()
|
|
{
|
|
IsPermanent = false;
|
|
TurnsLimit = 1;
|
|
}
|
|
|
|
public override SkillType GetSkillType()
|
|
{
|
|
return SkillType.ReimuOfudaExterminationOffering;
|
|
}
|
|
|
|
public override void OnTurnEnd(IdentifierBase self, MapData mapData)
|
|
{
|
|
if (self is not UnitData unit) return;
|
|
if (unit.GetSkill(SkillType.ReimuExtermination, out var extermination) && extermination.Level > 0)
|
|
AwardOriginOwner(mapData, unit, extermination.Level);
|
|
Consume(mapData, unit);
|
|
}
|
|
}
|
|
}
|