ModernUO/Projects/UOContent/Engines/Factions/Core/Election.cs
Kamron Batman 360143478a
feat: Adds tidy option for serialization. Codegens ballotbox. Fixes pooled timer leaking (#681)
* Fixes pooled timer leaking
* Fixes `[dumptimers` command so it outputs properly, adds spacing, and stacktraces
* Adds `[Tidy]` for serializing Lists. This will remove deleted entities during world save before serializing the list.
* Adds helpers for managing Lists/Sets/Dictionaries


### New API

```cs
// Creates the list if it is null, then adds
Utility.Add(ref list, value);
Utility.Add(ref set, value);
Utility.Add(ref dict, key, value);

// Nulls the variable if the count is zero
Utility.Remove(ref list, value);
Utility.Remove(ref set, value);
Utility.Remove(ref dict, key);

// Marks entity as dirty in addition to doing the action
entity.Add(list, value);
// Marks entity as dirty, and will create list if it doesn't exist
entity.Add(ref list, value);

// Marks entity as dirty in addition to doing the action
entity.Remove(list, value);
// Marks entity as dirty, and will null the list count is zero
entity.Remove(ref list, value);
```


### Updates to [dumptimers
<img width="825" alt="Screen Shot 2021-08-14 at 2 55 10 AM" src="https://user-images.githubusercontent.com/3953314/129442449-ccf7fe14-29d6-4f3f-9366-c8eb7b9828a7.png">
2021-08-14 03:02:53 -07:00

561 lines
16 KiB
C#

using System;
using System.Collections.Generic;
using System.Net;
using Server.Mobiles;
namespace Server.Factions
{
public class Election
{
public const int MaxCandidates = 10;
public const int CandidateRank = 5;
public static readonly TimeSpan PendingPeriod = TimeSpan.FromDays(5.0);
public static readonly TimeSpan CampaignPeriod = TimeSpan.FromDays(1.0);
public static readonly TimeSpan VotingPeriod = TimeSpan.FromDays(3.0);
private Timer _timer;
public Election(Faction faction)
{
Faction = faction;
Candidates = new List<Candidate>();
StartTimer();
}
public Election(IGenericReader reader)
{
var version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
Faction = Faction.ReadReference(reader);
LastStateTime = reader.ReadDateTime();
CurrentState = (ElectionState)reader.ReadEncodedInt();
Candidates = new List<Candidate>();
var count = reader.ReadEncodedInt();
for (var i = 0; i < count; ++i)
{
var cd = new Candidate(reader);
if (cd.Mobile != null)
{
Candidates.Add(cd);
}
}
break;
}
}
StartTimer();
}
public Faction Faction { get; }
public List<Candidate> Candidates { get; }
public ElectionState State
{
get => CurrentState;
set
{
CurrentState = value;
LastStateTime = Core.Now;
}
}
public DateTime LastStateTime { get; private set; }
[CommandProperty(AccessLevel.GameMaster)]
public ElectionState CurrentState { get; private set; }
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
public TimeSpan NextStateTime
{
get
{
var period = CurrentState switch
{
ElectionState.Pending => PendingPeriod,
ElectionState.Election => VotingPeriod,
ElectionState.Campaign => CampaignPeriod,
_ => PendingPeriod
};
var until = Utility.Max(LastStateTime + period - Core.Now, TimeSpan.Zero);
return until;
}
set
{
var period = CurrentState switch
{
ElectionState.Pending => PendingPeriod,
ElectionState.Election => VotingPeriod,
ElectionState.Campaign => CampaignPeriod,
_ => PendingPeriod
};
LastStateTime = Core.Now - period + value;
}
}
public void StartTimer()
{
_timer = Timer.DelayCall(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0), Slice);
}
public void Serialize(IGenericWriter writer)
{
writer.WriteEncodedInt(0); // version
Faction.WriteReference(writer, Faction);
writer.Write(LastStateTime);
writer.WriteEncodedInt((int)CurrentState);
writer.WriteEncodedInt(Candidates.Count);
for (var i = 0; i < Candidates.Count; ++i)
{
Candidates[i].Serialize(writer);
}
}
public void AddCandidate(Mobile mob)
{
if (IsCandidate(mob))
{
return;
}
Candidates.Add(new Candidate(mob));
mob.SendLocalizedMessage(1010117); // You are now running for office.
}
public void RemoveVoter(Mobile mob)
{
if (CurrentState == ElectionState.Election)
{
for (var i = 0; i < Candidates.Count; ++i)
{
var voters = Candidates[i].Voters;
for (var j = 0; j < voters.Count; ++j)
{
var voter = voters[j];
if (voter.From == mob)
{
voters.RemoveAt(j--);
}
}
}
}
}
public void RemoveCandidate(Mobile mob)
{
var cd = FindCandidate(mob);
if (cd == null)
{
return;
}
Candidates.Remove(cd);
mob.SendLocalizedMessage(1038031);
if (CurrentState == ElectionState.Election)
{
if (Candidates.Count == 1)
{
// There are no longer any valid candidates in the Faction Commander election.
Faction.Broadcast(1038031);
var winner = Candidates[0];
var winMob = winner.Mobile;
var pl = PlayerState.Find(winMob);
if (pl == null || pl.Faction != Faction || winMob == Faction.Commander)
{
Faction.Broadcast(1038026); // Faction leadership has not changed.
}
else
{
Faction.Broadcast(1038028); // The faction has a new commander.
Faction.Commander = winMob;
}
Candidates.Clear();
State = ElectionState.Pending;
}
else if (Candidates.Count == 0) // well, I guess this'll never happen
{
// There are no longer any valid candidates in the Faction Commander election.
Faction.Broadcast(1038031);
Candidates.Clear();
State = ElectionState.Pending;
}
}
}
public bool IsCandidate(Mobile mob) => FindCandidate(mob) != null;
public bool CanVote(Mobile mob) => CurrentState == ElectionState.Election && !HasVoted(mob);
public bool HasVoted(Mobile mob) => FindVoter(mob) != null;
public Candidate FindCandidate(Mobile mob)
{
for (var i = 0; i < Candidates.Count; ++i)
{
if (Candidates[i].Mobile == mob)
{
return Candidates[i];
}
}
return null;
}
public Candidate FindVoter(Mobile mob)
{
for (var i = 0; i < Candidates.Count; ++i)
{
var voters = Candidates[i].Voters;
for (var j = 0; j < voters.Count; ++j)
{
var voter = voters[j];
if (voter.From == mob)
{
return Candidates[i];
}
}
}
return null;
}
public bool CanBeCandidate(Mobile mob)
{
if (IsCandidate(mob))
{
return false;
}
if (Candidates.Count >= MaxCandidates)
{
return false;
}
if (CurrentState != ElectionState.Campaign)
{
return false; // sanity..
}
var pl = PlayerState.Find(mob);
return pl != null && pl.Faction == Faction && pl.Rank.Rank >= CandidateRank;
}
public void Slice()
{
if (Faction.Election != this)
{
_timer?.Stop();
_timer = null;
return;
}
switch (CurrentState)
{
case ElectionState.Pending:
{
if (LastStateTime + PendingPeriod > Core.Now)
{
break;
}
Faction.Broadcast(1038023); // Campaigning for the Faction Commander election has begun.
Candidates.Clear();
State = ElectionState.Campaign;
break;
}
case ElectionState.Campaign:
{
if (LastStateTime + CampaignPeriod > Core.Now)
{
break;
}
if (Candidates.Count == 0)
{
Faction.Broadcast(1038025); // Nobody ran for office.
State = ElectionState.Pending;
}
else if (Candidates.Count == 1)
{
Faction.Broadcast(1038029); // Only one member ran for office.
var winner = Candidates[0];
var mob = winner.Mobile;
var pl = PlayerState.Find(mob);
if (pl == null || pl.Faction != Faction || mob == Faction.Commander)
{
Faction.Broadcast(1038026); // Faction leadership has not changed.
}
else
{
Faction.Broadcast(1038028); // The faction has a new commander.
Faction.Commander = mob;
}
Candidates.Clear();
State = ElectionState.Pending;
}
else
{
Faction.Broadcast(1038030);
State = ElectionState.Election;
}
break;
}
case ElectionState.Election:
{
if (LastStateTime + VotingPeriod > Core.Now)
{
break;
}
Faction.Broadcast(1038024); // The results for the Faction Commander election are in
Candidate winner = null;
for (var i = 0; i < Candidates.Count; ++i)
{
var cd = Candidates[i];
var pl = PlayerState.Find(cd.Mobile);
if (pl == null || pl.Faction != Faction)
{
continue;
}
// cd.CleanMuleVotes();
if (winner == null || cd.Votes > winner.Votes)
{
winner = cd;
}
}
if (winner == null)
{
Faction.Broadcast(1038026); // Faction leadership has not changed.
}
else if (winner.Mobile == Faction.Commander)
{
Faction.Broadcast(1038027); // The incumbent won the election.
}
else
{
Faction.Broadcast(1038028); // The faction has a new commander.
Faction.Commander = winner.Mobile;
}
Candidates.Clear();
State = ElectionState.Pending;
break;
}
}
}
}
public class Voter
{
public Voter(Mobile from, Mobile candidate)
{
From = from;
Candidate = candidate;
if (From.NetState != null)
{
Address = From.NetState.Address;
}
else
{
Address = IPAddress.None;
}
Time = Core.Now;
}
public Voter(IGenericReader reader, Mobile candidate)
{
Candidate = candidate;
var version = reader.ReadEncodedInt();
switch (version)
{
case 0:
{
From = reader.ReadEntity<Mobile>();
Address = Utility.Intern(reader.ReadIPAddress());
Time = reader.ReadDateTime();
break;
}
}
}
public Mobile From { get; }
public Mobile Candidate { get; }
public IPAddress Address { get; }
public DateTime Time { get; }
public object[] AcquireFields()
{
var gameTime = TimeSpan.Zero;
if (From is PlayerMobile mobile)
{
gameTime = mobile.GameTime;
}
var kp = PlayerState.Find(From)?.KillPoints ?? 0;
var sk = From.Skills.Total;
var factorSkills = 50 + sk * 100 / 10000;
var factorKillPts = 100 + kp * 2;
var factorGameTime = 50 + (int)(gameTime.Ticks * 100 / TimeSpan.TicksPerDay);
var totalFactor = Math.Clamp(factorSkills * factorKillPts * Math.Max(factorGameTime, 100) / 10000, 0, 100);
return new object[] { From, Address, Time, totalFactor };
}
public void Serialize(IGenericWriter writer)
{
writer.WriteEncodedInt(0);
writer.Write(From);
writer.Write(Address);
writer.Write(Time);
}
}
public class Candidate
{
public Candidate(Mobile mob)
{
Mobile = mob;
Voters = new List<Voter>();
}
public Candidate(IGenericReader reader)
{
var version = reader.ReadEncodedInt();
switch (version)
{
case 1:
{
Mobile = reader.ReadEntity<Mobile>();
var count = reader.ReadEncodedInt();
Voters = new List<Voter>(count);
for (var i = 0; i < count; ++i)
{
var voter = new Voter(reader, Mobile);
if (voter.From != null)
{
Voters.Add(voter);
}
}
break;
}
case 0:
{
Mobile = reader.ReadEntity<Mobile>();
var mobs = reader.ReadEntityList<Mobile>();
Voters = new List<Voter>(mobs.Count);
for (var i = 0; i < mobs.Count; ++i)
{
Voters.Add(new Voter(mobs[i], Mobile));
}
break;
}
}
}
public Mobile Mobile { get; }
public List<Voter> Voters { get; }
public int Votes => Voters.Count;
public void CleanMuleVotes()
{
for (var i = 0; i < Voters.Count; ++i)
{
var voter = Voters[i];
if ((int)voter.AcquireFields()[3] < 90)
{
Voters.RemoveAt(i--);
}
}
}
public void Serialize(IGenericWriter writer)
{
writer.WriteEncodedInt(1); // version
writer.Write(Mobile);
writer.WriteEncodedInt(Voters.Count);
for (var i = 0; i < Voters.Count; ++i)
{
Voters[i].Serialize(writer);
}
}
}
public enum ElectionState
{
Pending,
Campaign,
Election
}
}