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.Now - 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.Now; 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.Now; } 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.Now; } } 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() ); } } } }