ModernUO/Projects/UOContent/Engines/ConPVP/Games/DoubleDom.cs
Kamron Batman e9c7aac510
perf(conpvp): zero-alloc trophy text via TrophyRank.LowerName (#2438)
## Summary

Phase 3.3 of the message-interpolation cleanup. Eliminates the `rank.ToString().ToLower()` two-allocation pattern in ConPVP trophy-award messages.

- Adds `TrophyRank.LowerName()` extension returning a static lowercase string per enum value via switch expression.
- Updates 10 call sites across Tournament, KingOfTheHill, DoubleDom, CTF, BombingRun (2 each - cash and no-cash branches).

The handler now appends a static interned string directly into the packet buffer; no `ToString()` formatter and no `ToLower()` allocation per call. Source comment in BombingRun.cs ("There is no formatting flag for Lowercase, we may need a custom interface to get rid of it") is now resolved at the call-site level.
2026-05-03 18:31:40 -07:00

1088 lines
28 KiB
C#

using System;
using System.Collections.Generic;
using ModernUO.Serialization;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Text;
namespace Server.Engines.ConPVP;
[SerializationGenerator(0, false)]
public sealed partial class DDBoard : Item
{
public DDTeamInfo m_TeamInfo;
[Constructible]
public DDBoard() : base(7774) => Movable = false;
public override string DefaultName => "scoreboard";
public override void OnDoubleClick(Mobile from)
{
if (m_TeamInfo?.Game != null)
{
DDBoardGump.DisplayTo(from, m_TeamInfo.Game);
}
}
}
public class DDBoardGump : DynamicGump
{
private const int LabelColor32 = 0xFFFFFF;
private const int BlackColor32 = 0x000000;
private readonly Mobile _mob;
private readonly DDGame _game;
private readonly DDTeamInfo _section;
public override bool Singleton => true;
private DDBoardGump(Mobile mob, DDGame game, DDTeamInfo section)
: base(60, 60)
{
_mob = mob;
_game = game;
_section = section;
}
public static void DisplayTo(Mobile mob, DDGame game, DDTeamInfo section = null)
{
if (mob?.NetState == null || game == null)
{
return;
}
mob.SendGump(new DDBoardGump(mob, game, section));
}
protected override void BuildLayout(ref DynamicGumpBuilder builder)
{
var ourTeam = _game.GetTeamInfo(_mob);
var entries = new List<IRankedCTF>();
if (_section == null)
{
for (var i = 0; i < _game.Context.Participants.Count; ++i)
{
var teamInfo = _game.Controller.TeamInfo[i % _game.Controller.TeamInfo.Length];
if (teamInfo != null)
{
entries.Add(teamInfo);
}
}
}
else
{
foreach (var player in _section.Players.Values)
{
if (player.Score > 0)
{
entries.Add(player);
}
}
}
entries.Sort((a, b) => b.Score - a.Score);
var height = 0;
if (_section == null)
{
height = 73 + entries.Count * 75 + 28;
}
builder.SetNoClose();
builder.AddPage();
builder.AddBackground(1, 1, 398, height, 3600);
builder.AddImageTiled(16, 15, 369, height - 29, 3604);
for (var i = 0; i < entries.Count; i += 1)
{
builder.AddImageTiled(22, 58 + i * 75, 357, 70, 0x2430);
}
builder.AddAlphaRegion(16, 15, 369, height - 29);
builder.AddImage(215, -45, 0xEE40);
AddBorderedText(ref builder, 22, 22, 294, 20, "DD Scoreboard".Center(), LabelColor32, BlackColor32);
builder.AddImageTiled(32, 50, 264, 1, 9107);
builder.AddImageTiled(42, 52, 264, 1, 9157);
if (_section == null)
{
for (var i = 0; i < entries.Count; ++i)
{
if (entries[i] is not DDTeamInfo teamInfo)
{
continue;
}
builder.AddImage(30, 70 + i * 75, 10152);
builder.AddImage(30, 85 + i * 75, 10151);
builder.AddImage(30, 100 + i * 75, 10151);
builder.AddImage(30, 106 + i * 75, 10154);
builder.AddImage(24, 60 + i * 75, teamInfo == ourTeam ? 9730 : 9727, teamInfo.Color - 1);
var borderColor = teamInfo.Color == 0x455 ? LabelColor32 : BlackColor32;
var nameColor = teamInfo.Color switch
{
0x47E => 0xFFFFFF,
0x4F2 => 0x3399FF,
0x4F7 => 0x33FF33,
0x4FC => 0xFF00FF,
0x021 => 0xFF3333,
0x01A => 0xFF66FF,
0x455 => 0x333333,
_ => LabelColor32
};
AddBorderedText(
ref builder,
60,
65 + i * 75,
250,
20,
$"{LadderGump.Rank(1 + i)}: {teamInfo.Name}",
nameColor,
borderColor
);
AddBorderedText(ref builder, 50 + 10, 85 + i * 75, 100, 20, "Score:", 0xFFC000, BlackColor32);
AddBorderedText(ref builder, 50 + 15, 105 + i * 75, 100, 20, $"{teamInfo.Score:N0}", 0xFFC000, BlackColor32);
AddBorderedText(ref builder, 110 + 10, 85 + i * 75, 100, 20, "Kills:", 0xFFC000, BlackColor32);
AddBorderedText(ref builder, 110 + 15, 105 + i * 75, 100, 20, $"{teamInfo.Kills:N0}", 0xFFC000, BlackColor32);
AddBorderedText(ref builder, 160 + 10, 85 + i * 75, 100, 20, "Captures:", 0xFFC000, BlackColor32);
AddBorderedText(
ref builder,
160 + 15,
105 + i * 75,
100,
20,
$"{teamInfo.Captures:N0}",
0xFFC000,
BlackColor32
);
var pl = teamInfo.Leader;
AddBorderedText(ref builder, 235 + 10, 85 + i * 75, 250, 20, "Leader:", 0xFFC000, BlackColor32);
if (pl != null)
{
AddBorderedText(ref builder, 235 + 15, 105 + i * 75, 250, 20, pl.Player.Name, 0xFFC000, BlackColor32);
}
}
}
builder.AddButton(314, height - 42, 247, 248, 1);
}
private static void AddBorderedText(ref DynamicGumpBuilder builder, int x, int y, int width, int height, string text, int color, int borderColor)
{
AddColoredText(ref builder, x - 1, y - 1, width, height, text, borderColor);
AddColoredText(ref builder, x - 1, y + 1, width, height, text, borderColor);
AddColoredText(ref builder, x + 1, y - 1, width, height, text, borderColor);
AddColoredText(ref builder, x + 1, y + 1, width, height, text, borderColor);
AddColoredText(ref builder, x, y, width, height, text, color);
}
private static void AddColoredText(ref DynamicGumpBuilder builder, int x, int y, int width, int height, string text, int color)
{
builder.AddHtml(x, y, width, height, color == 0 ? text : text.Color(color));
}
}
public sealed class DDPlayerInfo : IRankedCTF
{
private readonly DDTeamInfo m_TeamInfo;
private int m_Captures;
private int m_Kills;
private int m_Score;
public DDPlayerInfo(DDTeamInfo teamInfo, Mobile player)
{
m_TeamInfo = teamInfo;
Player = player;
}
public Mobile Player { get; }
public string Name => Player.Name;
public int Kills
{
get => m_Kills;
set
{
m_TeamInfo.Kills += value - m_Kills;
m_Kills = value;
}
}
public int Captures
{
get => m_Captures;
set
{
m_TeamInfo.Captures += value - m_Captures;
m_Captures = value;
}
}
public int Score
{
get => m_Score;
set
{
m_TeamInfo.Score += value - m_Score;
m_Score = value;
if (m_TeamInfo.Leader == null || m_Score > m_TeamInfo.Leader.Score)
{
m_TeamInfo.Leader = this;
}
}
}
}
[PropertyObject]
public sealed class DDTeamInfo : IRankedCTF
{
public DDTeamInfo(int teamID)
{
TeamID = teamID;
Players = new Dictionary<Mobile, DDPlayerInfo>();
}
public DDTeamInfo(int teamID, IGenericReader ip)
{
TeamID = teamID;
Players = new Dictionary<Mobile, DDPlayerInfo>();
var version = ip.ReadEncodedInt();
switch (version)
{
case 0:
{
Board = ip.ReadEntity<DDBoard>();
TeamName = ip.ReadString();
Color = ip.ReadEncodedInt();
Origin = ip.ReadPoint3D();
break;
}
}
}
public DDGame Game { get; set; }
public int TeamID { get; }
public DDPlayerInfo Leader { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DDBoard Board { get; set; }
public Dictionary<Mobile, DDPlayerInfo> Players { get; }
public DDPlayerInfo this[Mobile mob]
{
get
{
if (mob == null)
{
return null;
}
if (!Players.TryGetValue(mob, out var val))
{
Players[mob] = val = new DDPlayerInfo(this, mob);
}
return val;
}
}
[CommandProperty(AccessLevel.GameMaster)]
public int Color { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public string TeamName { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public Point3D Origin { get; set; }
public string Name => $"{TeamName} Team";
public int Kills { get; set; }
public int Captures { get; set; }
public int Score { get; set; }
public void Reset()
{
Kills = 0;
Captures = 0;
Score = 0;
Leader = null;
Players.Clear();
Board?.m_TeamInfo = this;
}
public void Serialize(IGenericWriter op)
{
op.WriteEncodedInt(0); // version
op.Write(Board);
op.Write(TeamName);
op.WriteEncodedInt(Color);
op.Write(Origin);
}
public override string ToString() => "...";
}
public sealed class DDController : EventController
{
[Constructible]
public DDController()
{
Visible = false;
Movable = false;
Duration = TimeSpan.FromMinutes(30.0);
TeamInfo = new DDTeamInfo[2];
for (var i = 0; i < TeamInfo.Length; ++i)
{
TeamInfo[i] = new DDTeamInfo(i);
}
}
public DDController(Serial serial)
: base(serial)
{
}
public DDTeamInfo[] TeamInfo { get; private set; }
[CommandProperty(AccessLevel.GameMaster)]
public DDTeamInfo Team1 => TeamInfo[0];
[CommandProperty(AccessLevel.GameMaster)]
public DDTeamInfo Team2 => TeamInfo[1];
[CommandProperty(AccessLevel.GameMaster)]
public DDWayPoint PointA { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public DDWayPoint PointB { get; set; }
[CommandProperty(AccessLevel.GameMaster)]
public TimeSpan Duration { get; set; }
public override string Title => "DoubleDom";
public override string DefaultName => "DD Controller";
public override string GetTeamName(int teamID) => TeamInfo[teamID % TeamInfo.Length].Name;
public override EventGame Construct(DuelContext context) => new DDGame(this, context);
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.Write(0);
writer.Write(Duration);
writer.WriteEncodedInt(TeamInfo.Length);
for (var i = 0; i < TeamInfo.Length; ++i)
{
TeamInfo[i].Serialize(writer);
}
writer.Write(PointA);
writer.Write(PointB);
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
var version = reader.ReadInt();
switch (version)
{
case 0:
{
Duration = reader.ReadTimeSpan();
TeamInfo = new DDTeamInfo[reader.ReadEncodedInt()];
for (var i = 0; i < TeamInfo.Length; ++i)
{
TeamInfo[i] = new DDTeamInfo(i, reader);
}
PointA = reader.ReadEntity<DDWayPoint>();
PointB = reader.ReadEntity<DDWayPoint>();
break;
}
}
}
}
public sealed class DDGame : EventGame
{
private int m_CapStage;
private bool m_Capturable = true;
private TimerExecutionToken _captureTimerToken;
private TimerExecutionToken _finishTimerToken;
private TimerExecutionToken _uncaptureTimerToken;
public DDGame(DDController controller, DuelContext context) : base(context) => Controller = controller;
public DDController Controller { get; }
public Map Facet => m_Context.Arena?.Facet ?? Controller.Map;
public void Alert(string text)
{
m_Context.m_Tournament?.Alert(text);
for (var i = 0; i < m_Context.Participants.Count; ++i)
{
var p = m_Context.Participants[i];
for (var j = 0; j < p.Players.Length; ++j)
{
p.Players[j]?.Mobile.SendMessage(0x35, text);
}
}
}
public DDTeamInfo GetTeamInfo(Mobile mob)
{
var teamID = GetTeamID(mob);
if (teamID >= 0)
{
return Controller.TeamInfo[teamID % Controller.TeamInfo.Length];
}
return null;
}
public int GetTeamID(Mobile mob)
{
if (mob is not PlayerMobile pm)
{
return -1;
}
if (pm.DuelContext == null || pm.DuelContext != m_Context)
{
return -1;
}
if (pm.DuelPlayer?.Eliminated != false)
{
return -1;
}
return pm.DuelContext.Participants.IndexOf(pm.DuelPlayer.Participant);
}
public int GetColor(Mobile mob) => GetTeamInfo(mob)?.Color ?? -1;
private void ApplyHues(Participant p, int hueOverride)
{
for (var i = 0; i < p.Players.Length; ++i)
{
p.Players[i]?.Mobile.SolidHueOverride = hueOverride;
}
}
public void DelayBounce(TimeSpan ts, Mobile mob, Container corpse)
{
Timer.StartTimer(ts, () => DelayBounce_Callback(mob, corpse));
}
private void DelayBounce_Callback(Mobile mob, Container corpse)
{
var dp = (mob as PlayerMobile)?.DuelPlayer;
m_Context.RemoveAggressions(mob);
if (dp?.Eliminated == false)
{
mob.MoveToWorld(m_Context.Arena.GetBaseStartPoint(GetTeamID(mob)), Facet);
}
else
{
m_Context.SendOutside(mob);
}
m_Context.Refresh(mob, corpse);
DuelContext.Debuff(mob);
DuelContext.CancelSpell(mob);
mob.Frozen = false;
}
public override bool OnDeath(Mobile mob, Container corpse)
{
var killer = mob.FindMostRecentDamager(false);
if (killer?.Player == true)
{
var teamInfo = GetTeamInfo(killer);
var victInfo = GetTeamInfo(mob);
if (teamInfo != null && teamInfo != victInfo)
{
var playerInfo = teamInfo[killer];
if (playerInfo != null)
{
playerInfo.Kills += 1;
playerInfo.Score += 1; // base frag
// extra points for killing someone on the waypoint
if (Controller.PointA != null)
{
if (mob.InRange(Controller.PointA, 2))
{
playerInfo.Score += 1;
}
}
if (Controller.PointB != null)
{
if (mob.InRange(Controller.PointB, 2))
{
playerInfo.Score += 1;
}
}
}
playerInfo = victInfo[mob];
playerInfo?.Score -= 1;
}
}
DDBoardGump.DisplayTo(mob, this);
m_Context.Requip(mob, corpse);
DelayBounce(TimeSpan.FromSeconds(30.0), mob, corpse);
return false;
}
public override void OnStart()
{
m_Capturable = true;
_captureTimerToken.Cancel();
_uncaptureTimerToken.Cancel();
for (var i = 0; i < Controller.TeamInfo.Length; ++i)
{
var teamInfo = Controller.TeamInfo[i];
teamInfo.Game = this;
teamInfo.Reset();
}
Controller.PointA?.Game = this;
Controller.PointB?.Game = this;
for (var i = 0; i < m_Context.Participants.Count; ++i)
{
ApplyHues(
m_Context.Participants[i],
Controller.TeamInfo[i % Controller.TeamInfo.Length].Color
);
}
_finishTimerToken.Cancel();
Timer.StartTimer(Controller.Duration, Finish_Callback, out _finishTimerToken);
}
private void Finish_Callback()
{
var teams = new List<DDTeamInfo>();
for (var i = 0; i < m_Context.Participants.Count; ++i)
{
var teamInfo = Controller.TeamInfo[i % Controller.TeamInfo.Length];
if (teamInfo != null)
{
teams.Add(teamInfo);
}
}
teams.Sort((a, b) => b.Score - a.Score);
var tourney = m_Context.m_Tournament;
using var sb = ValueStringBuilder.Create(256);
if (tourney != null)
{
if (tourney.TourneyType == TourneyType.FreeForAll)
{
sb.Append(m_Context.Participants.Count * tourney.PlayersPerParticipant);
sb.Append("-man FFA");
}
else if (tourney.TourneyType == TourneyType.RandomTeam)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-team");
}
else if (tourney.TourneyType == TourneyType.RedVsBlue)
{
sb.Append("Red v Blue");
}
else if (tourney.TourneyType == TourneyType.Faction)
{
sb.Append(tourney.ParticipantsPerMatch);
sb.Append("-team Faction");
}
else
{
for (var i = 0; i < tourney.ParticipantsPerMatch; ++i)
{
if (sb.Length > 0)
{
sb.Append('v');
}
sb.Append(tourney.PlayersPerParticipant);
}
}
}
if (Controller != null)
{
sb.Append(' ');
sb.Append(Controller.Title);
}
var title = sb.ToString();
var winner = teams.Count > 0 ? teams[0] : null;
for (var i = 0; i < teams.Count; ++i)
{
var rank = TrophyRank.Bronze;
if (i == 0)
{
rank = TrophyRank.Gold;
}
else if (i == 1)
{
rank = TrophyRank.Silver;
}
var leader = teams[i].Leader;
foreach (var pl in teams[i].Players.Values)
{
var mob = pl.Player;
if (mob == null)
{
continue;
}
// "Red v Blue DD Champion"
sb.Reset();
sb.Append(title);
if (pl == leader)
{
sb.Append(" Leader");
}
if (pl.Score > 0)
{
sb.Append(": ");
sb.Append(pl.Score.ToString("N0"));
sb.Append(pl.Score == 1 ? " point" : " points");
if (pl.Kills > 0)
{
sb.Append(", ");
sb.Append(pl.Kills.ToString("N0"));
sb.Append(pl.Kills == 1 ? " kill" : " kills");
}
if (pl.Captures > 0)
{
sb.Append(", ");
sb.Append(pl.Captures.ToString("N0"));
sb.Append(pl.Captures == 1 ? " capture" : " captures");
}
}
Item item = new Trophy(sb.ToString(), rank);
if (pl == leader)
{
item.ItemID = 4810;
}
item.Name = $"{item.Name}, {teams[i].Name.ToLower()} team";
if (!mob.PlaceInBackpack(item))
{
mob.BankBox.DropItem(item);
}
var cash = pl.Score * 250;
if (cash > 0)
{
item = new BankCheck(cash);
if (!mob.PlaceInBackpack(item))
{
mob.BankBox.DropItem(item);
}
mob.SendMessage(
$"You have been awarded a {rank.LowerName()} trophy and {cash:N0}gp for your participation in this tournament."
);
}
else
{
mob.SendMessage(
$"You have been awarded a {rank.LowerName()} trophy for your participation in this tournament."
);
}
}
}
for (var i = 0; i < m_Context.Participants.Count; ++i)
{
var p = m_Context.Participants[i];
for (var j = 0; j < p.Players.Length; ++j)
{
var dp = p.Players[j];
if (dp?.Mobile != null)
{
DDBoardGump.DisplayTo(dp.Mobile, this);
}
}
if (i == winner?.TeamID)
{
continue;
}
for (var j = 0; j < p.Players.Length; ++j)
{
p.Players[j]?.Eliminated = true;
}
}
if (winner != null)
{
m_Context.Finish(m_Context.Participants[winner.TeamID]);
}
}
public override void OnStop()
{
for (var i = 0; i < Controller.TeamInfo.Length; ++i)
{
var teamInfo = Controller.TeamInfo[i];
teamInfo.Board?.m_TeamInfo = null;
teamInfo.Game = null;
}
Controller.PointA?.Game = null;
Controller.PointB?.Game = null;
m_Capturable = false;
_captureTimerToken.Cancel();
_uncaptureTimerToken.Cancel();
for (var i = 0; i < m_Context.Participants.Count; ++i)
{
ApplyHues(m_Context.Participants[i], -1);
}
_finishTimerToken.Cancel();
}
public void Dominate(DDWayPoint point, Mobile from, DDTeamInfo team)
{
if (point == null || from == null || team == null || !m_Capturable)
{
return;
}
var wasDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner &&
Controller.PointA?.TeamOwner != null;
point.TeamOwner = team;
Alert($"{team.Name} has captured {point.Name}!");
var isDom = Controller.PointA?.TeamOwner == Controller.PointB?.TeamOwner && Controller.PointA?.TeamOwner != null;
if (wasDom && !isDom)
{
Alert("Domination averted!");
Controller.PointA?.SetNonCaptureHue();
Controller.PointB?.SetNonCaptureHue();
_captureTimerToken.Cancel();
}
if (!wasDom && isDom)
{
m_CapStage = 0;
Timer.StartTimer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0), CaptureTick, out _captureTimerToken);
}
}
private void CaptureTick()
{
var team = Controller.PointA?.TeamOwner ?? Controller.PointB?.TeamOwner;
if (team == null)
{
m_Capturable = true;
_captureTimerToken.Cancel();
return;
}
if (++m_CapStage < 10)
{
Alert($"{team.Name} is dominating... {10 - m_CapStage}");
Controller.PointA?.SetCaptureHue(m_CapStage);
Controller.PointB?.SetCaptureHue(m_CapStage);
}
else
{
Alert($"{team.Name} has scored!");
team.Score += 100;
team.Captures += 1;
m_Capturable = false;
m_CapStage = 0;
_captureTimerToken.Cancel();
if (Controller.PointA != null)
{
Controller.PointA.TeamOwner = null;
Controller.PointA.SetUncapturableHue();
}
if (Controller.PointB != null)
{
Controller.PointB.TeamOwner = null;
Controller.PointB.SetUncapturableHue();
}
Timer.StartTimer(TimeSpan.FromSeconds(30.0), UncaptureTick, out _uncaptureTimerToken);
}
}
private void UncaptureTick()
{
m_Capturable = true;
_captureTimerToken.Cancel();
_uncaptureTimerToken.Cancel();
if (Controller.PointA != null)
{
Controller.PointA.TeamOwner = null;
Controller.PointA.SetNonCaptureHue();
}
if (Controller.PointB != null)
{
Controller.PointB.TeamOwner = null;
Controller.PointB.SetNonCaptureHue();
}
}
}
[SerializationGenerator(0, false)]
public partial class DDWayPoint : BaseAddon
{
public const int UncapturableHue = 0x497;
public const int NonCapturedHue = 0x38A;
private DDGame m_Game;
private DDTeamInfo m_TeamOwner;
[Constructible]
public DDWayPoint()
{
ItemID = 0x519;
Visible = true;
Name = "SET MY NAME";
AddComponent(new DDStep(0x7A8), -1, -1, -5);
AddComponent(new DDStep(0x7A6), 0, -1, -5);
AddComponent(new DDStep(0x7AA), 1, -1, -5);
AddComponent(new DDStep(0x7A5), 1, 0, -5);
AddComponent(new DDStep(0x7A9), 1, 1, -5);
AddComponent(new DDStep(0x7A4), 0, 1, -5);
AddComponent(new DDStep(0x7AB), -1, 1, -5);
AddComponent(new DDStep(0x7A7), -1, 0, -5);
SetUncapturableHue();
}
public override bool ShareHue => false;
public DDGame Game
{
get => m_Game;
set
{
m_Game = value;
m_TeamOwner = null;
if (m_Game != null)
{
SetNonCaptureHue();
}
else
{
SetUncapturableHue();
}
}
}
public DDTeamInfo TeamOwner
{
get => m_TeamOwner;
set
{
m_TeamOwner = value;
SetNonCaptureHue();
}
}
public void SetUncapturableHue()
{
for (var i = 0; i < Components.Count; i++)
{
Components[i].Hue = UncapturableHue;
}
Hue = UncapturableHue;
}
public void SetNonCaptureHue()
{
for (var i = 0; i < Components.Count; i++)
{
Components[i].Hue = NonCapturedHue;
}
if (m_TeamOwner != null)
{
Hue = m_TeamOwner.Color;
}
else
{
Hue = NonCapturedHue;
}
}
public void SetCaptureHue(int stage)
{
if (m_TeamOwner == null)
{
return;
}
Hue = m_TeamOwner.Color;
for (var i = 0; i < Components.Count; i++)
{
if (i < stage)
{
Components[i].Hue = m_TeamOwner.Color;
}
else
{
Components[i].Hue = NonCapturedHue;
}
}
}
public override bool OnMoveOver(Mobile from)
{
if (m_Game == null)
{
SetUncapturableHue();
}
else if (from.Alive)
{
var team = m_Game.GetTeamInfo(from);
if (team != null && team != TeamOwner)
{
m_Game.Dominate(this, from, team);
}
}
return true;
}
[SerializationGenerator(0, false)]
public partial class DDStep : AddonComponent
{
public DDStep(int itemID) : base(itemID) => Visible = true;
public override bool OnMoveOver(Mobile m) => Addon.OnMoveOver(m);
}
}