modernuo/Projects/UOContent/Spells/Base/SpellHelper.cs

1400 lines
43 KiB
C#

using System;
using System.Collections.Generic;
using Server.Engines.CannedEvil;
using Server.Engines.ConPVP;
using Server.Engines.PartySystem;
using Server.Factions;
using Server.Guilds;
using Server.Items;
using Server.Misc;
using Server.Mobiles;
using Server.Multis;
using Server.Regions;
using Server.Spells.Fifth;
using Server.Spells.Necromancy;
using Server.Spells.Ninjitsu;
using Server.Spells.Seventh;
using Server.Targeting;
namespace Server
{
#pragma warning disable CA1052
public class DefensiveSpell
{
public static void Nullify(Mobile from)
{
if (!from.CanBeginAction<DefensiveSpell>())
{
new InternalTimer(from).Start();
}
}
private class InternalTimer : Timer
{
private readonly Mobile m_Mobile;
public InternalTimer(Mobile m) : base(TimeSpan.FromMinutes(1.0))
{
m_Mobile = m;
}
protected override void OnTick()
{
m_Mobile.EndAction<DefensiveSpell>();
}
}
}
#pragma warning restore CA1052
}
namespace Server.Spells
{
public enum TravelCheckType
{
RecallFrom,
RecallTo,
GateFrom,
GateTo,
Mark,
TeleportFrom,
TeleportTo
}
public static class SpellHelper
{
private static readonly TimeSpan AosDamageDelay = TimeSpan.FromSeconds(1.25);
private static readonly TimeSpan OldDamageDelay = TimeSpan.FromSeconds(0.5);
private static readonly TimeSpan CombatHeatDelay = TimeSpan.FromSeconds(30.0);
private static readonly bool RestrictTravelCombat = true;
private static readonly int[] m_Offsets =
{
-1, -1,
-1, 0,
-1, 1,
0, -1,
0, 1,
1, -1,
1, 0,
1, 1
};
private static readonly TravelValidator[] m_Validators =
{
IsFeluccaT2A,
IsKhaldun,
IsIlshenar,
IsTrammelWind,
IsFeluccaWind,
IsFeluccaDungeon,
IsTrammelSolenHive,
IsFeluccaSolenHive,
IsCrystalCave,
IsDoomGauntlet,
IsDoomFerry,
IsSafeZone,
IsFactionStronghold,
IsChampionSpawn,
IsTokunoDungeon,
IsLampRoom,
IsGuardianRoom,
IsHeartwood,
IsMLDungeon
};
// TODO: Move to configuration
private static readonly bool[,] m_Rules =
{
/* T2A(Fel), Khaldun, Ilshenar, Wind(Tram), Wind(Fel), Dungeons(Fel), Solen(Tram), Solen(Fel), CrystalCave(Malas), Gauntlet(Malas), Gauntlet(Ferry), SafeZone, Stronghold, ChampionSpawn, Dungeons(Tokuno[Malas]), LampRoom(Doom), GuardianRoom(Doom), Heartwood, MLDungeons */
/* Recall From */
{
false, false, true, true, false, false, true, false, false, false, false, true, true, false, true, false,
false, false, false
},
/* Recall To */
{
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false
},
/* Gate From */
{
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false
},
/* Gate To */
{
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false
},
/* Mark In */
{
false, false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false
},
/* Tele From */
{
true, true, true, true, true, true, true, true, false, true, true, true, false, true, true, true, true,
false, true
},
/* Tele To */
{
true, true, true, true, true, true, true, true, false, true, false, false, false, true, true, true, true,
false, false
}
};
private static Mobile m_TravelCaster;
private static TravelCheckType m_TravelType;
public static TimeSpan GetDamageDelayForSpell(Spell sp) =>
!sp.DelayedDamage ? TimeSpan.Zero :
Core.AOS ? AosDamageDelay : OldDamageDelay;
public static bool CheckMulti(Point3D p, Map map, bool houses = true, int housingrange = 0)
{
if (map == null || map == Map.Internal)
{
return false;
}
foreach (var multi in map.GetMultisInSector(p))
{
if (multi is BaseHouse bh)
{
if (houses && bh.IsInside(p, 16) || housingrange > 0 && bh.InRange(p, housingrange))
{
return true;
}
}
else if (multi.Contains(p))
{
return true;
}
}
return false;
}
public static void Turn(Mobile from, IPoint3D to)
{
if (from == null)
{
return;
}
var root = (to as Item)?.RootParent;
if (from != root)
{
to = root;
}
if (!from.Equals(to))
{
from.Direction = from.GetDirectionTo(to);
}
}
public static bool CheckCombat(Mobile m)
{
if (!RestrictTravelCombat)
{
return false;
}
for (var i = 0; i < m.Aggressed.Count; ++i)
{
var info = m.Aggressed[i];
if (info.Defender.Player && Core.Now - info.LastCombatTime < CombatHeatDelay)
{
return true;
}
}
if (Core.AOS)
{
for (var i = 0; i < m.Aggressors.Count; ++i)
{
var info = m.Aggressors[i];
if (info.Attacker.Player && info.Attacker == m && Core.Now - info.LastCombatTime < CombatHeatDelay)
{
return true;
}
}
}
return false;
}
public static bool AdjustField(ref Point3D p, Map map, int height, bool mobsBlock)
{
if (map == null)
{
return false;
}
for (var offset = 0; offset < 10; ++offset)
{
var loc = new Point3D(p.X, p.Y, p.Z - offset);
if (map.CanFit(loc, height, true, mobsBlock))
{
p = loc;
return true;
}
}
return false;
}
public static bool GetEastToWest(Point3D from, Point3D target)
{
var dx = from.X - target.X;
var dy = from.Y - target.Y;
var rx = (dx - dy) * 44;
var ry = (dx + dy) * 44;
return rx >= 0 && ry < 0 || ry >= 0 && rx < 0;
}
public static bool CanRevealCaster(Mobile m) => m is BaseCreature { Controlled: false };
public static void GetSurfaceTop(ref IPoint3D p)
{
if (p is Item item)
{
p = item.GetSurfaceTop();
}
else if (p is StaticTarget t)
{
var z = t.Z;
if ((t.Flags & TileFlag.Surface) == 0)
{
z -= TileData.ItemTable[t.ItemID & TileData.MaxItemValue].CalcHeight;
}
p = new Point3D(t.X, t.Y, z);
}
}
public static bool AddStatOffset(Mobile m, StatType type, int offset, TimeSpan duration) =>
offset > 0
? AddStatBonus(m, m, type, offset, duration)
: offset >= 0 || AddStatCurse(m, m, type, -offset, duration);
public static bool AddStatBonus(Mobile caster, Mobile target, StatType type, TimeSpan duration, bool skillCheck = true) =>
AddStatBonus(
caster,
target,
type,
GetOffset(caster, target, type, false),
duration,
skillCheck
);
public static bool AddStatBonus(
Mobile caster, Mobile target, StatType type, int bonus, TimeSpan duration, bool skillCheck = false
)
{
var name = $"[Magic] {type} Buff";
var mod = target.GetStatMod(name);
if (mod != null)
{
if (mod.Offset >= bonus)
{
return false;
}
target.RemoveStatMod(mod);
}
if (skillCheck)
{
caster.CheckSkill(SkillName.EvalInt, 0.0, 120.0);
}
target.AddStatMod(new StatMod(type, name, bonus, duration));
return true;
}
public static int GetCurse(Mobile caster, Mobile target, StatType type)
{
var name = $"[Magic] {type} Curse";
var mod = target.GetStatMod(name);
return mod?.Offset ?? 0;
}
public static bool AddStatCurse(Mobile caster, Mobile target, StatType type, TimeSpan duration, bool skillCheck = true) =>
AddStatCurse(
caster,
target,
type,
GetOffset(caster, target, type, true),
duration,
skillCheck
);
public static bool AddStatCurse(
Mobile caster, Mobile target, StatType type, int curse, TimeSpan duration, bool skillCheck = false
)
{
var malus = -curse;
var name = $"[Magic] {type} Curse";
var mod = target.GetStatMod(name);
if (mod != null)
{
if (mod.Offset <= malus)
{
return false;
}
target.RemoveStatMod(mod);
}
if (skillCheck)
{
caster.CheckSkill(SkillName.EvalInt, 0.0, 120.0);
target.CheckSkill(SkillName.MagicResist, 0.0, 120.0);
}
target.AddStatMod(new StatMod(type, name, malus, duration));
return true;
}
public static TimeSpan GetDuration(Mobile caster, Mobile target) =>
// TODO: Is this accurate for Curse? Sources say it is magery. Should confirm at least for newest era.
TimeSpan.FromSeconds(6 * (Core.AOS ? caster.Skills.EvalInt.Value : caster.Skills.Magery.Value) / 5.0);
public static double GetOffsetScalar(Mobile caster, Mobile target, bool curse)
{
var percent = curse
? 8 + (caster.Skills.EvalInt.Value - target.Skills.MagicResist.Value) / 10
: 1 + caster.Skills.EvalInt.Value / 10;
percent *= 0.01;
return Math.Max(percent, 0);
}
public static int GetOffset(Mobile caster, Mobile target, StatType type, bool curse)
{
if (!Core.AOS)
{
return 1 + (int)(caster.Skills.Magery.Value * 0.1);
}
var percent = GetOffsetScalar(caster, target, curse);
return type switch
{
StatType.Str => (int)(target.RawStr * percent),
StatType.Dex => (int)(target.RawDex * percent),
StatType.Int => (int)(target.RawInt * percent)
};
}
public static Guild GetGuildFor(Mobile m)
{
var g = m.Guild as Guild;
if (g == null && m is BaseCreature c)
{
m = c.ControlMaster;
if (m != null)
{
g = m.Guild as Guild;
}
if (g == null)
{
m = c.SummonMaster;
if (m != null)
{
g = m.Guild as Guild;
}
}
}
return g;
}
public static bool ValidIndirectTarget(Mobile from, Mobile to)
{
if (from == to)
{
return true;
}
if (to.Hidden && to.AccessLevel > from.AccessLevel)
{
return false;
}
var bcFrom = from as BaseCreature;
var bcTarg = to as BaseCreature;
var pmFrom = (bcFrom?.Summoned == true ? bcFrom.SummonMaster : from) as PlayerMobile;
var pmTarg = (bcTarg?.Summoned == true ? bcTarg.SummonMaster : to) as PlayerMobile;
if (pmFrom?.DuelContext != null && pmFrom.DuelContext == pmTarg?.DuelContext && pmFrom.DuelContext.Started &&
pmFrom.DuelPlayer != null && pmTarg?.DuelPlayer != null)
{
return pmFrom.DuelPlayer.Participant != pmTarg.DuelPlayer.Participant;
}
var fromGuild = GetGuildFor(from);
var toGuild = GetGuildFor(to);
if (fromGuild != null && toGuild != null && (fromGuild == toGuild || fromGuild.IsAlly(toGuild)))
{
return false;
}
var p = Party.Get(from);
if (p?.Contains(to) == true)
{
return false;
}
if (bcTarg != null && (bcTarg.Controlled || bcTarg.Summoned))
{
if (bcTarg.ControlMaster == from || bcTarg.SummonMaster == from)
{
return false;
}
if (p != null && (p.Contains(bcTarg.ControlMaster) || p.Contains(bcTarg.SummonMaster)))
{
return false;
}
}
if (bcFrom != null && (bcFrom.Controlled || bcFrom.Summoned))
{
if (bcFrom.ControlMaster == to || bcFrom.SummonMaster == to)
{
return false;
}
p = Party.Get(to);
if (p != null && (p.Contains(bcFrom.ControlMaster) || p.Contains(bcFrom.SummonMaster)))
{
return false;
}
}
return bcTarg?.Controlled == false && bcTarg.InitialInnocent ||
Notoriety.Compute(from, to) != Notoriety.Innocent || from.Kills >= 5;
}
public static void Summon(
BaseCreature creature, Mobile caster, int sound, TimeSpan duration, bool scaleDuration,
bool scaleStats
)
{
var map = caster.Map;
if (map == null)
{
return;
}
var scale = 1.0 + (caster.Skills.Magery.Value - 100.0) / 200.0;
if (scaleDuration)
{
duration *= scale;
}
if (scaleStats)
{
creature.RawStr = (int)(creature.RawStr * scale);
creature.Hits = creature.HitsMax;
creature.RawDex = (int)(creature.RawDex * scale);
creature.Stam = creature.StamMax;
creature.RawInt = (int)(creature.RawInt * scale);
creature.Mana = creature.ManaMax;
}
var p = new Point3D(caster);
if (FindValidSpawnLocation(map, ref p, true))
{
BaseCreature.Summon(creature, caster, p, sound, duration);
return;
}
/*
int offset = Utility.Random( 8 ) * 2;
for ( int i = 0; i < m_Offsets.Length; i += 2 )
{
int x = caster.X + m_Offsets[(offset + i) % m_Offsets.Length];
int y = caster.Y + m_Offsets[(offset + i + 1) % m_Offsets.Length];
if (map.CanSpawnMobile( x, y, caster.Z ))
{
BaseCreature.Summon( creature, caster, new Point3D( x, y, caster.Z ), sound, duration );
return;
}
else
{
int z = map.GetAverageZ( x, y );
if (map.CanSpawnMobile( x, y, z ))
{
BaseCreature.Summon( creature, caster, new Point3D( x, y, z ), sound, duration );
return;
}
}
}
* */
creature.Delete();
caster.SendLocalizedMessage(501942); // That location is blocked.
}
public static bool FindValidSpawnLocation(Map map, ref Point3D p, bool surroundingsOnly)
{
if (map == null) // sanity
{
return false;
}
if (!surroundingsOnly)
{
if (map.CanSpawnMobile(p)) // p's fine.
{
p = new Point3D(p);
return true;
}
var z = map.GetAverageZ(p.X, p.Y);
if (map.CanSpawnMobile(p.X, p.Y, z))
{
p = new Point3D(p.X, p.Y, z);
return true;
}
}
var offset = Utility.Random(8) * 2;
for (var i = 0; i < m_Offsets.Length; i += 2)
{
var x = p.X + m_Offsets[(offset + i) % m_Offsets.Length];
var y = p.Y + m_Offsets[(offset + i + 1) % m_Offsets.Length];
if (map.CanSpawnMobile(x, y, p.Z))
{
p = new Point3D(x, y, p.Z);
return true;
}
var z = map.GetAverageZ(x, y);
if (map.CanSpawnMobile(x, y, z))
{
p = new Point3D(x, y, z);
return true;
}
}
return false;
}
private static TextDefinition _travelTo = 1019004; // You are not allowed to travel there.
private static TextDefinition _travelFrom = 502361; // You cannot teleport into that area from here.
private static TextDefinition _teleportTo = 501035; // You cannot teleport from here to the destination.
private static TextDefinition _genericSpell = 501802; // Thy spell doth not appear to work...
public static TextDefinition InvalidTravelMessage(TravelCheckType type)
{
return type switch
{
TravelCheckType.RecallTo or TravelCheckType.GateTo => _travelTo,
TravelCheckType.RecallFrom or TravelCheckType.TeleportFrom => _travelFrom,
TravelCheckType.TeleportTo => _teleportTo,
_ => _genericSpell
};
}
public static bool CheckTravel(Mobile caster, TravelCheckType type, out TextDefinition message) =>
CheckTravel(caster, caster.Map, caster.Location, type, out message);
public static bool CheckTravel(Map map, Point3D loc, TravelCheckType type, out TextDefinition message) =>
CheckTravel(null, map, loc, type, out message);
public static bool CheckTravel(Mobile caster, Map map, Point3D loc, TravelCheckType type, out TextDefinition message)
{
message = null;
if (IsInvalid(map, loc)) // null, internal, out of bounds
{
message = InvalidTravelMessage(type);
return false;
}
// Always allow monsters to teleport
if (type is TravelCheckType.TeleportTo or TravelCheckType.TeleportFrom &&
caster is BaseCreature { Controlled: false, Summoned: false })
{
return true;
}
m_TravelCaster = caster;
m_TravelType = type;
var v = (int)type;
if (caster != null)
{
var destination = Region.Find(loc, map) as BaseRegion;
var current = Region.Find(caster.Location, caster.Map) as BaseRegion;
if (destination?.CheckTravel(caster, loc, type, out message) == false || current?.CheckTravel(caster, loc, type, out message) == false)
{
message ??= InvalidTravelMessage(type);
return false;
}
}
for (var i = 0; i < m_Validators.Length; ++i)
{
var isValid = m_Rules[v, i] || !m_Validators[i](map, loc);
if (!isValid)
{
message = InvalidTravelMessage(type);
return false;
}
}
return true;
}
public static bool IsWindLoc(Point3D loc)
{
int x = loc.X, y = loc.Y;
return x >= 5120 && y >= 0 && x < 5376 && y < 256;
}
public static bool IsFeluccaWind(Map map, Point3D loc) => map == Map.Felucca && IsWindLoc(loc);
public static bool IsTrammelWind(Map map, Point3D loc) => map == Map.Trammel && IsWindLoc(loc);
public static bool IsIlshenar(Map map, Point3D loc) => map == Map.Ilshenar;
public static bool IsSolenHiveLoc(Point3D loc)
{
int x = loc.X, y = loc.Y;
return x >= 5640 && y >= 1776 && x < 5935 && y < 2039;
}
public static bool IsTrammelSolenHive(Map map, Point3D loc) => map == Map.Trammel && IsSolenHiveLoc(loc);
public static bool IsFeluccaSolenHive(Map map, Point3D loc) => map == Map.Felucca && IsSolenHiveLoc(loc);
public static bool IsFeluccaT2A(Map map, Point3D loc)
{
int x = loc.X, y = loc.Y;
return map == Map.Felucca && x >= 5120 && y >= 2304 && x < 6144 && y < 4096;
}
public static bool IsAnyT2A(Map map, Point3D loc)
{
int x = loc.X, y = loc.Y;
return (map == Map.Trammel || map == Map.Felucca) && x >= 5120 && y >= 2304 && x < 6144 && y < 4096;
}
public static bool IsFeluccaDungeon(Map map, Point3D loc)
{
var region = Region.Find(loc, map);
return region.IsPartOf<DungeonRegion>() && region.Map == Map.Felucca;
}
public static bool IsKhaldun(Map map, Point3D loc) => Region.Find(loc, map).Name == "Khaldun";
public static bool IsCrystalCave(Map map, Point3D loc)
{
if (map != Map.Malas || loc.Z >= -80)
{
return false;
}
int x = loc.X, y = loc.Y;
return x >= 1182 && y >= 437 && x < 1211 && y < 470
|| x >= 1156 && y >= 470 && x < 1211 && y < 503
|| x >= 1176 && y >= 503 && x < 1208 && y < 509
|| x >= 1188 && y >= 509 && x < 1201 && y < 513;
}
public static bool IsSafeZone(Map map, Point3D loc) =>
Region.Find(loc, map).IsPartOf<SafeZone>() &&
m_TravelType is TravelCheckType.TeleportTo or TravelCheckType.TeleportFrom
&& (m_TravelCaster as PlayerMobile)?.DuelPlayer?.Eliminated == false;
public static bool IsFactionStronghold(Map map, Point3D loc) => Region.Find(loc, map).IsPartOf<StrongholdRegion>();
public static bool IsChampionSpawn(Map map, Point3D loc) => Region.Find(loc, map).IsPartOf<ChampionSpawnRegion>();
public static bool IsDoomFerry(Map map, Point3D loc)
{
if (map != Map.Malas)
{
return false;
}
int x = loc.X, y = loc.Y;
return x >= 426 && y >= 314 && x <= 430 && y <= 331 || x >= 406 && y >= 247 && x <= 410 && y <= 264;
}
public static bool IsTokunoDungeon(Map map, Point3D loc)
{
// The tokuno dungeons are really inside malas
if (map != Map.Malas)
{
return false;
}
int x = loc.X, y = loc.Y, z = loc.Z;
var r1 = x >= 0 && y >= 0 && x <= 128 && y <= 128;
var r2 = x >= 45 && y >= 320 && x < 195 && y < 710;
return r1 || r2;
}
public static bool IsDoomGauntlet(Map map, Point3D loc)
{
if (map != Map.Malas)
{
return false;
}
int x = loc.X - 256, y = loc.Y - 304;
return x >= 0 && y >= 0 && x < 256 && y < 256;
}
public static bool IsLampRoom(Map map, Point3D loc)
{
if (map != Map.Malas)
{
return false;
}
int x = loc.X, y = loc.Y;
return x >= 465 && y >= 92 && x < 474 && y < 102;
}
public static bool IsGuardianRoom(Map map, Point3D loc)
{
if (map != Map.Malas)
{
return false;
}
int x = loc.X, y = loc.Y;
return x >= 356 && y >= 5 && x < 375 && y < 25;
}
public static bool IsHeartwood(Map map, Point3D loc)
{
int x = loc.X, y = loc.Y;
return (map == Map.Trammel || map == Map.Felucca) && x >= 6911 && y >= 254 && x < 7167 && y < 511;
}
public static bool IsMLDungeon(Map map, Point3D loc) => MondainsLegacy.IsMLRegion(Region.Find(loc, map));
public static bool IsInvalid(Map map, Point3D loc)
{
if (map == null || map == Map.Internal)
{
return true;
}
int x = loc.X, y = loc.Y;
return x < 0 || y < 0 || x >= map.Width || y >= map.Height;
}
public static bool IsTown(Point3D loc, Mobile caster)
{
var map = caster.Map;
if (map == null)
{
return false;
}
if (Region.Find(loc, map).GetRegion<SafeZone>() != null)
{
if (caster is PlayerMobile pm && (pm.DuelContext?.Started != true || pm.DuelPlayer?.Eliminated != false))
{
return true;
}
}
var reg = Region.Find(loc, map).GetRegion<GuardedRegion>();
return reg?.IsDisabled() == false;
}
public static bool CheckTown(IPoint3D ip, Mobile caster) =>
CheckTown((ip as Item)?.GetWorldLocation() ?? new Point3D(ip), caster);
public static bool CheckTown(Point3D loc, Mobile caster)
{
if (IsTown(loc, caster))
{
caster.SendLocalizedMessage(500946); // You cannot cast this in town!
return false;
}
return true;
}
// magic reflection
public static bool CheckReflect(int circle, Mobile caster, ref Mobile target) =>
CheckReflect(circle, ref caster, ref target);
public static bool CheckReflect(int circle, ref Mobile caster, ref Mobile target)
{
var reflect = false;
if (target.MagicDamageAbsorb > 0)
{
++circle;
target.MagicDamageAbsorb -= circle;
// This order isn't very intuitive, but you have to nullify reflect before target gets switched
reflect = target.MagicDamageAbsorb >= 0;
if (target.MagicDamageAbsorb <= 0)
{
target.MagicDamageAbsorb = 0;
DefensiveSpell.Nullify(target);
}
}
if (target is BaseCreature creature)
{
creature.CheckReflect(caster, ref reflect);
}
if (reflect)
{
target.FixedEffect(0x37B9, 10, 5);
(caster, target) = (target, caster);
}
return reflect;
}
public static void Damage(Spell spell, Mobile target, double damage)
{
var ts = GetDamageDelayForSpell(spell);
Damage(spell, ts, target, spell.Caster, damage);
}
public static void Damage(TimeSpan delay, Mobile target, double damage)
{
Damage(delay, target, null, damage);
}
public static void Damage(TimeSpan delay, Mobile target, Mobile from, double damage)
{
Damage(null, delay, target, from, damage);
}
public static void Damage(Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage)
{
var damageGiven = (int)damage;
if (delay == TimeSpan.Zero)
{
var bcFrom = from as BaseCreature;
var bcTarget = target as BaseCreature;
bcFrom?.AlterSpellDamageTo(target, ref damageGiven);
bcTarget?.AlterSpellDamageFrom(from, ref damageGiven);
target.Damage(damageGiven, from);
bcFrom?.OnDamageSpell(target, damageGiven);
if (from != null)
{
bcTarget?.OnHarmfulSpell(from);
bcTarget?.OnDamagedBySpell(from, damageGiven);
}
}
else
{
new SpellDamageTimer(spell, target, from, damageGiven, delay).Start();
}
}
public static void Damage(
Spell spell, Mobile target, double damage, int phys, int fire, int cold, int pois,
int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
)
{
Damage(
spell,
GetDamageDelayForSpell(spell),
target,
spell.Caster,
damage,
phys,
fire,
cold,
pois,
nrgy,
chaos,
dfa
);
}
public static void Damage(
TimeSpan delay, Mobile target, double damage, int phys, int fire, int cold, int pois,
int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
)
{
Damage(delay, target, null, damage, phys, fire, cold, pois, nrgy, chaos, dfa);
}
public static void Damage(
TimeSpan delay, Mobile target, Mobile from, double damage, int phys, int fire, int cold,
int pois, int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
)
{
Damage(null, delay, target, from, damage, phys, fire, cold, pois, nrgy, chaos, dfa);
}
public static void Damage(
Spell spell, TimeSpan delay, Mobile target, Mobile from, double damage, int phys, int fire,
int cold, int pois, int nrgy, int chaos = 0, DFAlgorithm dfa = DFAlgorithm.Standard
)
{
var dmg = (int)damage;
if (delay == TimeSpan.Zero)
{
var bcFrom = from as BaseCreature;
var bcTarget = target as BaseCreature;
bcFrom?.AlterSpellDamageTo(target, ref dmg);
bcTarget?.AlterSpellDamageFrom(from, ref dmg);
if (Feint.GetDamageReduction(from, target, out int feintReduction))
{
// example: 35 damage * 50 / 100 = 17 damage
dmg -= dmg * feintReduction / 100;
}
StaminaSystem.DFA = dfa;
var damageGiven = AOS.Damage(target, from, dmg, phys, fire, cold, pois, nrgy, chaos);
Mysticism.SpellPlagueSpell.OnMobileDamaged(target);
StaminaSystem.DFA = DFAlgorithm.Standard;
bcFrom?.OnDamageSpell(target, damageGiven);
if (bcTarget != null && from != null && delay == TimeSpan.Zero)
{
bcTarget.OnHarmfulSpell(from);
bcTarget.OnDamagedBySpell(from, damageGiven);
}
}
else
{
new SpellDamageTimerAOS(spell, delay, target, from, dmg, phys, fire, cold, pois, nrgy, chaos, dfa).Start();
}
}
public static void DoLeech(int damageGiven, Mobile from, Mobile target)
{
if (target == null)
{
return;
}
var context = TransformationSpellHelper.GetContext(from);
if (context == null) /* cleanup */
{
return;
}
if (context.Type == typeof(WraithFormSpell))
{
WraithFormSpell.DoWraithLeech(from, target, damageGiven);
}
else if (context.Type == typeof(VampiricEmbraceSpell))
{
from.Hits += Math.Min(target.Hits, AOS.Scale(damageGiven, 20));
from.PlaySound(0x44D);
}
}
public static void Heal(int amount, Mobile target, Mobile from, bool message = true)
{
// TODO: All Healing *spells* go through ArcaneEmpowerment
target.Heal(amount, from, message);
}
private delegate bool TravelValidator(Map map, Point3D loc);
private class SpellDamageTimer : Timer
{
private readonly Mobile m_From;
private readonly Spell m_Spell;
private readonly Mobile m_Target;
private int m_Damage;
public SpellDamageTimer(Spell s, Mobile target, Mobile from, int damage, TimeSpan delay) : base(delay)
{
m_Target = target;
m_From = from;
m_Damage = damage;
m_Spell = s;
if (m_Spell?.DelayedDamage == true)
{
m_Spell.StartDelayedDamageContext(target, this);
}
}
protected override void OnTick()
{
(m_From as BaseCreature)?.AlterSpellDamageTo(m_Target, ref m_Damage);
(m_Target as BaseCreature)?.AlterSpellDamageFrom(m_From, ref m_Damage);
m_Target.Damage(m_Damage);
m_Spell?.RemoveDelayedDamageContext(m_Target);
}
}
private class SpellDamageTimerAOS : Timer
{
private readonly int m_Chaos;
private readonly int m_Cold;
private readonly DFAlgorithm m_DFA;
private readonly int m_Fire;
private readonly Mobile m_From;
private readonly int m_Nrgy;
private readonly int m_Phys;
private readonly int m_Pois;
private readonly Spell m_Spell;
private readonly Mobile m_Target;
private int m_Damage;
public SpellDamageTimerAOS(
Spell s, TimeSpan delay, Mobile target, Mobile from, int damage, int phys, int fire, int cold,
int pois, int nrgy, int chaos, DFAlgorithm dfa
) : base(delay)
{
m_Target = target;
m_From = from;
m_Damage = damage;
m_Phys = phys;
m_Fire = fire;
m_Cold = cold;
m_Pois = pois;
m_Nrgy = nrgy;
m_Chaos = chaos;
m_DFA = dfa;
m_Spell = s;
if (m_Spell?.DelayedDamage == true)
{
m_Spell.StartDelayedDamageContext(target, this);
}
}
protected override void OnTick()
{
if (m_Target.Deleted || !m_Target.Alive)
{
return;
}
Damage(
m_Spell,
TimeSpan.Zero,
m_Target,
m_From,
m_Damage,
m_Phys,
m_Fire,
m_Cold,
m_Pois,
m_Nrgy,
m_Chaos,
m_DFA
);
m_Spell?.RemoveDelayedDamageContext(m_Target);
}
}
}
public static class TransformationSpellHelper
{
private static readonly Dictionary<Mobile, TransformContext> _table = new();
public static bool CheckCast(Mobile caster, Spell spell)
{
if (Sigil.ExistsOn(caster))
{
caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return false;
}
if (!caster.CanBeginAction<PolymorphSpell>())
{
caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
return false;
}
if (AnimalForm.UnderTransformation(caster))
{
caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
return false;
}
return true;
}
public static bool OnCast(Mobile caster, Spell spell)
{
if (spell is not ITransformationSpell transformSpell)
{
return false;
}
if (Sigil.ExistsOn(caster))
{
caster.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (!caster.CanBeginAction<PolymorphSpell>())
{
caster.SendLocalizedMessage(1061628); // You can't do that while polymorphed.
}
else if (DisguisePersistence.IsDisguised(caster))
{
caster.SendLocalizedMessage(1061631); // You can't do that while disguised.
return false;
}
else if (AnimalForm.UnderTransformation(caster))
{
caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}
else if (!caster.CanBeginAction<IncognitoSpell>() || caster.IsBodyMod && GetContext(caster) == null)
{
spell.DoFizzle();
}
else if (spell.CheckSequence())
{
var context = GetContext(caster);
var ourType = spell.GetType();
var wasTransformed = context != null;
var ourTransform = wasTransformed && context.Type == ourType;
if (wasTransformed)
{
RemoveContext(caster, context, ourTransform);
if (ourTransform)
{
caster.PlaySound(0xFA);
caster.FixedParticles(0x3728, 1, 13, 5042, EffectLayer.Waist);
}
}
if (!ourTransform)
{
if (transformSpell.PhysResistOffset != 0)
{
caster.AddResistanceMod(new ResistanceMod(ResistanceType.Physical, "TransformSpell", transformSpell.PhysResistOffset));
}
if (transformSpell.FireResistOffset != 0)
{
caster.AddResistanceMod(new ResistanceMod(ResistanceType.Fire, "TransformSpell", transformSpell.FireResistOffset));
}
if (transformSpell.ColdResistOffset != 0)
{
caster.AddResistanceMod(new ResistanceMod(ResistanceType.Cold, "TransformSpell", transformSpell.ColdResistOffset));
}
if (transformSpell.PoisResistOffset != 0)
{
caster.AddResistanceMod(new ResistanceMod(ResistanceType.Poison, "TransformSpell", transformSpell.PoisResistOffset));
}
if (transformSpell.NrgyResistOffset != 0)
{
caster.AddResistanceMod(new ResistanceMod(ResistanceType.Energy, "TransformSpell", transformSpell.NrgyResistOffset));
}
if (!((Body)transformSpell.Body).IsHuman)
{
var mt = caster.Mount;
if (mt != null)
{
mt.Rider = null;
}
}
caster.BodyMod = transformSpell.Body;
caster.HueMod = transformSpell.Hue;
transformSpell.DoEffect(caster);
Timer timer = new TransformTimer(caster, transformSpell);
timer.Start();
AddContext(caster, new TransformContext(timer, ourType, transformSpell));
return true;
}
}
return false;
}
public static void AddContext(Mobile m, TransformContext context)
{
_table[m] = context;
}
public static void RemoveContext(Mobile m, bool resetGraphics)
{
var context = GetContext(m);
if (context != null)
{
RemoveContext(m, context, resetGraphics);
}
}
public static void RemoveContext(Mobile m, TransformContext context, bool resetGraphics)
{
if (!_table.Remove(m))
{
return;
}
m.RemoveResistanceMod("TransformSpell");
if (resetGraphics)
{
m.HueMod = -1;
m.BodyMod = 0;
}
context.Timer.Stop();
context.Spell.RemoveEffect(m);
}
public static TransformContext GetContext(Mobile m)
{
_table.TryGetValue(m, out var context);
return context;
}
public static bool UnderTransformation(Mobile m) => GetContext(m) != null;
public static bool UnderTransformation(Mobile m, Type type) => GetContext(m)?.Type == type;
}
public interface ITransformationSpell
{
int Body { get; }
int Hue { get; }
int PhysResistOffset { get; }
int FireResistOffset { get; }
int ColdResistOffset { get; }
int PoisResistOffset { get; }
int NrgyResistOffset { get; }
double TickRate { get; }
void OnTick(Mobile m);
void DoEffect(Mobile m);
void RemoveEffect(Mobile m);
}
public class TransformContext
{
public TransformContext(Timer timer, Type type, ITransformationSpell spell)
{
Timer = timer;
Type = type;
Spell = spell;
}
public Timer Timer { get; }
public Type Type { get; }
public ITransformationSpell Spell { get; }
}
public class TransformTimer : Timer
{
private readonly Mobile m_Mobile;
private readonly ITransformationSpell m_Spell;
public TransformTimer(Mobile from, ITransformationSpell spell)
: base(TimeSpan.FromSeconds(spell.TickRate), TimeSpan.FromSeconds(spell.TickRate))
{
m_Mobile = from;
m_Spell = spell;
}
protected override void OnTick()
{
if (m_Mobile.Deleted || !m_Mobile.Alive || m_Mobile.Body != m_Spell.Body || m_Mobile.Hue != m_Spell.Hue)
{
TransformationSpellHelper.RemoveContext(m_Mobile, true);
Stop();
}
else
{
m_Spell.OnTick(m_Mobile);
}
}
}
}