Convert movement, actions (lift/use), combat, and spells to use Core.TickCount and avoid DateTime caveats (performance, system time dependency, etc) Refactor DateTime.Now to DateTime.UtcNow Add LOS check for Iron Maiden addon
643 lines
No EOL
15 KiB
C#
643 lines
No EOL
15 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using Server;
|
|
using Server.Gumps;
|
|
using Server.Network;
|
|
using Server.Prompts;
|
|
|
|
namespace Server.Misc
|
|
{
|
|
public class ShardPoller : Item
|
|
{
|
|
private string m_Title;
|
|
|
|
private ShardPollOption[] m_Options;
|
|
private IPAddress[] m_Addresses;
|
|
|
|
private TimeSpan m_Duration;
|
|
private DateTime m_StartTime;
|
|
|
|
private bool m_Active;
|
|
|
|
public ShardPollOption[] Options
|
|
{
|
|
get{ return m_Options; }
|
|
set{ m_Options = value; }
|
|
}
|
|
|
|
public IPAddress[] Addresses
|
|
{
|
|
get{ return m_Addresses; }
|
|
set{ m_Addresses = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public string Title
|
|
{
|
|
get{ return m_Title; }
|
|
set{ m_Title = ShardPollPrompt.UrlToHref( value ); }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public TimeSpan Duration
|
|
{
|
|
get{ return m_Duration; }
|
|
set{ m_Duration = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public DateTime StartTime
|
|
{
|
|
get{ return m_StartTime; }
|
|
set{ m_StartTime = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public TimeSpan TimeRemaining
|
|
{
|
|
get
|
|
{
|
|
if ( m_StartTime == DateTime.MinValue || !m_Active )
|
|
return TimeSpan.Zero;
|
|
|
|
try
|
|
{
|
|
TimeSpan ts = (m_StartTime + m_Duration) - DateTime.UtcNow;
|
|
|
|
if ( ts < TimeSpan.Zero )
|
|
return TimeSpan.Zero;
|
|
|
|
return ts;
|
|
}
|
|
catch
|
|
{
|
|
return TimeSpan.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public bool Active
|
|
{
|
|
get{ return m_Active; }
|
|
set
|
|
{
|
|
if ( m_Active == value )
|
|
return;
|
|
|
|
m_Active = value;
|
|
|
|
if ( m_Active )
|
|
{
|
|
m_StartTime = DateTime.UtcNow;
|
|
m_ActivePollers.Add( this );
|
|
}
|
|
else
|
|
{
|
|
m_ActivePollers.Remove( this );
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool HasAlreadyVoted( NetState ns )
|
|
{
|
|
for ( int i = 0; i < m_Options.Length; ++i )
|
|
{
|
|
if ( m_Options[i].HasAlreadyVoted( ns ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void AddVote( NetState ns, ShardPollOption option )
|
|
{
|
|
option.AddVote( ns );
|
|
}
|
|
|
|
public void RemoveOption( ShardPollOption option )
|
|
{
|
|
int index = Array.IndexOf( m_Options, option );
|
|
|
|
if ( index < 0 )
|
|
return;
|
|
|
|
ShardPollOption[] old = m_Options;
|
|
m_Options = new ShardPollOption[old.Length - 1];
|
|
|
|
for ( int i = 0; i < index; ++i )
|
|
m_Options[i] = old[i];
|
|
|
|
for ( int i = index; i < m_Options.Length; ++i )
|
|
m_Options[i] = old[i + 1];
|
|
}
|
|
|
|
public void AddOption( ShardPollOption option )
|
|
{
|
|
ShardPollOption[] old = m_Options;
|
|
m_Options = new ShardPollOption[old.Length + 1];
|
|
|
|
for ( int i = 0; i < old.Length; ++i )
|
|
m_Options[i] = old[i];
|
|
|
|
m_Options[old.Length] = option;
|
|
}
|
|
|
|
public override string DefaultName
|
|
{
|
|
get { return "shard poller"; }
|
|
}
|
|
|
|
[Constructable( AccessLevel.Administrator )]
|
|
public ShardPoller() : base( 0x1047 )
|
|
{
|
|
m_Duration = TimeSpan.FromHours( 24.0 );
|
|
m_Options = new ShardPollOption[0];
|
|
m_Addresses = new IPAddress[0];
|
|
|
|
Movable = false;
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
EventSink.Login += new LoginEventHandler( EventSink_Login );
|
|
}
|
|
|
|
private static List<ShardPoller> m_ActivePollers = new List<ShardPoller>();
|
|
|
|
private static void EventSink_Login( LoginEventArgs e )
|
|
{
|
|
if ( m_ActivePollers.Count == 0 )
|
|
return;
|
|
|
|
Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), new TimerStateCallback( EventSink_Login_Callback ), e.Mobile );
|
|
}
|
|
|
|
private static void EventSink_Login_Callback( object state )
|
|
{
|
|
Mobile from = (Mobile)state;
|
|
NetState ns = from.NetState;
|
|
|
|
if ( ns == null )
|
|
return;
|
|
|
|
ShardPollGump spg = null;
|
|
|
|
for ( int i = 0; i < m_ActivePollers.Count; ++i )
|
|
{
|
|
ShardPoller poller = m_ActivePollers[i];
|
|
|
|
if ( poller.Deleted || !poller.Active )
|
|
continue;
|
|
|
|
if ( poller.TimeRemaining > TimeSpan.Zero )
|
|
{
|
|
if ( poller.HasAlreadyVoted( ns ) )
|
|
continue;
|
|
|
|
if ( spg == null )
|
|
{
|
|
spg = new ShardPollGump( from, poller, false, null );
|
|
from.SendGump( spg );
|
|
}
|
|
else
|
|
{
|
|
spg.QueuePoll( poller );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
poller.Active = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SendQueuedPoll_Callback( object state )
|
|
{
|
|
object[] states = (object[])state;
|
|
Mobile from = (Mobile)states[0];
|
|
Queue<ShardPoller> queue = (Queue<ShardPoller>)states[1];
|
|
|
|
from.SendGump( new ShardPollGump( from, this, false, queue ) );
|
|
}
|
|
|
|
public override void OnDoubleClick( Mobile from )
|
|
{
|
|
if ( from.AccessLevel >= AccessLevel.Administrator )
|
|
from.SendGump( new ShardPollGump( from, this, true, null ) );
|
|
}
|
|
|
|
public ShardPoller( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 0 ); // version
|
|
|
|
writer.Write( m_Title );
|
|
writer.Write( m_Duration );
|
|
writer.Write( m_StartTime );
|
|
writer.Write( m_Active );
|
|
|
|
writer.Write( m_Options.Length );
|
|
|
|
for ( int i = 0; i < m_Options.Length; ++i )
|
|
m_Options[i].Serialize( writer );
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
switch ( version )
|
|
{
|
|
case 0:
|
|
{
|
|
m_Title = reader.ReadString();
|
|
m_Duration = reader.ReadTimeSpan();
|
|
m_StartTime = reader.ReadDateTime();
|
|
m_Active = reader.ReadBool();
|
|
|
|
m_Options = new ShardPollOption[reader.ReadInt()];
|
|
|
|
for ( int i = 0; i < m_Options.Length; ++i )
|
|
m_Options[i] = new ShardPollOption( reader );
|
|
|
|
if ( m_Active )
|
|
m_ActivePollers.Add( this );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnDelete()
|
|
{
|
|
base.OnDelete();
|
|
|
|
Active = false;
|
|
}
|
|
}
|
|
|
|
public class ShardPollOption
|
|
{
|
|
private string m_Title;
|
|
private int m_LineBreaks;
|
|
private IPAddress[] m_Voters;
|
|
|
|
public string Title{ get{ return m_Title; } set{ m_Title = value; m_LineBreaks = GetBreaks( m_Title ); } }
|
|
public int LineBreaks{ get{ return m_LineBreaks; } }
|
|
|
|
public int Votes{ get{ return m_Voters.Length; } }
|
|
public IPAddress[] Voters{ get{ return m_Voters; } set{ m_Voters = value; } }
|
|
|
|
public ShardPollOption( string title )
|
|
{
|
|
m_Title = title;
|
|
m_LineBreaks = GetBreaks( m_Title );
|
|
m_Voters = new IPAddress[0];
|
|
}
|
|
|
|
public bool HasAlreadyVoted( NetState ns )
|
|
{
|
|
if ( ns == null )
|
|
return false;
|
|
|
|
IPAddress ipAddress = ns.Address;
|
|
|
|
for ( int i = 0; i < m_Voters.Length; ++i )
|
|
{
|
|
if ( Utility.IPMatchClassC( m_Voters[i], ipAddress ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void AddVote( NetState ns )
|
|
{
|
|
if ( ns == null )
|
|
return;
|
|
|
|
IPAddress[] old = m_Voters;
|
|
m_Voters = new IPAddress[old.Length + 1];
|
|
|
|
for ( int i = 0; i < old.Length; ++i )
|
|
m_Voters[i] = old[i];
|
|
|
|
m_Voters[old.Length] = ns.Address;
|
|
}
|
|
|
|
public int ComputeHeight()
|
|
{
|
|
int height = m_LineBreaks * 18;
|
|
|
|
if ( height > 30 )
|
|
return height;
|
|
|
|
return 30;
|
|
}
|
|
|
|
public int GetBreaks( string title )
|
|
{
|
|
if ( title == null )
|
|
return 1;
|
|
|
|
int count = 0;
|
|
int index = -1;
|
|
|
|
do
|
|
{
|
|
++count;
|
|
index = title.IndexOf( "<br>", index + 1 );
|
|
} while ( index >= 0 );
|
|
|
|
return count;
|
|
}
|
|
|
|
public ShardPollOption( GenericReader reader )
|
|
{
|
|
int version = reader.ReadInt();
|
|
|
|
switch ( version )
|
|
{
|
|
case 0:
|
|
{
|
|
m_Title = reader.ReadString();
|
|
m_LineBreaks = GetBreaks( m_Title );
|
|
|
|
m_Voters = new IPAddress[reader.ReadInt()];
|
|
|
|
for ( int i = 0; i < m_Voters.Length; ++i )
|
|
m_Voters[i] = Utility.Intern( reader.ReadIPAddress() );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Serialize( GenericWriter writer )
|
|
{
|
|
writer.Write( (int) 0 ); // version
|
|
|
|
writer.Write( m_Title );
|
|
|
|
writer.Write( m_Voters.Length );
|
|
|
|
for ( int i = 0; i < m_Voters.Length; ++i )
|
|
writer.Write( m_Voters[i] );
|
|
}
|
|
}
|
|
|
|
public class ShardPollGump : Gump
|
|
{
|
|
private Mobile m_From;
|
|
private ShardPoller m_Poller;
|
|
private bool m_Editing;
|
|
private Queue<ShardPoller> m_Polls;
|
|
|
|
public bool Editing{ get{ return m_Editing; } }
|
|
|
|
public void QueuePoll( ShardPoller poller )
|
|
{
|
|
if ( m_Polls == null )
|
|
m_Polls = new Queue<ShardPoller>( 4 );
|
|
|
|
m_Polls.Enqueue( poller );
|
|
}
|
|
|
|
public string Center( string text )
|
|
{
|
|
return String.Format( "<CENTER>{0}</CENTER>", text );
|
|
}
|
|
|
|
public string Color( string text, int color )
|
|
{
|
|
return String.Format( "<BASEFONT COLOR=#{0:X6}>{1}</BASEFONT>", color, text );
|
|
}
|
|
|
|
private const int LabelColor32 = 0xFFFFFF;
|
|
|
|
public ShardPollGump( Mobile from, ShardPoller poller, bool editing, Queue<ShardPoller> polls ) : base( 50, 50 )
|
|
{
|
|
m_From = from;
|
|
m_Poller = poller;
|
|
m_Editing = editing;
|
|
m_Polls = polls;
|
|
|
|
Closable = false;
|
|
|
|
AddPage( 0 );
|
|
|
|
int totalVotes = 0;
|
|
int totalOptionHeight = 0;
|
|
|
|
for ( int i = 0; i < poller.Options.Length; ++i )
|
|
{
|
|
totalVotes += poller.Options[i].Votes;
|
|
totalOptionHeight += poller.Options[i].ComputeHeight() + 5;
|
|
}
|
|
|
|
bool isViewingResults = editing && poller.Active;
|
|
bool isCompleted = totalVotes > 0 && !poller.Active;
|
|
|
|
if ( editing && !isViewingResults )
|
|
totalOptionHeight += 35;
|
|
|
|
int height = 115 + totalOptionHeight;
|
|
|
|
AddBackground( 1, 1, 398, height - 2, 3600 );
|
|
AddAlphaRegion( 16, 15, 369, height - 31 );
|
|
|
|
AddItem( 308, 30, 0x1E5E );
|
|
|
|
string title;
|
|
|
|
if ( editing )
|
|
title = ( isCompleted ? "Poll Completed" : "Poll Editor" );
|
|
else
|
|
title = "Shard Poll";
|
|
|
|
AddHtml( 22, 22, 294, 20, Color( Center( title ), LabelColor32 ), false, false );
|
|
|
|
if ( editing )
|
|
{
|
|
AddHtml( 22, 22, 294, 20, Color( String.Format( "{0} total", totalVotes ), LabelColor32 ), false, false );
|
|
AddButton( 287, 23, 0x2622, 0x2623, 2, GumpButtonType.Reply, 0 );
|
|
}
|
|
|
|
AddHtml( 22, 50, 294, 40, Color( poller.Title, 0x99CC66 ), false, false );
|
|
|
|
AddImageTiled( 32, 88, 264, 1, 9107 );
|
|
AddImageTiled( 42, 90, 264, 1, 9157 );
|
|
|
|
int y = 100;
|
|
|
|
for ( int i = 0; i < poller.Options.Length; ++i )
|
|
{
|
|
ShardPollOption option = poller.Options[i];
|
|
string text = option.Title;
|
|
|
|
if ( editing && totalVotes > 0 )
|
|
{
|
|
double perc = option.Votes / (double)totalVotes;
|
|
|
|
text = String.Format( "[{1}: {2}%] {0}", text, option.Votes, (int)(perc*100) );
|
|
}
|
|
|
|
int optHeight = option.ComputeHeight();
|
|
|
|
y += optHeight/2;
|
|
|
|
if ( isViewingResults )
|
|
AddImage( 24, y - 15, 0x25FE );
|
|
else
|
|
AddRadio( 24, y - 15, 0x25F9, 0x25FC, false, 1 + i );
|
|
|
|
AddHtml( 60, y - (9 * option.LineBreaks), 250, 18 * option.LineBreaks, Color( text, LabelColor32 ), false, false );
|
|
|
|
y += optHeight/2;
|
|
y += 5;
|
|
}
|
|
|
|
if ( editing && !isViewingResults )
|
|
{
|
|
AddRadio( 24, y + 15 - 15, 0x25F9, 0x25FC, false, 1 + poller.Options.Length );
|
|
AddHtml( 60, y + 15 - 9, 250, 18, Color( "Create new option.", 0x99CC66 ), false, false );
|
|
}
|
|
|
|
AddButton( 314, height - 73, 247, 248, 1, GumpButtonType.Reply, 0 );
|
|
AddButton( 314, height - 47, 242, 241, 0, GumpButtonType.Reply, 0 );
|
|
}
|
|
|
|
public override void OnResponse( NetState sender, RelayInfo info )
|
|
{
|
|
if ( m_Polls != null && m_Polls.Count > 0 )
|
|
{
|
|
ShardPoller poller = m_Polls.Dequeue();
|
|
|
|
if ( poller != null )
|
|
Timer.DelayCall( TimeSpan.FromSeconds( 1.0 ), new TimerStateCallback( poller.SendQueuedPoll_Callback ), new object[]{ m_From, m_Polls } );
|
|
}
|
|
|
|
if ( info.ButtonID == 1 )
|
|
{
|
|
int[] switches = info.Switches;
|
|
|
|
if ( switches.Length == 0 )
|
|
return;
|
|
|
|
int switched = switches[0] - 1;
|
|
ShardPollOption opt = null;
|
|
|
|
if ( switched >= 0 && switched < m_Poller.Options.Length )
|
|
opt = m_Poller.Options[switched];
|
|
|
|
if ( opt == null && !m_Editing )
|
|
return;
|
|
|
|
if ( m_Editing )
|
|
{
|
|
if ( !m_Poller.Active )
|
|
{
|
|
m_From.SendMessage( "Enter a title for the option. Escape to cancel.{0}", opt == null ? "" : " Use \"DEL\" to delete." );
|
|
m_From.Prompt = new ShardPollPrompt( m_Poller, opt );
|
|
}
|
|
else
|
|
{
|
|
m_From.SendMessage( "You may not edit an active poll. Deactivate it first." );
|
|
m_From.SendGump( new ShardPollGump( m_From, m_Poller, m_Editing, m_Polls ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !m_Poller.Active )
|
|
m_From.SendMessage( "The poll has been deactivated." );
|
|
else if ( m_Poller.HasAlreadyVoted( sender ) )
|
|
m_From.SendMessage( "You have already voted on this poll." );
|
|
else
|
|
m_Poller.AddVote( sender, opt );
|
|
}
|
|
}
|
|
else if ( info.ButtonID == 2 && m_Editing )
|
|
{
|
|
m_From.SendGump( new ShardPollGump( m_From, m_Poller, m_Editing, m_Polls ) );
|
|
m_From.SendGump( new PropertiesGump( m_From, m_Poller ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ShardPollPrompt : Prompt
|
|
{
|
|
private ShardPoller m_Poller;
|
|
private ShardPollOption m_Option;
|
|
|
|
public ShardPollPrompt( ShardPoller poller, ShardPollOption opt )
|
|
{
|
|
m_Poller = poller;
|
|
m_Option = opt;
|
|
}
|
|
|
|
public override void OnCancel( Mobile from )
|
|
{
|
|
from.SendGump( new ShardPollGump( from, m_Poller, true, null ) );
|
|
}
|
|
|
|
private static Regex m_UrlRegex = new Regex( @"\[url(?:=(.*?))?\](.*?)\[/url\]", RegexOptions.IgnoreCase | RegexOptions.Compiled );
|
|
|
|
private static string UrlRegex_Match( Match m )
|
|
{
|
|
if ( m.Groups[1].Success )
|
|
{
|
|
if ( m.Groups[2].Success )
|
|
return String.Format( "<a href=\"{0}\">{1}</a>", m.Groups[1].Value, m.Groups[2].Value );
|
|
}
|
|
else if ( m.Groups[2].Success )
|
|
{
|
|
return String.Format( "<a href=\"{0}\">{0}</a>", m.Groups[2].Value );
|
|
}
|
|
|
|
return m.Value;
|
|
}
|
|
|
|
public static string UrlToHref( string text )
|
|
{
|
|
if ( text == null )
|
|
return null;
|
|
|
|
return m_UrlRegex.Replace( text, new MatchEvaluator( UrlRegex_Match ) );
|
|
}
|
|
|
|
public override void OnResponse( Mobile from, string text )
|
|
{
|
|
if ( m_Poller.Active )
|
|
{
|
|
from.SendMessage( "You may not edit an active poll. Deactivate it first." );
|
|
}
|
|
else if ( text == "DEL" )
|
|
{
|
|
if ( m_Option != null )
|
|
m_Poller.RemoveOption( m_Option );
|
|
}
|
|
else
|
|
{
|
|
text = UrlToHref( text );
|
|
|
|
if ( m_Option == null )
|
|
m_Poller.AddOption( new ShardPollOption( text ) );
|
|
else
|
|
m_Option.Title = text;
|
|
}
|
|
|
|
from.SendGump( new ShardPollGump( from, m_Poller, true, null ) );
|
|
}
|
|
}
|
|
} |