659 lines
21 KiB
C#
659 lines
21 KiB
C#
using System;
|
|
using Server;
|
|
using Server.Mobiles;
|
|
using Server.Items;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using Server.Accounting;
|
|
using System.Linq;
|
|
using Server.Gumps;
|
|
|
|
namespace Server.Engines.CityLoyalty
|
|
{
|
|
[PropertyObject]
|
|
public class CityElection
|
|
{
|
|
public static readonly int VotePeriod = 7;
|
|
public static readonly int NominationDeadline = 24;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public CityLoyaltySystem City { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ElectionEnded { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Ongoing { get { return CanNominate() || CanVote(); } }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime AutoPickGovernor { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string Time1
|
|
{
|
|
get { return StartTimes.Length > 0 ? StartTimes[0].ToShortDateString() : "Empty"; }
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string Time2
|
|
{
|
|
get { return StartTimes.Length > 1 ? StartTimes[1].ToShortDateString() : "Empty"; }
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string Time3
|
|
{
|
|
get { return StartTimes.Length > 2 ? StartTimes[2].ToShortDateString() : "Empty"; }
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string Time4
|
|
{
|
|
get { return StartTimes.Length > 3 ? StartTimes[3].ToShortDateString() : "Empty"; }
|
|
}
|
|
|
|
public List<BallotEntry> Candidates { get; set; }
|
|
|
|
public DateTime[] StartTimes { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ForceStart
|
|
{
|
|
get { return false; }
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
if (Ongoing)
|
|
EndElection();
|
|
|
|
StartTimes = new DateTime[1];
|
|
StartTimes[0] = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "...";
|
|
}
|
|
|
|
public CityElection(CityLoyaltySystem city)
|
|
{
|
|
City = city;
|
|
|
|
ElectionEnded = true;
|
|
Candidates = new List<BallotEntry>();
|
|
|
|
GetDefaultStartTimes();
|
|
}
|
|
|
|
public bool TryNominate(PlayerMobile pm)
|
|
{
|
|
CityLoyaltyEntry pentry = City.GetPlayerEntry<CityLoyaltyEntry>(pm);
|
|
|
|
if (pm.Young)
|
|
pm.SendMessage("Young players cannot be nominated for the ballot!");
|
|
else if (!City.IsCitizen(pm) || pentry == null)
|
|
pm.SendLocalizedMessage(1153890); // You must be a citizen of this City to nominate yourself for the ballot!
|
|
else if (City.GetLoyaltyRating(pm) < LoyaltyRating.Adored)
|
|
pm.SendLocalizedMessage(1153891); // You must at least be adored within the City to nominate yourself for the ballot.
|
|
else
|
|
{
|
|
Account a = pm.Account as Account;
|
|
for (int i = 0; i < a.Length; i++)
|
|
{
|
|
Mobile m = a[i];
|
|
|
|
if(!(m is PlayerMobile))
|
|
continue;
|
|
|
|
BallotEntry ballot = Candidates.FirstOrDefault(entry => entry.Player == m);
|
|
|
|
if (ballot != null && m is PlayerMobile)
|
|
{
|
|
pm.SendLocalizedMessage(ballot.Endorsements.Count > 0 ? 1153917 : 1153889); // A character from this account is currently endorsed for Candidacy and cannot be nominated. // A character from this account has already been nominated to run for office.
|
|
return false; // A character from this account has already been nominated to run for office.
|
|
}
|
|
|
|
ballot = Candidates.FirstOrDefault(entry => entry.Endorsements.Contains(m));
|
|
|
|
if (ballot != null && m is PlayerMobile)
|
|
{
|
|
pm.SendLocalizedMessage(1153892); // A character from this account has already endorsed a nominee!
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Candidates.Add(new BallotEntry(pm, pentry.Love, pentry.Hate));
|
|
pm.PrivateOverheadMessage(Network.MessageType.Regular, 0x3B2, 1153905, pm.NetState); // *You etch your name into the stone*
|
|
pm.SendLocalizedMessage(1154087); // You have 24 hours to get your nomination endorsed. If you do not get an endorsement within that period you will need to re-nominate yourself.
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool TryEndorse(PlayerMobile pm, PlayerMobile nominee)
|
|
{
|
|
if (pm.Young)
|
|
pm.SendMessage("Young players cannot endorose an nominee!");
|
|
else if (!City.IsCitizen(pm))
|
|
pm.SendLocalizedMessage(1153893); // You must be a citizen of this City to endorse a nominee for the ballot!
|
|
else
|
|
{
|
|
Account a = pm.Account as Account;
|
|
|
|
for (int i = 0; i < a.Length; i++)
|
|
{
|
|
Mobile m = a[i];
|
|
|
|
if(!(m is PlayerMobile))
|
|
continue;
|
|
|
|
BallotEntry ballot = Candidates.FirstOrDefault(entry => entry.Endorsements.Contains(m as PlayerMobile));
|
|
|
|
if (m is PlayerMobile && ballot != null)
|
|
{
|
|
pm.SendLocalizedMessage(1153892); // A character from this account has already endorsed a nominee!
|
|
return false;
|
|
}
|
|
|
|
BallotEntry ballot2 = Candidates.FirstOrDefault(entry => entry.Player == m);
|
|
|
|
if (m is PlayerMobile && ballot2 != null)
|
|
{
|
|
pm.SendLocalizedMessage(1153912); // A character from this account is currently nominated for candidacy and cannot offer an endorsement.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
BallotEntry pentry = Candidates.FirstOrDefault(e => e.Player == nominee);
|
|
|
|
if (pentry != null)
|
|
{
|
|
//<CENTER>Are you sure you wish to endorse this nominee? All endorsements are final and cannot be changed!</CENTER>
|
|
pm.SendGump(new ConfirmCallbackGump(pm, null, 1154091, pentry, null, (m, o) =>
|
|
{
|
|
BallotEntry e = o as BallotEntry;
|
|
e.Endorsements.Add(m as PlayerMobile);
|
|
m.PrivateOverheadMessage(Network.MessageType.Regular, 0x3B2, 1153913, m.NetState); // *You etch your endorsement for the nominee into the stone*
|
|
|
|
}));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool TryVote(PlayerMobile voter, PlayerMobile candidate)
|
|
{
|
|
if (!CanVote())
|
|
voter.SendLocalizedMessage(1153919); // The stone is not currently accepting votes.
|
|
else if (voter.Young)
|
|
voter.SendMessage("Young players cannot vote in a city election!");
|
|
else if (!City.IsCitizen(voter))
|
|
voter.SendLocalizedMessage(1153894); // You must be a citizen of this City to vote!
|
|
else if (City.GetLoyaltyRating(voter) < LoyaltyRating.Respected)
|
|
voter.SendLocalizedMessage(1154579); // You must be at least respected within the city to vote.
|
|
else
|
|
{
|
|
Account a = voter.Account as Account;
|
|
|
|
for (int i = 0; i < a.Length; i++)
|
|
{
|
|
Mobile m = a[i];
|
|
|
|
if (m is PlayerMobile && Candidates.FirstOrDefault(e => e.Votes.Contains(m)) != null)
|
|
{
|
|
voter.SendLocalizedMessage(1153922); // This account has already cast a vote in this election. You may only vote once.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
BallotEntry pentry = Candidates.FirstOrDefault(e => e.Player == candidate);
|
|
|
|
if (pentry != null)
|
|
{
|
|
//<CENTER>Are you sure you wish to cast your vote for this candidate? All votes are final and cannot be changed!</CENTER>
|
|
voter.SendGump(new ConfirmCallbackGump(voter, null, 1153921, pentry, null, (m, o) =>
|
|
{
|
|
BallotEntry e = o as BallotEntry;
|
|
e.Votes.Add(voter);
|
|
m.PrivateOverheadMessage(Network.MessageType.Regular, 0x3B2, 1153923, voter.NetState); // *You etch your vote into the stone*
|
|
}));
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void TryWithdraw(PlayerMobile pm)
|
|
{
|
|
BallotEntry entry = Candidates.FirstOrDefault(e => e.Player == pm);
|
|
|
|
if (entry != null)
|
|
{
|
|
//Are you sure you wish to withdrawal?
|
|
pm.SendGump(new ConfirmCallbackGump(pm, null, 1153918, entry, null, (m, o) =>
|
|
{
|
|
BallotEntry e = (BallotEntry)o;
|
|
|
|
if (Candidates.Contains(e))
|
|
{
|
|
Candidates.Remove(e);
|
|
m.PrivateOverheadMessage(Network.MessageType.Regular, 0x3B2, 1153911, m.NetState); // *You smudge your name off the stone*
|
|
}
|
|
|
|
}));
|
|
}
|
|
else
|
|
pm.SendLocalizedMessage(1153924); // You are not currently on any ballot to withdraw from.
|
|
}
|
|
|
|
public double GetStanding(BallotEntry entry)
|
|
{
|
|
if (Candidates.Contains(entry))
|
|
{
|
|
if (entry.Votes.Count <= 0)
|
|
return 0.0;
|
|
|
|
return (entry.Votes.Count / GetTotalVotes()) * 100;
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
public int GetTotalVotes()
|
|
{
|
|
int votes = 0;
|
|
|
|
Candidates.ForEach(c =>
|
|
{
|
|
votes += c.Votes.Count;
|
|
});
|
|
|
|
return votes;
|
|
}
|
|
|
|
public void OnTick()
|
|
{
|
|
foreach(DateTime dt in StartTimes)
|
|
{
|
|
if (dt.Year == DateTime.Now.Year && DateTime.Now.Month == dt.Month && DateTime.Now.Day > 14 && !ElectionEnded)
|
|
{
|
|
EndElection();
|
|
}
|
|
else if (dt.Year == DateTime.Now.Year && DateTime.Now.Month == dt.Month && DateTime.Now.Day == dt.Day && ElectionEnded)
|
|
{
|
|
StartNewElection();
|
|
}
|
|
}
|
|
|
|
if (City.Governor == null && AutoPickGovernor != DateTime.MinValue && DateTime.Now > AutoPickGovernor)
|
|
{
|
|
if (City.GovernorElect != null)
|
|
{
|
|
if (Candidates.Count > 0)
|
|
{
|
|
BallotEntry entry = Candidates.FirstOrDefault(c => c.Player == City.GovernorElect);
|
|
|
|
if (entry != null)
|
|
Candidates.Remove(entry);
|
|
}
|
|
|
|
City.GovernorElect = null;
|
|
}
|
|
|
|
if (Candidates.Count > 0)
|
|
{
|
|
Candidates.Sort();
|
|
City.GovernorElect = Candidates[0].Player;
|
|
}
|
|
|
|
AutoPickGovernor = DateTime.MinValue;
|
|
}
|
|
|
|
if (CanNominate())
|
|
{
|
|
Candidates.ForEach(entry =>
|
|
{
|
|
if (entry.TimeOfNomination + TimeSpan.FromHours(NominationDeadline) < DateTime.Now && entry.Endorsements.Count == 0)
|
|
Candidates.Remove(entry);
|
|
});
|
|
}
|
|
}
|
|
|
|
public void StartNewElection()
|
|
{
|
|
ElectionEnded = false;
|
|
|
|
Candidates.Clear();
|
|
Candidates.TrimExcess();
|
|
|
|
AutoPickGovernor = DateTime.MinValue;
|
|
}
|
|
|
|
private static int[] _Periods = { 3, 6, 9, 12 };
|
|
|
|
public void GetDefaultStartTimes()
|
|
{
|
|
StartTimes = new DateTime[4];
|
|
|
|
for(int i = 0; i < _Periods.Length; i++)
|
|
{
|
|
DateTime dt = new DateTime(DateTime.Now.Year, _Periods[i], 1);
|
|
|
|
if (dt < DateTime.Now)
|
|
dt = new DateTime(dt.Year + 1, dt.Month, dt.Day);
|
|
|
|
StartTimes[i] = dt;
|
|
}
|
|
}
|
|
|
|
public static DateTime[] ValidateStartTimes(Mobile m, int[] times)
|
|
{
|
|
if (times != null && times.Length > 0)
|
|
{
|
|
DateTime[] starttimes = new DateTime[times.Length];
|
|
|
|
for (int i = 0; i < times.Length; i++)
|
|
{
|
|
int month = times[i];
|
|
|
|
if (month <= 0 || month > 12)
|
|
{
|
|
m.SendMessage("Invalid month for #{0}. It must be between 0 and 12.", i + 1);
|
|
return null;
|
|
}
|
|
|
|
DateTime dt = new DateTime(DateTime.Now.Year, month, 1);
|
|
|
|
if (dt < DateTime.Now)
|
|
dt = new DateTime(dt.Year + 1, dt.Month, dt.Day);
|
|
|
|
starttimes[i] = dt;
|
|
}
|
|
|
|
if (CheckConflict(starttimes))
|
|
{
|
|
m.SendMessage("times are too close to another start time. Please try again.");
|
|
return null;
|
|
}
|
|
|
|
return starttimes;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static bool CheckConflict(DateTime[] times)
|
|
{
|
|
if (times == null || times.Length == 0)
|
|
return false;
|
|
|
|
for(int i = 0; i < times.Length; i++)
|
|
{
|
|
DateTime t = times[i];
|
|
|
|
for (int j = 0; j < times.Length; j++)
|
|
{
|
|
if (i == j)
|
|
continue;
|
|
|
|
if ((times[j] > t && times[j] - t < TimeSpan.FromDays(30)) || times[j] < t && t - times[j] < TimeSpan.FromDays(30))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public DateTime NextElection()
|
|
{
|
|
if (CanNominate() || CanVote())
|
|
return DateTime.MinValue;
|
|
|
|
DateTime closest = DateTime.MinValue;
|
|
|
|
foreach (DateTime dt in StartTimes)
|
|
{
|
|
if (DateTime.Now < dt)
|
|
{
|
|
if (closest == DateTime.MinValue || dt - DateTime.Now < closest - DateTime.Now)
|
|
{
|
|
closest = dt;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest;
|
|
}
|
|
|
|
public bool CanNominate()
|
|
{
|
|
DateTime until = DateTime.MinValue;
|
|
return CanNominate(out until);
|
|
}
|
|
|
|
public bool CanNominate(out DateTime until)
|
|
{
|
|
until = DateTime.Now;
|
|
foreach (DateTime dt in StartTimes)
|
|
{
|
|
if (dt.Year == DateTime.Now.Year && DateTime.Now.Month == dt.Month && DateTime.Now <= dt.AddDays(VotePeriod))
|
|
{
|
|
until = dt.AddDays(VotePeriod);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool CanVote()
|
|
{
|
|
DateTime until = DateTime.MinValue;
|
|
return CanVote(out until);
|
|
}
|
|
|
|
public bool CanVote(out DateTime until)
|
|
{
|
|
until = DateTime.Now;
|
|
foreach (DateTime dt in StartTimes)
|
|
{
|
|
if (dt.Year == DateTime.Now.Year && DateTime.Now.Month == dt.Month && DateTime.Now > dt.AddDays(VotePeriod) && DateTime.Now <= dt.AddDays(VotePeriod * 2))
|
|
{
|
|
until = dt.AddDays(VotePeriod * 2);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void EndElection()
|
|
{
|
|
ElectionEnded = true;
|
|
|
|
if (Candidates.Count > 0)
|
|
{
|
|
Candidates.Sort();
|
|
City.GovernorElect = Candidates[0].Player;
|
|
|
|
AutoPickGovernor = DateTime.Now + TimeSpan.FromDays(7);
|
|
}
|
|
|
|
if (City.GovernorElect == null)
|
|
{
|
|
City.PendingGovernor = true;
|
|
AutoPickGovernor = DateTime.Now + TimeSpan.FromDays(Utility.RandomMinMax(2, 4));
|
|
|
|
if (City.Stone != null)
|
|
City.Stone.InvalidateProperties();
|
|
}
|
|
|
|
for(int i = 0; i < StartTimes.Length; i++)
|
|
{
|
|
DateTime dt = StartTimes[i];
|
|
|
|
if (dt == DateTime.MinValue)
|
|
continue;
|
|
|
|
if (dt < DateTime.Now)
|
|
dt = new DateTime(dt.Year + 1, dt.Month, dt.Day);
|
|
}
|
|
}
|
|
|
|
public void OnRejectOffice(PlayerMobile pm)
|
|
{
|
|
BallotEntry entry = Candidates.FirstOrDefault(c => c.Player == pm);
|
|
|
|
if (entry != null)
|
|
Candidates.Remove(entry);
|
|
|
|
AutoPickGovernor = DateTime.Now + TimeSpan.FromDays(Utility.RandomMinMax(2, 4));
|
|
}
|
|
|
|
public void Serialize(GenericWriter writer)
|
|
{
|
|
writer.Write(0);
|
|
|
|
writer.Write(ElectionEnded);
|
|
writer.Write(AutoPickGovernor);
|
|
|
|
writer.Write(StartTimes.Length);
|
|
foreach (DateTime dt in StartTimes)
|
|
writer.Write(dt);
|
|
|
|
writer.Write(Candidates.Count);
|
|
Candidates.ForEach(entry => entry.Serialize(writer));
|
|
}
|
|
|
|
public CityElection(CityLoyaltySystem city, GenericReader reader)
|
|
{
|
|
City = city;
|
|
Candidates = new List<BallotEntry>();
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
ElectionEnded = reader.ReadBool();
|
|
AutoPickGovernor = reader.ReadDateTime();
|
|
|
|
int c = reader.ReadInt();
|
|
StartTimes = new DateTime[c];
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
DateTime time = reader.ReadDateTime();
|
|
|
|
if (time < DateTime.Now && ElectionEnded)
|
|
time = new DateTime(time.Year + 1, time.Month, time.Day);
|
|
|
|
StartTimes[i] = time;
|
|
}
|
|
|
|
c = reader.ReadInt();
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
BallotEntry entry = new BallotEntry(reader);
|
|
if (entry.Player != null)
|
|
Candidates.Add(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class BallotEntry : IComparable<BallotEntry>
|
|
{
|
|
public PlayerMobile Player { get; set; }
|
|
public DateTime TimeOfNomination { get; set; }
|
|
|
|
public int Love { get; set; }
|
|
public int Hate { get; set; }
|
|
|
|
public List<PlayerMobile> Endorsements { get; set; }
|
|
public List<PlayerMobile> Votes { get; set; }
|
|
|
|
public BallotEntry(PlayerMobile pm, int love, int hate)
|
|
{
|
|
Player = pm;
|
|
TimeOfNomination = DateTime.Now;
|
|
|
|
Love = love;
|
|
Hate = hate;
|
|
|
|
Endorsements = new List<PlayerMobile>();
|
|
Votes = new List<PlayerMobile>();
|
|
}
|
|
|
|
public int CompareTo(BallotEntry entry)
|
|
{
|
|
if (Player == null)
|
|
return -1;
|
|
|
|
if (Votes.Count > entry.Votes.Count)
|
|
return 1;
|
|
else if (Votes.Count < entry.Votes.Count)
|
|
return -1;
|
|
else
|
|
{
|
|
if (Love > entry.Love || Hate < entry.Hate)
|
|
return 1;
|
|
else if (Love == entry.Love && Hate == entry.Hate && Utility.RandomBool())
|
|
return 1;
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public void Serialize(GenericWriter writer)
|
|
{
|
|
writer.Write(0);
|
|
|
|
writer.Write(Player);
|
|
writer.Write(TimeOfNomination);
|
|
|
|
writer.Write(Love);
|
|
writer.Write(Hate);
|
|
|
|
writer.Write(Endorsements.Count);
|
|
Endorsements.ForEach(p => writer.Write(p));
|
|
|
|
writer.Write(Votes.Count);
|
|
Votes.ForEach(p => writer.Write(p));
|
|
}
|
|
|
|
public BallotEntry(GenericReader reader)
|
|
{
|
|
Endorsements = new List<PlayerMobile>();
|
|
Votes = new List<PlayerMobile>();
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
Player = reader.ReadMobile() as PlayerMobile;
|
|
TimeOfNomination = reader.ReadDateTime();
|
|
|
|
Love = reader.ReadInt();
|
|
Hate = reader.ReadInt();
|
|
|
|
int c = reader.ReadInt();
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
PlayerMobile p = reader.ReadMobile() as PlayerMobile;
|
|
if (p != null)
|
|
Endorsements.Add(p);
|
|
}
|
|
|
|
c = reader.ReadInt();
|
|
for (int i = 0; i < c; i++)
|
|
{
|
|
PlayerMobile p = reader.ReadMobile() as PlayerMobile;
|
|
if (p != null)
|
|
Votes.Add(p);
|
|
}
|
|
}
|
|
}
|
|
} |