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(); 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(); 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 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(); 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(); } public Candidate(IGenericReader reader) { var version = reader.ReadEncodedInt(); switch (version) { case 1: { Mobile = reader.ReadEntity(); var count = reader.ReadEncodedInt(); Voters = new List(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(); var mobs = reader.ReadEntityList(); Voters = new List(mobs.Count); for (var i = 0; i < mobs.Count; ++i) { Voters.Add(new Voter(mobs[i], Mobile)); } break; } } } public Mobile Mobile { get; } public List 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 } }