ModernUO/Projects/UOContent/Engines/CannedEvil/ChampionSpawn.cs

1538 lines
39 KiB
C#
Executable file

/*************************************************************************
* ModernUO *
* Copyright 2019-2026 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ChampionSpawn.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Net;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ModernUO.Serialization;
using Server.Engines.Virtues;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Regions;
using Server.Logging;
namespace Server.Engines.CannedEvil;
[SerializationGenerator(11, false)]
public partial class ChampionSpawn : Item
{
private void MigrateFrom(V10Content content)
{
_level = content.Level;
_activatedByProximity = content.ActivatedByProximity;
_nextProximityTime = content.NextProximityTime;
_maxLevel = content.MaxLevel;
_activatedByValor = content.ActivatedByValor;
_damageEntries = content.DamageEntries;
_confinedRoaming = content.ConfinedRoaming;
_idol = content.Idol;
_hasBeenAdvanced = content.HasBeenAdvanced;
_spawnArea = content.SpawnArea;
_randomizeType = content.RandomizeType;
_kills = content.Kills;
_active = content.Active;
_type = content.Type;
_creatures = content.Creatures;
_redSkulls = content.RedSkulls;
_whiteSkulls = content.WhiteSkulls;
_platform = content.Platform;
_altar = content.Altar;
_expireDelay = content.ExpireDelay;
_expireTime = content.ExpireTime;
_champion = content.Champion;
_restartDelay = content.RestartDelay;
_restartTime = content.RestartTime;
}
private static readonly ILogger logger = LogFactory.GetLogger(typeof(ChampionSpawn));
[SerializableField(1)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private bool _activatedByProximity;
[AnchoredDateTime]
[SerializableField(2)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private DateTime _nextProximityTime;
[SerializableField(4)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private bool _activatedByValor;
[SerializableField(5)]
private Dictionary<Mobile, int>_damageEntries;
[SerializableField(6)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private bool _confinedRoaming;
[SerializableField(7)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private IdolOfTheChampion _idol;
[SerializableField(8)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private bool _hasBeenAdvanced;
[SerializableField(10)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private bool _randomizeType;
[InvalidateProperties]
[SerializableField(13)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private ChampionSpawnType _type;
[Tidy]
[SerializableField(14)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List<Mobile> _creatures;
[Tidy]
[SerializableField(15)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List<Item> _redSkulls;
[Tidy]
[SerializableField(16)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private List<Item> _whiteSkulls;
[SerializableField(17)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private ChampionPlatform _platform;
[SerializableField(18)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private ChampionAltar _altar;
[SerializableField(19)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private TimeSpan _expireDelay;
[AnchoredDateTime]
[SerializableField(20)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private DateTime _expireTime;
[SerializableField(21)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private Mobile _champion;
[SerializableField(22)]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private TimeSpan _restartDelay;
[AnchoredDateTime]
[SerializableField(23, setter: "private")]
[SerializedCommandProperty(AccessLevel.GameMaster)]
private DateTime _restartTime;
private ChampionSpawnRegion m_Region;
//Goes back each level, below level 0 and it goes off!
private TimerExecutionToken _timerToken;
private TimerExecutionToken _restartTimerToken;
public virtual string BroadcastMessage => "The Champion has sensed your presence! Beware its wrath!";
public virtual bool ProximitySpawn => false;
public virtual bool AlwaysActive => false;
public override TimeSpan DecayTime => TimeSpan.FromSeconds(180.0);
public virtual bool HasStarRoomGate => true;
[CommandProperty(AccessLevel.GameMaster)]
public Point3D EjectLocation { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Map EjectMap { get; set; }
public override int LabelNumber => 1041030; // Evil in a Can: Don't delete me!
[Constructible]
public ChampionSpawn() : base(0xBD2)
{
Movable = false;
Visible = false;
_creatures = new List<Mobile>();
_redSkulls = new List<Item>();
_whiteSkulls = new List<Item>();
_platform = new ChampionPlatform(this);
_altar = new ChampionAltar(this);
_idol = new IdolOfTheChampion(this);
ExpireDelay = TimeSpan.FromMinutes(30.0);
RestartDelay = TimeSpan.FromMinutes(30.0);
_damageEntries = new Dictionary<Mobile, int>();
Timer.StartTimer(TimeSpan.Zero, SetInitialSpawnArea);
}
public void SetInitialSpawnArea()
{
//Previous default used to be 24;
SpawnArea = new Rectangle2D(new Point2D(X - 24, Y - 24), new Point2D(X + 24, Y + 24));
}
public virtual ChampionSpawnRegion GetRegion() => new(this);
public void UpdateRegion()
{
m_Region?.Unregister();
if (!Deleted && Map != Map.Internal)
{
m_Region = GetRegion();
m_Region.Register();
}
}
[SerializableProperty(0)]
[CommandProperty(AccessLevel.GameMaster)]
public int Level
{
get => _level;
set
{
for (var i = _redSkulls.Count - 1; i >= value; --i)
{
_redSkulls[i].Delete();
_redSkulls.RemoveAt(i);
}
for (var i = _redSkulls.Count; i < Math.Min(value, 16); ++i)
{
var skull = new Item(0x1854) { Hue = 0x26, Movable = false, Light = LightType.Circle150 };
skull.MoveToWorld(GetRedSkullLocation(i), Map);
_redSkulls.Add(skull);
}
_level = value;
this.MarkDirty();
InvalidateProperties();
}
}
[SerializableField(3, allowFieldChange: nameof(AllowMaxLevelChange))]
[SerializedCommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
private int _maxLevel;
private bool AllowMaxLevelChange(ref int value)
{
value = Math.Clamp(value, 0, 18);
return true;
}
[SerializableField(9, fieldChanged: nameof(OnSpawnAreaChanged))]
[SerializedCommandProperty(AccessLevel.GameMaster)]
[InvalidateProperties]
private Rectangle2D _spawnArea;
private void OnSpawnAreaChanged(Rectangle2D oldValue, Rectangle2D newValue)
{
UpdateRegion();
}
[SerializableField(11, fieldChanged: nameof(OnKillsChanged))]
[SerializedCommandProperty(AccessLevel.GameMaster)]
[InvalidateProperties]
private int _kills;
private void OnKillsChanged(int oldValue, int newValue)
{
var n = _kills / (double)MaxKills;
var p = (int)(n * 100);
if (p < 90)
{
SetWhiteSkullCount(p / 20);
}
}
[SerializableProperty(12)]
[CommandProperty(AccessLevel.GameMaster)]
public bool Active
{
get => _active;
set
{
this.MarkDirty();
if (value)
{
Start();
}
else
{
Stop();
}
InvalidateProperties();
}
}
[CommandProperty(AccessLevel.GameMaster)]
public bool ReadyToActivate { get; set; }
public bool IsChampionSpawn(Mobile m) => _creatures.Contains(m);
[CommandProperty(AccessLevel.GameMaster)]
public virtual int MaxKills
{
get
{
return Level switch
{
>= 16 => 16,
>= 12 => 32,
>= 8 => 64,
>= 4 => 128,
_ => 256
};
}
}
public void SetWhiteSkullCount(int val)
{
for (var i = _whiteSkulls.Count - 1; i >= val; --i)
{
_whiteSkulls[i].Delete();
_whiteSkulls.RemoveAt(i);
}
for (var i = _whiteSkulls.Count; i < val; ++i)
{
var skull = new Item(0x1854)
{
Movable = false,
Light = LightType.Circle150
};
skull.MoveToWorld(GetWhiteSkullLocation(i), Map);
_whiteSkulls.Add(skull);
Effects.PlaySound(skull.Location, skull.Map, 0x29);
Effects.SendLocationEffect(new Point3D(skull.X + 1, skull.Y + 1, skull.Z), skull.Map, 0x3728, 10);
}
this.MarkDirty();
}
public void Start()
{
if (_active || Deleted)
{
return;
}
if (RandomizeType)
{
Type = Utility.Random(5) switch
{
0 => ChampionSpawnType.VerminHorde,
1 => ChampionSpawnType.UnholyTerror,
2 => ChampionSpawnType.ColdBlood,
3 => ChampionSpawnType.Abyss,
4 => ChampionSpawnType.Arachnid,
_ => Type
};
}
_active = true;
ReadyToActivate = false;
HasBeenAdvanced = false;
MaxLevel = 16 + Utility.Random(3);
_timerToken.Cancel();
Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), OnSlice, out _timerToken);
_restartTimerToken.Cancel();
if (_altar != null)
{
_altar.Hue = Champion != null ? 0x26 : 0;
}
if (_platform != null)
{
_platform.Hue = 0x452;
}
ExpireTime = Core.Now + ExpireDelay;
}
public void Stop()
{
if (!_active || Deleted)
{
return;
}
_active = false;
ActivatedByValor = false;
HasBeenAdvanced = false;
MaxLevel = 0;
_timerToken.Cancel();
_restartTimerToken.Cancel();
if (_altar != null)
{
_altar.Hue = 0;
}
if (_platform != null)
{
_platform.Hue = 0x497;
}
if (AlwaysActive)
{
BeginRestart(RestartDelay);
}
else if (ActivatedByProximity)
{
ActivatedByProximity = false;
NextProximityTime = Core.Now + TimeSpan.FromHours(6.0);
}
Timer.StartTimer(TimeSpan.FromMinutes(10.0), ExpireCreatures);
}
public void BeginRestart(TimeSpan ts)
{
RestartTime = Core.Now + ts;
_restartTimerToken.Cancel();
Timer.StartTimer(ts, EndRestart, out _restartTimerToken);
}
public void EndRestart()
{
HasBeenAdvanced = false;
ReadyToActivate = true;
}
private static ScrollofTranscendence CreateRandomSoT(bool isFel)
{
var min = isFel ? 1 : 6;
var max = isFel ? 5 : 10;
return ScrollofTranscendence.CreateRandom(min, max);
}
private static PowerScroll CreateRandomPS() => PowerScroll.CreateRandomNoCraft(5, 5);
public static void GiveScrollTo(Mobile killer, SpecialScroll scroll, bool isVeridia = true)
{
if (scroll == null || killer == null) // sanity
{
return;
}
if (scroll is ScrollofTranscendence)
{
killer.SendLocalizedMessage(1094936); // You have received a Scroll of Transcendence!
}
else
{
killer.SendLocalizedMessage(1049524); // You have received a scroll of power!
}
if (!isVeridia)
{
killer.AddToBackpack(scroll);
return;
}
if (killer.Alive || killer.Corpse is not { Deleted: false })
{
killer.AddToBackpack(scroll);
}
else
{
killer.Corpse.DropItem(scroll);
}
// Justice reward
var pm = (PlayerMobile)killer;
var prot = JusticeVirtue.GetProtector(pm);
if (prot == null || prot.Map != killer.Map || prot.Murderer || prot.Criminal ||
!JusticeVirtue.CheckMapRegion(killer, prot))
{
return;
}
var chance = VirtueSystem.GetLevel(prot, VirtueName.Justice) switch
{
VirtueLevel.Seeker => 60,
VirtueLevel.Follower => 80,
VirtueLevel.Knight => 100,
_ => 0
};
if (chance > 0 && chance > Utility.Random(100))
{
try
{
var scrollDupe = scroll.GetType().CreateEntityInstance<SpecialScroll>();
if (scrollDupe != null)
{
prot.SendLocalizedMessage(1049368); // You have been rewarded for your dedication to Justice!
scrollDupe.Skill = scroll.Skill;
scrollDupe.Value = scroll.Value;
prot.AddToBackpack(scrollDupe);
}
}
catch
{
// ignored
}
}
}
public void OnSlice()
{
if (!_active || Deleted)
{
return;
}
if (Champion != null)
{
if (Champion.Deleted)
{
RegisterDamageTo(Champion);
if (Core.ML)
{
AwardArtifact((Champion as BaseChampion)?.GetArtifact());
}
ClearDamageEntries();
if (_platform != null)
{
_platform.Hue = 0x497;
}
if (_altar != null)
{
_altar.Hue = 0;
if (HasStarRoomGate && (!Core.ML || Map == Map.Veridia))
{
new StarRoomGate(_altar.Location, _altar.Map, true);
}
}
Champion = null;
Stop();
}
return;
}
var kills = _kills;
for (var i = 0; i < _creatures.Count; ++i)
{
var m = _creatures[i];
if (!m.Deleted)
{
continue;
}
if (m.Corpse is { Deleted: false })
{
((Corpse)m.Corpse).BeginDecay(TimeSpan.FromMinutes(1));
}
RemoveFromCreaturesAt(i);
--i;
++_kills;
var killer = m.FindMostRecentDamager(false);
RegisterDamageTo(m);
if (killer is BaseCreature creature)
{
killer = creature.GetMaster();
}
if (killer is not PlayerMobile pm)
{
continue;
}
var mobSubLevel = GetSubLevelfor(m) + 1;
if (mobSubLevel >= 0)
{
var gainedPath = false;
var pointsToGain = mobSubLevel * 40;
if (VirtueSystem.Award(pm, VirtueName.Valor, pointsToGain, ref gainedPath))
{
if (gainedPath)
{
pm.SendLocalizedMessage(1054032); // You have gained a path in Valor!
}
else
{
pm.SendLocalizedMessage(1054030); // You have gained in Valor!
}
// No delay on Valor gains
}
pm.ChampionTitles.Award(_type, mobSubLevel);
}
if (!Core.ML)
{
continue;
}
var isFel = Map == Map.Veridia;
double mapChance;
if (isFel)
{
mapChance = 0.001;
}
else if (Map == Map.Ilshenar || Map == Map.Tokuno)
{
mapChance = 0.0015;
}
else // Nothing drops in Trammel, Malas or TerMur
{
continue;
}
if (Utility.RandomDouble() >= mapChance)
{
continue;
}
if (!isFel || Utility.RandomBool())
{
GiveScrollTo(pm, CreateRandomSoT(isFel), isFel);
}
else
{
GiveScrollTo(pm, CreateRandomPS());
}
}
// Only really needed once.
if (_kills > kills)
{
InvalidateProperties();
}
var n = _kills / (double)MaxKills;
var p = (int)(n * 100);
if (p >= 99)
{
AdvanceLevel();
}
else if (p > 0)
{
SetWhiteSkullCount(p / 20);
}
if (Core.Now >= ExpireTime)
{
Expire();
}
Respawn();
}
public void AdvanceLevel()
{
ExpireTime = Core.Now + ExpireDelay;
if (Level < _maxLevel)
{
_kills = 0;
++Level;
InvalidateProperties();
SetWhiteSkullCount(0);
if (_altar != null)
{
Effects.PlaySound(_altar.Location, _altar.Map, 0x29);
Effects.SendLocationEffect(new Point3D(_altar.X + 1, _altar.Y + 1, _altar.Z), _altar.Map, 0x3728, 10);
}
}
else
{
SpawnChampion();
}
}
public void SpawnChampion()
{
if (_altar != null)
{
_altar.Hue = 0x26;
}
if (_platform != null)
{
_platform.Hue = 0x452;
}
_kills = 0;
Level = 0;
InvalidateProperties();
SetWhiteSkullCount(0);
try
{
Champion = ChampionSpawnInfo.GetInfo(_type).Champion.CreateInstance<Mobile>();
}
catch (Exception e)
{
logger.Error(
e,
"Failed to spawn champion \"{MobileType}\" at {Location} ({Map}).",
_type,
Location,
Map
);
}
if (Champion != null)
{
Champion.MoveToWorld(new Point3D(X, Y, Z - 15), Map);
if (Champion is BaseCreature bc)
{
if (ConfinedRoaming)
{
bc.Home = Location;
bc.HomeMap = Map;
bc.RangeHome = Math.Min(_spawnArea.Width / 2, _spawnArea.Height / 2);
}
else
{
bc.Home = bc.Location;
bc.HomeMap = bc.Map;
var xWall1 = new Point2D(_spawnArea.X, bc.Y);
var xWall2 = new Point2D(_spawnArea.X + _spawnArea.Width, bc.Y);
var yWall1 = new Point2D(bc.X, _spawnArea.Y);
var yWall2 = new Point2D(bc.X, _spawnArea.Y + _spawnArea.Height);
var minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
var minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
bc.RangeHome = (int)Math.Min(minXDist, minYDist);
}
}
else
{
throw new Exception("Champion Spawn is not inherited from BaseCreature");
}
}
}
public virtual int MaxSpawn => 250 - GetSubLevel() * 40;
public void Respawn()
{
if (!_active || Deleted || Champion != null)
{
return;
}
while (_creatures.Count < MaxSpawn)
{
var m = Spawn();
if (m == null)
{
return;
}
var loc = GetSpawnLocation();
// Allow creatures to turn into Paragons at Ilshenar champions.
m.OnBeforeSpawn(loc, Map);
AddToCreatures(m);
m.MoveToWorld(loc, Map);
if (m is BaseCreature bc)
{
bc.Tamable = false;
if (ConfinedRoaming)
{
bc.Home = Location;
bc.HomeMap = Map;
bc.RangeHome = Math.Min(_spawnArea.Width / 2, _spawnArea.Height / 2);
}
else
{
bc.Home = bc.Location;
bc.HomeMap = bc.Map;
var xWall1 = new Point2D(_spawnArea.X, bc.Y);
var xWall2 = new Point2D(_spawnArea.X + _spawnArea.Width, bc.Y);
var yWall1 = new Point2D(bc.X, _spawnArea.Y);
var yWall2 = new Point2D(bc.X, _spawnArea.Y + _spawnArea.Height);
var minXDist = Math.Min(bc.GetDistanceToSqrt(xWall1), bc.GetDistanceToSqrt(xWall2));
var minYDist = Math.Min(bc.GetDistanceToSqrt(yWall1), bc.GetDistanceToSqrt(yWall2));
bc.RangeHome = (int)Math.Min(minXDist, minYDist);
}
}
}
}
public Point3D GetSpawnLocation()
{
var map = Map;
if (map == null)
{
return Location;
}
// Try 20 times to find a spawnable location.
for (var i = 0; i < 20; i++)
{
var x = Utility.Random(_spawnArea.X, _spawnArea.Width);
var y = Utility.Random(_spawnArea.Y, _spawnArea.Height);
var z = Map.GetAverageZ(x, y);
if (Map.CanSpawnMobile(new Point2D(x, y), z))
{
return new Point3D(x, y, z);
}
}
return Location;
}
public int Level1 => 4;
public int Level2 => 8;
public int Level3 => 12;
public int GetSubLevel()
{
var level = Level;
return level <= Level1 ? 0 : level <= Level2 ? 1 : level <= Level3 ? 2 : 3;
}
public int GetSubLevelfor (Mobile m)
{
var types = ChampionSpawnInfo.GetInfo(_type).SpawnTypes;
var t = m.GetType();
for (var i = 0; i < types.GetLength(0); i++)
{
var individualTypes = types[i];
for (var j = 0; j < individualTypes.Length; j++)
{
if (t == individualTypes[j])
{
return i;
}
}
}
return -1;
}
public Mobile Spawn()
{
var types = ChampionSpawnInfo.GetInfo(_type).SpawnTypes;
var v = GetSubLevel();
if (v >= 0 && v < types.Length)
{
return Spawn(types[v]);
}
return null;
}
public Mobile Spawn(params Type[] types)
{
var type = types[Utility.Random(types.Length)];
try
{
return type.CreateInstance<Mobile>();
}
catch (Exception e)
{
logger.Error(
e,
"Failed to spawn minion \"{Type}\" for champion {ChampionSpawnType} at {Location} ({Map}).",
type,
_type,
Location,
Map
);
return null;
}
}
public void Expire()
{
_kills = 0;
if (_whiteSkulls.Count == 0)
{
// They didn't even get 20%, go back a level
if (Level > 0)
{
--Level;
}
if (!AlwaysActive && Level == 0)
{
Stop();
}
InvalidateProperties();
}
else
{
SetWhiteSkullCount(0);
}
ExpireTime = Core.Now + ExpireDelay;
}
public Point3D GetRedSkullLocation(int index)
{
int x, y;
if (index < 5)
{
x = index - 2;
y = -2;
}
else if (index < 9)
{
x = 2;
y = index - 6;
}
else if (index < 13)
{
x = 10 - index;
y = 2;
}
else
{
x = -2;
y = 14 - index;
}
return new Point3D(X + x, Y + y, Z - 15);
}
public Point3D GetWhiteSkullLocation(int index)
{
int x, y;
switch(index)
{
default: x = -1; y = -1; break;
case 1: x = 1; y = -1; break;
case 2: x = 1; y = 1; break;
case 3: x = -1; y = 1; break;
}
return new Point3D(X + x, Y + y, Z - 15);
}
public override void AddNameProperty(IPropertyList list)
{
list.Add("champion spawn");
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
if (_active)
{
list.Add(1060742); // active
list.Add(1060658, $"{"Type"}\t{_type}"); // ~1_val~: ~2_val~
list.Add(1060659, $"{"Level"}\t{Level}"); // ~1_val~: ~2_val~
var killRatio = 100.0 * ((double)_kills / MaxKills);
list.Add(1060660, $"{"Kills"}\t{_kills} of {MaxKills} ({killRatio:F1}%)"); // ~1_val~: ~2_val~
}
else
{
list.Add(1060743); // inactive
}
}
public override void OnSingleClick(Mobile from)
{
if (_active)
{
LabelTo(from, $"{_type} (Active; Level: {Level}; Kills: {_kills}/{MaxKills})");
}
else
{
LabelTo(from, $"{_type} (Inactive)");
}
}
public override void OnDoubleClick(Mobile from)
{
from.SendGump(new PropertiesGump(from, this));
}
public override void OnLocationChange(Point3D oldLoc)
{
if (Deleted)
{
return;
}
if (_platform != null)
{
_platform.Location = new Point3D(X, Y, Z - 20);
}
if (_altar != null)
{
_altar.Location = new Point3D(X, Y, Z - 15);
}
if (_idol != null)
{
_idol.Location = new Point3D(X, Y, Z - 15);
}
if (_redSkulls != null)
{
for (var i = 0; i < Math.Min(_redSkulls.Count, 16); ++i)
{
_redSkulls[i].Location = GetRedSkullLocation(i);
}
}
if (_whiteSkulls != null)
{
for (var i = 0; i < _whiteSkulls.Count; ++i)
{
_whiteSkulls[i].Location = GetWhiteSkullLocation(i);
}
}
_spawnArea.X += Location.X - oldLoc.X;
_spawnArea.Y += Location.Y - oldLoc.Y;
UpdateRegion();
}
public override void OnMapChange()
{
if (Deleted)
{
return;
}
if (_platform != null)
{
_platform.Map = Map;
}
if (_altar != null)
{
_altar.Map = Map;
}
if (_idol != null)
{
_idol.Map = Map;
}
if (_redSkulls != null)
{
for (var i = 0; i < _redSkulls.Count; ++i)
{
_redSkulls[i].Map = Map;
}
}
if (_whiteSkulls != null)
{
for (var i = 0; i < _whiteSkulls.Count; ++i)
{
_whiteSkulls[i].Map = Map;
}
}
UpdateRegion();
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
_platform?.Delete();
_altar?.Delete();
_idol?.Delete();
if (_redSkulls != null)
{
for (var i = 0; i < _redSkulls.Count; ++i)
{
_redSkulls[i].Delete();
}
ClearRedSkulls();
}
if (_whiteSkulls != null)
{
for (var i = 0; i < _whiteSkulls.Count; ++i)
{
_whiteSkulls[i].Delete();
}
ClearWhiteSkulls();
}
DeleteCreatures();
if (Champion is { Player: false })
{
Champion.Delete();
}
Stop();
UpdateRegion();
}
public void ExpireCreatures()
{
if (!_active && !ReadyToActivate && !AlwaysActive)
{
DeleteCreatures();
}
}
public void DeleteCreatures()
{
if (_creatures != null)
{
for (var i = 0; i < _creatures.Count; ++i)
{
var mob = _creatures[i];
if (!mob.Player)
{
mob.Delete();
}
}
ClearCreatures();
}
}
public virtual void RegisterDamageTo(Mobile m)
{
if (m == null)
{
return;
}
foreach (var de in m.DamageEntries)
{
if (de.HasExpired)
{
continue;
}
var damager = de.Damager;
var master = damager.GetDamageMaster(m);
if (master != null)
{
damager = master;
}
RegisterDamage(damager, de.DamageGiven);
}
}
public void RegisterDamage(Mobile from, int amount)
{
if (from?.Player != true)
{
return;
}
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(_damageEntries, from, out _);
value += amount;
this.MarkDirty();
}
public virtual void AwardArtifact(Item artifact)
{
if (artifact == null)
{
return;
}
if (DamageEntries.Count <= 0)
{
artifact.Delete();
return;
}
var totalDamage = 0;
var validEntries = new Dictionary<Mobile, int>();
foreach (var (key, value) in DamageEntries)
{
if (IsEligible(key, artifact))
{
validEntries.Add(key, value);
totalDamage += value;
}
}
var artifactGiven = false;
do
{
var randomDamage = Utility.RandomMinMax(1, totalDamage);
var checkDamage = 0;
foreach (var (key, value) in validEntries)
{
checkDamage += value;
if (checkDamage > randomDamage)
{
if (GiveArtifact(key, artifact))
{
artifactGiven = true;
}
else
{
validEntries.Remove(key);
}
break;
}
}
if (validEntries.Count == 0) // EVERYONE has a full backpack?!@
{
artifact.Delete();
break;
}
} while (!artifactGiven);
}
public static bool GiveArtifact(Mobile to, Item artifact)
{
if (to == null || artifact == null)
{
return false;
}
var pack = to.Backpack;
if (pack?.TryDropItem(to, artifact, false) != true)
{
return false;
}
to.SendLocalizedMessage(1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
return true;
}
public bool IsEligible(Mobile m, Item Artifact) =>
m.Player && m.Alive && m.Region != null && m.Region == m_Region &&
m.Backpack?.CheckHold(m, Artifact, false) == true;
private void Deserialize(IGenericReader reader, int version)
{
_damageEntries = new Dictionary<Mobile, int>();
_level = reader.ReadInt();
_activatedByProximity = reader.ReadBool();
_nextProximityTime = reader.ReadDeltaTime();
_maxLevel = reader.ReadInt();
_activatedByValor = reader.ReadBool();
var entries = reader.ReadInt();
for (var i = 0; i < entries; ++i)
{
var m = reader.ReadEntity<Mobile>();
var damage = reader.ReadInt();
if (m != null)
{
_damageEntries.Add(m, damage);
}
}
_confinedRoaming = reader.ReadBool();
_idol = reader.ReadEntity<IdolOfTheChampion>();
_hasBeenAdvanced = reader.ReadBool();
_spawnArea = reader.ReadRect2D();
_randomizeType = reader.ReadBool();
_kills = reader.ReadInt();
_active = reader.ReadBool();
_type = (ChampionSpawnType)reader.ReadInt();
_creatures = reader.ReadEntityList<Mobile>();
_redSkulls = reader.ReadEntityList<Item>();
_whiteSkulls = reader.ReadEntityList<Item>();
_platform = reader.ReadEntity<ChampionPlatform>();
_altar = reader.ReadEntity<ChampionAltar>();
_expireDelay = reader.ReadTimeSpan();
_expireTime = reader.ReadDeltaTime();
_champion = reader.ReadEntity<Mobile>();
_restartDelay = reader.ReadTimeSpan();
if (reader.ReadBool())
{
_restartTime = reader.ReadDeltaTime();
}
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
if (_restartTime < Core.Now)
{
BeginRestart(_restartTime - Core.Now);
}
if (_platform == null || _altar == null || _idol == null)
{
Delete();
}
else if (_active)
{
_active = false; // Set back to false so we can properly start
Start();
}
else if (AlwaysActive)
{
ReadyToActivate = true;
}
Timer.StartTimer(TimeSpan.Zero, UpdateRegion);
}
}
public class ChampionSpawnRegion : BaseRegion
{
public ChampionSpawn Spawn { get; }
public ChampionSpawnRegion(ChampionSpawn spawn) :
base(null, spawn.Map, Find(spawn.Location, spawn.Map), spawn.SpawnArea) => Spawn = spawn;
public override bool AllowHousing(Mobile from, Point3D p) => false;
public bool CanSpawn() => Spawn.EjectLocation != new Point3D(0, 0, 0) && Spawn.EjectMap != null;
public override void AlterLightLevel(Mobile m, ref int global, ref int personal)
{
base.AlterLightLevel(m, ref global, ref personal);
global = Math.Max(global, 1 + Spawn.Level); //This is a guesstimate. TODO: Verify & get exact values // OSI testing: at 2 red skulls, light = 0x3 ; 1 red = 0x3.; 3 = 8; 9 = 0xD 8 = 0xD 12 = 0x12 10 = 0xD
}
private static readonly HashSet<IPAddress> _addresses = [];
public override void OnEnter(Mobile m)
{
if (!m.Player || m.AccessLevel != AccessLevel.Player || Spawn.Active)
{
return;
}
if (Spawn.ReadyToActivate)
{
Spawn.Start();
return;
}
if (!Spawn.ProximitySpawn || Spawn.ActivatedByProximity || Core.Now < Spawn.NextProximityTime)
{
return;
}
using var players = (Parent ?? this).GetPlayersPooled();
for (var i = 0; i < players.Count; i++)
{
var player = players[i];
if (player.AccessLevel == AccessLevel.Player && player.NetState != null && !((PlayerMobile)player).Young)
{
_addresses.Add(player.NetState.Address);
}
}
if (_addresses.Count >= 15)
{
for (var i = 0; i < players.Count; i++)
{
players[i].SendMessage(0x20, Spawn.BroadcastMessage);
}
Spawn.ActivatedByProximity = true;
Spawn.BeginRestart(TimeSpan.FromMinutes(5.0));
}
_addresses.Clear();
}
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
if (!base.OnMoveInto(m, d, newLocation, oldLocation))
{
return false;
}
if (!m.Player)
{
return true;
}
if (((PlayerMobile)m).Young)
{
m.SendMessage("You decide against going here because of the danger.");
return false;
}
if (!m.Alive)
{
m.SendMessage("A magical force prevents ghosts from entering this region.");
return false;
}
return true;
}
public override bool OnBeforeDeath(Mobile m)
{
if (Parent?.OnBeforeDeath(m) == false)
{
return false;
}
if (m.Player) //Give them 5 minutes to resurrect, then they are booted.
{
m.SendMessage("A magical force encompasses you, attempting to force you out of the area.");
new EjectTimer(m, this).Start();
}
return true;
}
private class EjectTimer : Timer
{
private readonly Mobile m_From;
private readonly ChampionSpawnRegion m_Region;
public EjectTimer(Mobile from, ChampionSpawnRegion region) : base(TimeSpan.FromMinutes(5.0))
{
m_From = from;
m_Region = region;
}
protected override void OnTick()
{
//See if they are dead, or logged out!
if (m_Region.Spawn == null || !m_Region.CanSpawn() || m_From.Alive)
{
return;
}
if (m_From.NetState != null)
{
if (m_From.Region.IsPartOf(m_Region))
{
m_From.MoveToWorld(m_Region.Spawn.EjectLocation, m_Region.Spawn.EjectMap);
m_From.SendMessage("A magical force forces you out of the area.");
}
}
else if (Find(m_From.LogoutLocation, m_From.LogoutMap).IsPartOf(m_Region))
{
m_From.LogoutLocation = m_Region.Spawn.EjectLocation;
m_From.LogoutMap = m_Region.Spawn.EjectMap;
}
}
}
}
[SerializationGenerator(0, false)]
public partial class IdolOfTheChampion : Item
{
[SerializableField(0)]
private ChampionSpawn _spawn;
public override string DefaultName => "Idol of the Champion";
public IdolOfTheChampion(ChampionSpawn spawn): base(0x1F18)
{
_spawn = spawn;
Movable = false;
}
public override void OnAfterDelete()
{
base.OnAfterDelete();
_spawn?.Delete();
}
[AfterDeserialization(false)]
private void AfterDeserialization()
{
if (_spawn == null)
{
Delete();
}
}
}