ModernUO/Projects/UOContent/Items/Misc/PublicMoongate.cs
Jack 6745cf2075
feat: Adds Mobile.Murderer virtual property, consolidates kill-threshold checks (#2355)
## Summary

- Adds `public virtual bool Murderer => Kills >= 5` property to `Mobile`, replacing ~30 scattered `Kills >= 5` / `Kills < 5` magic-number checks across the codebase
- `BaseCreature` overrides `Murderer` to also return `true` when `AlwaysMurderer` is set
- Updates `Corpse` serialization to v15, storing `_murderer` as a `bool` field (migrated from `int Kills >= 5` in earlier versions)
2026-03-06 08:36:41 -08:00

514 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using ModernUO.Serialization;
using Server.Collections;
using Server.Factions;
using Server.Gumps;
using Server.Maps;
using Server.Mobiles;
using Server.Network;
using Server.Spells;
namespace Server.Items;
[SerializationGenerator(0, false)]
public partial class PublicMoongate : Item
{
[Constructible]
public PublicMoongate() : base(0xF6C)
{
Movable = false;
Light = LightType.Circle300;
}
public override bool ForceShowProperties => ObjectPropertyList.Enabled;
public override bool HandlesOnMovement => true;
public override void OnDoubleClick(Mobile from)
{
if (!from.Player)
{
return;
}
if (from.InRange(GetWorldLocation(), 1))
{
UseGate(from);
}
else
{
from.SendLocalizedMessage(500446); // That is too far away.
}
}
public override bool OnMoveOver(Mobile m)
{
// Changed so criminals are not blocked by it.
if (m.Player)
{
UseGate(m);
}
return true;
}
public override void OnMovement(Mobile m, Point3D oldLocation)
{
if (m is PlayerMobile)
{
if (!Utility.InRange(m.Location, Location, 1) && Utility.InRange(oldLocation, Location, 1))
{
m.CloseGump<MoongateGump>();
}
}
}
public bool UseGate(Mobile m)
{
if (m.Criminal)
{
m.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
return false;
}
if (SpellHelper.CheckCombat(m))
{
m.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
return false;
}
if (m.Spell != null)
{
m.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
return false;
}
m.SendGump(new MoongateGump(m, this));
if (!m.Hidden || m.AccessLevel == AccessLevel.Player)
{
Effects.PlaySound(m.Location, m.Map, 0x20E);
}
return true;
}
public static void Configure()
{
CommandSystem.Register("MoonGen", AccessLevel.Developer, MoonGen_OnCommand);
}
[Usage("MoonGen"), Description("Generates public moongates. Removes all old moongates.")]
public static void MoonGen_OnCommand(CommandEventArgs e)
{
DeleteAll();
var count = 0;
count += MoonGen(PMList.Trammel, MapSelectionFlags.Trammel);
count += MoonGen(PMList.Felucca, MapSelectionFlags.Felucca);
count += MoonGen(PMList.Ilshenar, MapSelectionFlags.Ilshenar);
count += MoonGen(PMList.Malas, MapSelectionFlags.Malas);
count += MoonGen(PMList.Tokuno, MapSelectionFlags.Tokuno);
World.Broadcast(0x35, true, $"{count} moongates generated.");
}
private static void DeleteAll()
{
var list = new List<Item>();
foreach (var item in World.Items.Values)
{
if (item is PublicMoongate)
{
list.Add(item);
}
}
foreach (var item in list)
{
item.Delete();
}
if (list.Count > 0)
{
World.Broadcast(0x35, true, $"{list.Count} moongates removed.");
}
}
private static int MoonGen(PMList list, MapSelectionFlags flag)
{
if (!ExpansionInfo.CoreExpansion.MapSelectionFlags.Includes(flag))
{
return 0;
}
foreach (var entry in list.Entries)
{
var item = new PublicMoongate();
item.MoveToWorld(entry.Location, list.Map);
if (entry.Number == 1060642) // Umbra
{
item.Hue = 0x497;
}
}
return list.Entries.Length;
}
}
public class PMEntry
{
public PMEntry(Point3D loc, int number)
{
Location = loc;
Number = number;
}
public Point3D Location { get; }
public int Number { get; }
}
public class PMList
{
public static PMList Trammel =
new(
1012000,
1012012,
Map.Trammel,
new[]
{
new PMEntry(new Point3D(4467, 1283, 5), 1012003), // Moonglow
new PMEntry(new Point3D(1336, 1997, 5), 1012004), // Britain
new PMEntry(new Point3D(1499, 3771, 5), 1012005), // Jhelom
new PMEntry(new Point3D(771, 752, 5), 1012006), // Yew
new PMEntry(new Point3D(2701, 692, 5), 1012007), // Minoc
new PMEntry(new Point3D(1828, 2948, -20), 1012008), // Trinsic
new PMEntry(new Point3D(643, 2067, 5), 1012009), // Skara Brae
/* Dynamic Z for Magincia to support both old and new maps. */
new PMEntry(new Point3D(3563, 2139, Map.Trammel.GetAverageZ(3563, 2139)), 1012010), // (New) Magincia
new PMEntry(new Point3D(3450, 2677, 25), 1078098) // New Haven
}
);
public static PMList Felucca =
new(
1012001,
1012013,
Map.Felucca,
new[]
{
new PMEntry(new Point3D(4467, 1283, 5), 1012003), // Moonglow
new PMEntry(new Point3D(1336, 1997, 5), 1012004), // Britain
new PMEntry(new Point3D(1499, 3771, 5), 1012005), // Jhelom
new PMEntry(new Point3D(771, 752, 5), 1012006), // Yew
new PMEntry(new Point3D(2701, 692, 5), 1012007), // Minoc
new PMEntry(new Point3D(1828, 2948, -20), 1012008), // Trinsic
new PMEntry(new Point3D(643, 2067, 5), 1012009), // Skara Brae
/* Dynamic Z for Magincia to support both old and new maps. */
new PMEntry(new Point3D(3563, 2139, Map.Felucca.GetAverageZ(3563, 2139)), 1012010), // (New) Magincia
new PMEntry(new Point3D(2711, 2234, 0), 1019001) // Buccaneer's Den
}
);
public static PMList Ilshenar =
new(
1012002,
1012014,
Map.Ilshenar,
new[]
{
new PMEntry(new Point3D(1215, 467, -13), 1012015), // Compassion
new PMEntry(new Point3D(722, 1366, -60), 1012016), // Honesty
new PMEntry(new Point3D(744, 724, -28), 1012017), // Honor
new PMEntry(new Point3D(281, 1016, 0), 1012018), // Humility
new PMEntry(new Point3D(987, 1011, -32), 1012019), // Justice
new PMEntry(new Point3D(1174, 1286, -30), 1012020), // Sacrifice
new PMEntry(new Point3D(1532, 1340, -3), 1012021), // Spirituality
new PMEntry(new Point3D(528, 216, -45), 1012022), // Valor
new PMEntry(new Point3D(1721, 218, 96), 1019000) // Chaos
}
);
public static PMList Malas =
new(
1060643,
1062039,
Map.Malas,
new[]
{
new PMEntry(new Point3D(1015, 527, -65), 1060641), // Luna
new PMEntry(new Point3D(1997, 1386, -85), 1060642) // Umbra
}
);
public static PMList Tokuno =
new(
1063258,
1063415,
Map.Tokuno,
new[]
{
new PMEntry(new Point3D(1169, 998, 41), 1063412), // Isamu-Jima
new PMEntry(new Point3D(802, 1204, 25), 1063413), // Makoto-Jima
new PMEntry(new Point3D(270, 628, 15), 1063414) // Homare-Jima
}
);
public static PMList TerMur =
new(
1113602,
1113604,
Map.TerMur,
new[]
{
new PMEntry(new Point3D(850, 3525, -38), 1113603), // Royal City
}
);
public static PMList TerMurEodon =
new(
1113602,
1113604,
Map.TerMur,
new[]
{
new PMEntry(new Point3D(850, 3525, -38), 1113603), // Royal City
new PMEntry(new Point3D(719, 1863, 40), 1156262) // Valley of Eodon
}
);
public static PMList[] NoTrammelLists = { Felucca };
public static PMList[] T2ALists = { Trammel, Felucca };
public static PMList[] T2AListsYoung = { Trammel };
public static PMList[] LBRLists = { Trammel, Felucca, Ilshenar };
public static PMList[] LBRListsYoung = { Trammel, Ilshenar };
public static PMList[] AOSLists = { Trammel, Felucca, Ilshenar, Malas };
public static PMList[] AOSListsYoung = { Trammel, Ilshenar, Malas };
public static PMList[] SELists = { Trammel, Felucca, Ilshenar, Malas, Tokuno };
public static PMList[] SEListsYoung = { Trammel, Ilshenar, Malas, Tokuno };
public static PMList[] SALists = { Trammel, Felucca, Ilshenar, Malas, Tokuno, TerMur };
public static PMList[] SAListsYoung = { Trammel, Ilshenar, Malas, Tokuno, TerMur };
public static PMList[] TOLLists = { Trammel, Felucca, Ilshenar, Malas, Tokuno, TerMurEodon };
public static PMList[] TOLListsYoung = { Trammel, Ilshenar, Malas, Tokuno, TerMurEodon };
public static PMList[] RedLists = { Felucca };
public static PMList[] SigilLists = { Felucca };
public PMList(int number, int selNumber, Map map, PMEntry[] entries)
{
Number = number;
SelNumber = selNumber;
Map = map;
Entries = entries;
}
public int Number { get; }
public int SelNumber { get; }
public Map Map { get; }
public PMEntry[] Entries { get; }
}
public class MoongateGump : Gump
{
private PMList[] _lists;
private Mobile _mobile;
private Item _moongate;
public override bool Singleton => true;
public MoongateGump(Mobile mobile, Item moongate) : base(100, 100)
{
_mobile = mobile;
_moongate = moongate;
PMList[] checkLists;
if (!mobile.Player)
{
checkLists = PMList.SELists;
}
else if (Sigil.ExistsOn(mobile))
{
checkLists = PMList.SigilLists;
}
else if (mobile.Murderer)
{
checkLists = PMList.RedLists;
}
else
{
var flags = mobile.NetState?.Flags ?? ClientFlags.None;
var young = mobile is PlayerMobile { Young: true };
checkLists = Core.Expansion switch
{
>= Expansion.TOL when (flags & ClientFlags.TerMur) != 0 =>
young ? PMList.TOLListsYoung : PMList.TOLLists,
>= Expansion.SA when (flags & ClientFlags.TerMur) != 0 => young ? PMList.SAListsYoung : PMList.SALists,
>= Expansion.SE when (flags & ClientFlags.Tokuno) != 0 => young ? PMList.SEListsYoung : PMList.SELists,
>= Expansion.AOS when (flags & ClientFlags.Malas) != 0 => young ? PMList.AOSListsYoung : PMList.AOSLists,
>= Expansion.LBR when (flags & ClientFlags.Ilshenar) != 0 =>
young ? PMList.LBRListsYoung : PMList.LBRLists,
>= Expansion.T2A when (flags & ClientFlags.Trammel) != 0 =>
young ? PMList.T2AListsYoung : PMList.T2ALists,
_ => PMList.NoTrammelLists
};
}
var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags;
using var filteredBySelectedMaps = PooledRefList<PMList>.Create();
for (var i = 0; i < checkLists.Length; ++i)
{
var pmList = checkLists[i];
if (availableMaps.Includes(pmList.Map.ToSelectionFlag()))
{
filteredBySelectedMaps.Add(pmList);
}
}
var mapCount = filteredBySelectedMaps.Count;
_lists = new PMList[mapCount];
for (var i = 0; i < mapCount; i++)
{
_lists[i] = filteredBySelectedMaps[i];
}
for (var i = 0; i < _lists.Length; ++i)
{
if (_lists[i].Map == mobile.Map)
{
(_lists[i], _lists[0]) = (_lists[0], _lists[i]);
break;
}
}
AddPage(0);
AddBackground(0, 0, 380, 280, 5054);
AddButton(10, 210, 4005, 4007, 1);
AddHtmlLocalized(45, 210, 140, 25, 1011036); // OKAY
AddButton(10, 235, 4005, 4007, 0);
AddHtmlLocalized(45, 235, 140, 25, 1011012); // CANCEL
AddHtmlLocalized(5, 5, 200, 20, 1012011); // Pick your destination:
for (var i = 0; i < filteredBySelectedMaps.Count; ++i)
{
AddButton(10, 35 + i * 25, 2117, 2118, 0, GumpButtonType.Page, Array.IndexOf(_lists, filteredBySelectedMaps[i]) + 1);
AddHtmlLocalized(30, 35 + i * 25, 150, 20, filteredBySelectedMaps[i].Number);
}
for (var i = 0; i < _lists.Length; ++i)
{
RenderPage(i, filteredBySelectedMaps.IndexOf(_lists[i]));
}
}
private void RenderPage(int index, int offset)
{
var list = _lists[index];
AddPage(index + 1);
AddButton(10, 35 + offset * 25, 2117, 2118, 0, GumpButtonType.Page, index + 1);
AddHtmlLocalized(30, 35 + offset * 25, 150, 20, list.SelNumber);
var entries = list.Entries;
for (var i = 0; i < entries.Length; ++i)
{
AddRadio(200, 35 + i * 25, 210, 211, false, index * 100 + i);
AddHtmlLocalized(225, 35 + i * 25, 150, 20, entries[i].Number);
}
}
public override void OnResponse(NetState state, in RelayInfo info)
{
if (info.ButtonID == 0) // Cancel
{
return;
}
if (_mobile.Deleted || _moongate.Deleted || _mobile.Map == null)
{
return;
}
var switches = info.Switches;
if (switches.Length == 0)
{
return;
}
var switchID = switches[0];
var listIndex = switchID / 100;
var listEntry = switchID % 100;
if (listIndex < 0 || listIndex >= _lists.Length)
{
return;
}
var list = _lists[listIndex];
if (listEntry < 0 || listEntry >= list.Entries.Length)
{
return;
}
var entry = list.Entries[listEntry];
if (!_mobile.InRange(_moongate.GetWorldLocation(), 1) || _mobile.Map != _moongate.Map)
{
_mobile.SendLocalizedMessage(1019002); // You are too far away to use the gate.
}
else if (_mobile.Player && _mobile.Murderer && list.Map != Map.Felucca)
{
_mobile.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (Sigil.ExistsOn(_mobile) && list.Map != Faction.Facet)
{
_mobile.SendLocalizedMessage(1019004); // You are not allowed to travel there.
}
else if (_mobile.Criminal)
{
_mobile.SendLocalizedMessage(1005561, "", 0x22); // Thou'rt a criminal and cannot escape so easily.
}
else if (SpellHelper.CheckCombat(_mobile))
{
_mobile.SendLocalizedMessage(1005564, "", 0x22); // Wouldst thou flee during the heat of battle??
}
else if (_mobile.Spell != null)
{
_mobile.SendLocalizedMessage(1049616); // You are too busy to do that at the moment.
}
else if (_mobile.Map == list.Map && _mobile.InRange(entry.Location, 1))
{
_mobile.SendLocalizedMessage(1019003); // You are already there.
}
else
{
BaseCreature.TeleportPets(_mobile, entry.Location, list.Map);
_mobile.Combatant = null;
_mobile.Warmode = false;
_mobile.Hidden = true;
_mobile.MoveToWorld(entry.Location, list.Map);
Effects.PlaySound(entry.Location, list.Map, 0x1FE);
}
}
}