mirror of
https://github.com/runuo/runuo
synced 2026-08-11 22:26:04 -04:00
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
571 lines
No EOL
13 KiB
C#
571 lines
No EOL
13 KiB
C#
using System;
|
|
using System.Collections;
|
|
using Server;
|
|
using Server.Gumps;
|
|
using Server.Network;
|
|
|
|
namespace Server.Items
|
|
{
|
|
public class PowerGenerator : BaseAddon
|
|
{
|
|
[Constructable]
|
|
public PowerGenerator() : this( Utility.RandomMinMax( 3, 6 ) )
|
|
{
|
|
}
|
|
|
|
[Constructable]
|
|
public PowerGenerator( int sideLength )
|
|
{
|
|
AddGeneratorComponent( 0x4FA1, 0, 0, 0 );
|
|
AddGeneratorComponent( 0x76, -1, 0, 0 );
|
|
AddGeneratorComponent( 0x75, 0, -1, 0 );
|
|
AddGeneratorComponent( 0x37F4, 0, 0, 13 );
|
|
|
|
AddComponent( new ControlPanel( sideLength ), 1, 0, -2 );
|
|
}
|
|
|
|
public override bool ShareHue{ get{ return false; } }
|
|
|
|
private void AddGeneratorComponent( int itemID, int x, int y, int z )
|
|
{
|
|
AddonComponent component = new AddonComponent( itemID );
|
|
component.Name = "a power generator";
|
|
component.Hue = 0x451;
|
|
|
|
AddComponent( component, x, y, z );
|
|
}
|
|
|
|
public PowerGenerator( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.WriteEncodedInt( (int) 0 ); // version
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadEncodedInt();
|
|
}
|
|
}
|
|
|
|
public class ControlPanel : AddonComponent
|
|
{
|
|
private static readonly TimeSpan m_UseTimeout = TimeSpan.FromMinutes( 2.0 );
|
|
|
|
public struct Node
|
|
{
|
|
private int m_X;
|
|
private int m_Y;
|
|
|
|
public int X{ get{ return m_X; } set{ m_X = value; } }
|
|
public int Y{ get{ return m_Y; } set{ m_Y = value; } }
|
|
|
|
public Node( int x, int y )
|
|
{
|
|
m_X = x;
|
|
m_Y = y;
|
|
}
|
|
}
|
|
|
|
private int m_SideLength;
|
|
private Node[] m_Path;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int SideLength
|
|
{
|
|
get{ return m_SideLength; }
|
|
set
|
|
{
|
|
if ( value < 3 )
|
|
value = 3;
|
|
else if ( value > 6 )
|
|
value = 6;
|
|
|
|
if ( m_SideLength != value )
|
|
{
|
|
m_SideLength = value;
|
|
InitPath();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Node[] Path{ get{ return m_Path; } }
|
|
|
|
public override string DefaultName
|
|
{
|
|
get { return "a control panel"; }
|
|
}
|
|
|
|
public ControlPanel( int sideLength ) : base( 0xBDC )
|
|
{
|
|
Hue = 0x835;
|
|
|
|
SideLength = sideLength;
|
|
}
|
|
|
|
private enum PathDirection
|
|
{
|
|
Left,
|
|
Up,
|
|
Right,
|
|
Down
|
|
}
|
|
|
|
public void InitPath()
|
|
{
|
|
// Depth-First Search algorithm
|
|
|
|
int totalNodes = SideLength * SideLength;
|
|
|
|
Node[] stack = new Node[totalNodes];
|
|
Node current = stack[0] = new Node( 0, 0 );
|
|
int stackSize = 1;
|
|
|
|
bool[,] visited = new bool[SideLength, SideLength];
|
|
visited[0, 0] = true;
|
|
|
|
while ( true )
|
|
{
|
|
PathDirection[] choices = new PathDirection[4];
|
|
int count = 0;
|
|
|
|
if ( current.X > 0 && !visited[current.X - 1, current.Y] )
|
|
choices[count++] = PathDirection.Left;
|
|
|
|
if ( current.Y > 0 && !visited[current.X, current.Y - 1] )
|
|
choices[count++] = PathDirection.Up;
|
|
|
|
if ( current.X < SideLength - 1 && !visited[current.X + 1, current.Y] )
|
|
choices[count++] = PathDirection.Right;
|
|
|
|
if ( current.Y < SideLength - 1 && !visited[current.X, current.Y + 1] )
|
|
choices[count++] = PathDirection.Down;
|
|
|
|
if ( count > 0 )
|
|
{
|
|
PathDirection dir = choices[Utility.Random( count )];
|
|
|
|
switch ( dir )
|
|
{
|
|
case PathDirection.Left:
|
|
current = new Node( current.X - 1, current.Y );
|
|
break;
|
|
case PathDirection.Up:
|
|
current = new Node( current.X, current.Y - 1 );
|
|
break;
|
|
case PathDirection.Right:
|
|
current = new Node( current.X + 1, current.Y );
|
|
break;
|
|
default:
|
|
current = new Node( current.X, current.Y + 1 );
|
|
break;
|
|
}
|
|
|
|
stack[stackSize++] = current;
|
|
|
|
if ( current.X == SideLength - 1 && current.Y == SideLength - 1 )
|
|
break;
|
|
|
|
visited[current.X, current.Y] = true;
|
|
}
|
|
else
|
|
{
|
|
current = stack[--stackSize - 1];
|
|
}
|
|
}
|
|
|
|
m_Path = new Node[stackSize];
|
|
|
|
for ( int i = 0; i < stackSize; i++ )
|
|
{
|
|
m_Path[i] = stack[i];
|
|
}
|
|
|
|
if ( m_User != null )
|
|
{
|
|
m_User.CloseGump( typeof( GameGump ) );
|
|
m_User = null;
|
|
}
|
|
}
|
|
|
|
private Mobile m_User;
|
|
private DateTime m_LastUse;
|
|
|
|
public override void OnDoubleClick( Mobile from )
|
|
{
|
|
if ( !from.InRange( this, 3 ) )
|
|
{
|
|
from.SendLocalizedMessage( 500446 ); // That is too far away.
|
|
return;
|
|
}
|
|
|
|
if ( m_User != null )
|
|
{
|
|
if ( m_User == from )
|
|
return;
|
|
|
|
if ( m_User.Deleted || m_User.Map != Map || !m_User.InRange( this, 3 )
|
|
|| m_User.NetState == null || DateTime.UtcNow - m_LastUse >= m_UseTimeout )
|
|
{
|
|
m_User.CloseGump( typeof( GameGump ) );
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage( "Someone is currently using the control panel." );
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_User = from;
|
|
m_LastUse = DateTime.UtcNow;
|
|
|
|
from.SendGump( new GameGump( this, from, 0, false ) );
|
|
}
|
|
|
|
private class GameGump : Gump
|
|
{
|
|
private enum NodeHue
|
|
{
|
|
Gray,
|
|
Blue,
|
|
Red
|
|
}
|
|
|
|
private ControlPanel m_Panel;
|
|
private Mobile m_From;
|
|
private int m_Step;
|
|
|
|
public GameGump( ControlPanel panel, Mobile from, int step, bool hint ) : base( 5, 30 )
|
|
{
|
|
m_Panel = panel;
|
|
m_From = from;
|
|
m_Step = step;
|
|
|
|
int sideLength = panel.SideLength;
|
|
|
|
AddBackground( 50, 0, 530, 410, 0xA28 );
|
|
|
|
AddImage( 0, 0, 0x28C8 );
|
|
AddImage( 547, 0, 0x28C9 );
|
|
|
|
AddBackground( 95, 20, 442, 90, 0xA28 );
|
|
|
|
AddHtml( 229, 35, 300, 45, "GENERATOR CONTROL PANEL", false, false );
|
|
|
|
AddHtml( 223, 60, 300, 70, "Use the Directional Controls to", false, false );
|
|
AddHtml( 253, 75, 300, 85, "Close the Grid Circuit", false, false );
|
|
|
|
AddImage( 140, 40, 0x28D3 );
|
|
AddImage( 420, 40, 0x28D3 );
|
|
|
|
AddBackground( 365, 120, 178, 210, 0x1400 );
|
|
|
|
AddImage( 365, 115, 0x28D4 );
|
|
AddImage( 365, 288, 0x28D4 );
|
|
|
|
AddImage( 414, 189, 0x589 );
|
|
AddImage( 435, 210, 0xA52 );
|
|
|
|
AddButton( 408, 222, 0x29EA, 0x29EC, 1, GumpButtonType.Reply, 0 ); // Left
|
|
AddButton( 448, 185, 0x29CC, 0x29CE, 2, GumpButtonType.Reply, 0 ); // Up
|
|
AddButton( 473, 222, 0x29D6, 0x29D8, 3, GumpButtonType.Reply, 0 ); // Right
|
|
AddButton( 448, 243, 0x29E0, 0x29E2, 4, GumpButtonType.Reply, 0 ); // Down
|
|
|
|
AddBackground( 90, 115, 30 + 40 * sideLength, 30 + 40 * sideLength, 0xA28 );
|
|
AddBackground( 100, 125, 10 + 40 * sideLength, 10 + 40 * sideLength, 0x1400 );
|
|
|
|
for ( int i = 0; i < sideLength; i++ )
|
|
{
|
|
for ( int j = 0; j < sideLength - 1; j++ )
|
|
{
|
|
AddImage( 120 + 40 * i, 162 + 40 * j, 0x13F9 );
|
|
}
|
|
}
|
|
|
|
for ( int i = 0; i < sideLength - 1; i++ )
|
|
{
|
|
for ( int j = 0; j < sideLength; j++ )
|
|
{
|
|
AddImage( 138 + 40 * i, 147 + 40 * j, 0x13FD );
|
|
}
|
|
}
|
|
|
|
Node[] path = panel.Path;
|
|
|
|
NodeHue[,] hues = new NodeHue[sideLength, sideLength];
|
|
|
|
for ( int i = 0; i <= step; i++ )
|
|
{
|
|
Node n = path[i];
|
|
hues[n.X, n.Y] = NodeHue.Blue;
|
|
}
|
|
|
|
Node lastNode = path[path.Length - 1];
|
|
hues[lastNode.X, lastNode.Y] = NodeHue.Red;
|
|
|
|
for ( int i = 0; i < sideLength; i++ )
|
|
{
|
|
for ( int j = 0; j < sideLength; j++ )
|
|
{
|
|
AddNode( 110 + 40 * i, 135 + 40 * j, hues[i, j] );
|
|
}
|
|
}
|
|
|
|
Node curNode = path[step];
|
|
AddImage( 118 + 40 * curNode.X, 143 + 40 * curNode.Y, 0x13A8 );
|
|
|
|
if ( hint )
|
|
{
|
|
Node nextNode = path[step + 1];
|
|
AddImage( 119 + 40 * nextNode.X, 143 + 40 * nextNode.Y, 0x939 );
|
|
}
|
|
|
|
if ( from.Skills.Lockpicking.Value >= 65.0 )
|
|
{
|
|
AddButton( 365, 350, 0xFA6, 0xFA7, 5, GumpButtonType.Reply, 0 );
|
|
AddHtml( 405, 345, 140, 40, "Attempt to Decipher the Circuit Path", false, false );
|
|
}
|
|
}
|
|
|
|
private void AddNode( int x, int y, NodeHue hue )
|
|
{
|
|
int id;
|
|
switch ( hue )
|
|
{
|
|
case NodeHue.Gray: id = 0x25F8; break;
|
|
case NodeHue.Blue: id = 0x868; break;
|
|
default: id = 0x9A8; break;
|
|
}
|
|
|
|
AddImage( x, y, id );
|
|
}
|
|
|
|
public override void OnResponse( NetState sender, RelayInfo info )
|
|
{
|
|
if ( m_Panel.Deleted || info.ButtonID == 0 || !m_From.CheckAlive() )
|
|
{
|
|
m_Panel.m_User = null;
|
|
return;
|
|
}
|
|
|
|
if ( m_From.Map != m_Panel.Map || !m_From.InRange( m_Panel, 3 ) )
|
|
{
|
|
m_From.SendLocalizedMessage( 500446 ); // That is too far away.
|
|
m_Panel.m_User = null;
|
|
return;
|
|
}
|
|
|
|
Node nextNode = m_Panel.Path[m_Step + 1];
|
|
|
|
if ( info.ButtonID == 5 ) // Attempt to Decipher
|
|
{
|
|
double lockpicking = m_From.Skills.Lockpicking.Value;
|
|
|
|
if ( lockpicking < 65.0 )
|
|
return;
|
|
|
|
m_From.PlaySound( 0x241 );
|
|
|
|
if ( 40.0 + Utility.RandomDouble() * 80.0 < lockpicking )
|
|
{
|
|
m_From.SendGump( new GameGump( m_Panel, m_From, m_Step, true ) );
|
|
m_Panel.m_LastUse = DateTime.UtcNow;
|
|
}
|
|
else
|
|
{
|
|
m_Panel.DoDamage( m_From );
|
|
m_Panel.m_User = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Node curNode = m_Panel.Path[m_Step];
|
|
|
|
int newX, newY;
|
|
switch ( info.ButtonID )
|
|
{
|
|
case 1: // Left
|
|
newX = curNode.X - 1;
|
|
newY = curNode.Y;
|
|
break;
|
|
case 2: // Up
|
|
newX = curNode.X;
|
|
newY = curNode.Y - 1;
|
|
break;
|
|
case 3: // Right
|
|
newX = curNode.X + 1;
|
|
newY = curNode.Y;
|
|
break;
|
|
case 4: // Down
|
|
newX = curNode.X;
|
|
newY = curNode.Y + 1;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if ( nextNode.X == newX && nextNode.Y == newY )
|
|
{
|
|
if ( m_Step + 1 == m_Panel.Path.Length - 1 )
|
|
{
|
|
m_Panel.Solve( m_From );
|
|
m_Panel.m_User = null;
|
|
}
|
|
else
|
|
{
|
|
m_From.PlaySound( 0x1F4 );
|
|
m_From.SendGump( new GameGump( m_Panel, m_From, m_Step + 1, false ) );
|
|
m_Panel.m_LastUse = DateTime.UtcNow;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_Panel.DoDamage( m_From );
|
|
m_Panel.m_User = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private Hashtable m_DamageTable = new Hashtable();
|
|
|
|
public void DoDamage( Mobile to )
|
|
{
|
|
to.Send( new UnicodeMessage( Serial, ItemID, MessageType.Regular, 0x3B2, 3, "", "", "The generator shoots an arc of electricity at you!" ) );
|
|
to.BoltEffect( 0 );
|
|
to.LocalOverheadMessage( MessageType.Regular, 0xC9, true, "* Your body convulses from electric shock *" );
|
|
to.NonlocalOverheadMessage( MessageType.Regular, 0xC9, true, string.Format( "* {0} spasms from electric shock *", to.Name ) );
|
|
|
|
AOS.Damage( to, to, 60, 0, 0, 0, 0, 100 );
|
|
|
|
if ( !to.Alive )
|
|
return;
|
|
|
|
if ( m_DamageTable[to] == null )
|
|
{
|
|
to.Frozen = true;
|
|
|
|
DamageTimer timer = new DamageTimer( this, to );
|
|
m_DamageTable[to] = timer;
|
|
|
|
timer.Start();
|
|
}
|
|
}
|
|
|
|
private class DamageTimer : Timer
|
|
{
|
|
private ControlPanel m_Panel;
|
|
private Mobile m_To;
|
|
private int m_Step;
|
|
|
|
public DamageTimer( ControlPanel panel, Mobile to ) : base( TimeSpan.FromSeconds( 5.0 ), TimeSpan.FromSeconds( 5.0 ) )
|
|
{
|
|
m_Panel = panel;
|
|
m_To = to;
|
|
m_Step = 0;
|
|
|
|
Priority = TimerPriority.TwoFiftyMS;
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if ( m_Panel.Deleted || m_To.Deleted || !m_To.Alive )
|
|
{
|
|
End();
|
|
return;
|
|
}
|
|
|
|
m_To.PlaySound( 0x28 );
|
|
|
|
m_To.LocalOverheadMessage( MessageType.Regular, 0xC9, true, "* Your body convulses from electric shock *" );
|
|
m_To.NonlocalOverheadMessage( MessageType.Regular, 0xC9, true, string.Format( "* {0} spasms from electric shock *", m_To.Name ) );
|
|
|
|
AOS.Damage( m_To, m_To, 20, 0, 0, 0, 0, 100 );
|
|
|
|
if ( ++m_Step >= 3 || !m_To.Alive )
|
|
{
|
|
End();
|
|
}
|
|
}
|
|
|
|
private void End()
|
|
{
|
|
m_Panel.m_DamageTable.Remove( m_To );
|
|
m_To.Frozen = false;
|
|
|
|
Stop();
|
|
}
|
|
}
|
|
|
|
public void Solve( Mobile from )
|
|
{
|
|
Effects.PlaySound( Location, Map, 0x211 );
|
|
Effects.PlaySound( Location, Map, 0x1F3 );
|
|
|
|
Effects.SendLocationEffect( Location, Map, 0x36B0, 4, 4 );
|
|
Effects.SendLocationEffect( new Point3D( X - 1, Y - 1, Z + 2 ), Map, 0x36B0, 4, 4 );
|
|
Effects.SendLocationEffect( new Point3D( X - 2, Y - 1, Z + 2 ), Map, 0x36B0, 4, 4 );
|
|
|
|
from.SendMessage( "You scrounge some gems from the wreckage." );
|
|
|
|
for ( int i = 0; i < SideLength; i++ )
|
|
{
|
|
from.AddToBackpack( new ArcaneGem() );
|
|
}
|
|
|
|
from.AddToBackpack( new Diamond( SideLength ) );
|
|
|
|
Item ore = new ShadowIronOre( 9 );
|
|
ore.MoveToWorld( new Point3D( X - 1, Y, Z + 2 ), Map );
|
|
|
|
ore = new ShadowIronOre( 14 );
|
|
ore.MoveToWorld( new Point3D( X - 2, Y - 1, Z + 2 ), Map );
|
|
|
|
Delete();
|
|
}
|
|
|
|
public ControlPanel( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.WriteEncodedInt( (int) 0 ); // version
|
|
|
|
writer.WriteEncodedInt( (int) m_SideLength );
|
|
|
|
writer.WriteEncodedInt( (int) m_Path.Length );
|
|
for ( int i = 0; i < m_Path.Length; i++ )
|
|
{
|
|
Node cur = m_Path[i];
|
|
|
|
writer.WriteEncodedInt( cur.X );
|
|
writer.WriteEncodedInt( cur.Y );
|
|
}
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadEncodedInt();
|
|
|
|
m_SideLength = reader.ReadEncodedInt();
|
|
|
|
m_Path = new Node[reader.ReadEncodedInt()];
|
|
for ( int i = 0; i < m_Path.Length; i++ )
|
|
{
|
|
m_Path[i] = new Node( reader.ReadEncodedInt(), reader.ReadEncodedInt() );
|
|
}
|
|
}
|
|
}
|
|
} |