2026-06-25 00:18:42 +08:00

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